Proposal: private shared memory

One of the recurring problems I see with adapting applications to work within snapd’s sandbox is making them conform to its shared memory segment naming rules. The reason for these restrictions is because the sandbox exposes the host system /dev/shm inside the sandbox. As a shared resource, snapd needs to make sure snaps do not cause other snaps or host system processes to misbehave.

In the majority of cases, they are using POSIX shared memory to communicate between processes within the sandbox, and do not really care about processes outside the sandbox seeing those segments. If the snap contains code that is hard to modify (either proprietary software, or large frameworks that the packager would prefer not to rebuild), then it would be desirable if the snap author could request a private /dev/shm and remove the naming restrictions.

There is already a shared-memory interface in snapd, so @pedronis suggested that this could be added as a new mode to the existing interface: a new system:shared-memory implicit slot would be added, and when a snap connects to this slot, a private directory will be mounted over /dev/shm.

This is somewhat unusual, since existing shared-memory plugs and slots are used to expose shared memory segments from one snap to another, and should not be able to connect to the new implicit slot or “private shm” plugs (and vice versa). At the same time, use of one mode of the interface is mutually exclusive with the other mode. So maybe combining the two is appropriate.

Here’s a summary of the changes I think would be necessary:

  1. Update shared-memory to make it implicit on both core and classic systems.
  2. Update shared-memory’s BeforePreparePlug implementation to check for a private attribute. If it is missing, default it to false. If private is set to true:
    • require that shared-memory is not set.
    • error out if there are any other shared-memory interface plugs or slots on the snap.
  3. Update shared-memory’s AppArmorConnectedPlug to check whether the private is set. If it is set, output an AppArmor rule granting read/write access to /dev/shm/*.
  4. Add a MountConnectedPlug method to shared-memory, which checks to see if private is set, and if so bind mounts /dev/shm/snap.${SNAP_INSTANCE_NAME} to /dev/shm.
  5. Update the base declaration to allow auto-connection of shared-memory interface plugs with private: true set to the implicit system slot.
  6. Update snap-update-ns so that if it creates the /dev/shm/snap.${SNAP_INSTANCE_NAME} directory, it does so with root ownership and 01777 permissions (i.e. sticky bit set).

I think this is basically what we want. As our private /dev/shm is actually a subdirectory of the host system directory, we should inherit any restrictions the host system places on the directory.

4 Likes

The existing approach to this with snapcraft-preload, while workable, is pretty confusing to explain / pick up, and the current alternative of recompiling programs to use namespaced folders in /dev/shm isn’t amazing either.

A clean solution like this would be a very nice improvement IMO.

Would the browser-support interface be changed if this were to exist? It provides some access to /dev/shm for common paths browsers use, but this also includes Electron applications for example, which aren’t really distinguishable from Chromium. Is there any way to migrate these over cutting off their access to each other?

4 Likes

Moving Electron apps over to using private shared memory would be worth while. The current grants to /dev/shm in the browser-support interface don’t provide any isolation between different snaps.

The current /dev/shm rules in browser-support would be superfluous if private shared memory is enabled, but shouldn’t be harmful. Maybe at some point in the future it will be possible to remove them (once all snaps depending on it have upgraded).

3 Likes

Thanks for this proposal James, this looks good to me and I think will help with a lot of snaps that have had this issue before.

This may conflict with the “automatic” setting of the value as the name of the interface plug that we do (same as content) in:

plugs:
  foo:
    interface: shared-memory

apps:
  something:
    cmd: things.sh
    plugs:
      - foo

We could possibly remove that behavior, but it is a nicety for snaps that are using the interface to share shared memory between two snaps similar to the content interface.

You could implement the AutoConnect method of the shared-memory interface such that if the plug specifies private: true then it cannot be connected to a slot that either has the shared-memory attribute set to something or possibly has the read or write attributes set. This would “prevent” auto-connecting the private: true plug to anything other than the implicit system one, though if I’m not mistaken, the plug could still be manually connected to a different slot. You could also potentially check in one of the methods that the slot comes from the “system” snaps, though I don’t think that we have done this before in the codebase so it may need some new wiring to set that sort of interaction up (that is to tell if a given snap.SlotInfo is coming from the “system” snap).

Did you mean inappropriate here?

1 Like

So the current logic in BeforePreparePlug / BeforePrepareSlot are essentially:

  1. if the shared-memory attribute is not set, set it to the plug/slot name.

I was envisioning that the logic on the plug side would change to:

  1. if the private attribute is not set, set it to false.
  2. if the private attribute is set to true and the shared-memory attribute is set, error out.
  3. if the private attribute is set to false and shared-memory attribute is not set, set the shared-memory attribute to the plug/slot name.

On the slot side, things would be essentially the same except for:

  1. if this is the implicit system slot, private is defaulted to true.
  2. if this is an app slot, it is an error for private to be set to true.

Having the private attribute on both plug and slot side will hopefully make it easier to express the autoconnect rules in the base declaration.

I meant appropriate in the sense that it would mean the mutual exclusion logic would be contained within the implementation of a single interface. If that isn’t seen as a benefit, then perhaps it would make sense to split them.

1 Like

Thanks for explaining that, that all makes sense

The proposal + clarifications look reasonable to me. We’ll need to see whether we hit issues implementing it.

1 Like

I’ve got an in-progress PR for this feature up here:

The basic functionality described above works, but there are a few security kinks to be worked out. If you install the snapd snap from the latest CI run for the PR, a snap can activate private shared memory mode by adding the following to the snapcraft.yaml:

plugs:
  shared-memory:
    private: true
2 Likes

Thanks for this @jamesh - we should also make sure to update review-tools so it can check and raise an error if both private and shared-memory are specified.