Allow snapped daemons to use polkit authorisation


#1

One request that has come up a few times is the ability for snapped daemons to make use of polkit authorisation. This essentially lets the daemon consult a system policy to decide whether to allow a non-root user to perform a particular action. When the polkitd daemon is absent, such applications typically fall back to requiring that the user be root.

I’ve been doing some analysis to see what needs to be exposed to enable this feature, and what some of the potential security concerns are.

Communicating with polkitd

The polkitd daemon resides on the D-Bus system bus under the name org.freedesktop.PolicyKit1 and exposes a number of methods on the org.freedesktop.PolicyKit1.Authority interface. This is a mix of methods intended for use by daemons, user-side authentication agents, and potentially a control panel type UI.

Of particular concern, the EnumerateActions method could potentially be used to determine the list of all polkit enabled daemons on the system.

Granting access to only CheckAuthorization and CancelCheckAuthorization should be mostly safe, but it could probably be used to probe for the presence of well known action IDs (e.g. request non-interactive auth of an action ID, and check what error is returned).

Determining the authorisation subject

The first argument to CheckAuthorization is a “subject” structure identifying the client the daemon wants to check. The PolkitSubject struct can take a number of forms:

  • PolkitSystemBusName: system bus unique name
  • PolkitUnixProcess: pid, process start time, uid
  • PolkitUnixSession: ConsoleKit session ID (is this deprecated?)

In the first case, no additional permissions should be needed: the D-Bus daemon passes the client’s unique bus name in the message.

The UNIX process subject type is needed when the client communicates with the daemon over something other than the D-Bus system bus. Snapd is an example, where we use polkit to authorise commands sent over a UNIX domain socket.

To handle this case, the daemon needs to be able to read the SO_PEERCRED socket option for the pid and uid, and read /proc/$pid/stat for the process start time. This last one is used to detect pid reuse attacks. This effectively means that the daemon needs to be given read access to /proc/*/stat, which grants access to far more information than just the start time of one particular process.

Given the size of this hole, it might make sense to leave ProcessUnixProcess support out initially, or hide it behind an interface option that review-tools can gate access to. Supporting D-Bus system daemons alone would be a decent improvement.

Installing action descriptions

The CheckAuthorization method takes an “action ID” string as an argument. These action IDs take the form of Java-style reversed DNS names and are chosen by the daemon author. In order to be usable though, it is necessary to publish a .policy file describing those actions. These are XML files that provide human readable descriptions of the actions (for use in prompt dialogs) and the default policy for the actions. The files need to live in /usr/share/polkit-1/actions.

So we need some way for a snap to install new policy files. This should only be done after validating the files to ensure it’s not something that will cause polkitd to misbehave.

One concern I have is snaps attempting to provide default policy for a second daemon’s action IDs. I still need to check what polkit does in a situation like this, but the worst case scenario would be a snap changing the default policy for an action from auth_admin to a simple yes. If polkit does guard against duplicate default policy, it might simply turn into a denial of service for that action ID. Neither option is particularly appealing.

Requiring that actions contain the snap name is one approach, but since actions IDs are generally hard coded into daemons this would likely require code modification in most cases. I’m not sure if there is a better solution though.

I don’t think there is any need to restrict what the default policy actually is: if a client is allowed to talk to a daemon, then the daemon could easily act without consulting polkitd. So there isn’t any real security benefit to e.g. rejecting a default permissive policy for an action ID.

Proposed implementation

  1. add a new polkit interface whose slot is implicitly provided by system on classic distros. Snaps that plug this interface are allowed to connect to the D-Bus system bus and issue the CheckAuthorization and CancelCheckAuthorization method calls to polkitd.
  2. snaps can provide a meta/polkit-1.policy file. On snap installation, this file will be validated to ensure it is valid XML and only provides actions named snap.$SNAP_NAME.*, and then installed to /usr/share/polkit-1/actions/snap.$SNAP_NAME.policy.

I think it should be safe to have these two parts operate independently: installing policy files for unused actions should effectively be a no-op. And if we don’t initially include support for PolkitUnixProcess subjects, then it is probably safe to allow auto-connection.


Fwupd snap, needed interfaces
#2

@jdstrand could you do a first pass at reviewing this plan


#3

I suspect system services as snaps (ie, ones that would typically use polkit) are going to need to be considered somewhat privileged in this regard. I’m not sure how we want to expose this in terms of interfaces, but it seems reasonable that something along the lines of a snap declaration would be needed to install a polkit-using service. That said, we allow for enumeration of other snaps in various other ways, so while it would be good if we could avoid another leak, we might consider it ok (especially if it is behind a snap declaration).

Unfortunately we’ve had to allow read access of /proc/$pid/stat for some time so this is not a blocker (and underscores the difficulties in trying to confine preexisting applications that weren’t designed with confinement in mind).

I have long thought we would treat this like the udev backend (not the one for the device cgroup, but the one for installing arbitrary udev rules) in that we would have snapd interfaces that use the polkit backend, and that interface would define the polkit actions for the snap. Eg, the network-manager interface uses the new polkit backend and when a snap that slots network-manager is installed, snapd splats out the polkit files (and likely updated dbus policy files). On the surface, this seems problematic since there might be different versions of software that need different polkit actions. However, if we consider that interfaces are contracts between snaps, then I think this idea is tractable (eg, the network-manager interface supports a particular dbus API and polkit policy; if a new network-manager comes along that breaks things, we need a new interface).

I’m pretty uncomfortable with snaps providing their own policy files and this goes against the general design of other parts of the system where snapd provides the apparmor, seccomp, dbus bus policy, systemd units, session autostart, etc. We are validating the desktop files and essentially only rewriting the Exec line (and even that makes me uncomfortable), but desktop files are quite a bit different from policy files.

Beyond a security POV on getting the validation right, I worry that competing snaps providing polkit files will install different policy for the same service (eg, competing NetworkManagers; I recognize your point 2 addresses this, but bear with me). Also, since polkit has the concept of overrides via the localauthority mechanism, it would be good if this aspect was preserved with snaps such that the snap can’t simply change its polkit policy and then obviate any vendor/site/whatever changes on the system.

I suspect we are looking at this from different angles though. I’m certainly looking at this from the POV of snaps providing known core system services (eg, where a network manager snap might be used instead of a deb/rpm). I suspect you might be looking at this from the POV of “I have this arbitrary (non-core-system) service that would like to use polkit for authorization to harden its security stance”. Assuming that is your POV, I think something along the lines of what you suggested with namespaced policy, etc might be ok, but we’d want to work through some of the more problematic areas.

Perhaps these POVs are simply two different snapd concepts where both need to be implemented to support different use cases where the polkit backend approach I outlined is suitable for these system services where other snaps are meant to connect to them, having the “interface contract” honored and somewhat enforced by snapd while the polkit-interface-plus-policy approach you outlined is suitable for snaps to opt into existing technologies (ie polkit) to improve their security stance.


Classic confinement request for deja-dup
#4

Sorry for taking a while to respond: I was gathering some of my notes about this topic together.

It’s not just enumeration of other snaps though: it could potentially be used to enumerate host system applications as well. For example, a CheckAuthorization call for org.freedesktop.NetworkManager.enable-disable-network could tell me whether NetworkManager is installed on the host.

I agree that requiring manual review to publish polkit using services on the store may be the best option if we can’t find a better way to lock things down.

I assume you’re talking about this rule in the base AppArmor policy?

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

That isn’t sufficient to handle ProcessUnixProcess subjects. It’d need to be:

@{PROC}/*/stat r,

The workflow for using polkit with a unix domain socket service is something like:

  1. look up SO_PEERCRED on the socket connection accepted from the client.
  2. read the client’s /proc/pid/stat to determine it’s monotonic start time.
  3. make the D-Bus method call to polkitd using a unix-process subject with details dict containing uid, pid, and start-time.

So we need to be able to read the status of arbitrary processes owned by arbitrary users (since usually the daemon will be root and the client will be a regular user). This is roughly the level of access you’d need to write a top utility, and reveals a lot about the host system.

But as I said, none of this is necessary for services that communicate via D-Bus. They will use the system bus name subject type, which requires no special access: just the unique bus name of the client, which they get through the client’s D-Bus method call. That’s why I suggested ignoring the unix-process subject type until we can identify a clear need for it.

One thing bearing in mind is that from the service side, polkit is entirely advisory: it is the service querying polkitd to ask whether the system policy allows a certain action by a client. The alternative is for the service to hard code a policy in the executable, so having the service dictate the default policy does not grant it privileges it wouldn’t otherwise have.

As for having snapd generate .policy files based on interface declarations, I think that is likely to be problematic: upstream applications add new polkit actions from release to release, so this would be difficult to work with.

Taking Network Manager as an example, during the 17.10 cycle I wrote an NM patch to allow disabling the connectivity check feature, with the user’s ability to toggle this gated by a new org.freedesktop.NetworkManager.enable-disable-connectivity-check polkit action. The patch landed upstream in the 1.10 release, and in the release on my 18.10 desktop the action’s message and description has been translated into six languages since then (some of which probably came in micro version updates) If snapd was going to take charge of generating policy files, then (a) NM would have to wait on a new snapd release before the new toggle worked, and (b) NM would be beholden to the snapd translators for the localisation of its authorisation prompts.

Now I agree that having multiple snaps provide policy for the same action IDs is a problem (or policy for actions provided by the host system). As I said in the original post, requiring the snap name be embedded in each action ID is one possibility, which could easily be enforced through validation. That would still be a bit painful since action IDs are generally embedded directly in the service’s source code, but I’m not sure of a better option. Perhaps claiming an action ID prefix, and have snapd refuse to allow other snaps use that prefix would work, but then we’d need to decide what prefixes snaps could claim.


#5

Yes, from experience imposing naming like that on snaps can be very hard. Also if there are local override policies they would be based on the original action names, no? We really need to explore what we can do to whitelist/enable preexisting naming decisions here.


#6

There are definitely a number of cases where a snap might want to own a Java-style reversed DNS name prefix. In addition to polkit action IDs, some others include:

  1. ability to acquire D-Bus well known names on system or session bus (currently covered by the dbus slot plus store review).
  2. .desktop file IDs – to support D-Bus activatable .desktop files and GNOME’s notification system, the name of the .desktop file needs to match the D-Bus name the application will acquire.

While we can handle each of these independently, I wonder if it would make sense to let the snap claim the prefix in all these cases simultaneously in a way that other installed snaps can not independently claim the prefix. I’m not sugesting this as a replacement for plugs/slots, but perhaps have the plugs or slots use the claimed prefixes as input for the policy they set up.