snapd fails to validate content interface settings, resulting in sandbox escape
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
snapd |
Fix Released
|
Undecided
|
Unassigned | ||
snapd (Ubuntu) |
Fix Released
|
Undecided
|
Unassigned | ||
Bionic |
Fix Released
|
Undecided
|
Unassigned | ||
Focal |
Fix Released
|
Undecided
|
Unassigned | ||
Impish |
Fix Released
|
Undecided
|
Unassigned | ||
Jammy |
Fix Released
|
Undecided
|
Unassigned |
Bug Description
Snapd does not properly or sufficiently validate the input strings used in content interface plugs/slots, meaning that snaps can be installed with strict confinement with malformed content interface slots which effectively grant any AppArmor rule to the plug side.
To exploit this, create two snaps, one which provides the slot and one which has a plug connected to the slot. For the purposes of the exploit, these can just be local snaps, but note that a snap publisher could upload both of these snaps to the store and the two snaps will have their plug/slot auto-connected due to rules about auto-connection of matching content interface plugs/slots for snaps of the same publisher.
The first snap has a content slot definition like this:
slots:
content-plug:
interface: content
content: mycont
read:
- "$SNAP/ rw, /** rw, } profile foobar (attach_
The embedded profile name does not really matter, but what is important is that this rule has arbitrary apparmor rules embedded inside it, abusing the "," character. For the plug side use a definition like this:
plugs:
content-plug:
interface: content
content: mycont
target: $SNAP_DATA/mycont
The plug side target does not matter at all, but the `content` attribute must match the slot definition in order for the plug and slot to be auto-connected.
What we are effectively doing is taking advantage of the fact that snapd just validates that the read setting is a "clean" filepath, and does no further validation and effectively ends up just copy-pasting the string into various places inside an AppArmor profile without any quoting or further validation.
There are really two profiles which get generated with this malicious string without validation, the one for snap-update-ns of the plugging snap in question, and the one for the snap itself. This actually presented something of a problem, as for snap-update-ns, the string appears as part of a mount rule source, which means that including other stuff here like we do makes apparmor_parser fail to compile the file for snap-update-ns, as it does not expect a mount rule to be formed like this. I still suspect it is possible to craft a string such that it somehow is valid both as a file source in a mount rule and as a file rule itself, but it turned out that actually it doesn't need to be a valid rule for both profiles in order to be exploited. This is because snapd just loads the profiles and if they fail to be loaded, snapd does nothing about it. This means we can craft a rule which is valid for just the profile for the app itself, (but not for the profile of snap-update-ns), and still be able to use our crafted rule. The one hiccup to this is that the mount namespace must already exist, so that snap run does not need to invoke snap-update-ns, otherwise presumably the exploit will not be exploitable since the app cannot be run. It might be possible to also avoid this by using a daemon and something like refresh-mode: endure, but I didn't take the time to figure out all those details.
To be clear, with the above plug, for the plugging app snap we get this as the tail end of the apparmor profile:
```
# In addition to the bind mount, add any AppArmor rules so that
# snaps may directly access the slot implementation's files
# read-only.
/snap/test-
}
```
Which for the purposes of this bug just demonstrates that we can inject arbitrary apparmor rules into the profile through the snap.yaml plug/slot definition.
Unfortunately, fixing it isn't quite as simple as just quoting the input string, as trying to compile this:
"/snap/
actually fails as a rule. It's unclear to me how much AppArmor profiles really support quoting things, so the proper fix for this may be to start enforcing stricter patterns of supported filepaths, and excluding paths with commas or braces in them for example.
We may also want to consider rolling back AppArmor profiles if they fail to be compiled to prevent issues like one profile of a set being compiled successfully, but another failing to. Currently we leave these profiles around on disk in whatever state we tried to write them as until they are overwritten by a new update, etc.
Note that running review-tools on the snap with malicious slot produces "OK", so uploading such a snap to the store today would likely be allowed.