Proposal: add polkit and polkit-agent interfaces to snapd

This is a cleaned up and more concrete version of my proposal about polkit support from a few years ago. The proposed design for the polkit interface is mostly finished. There are some areas where polkit-agent needs to be fleshed out a bit, but I think it is at a point where it could do with some feedback.

Here are one sentence summaries of what the two interfaces enable:

  • polkit: allow daemons permission to use polkitd to make access control decisions for requests from unprivileged clients.
  • polkit-agent: allow a snap to act as a polkit agent to grant access to a group of related processes.

The first is the more generally useful interface. The second would mostly be useful on Ubuntu Core based systems that implement a user session.

polkit interface

Description of access

To perform polkit authorization checks, a daemon needs to do two things:

  1. Install a .policy file to /usr/share/polkit-1/actions describing the actions it will use (codifying the type of administrative access a user might be granted).
  2. Before performing administrative work on behalf of a client app, make a CheckAuthorization D-Bus call to polkitd to ask if they have access. The D-Bus call passes a string action ID describing the access, and a “subject” struct describing the client application.

There are two primary ways a daemon can describe the subject of the check:

  1. For D-Bus daemons they can use a system-bus-name subject, sending the unique bus name of the client app.
  2. For non-D-Bus daemons, they can use a unix-process subject, sending the process ID (as retrieved through SO_PEERCRED or SCM_CREDENTIALS).

In the initial implementation of the interface, I suggest we focus on daemons using system-bus-name subjects, since they are more common and can be more strictly confined.

Proposed Implementation

I propose that a snap would define a plug with something like:

plugs:
  polkit:
    action-prefix: "org.example.foo"

The action-prefix plug attribute would be mandatory, and indicate that all actions published by the snap are equal to the action prefix or match ${action-prefix}.*.

An implicit polkit slot would be provided on systems where polkit authorisation is available. At present this means it would be implicitOnClassic. If we add polkit to Ubuntu Core in future, it
would make sense to provide the slot there as well.

The base declaration should probably look somewhat similar to e.g. system-files, requiring a snap declaration from the store to install that would give some oversight to make sure the prefix was set
appropriately.

The connected plug AppArmor rules for the interface would be as follows:

# Allow communication with polkitd on the D-Bus system bus
#include <abstractions/dbus-strict>
dbus (send)
    bus=system
    path="/org/freedesktop/PolicyKit1/Authority"
    interface="org.freedesktop.PolicyKit1.Authority"
    member="{,Cancel}CheckAuthorization"
    peer=(name="org.freedesktop.PolicyKit1", label=unconfined),
dbus (send)
    bus=system
    path="/org/freedesktop/PolicyKit1/Authority"
    interface="org.freedesktop.DBus.Properties"
    peer=(name="org.freedesktop.PolicyKit1", label=unconfined),
dbus (send)
    bus=system
    path="/org/freedesktop/PolicyKit1/Authority"
    interface="org.freedesktop.DBus.Introspectable"
    member="Introspect"
    peer=(name="org.freedesktop.PolicyKit1", label=unconfined),

In addition, the interface code will expect a file $SNAP/meta/${plug_name}.policy to exist. When the plug is connected, this file will be validated to make sure it conforms to the documented format and only describes actions matching the specified action-prefix. There is an initial implementation of this validation in snapd PR #9986.

When the plug is connected, the .policy file will be copied to /usr/share/polkit-1/actions/snap.${SNAP_INSTANCE_NAME}.${plug_name}.policy. When the plug is disconnected, the file will be removed. On snap removal, all snap.${SNAP_INSTANCE_NAME}.*.policy files should be
removed from that directory.

Questions

Could the .policy files be auto-generated instead of being provided by the snap?

This was something previously suggested by @jdstrand, with e.g. the set of actions for NetworkManager being encoded into the network-manager interface, and output when the corresponding interface is plugged. There are a number of issues that I think make this unworkable:

  1. the list of actions a particular daemon uses can change from version to version.
  2. the .policy file contains translations of action messages and descriptions. Even if the list of actions don’t change between two releases, it is not uncommon for the daemon to pick up new translations.

What about daemons not using D-Bus?

The main difficulty in using the unix-process subject type is that you need to include start-time of the process as a way to protect against pid reuse attacks. This information is not provided in the
struct ucred, and needs to be read from /proc.

So a daemon using this method will need read access to /proc/*/stat. We can’t restrict it with @owner either, since the whole point is to allow daemons running as root to act on behalf of unprivileged users.

We could handle this in one of two ways:

  1. require such daemons to plug system-observe, which grants the required access plus a lot more.
  2. add another plug attribute to enable the required access.

Should snaps be able to install polkit rules?

I think we should punt on this in the short term. At present, there are two incompatible formats for rules:

  1. the .pkla ini-style rule files supported by polkit <= 0.105
  2. the .rules JavaScript files supported by polkit > 0.105.

At present, Debian-derived distros (including Ubuntu) ship polkit 0.105 with many patches from later versions applied.

It wouldn’t be too difficult to validate .pkla files, but they will be ignored by most non-Debian distributions. In contrast, .rules files are arbitrary JavaScript. While polkitd does run them in a web browser style sandbox and imposes a strict time limit, it does represent a way to execute code in the context of an unconfined system daemon.

So I think it best to not support this until we have a concrete use case.

polkit-agent interface

Description of access

A polkit agent registers with polkitd as being responsible for authorising requests on behalf of other processes, usually those belonging to a particular logind session ID. The control flow essentially goes like this:

  1. agent registers itself with polkitd by calling its RegisterAuthenticationAgentWithOptions method. This provides an object path on which polkitd can send method calls to the agent.
  2. When polkitd is asked to perform an authorization check for a process the agent is responsible for, the agent receives a BeginAuthentication method call.
  3. The agent verifies that the user’s password with the help of the setuid /usr/libexec/polkit-agent-helper-1 helper.
  4. On completion, polkit-agent-helper-1 sends calls polkitd’s AuthenticationAgentResponse2 method to report the status.

The polkit-agent-helper-1 process uses PAM to validate the password, so can not reasonably run on a classic system where there could be any PAM modules registered. We have a bit more certainty on Ubuntu Core systems where the account system is more regular.

Proposed implementation

On relevant Ubuntu Core systems, an implicit system slot would be defined for the polkit-agent interface. Snaps that wish to act as polkit agents would have a corresponding plug. This interface would not require interface attributes.

The connected plug AppArmor rules for this interface would preferably transition to a sub-profile on execution of polikit-agent-helper-1. I’m not yet sure exactly what that part of the rules needs to look like though. The D-Bus parts would be something like this:

# Allow communication with polkitd on the D-Bus system bus
#include <abstractions/dbus-strict>
dbus (send)
    bus=system
    path="/org/freedesktop/PolicyKit1/Authority"
    interface="org.freedesktop.PolicyKit1.Authority"
    member="{RegisterAuthenticationAgent,RegisterAuthenticationAgentWithOptions,UnregisterAuthenticationAgent,AuthenticationAgentResponse,AuthenticationAgentResponse2}"
    peer=(name="org.freedesktop.PolicyKit1", label=unconfined),
dbus (send)
    bus=system
    path="/org/freedesktop/PolicyKit1/Authority"
    interface="org.freedesktop.DBus.Properties"
    peer=(name="org.freedesktop.PolicyKit1", label=unconfined),
dbus (send)
    bus=system
    path="/org/freedesktop/PolicyKit1/Authority"
    interface="org.freedesktop.DBus.Introspectable"
    member="Introspect"
    peer=(name="org.freedesktop.PolicyKit1", label=unconfined),

# Allow polkitd to communicate with the agent
dbus (receive)
    bus=system
    interface="org.freedesktop.PolicyKit1.AuthenticationAgent"
    peer=(name="org.freedesktop.PolicyKit1", label=unconfined),

# Allow agent to execute polkit-agent-helper-1
/usr/libexec/polkit-agent-helper-1 Cxr -> polkit_agent_helper,

profile polkit_agent_helper (attach_disconnected,mediate_deleted) {
...
},
1 Like

Regarding unix-process subjects, the base policy already specifies:

@{PROC}/@{pid}/stat r

which allows any snap to read /proc/$PID/stat for any other process (as there is no owner attribute here) assuming that traditional DAC permissions don’t get in the way. So I think it would be good to try and consider unix-process subjects in this implementation too.

I also think it is a good idea to not allow snaps to ship rules as this presents a large attack surface against untrusted input and on classic systems (whether these are javascript or not).

The argument around snaps shipping action policy makes a lot of sense as these are more closely coupled with the application logic itself (similar to desktop files) so this seems most appropriate to take the approach to validate them and allow snaps to provide them internally.

I also like the idea of the action-prefix but I wonder perhaps whether it would make more sense to try and unify this with the dbus slot review/granting mechanism - ie. a snap declares a particular reverse-DNS style name and whether this is used as a DBus slot and/or polkit and then from the store we grant the use of a given name for one or both after manual review. Would this make more sense to a snap author?

1 Like

Thanks for having a look at this!

I handn’t realised that the @{PROC}/@{pid} rules in the base template matched data for all processes. That means that there is no additional permissions needed for servers that use unix-process subjects rather than system-bus-name.

I agree that it might make sense to have a way to claim a reverse-dns prefix for multiple purposes in future. I’m not sure if that should be a prerequisite for this feature. I don’t think it is appropriate to simply snoop on the snap’s dbus interfaces to make a guess about what action prefixes it should be able to use: for a start, there’s no requirement that the server needs to own a system bus name to use polkit auth.

Could the manual review concerns be satisfied through new policy for reviewers? E.g. an assumption that a snap should be granted the ability to install action IDs prefixed by a system bus name they’ve previously been approved to own.