Security policy and sandboxing


#1

Without providing custom flags at installation, snaps run confined under a restrictive security sandbox. The security policies and store policies work together to allow developers to quickly update their applications and to provide safety to end users.

This document describes the sandbox and how to configure and work with the security policies for snaps.

How policy is applied

Application authors should not have to know about or understand the low-level implementation details on how security policy is enforced. Instead, all snaps run under a default security policy which can be extended through the use of interfaces. Slots, plugs, and the available interfaces available on the device can be seen with:

$ snap connections

Each command declared in apps snap metadata is tracked by the system assigning a security label to the command. This security label takes the form of snap.<snap>.<app> where <snap> is the name of the snap, and <app> is the application name. For example, if this is snap.yaml:

name: foo
...
apps:
  bar:
    command: ...
    ...

then the security label for the bar command is snap.foo.bar. This security label is used throughout the system including during the process confinement phase when running the application.

Under the hood, the application runner:

  • Sets up various environment variables:
    • HOME: set to SNAP_USER_DATA for all commands
    • SNAP: read-only install directory
    • SNAP_ARCH: the architecture of device (eg, amd64, arm64, armhf, i386, etc)
    • SNAP_DATA: writable area for a particular revision of the snap
    • SNAP_COMMON: writable area common across all revisions of the snap
    • SNAP_LIBRARY_PATH: additional directories which should be added to LD_LIBRARY_PATH
    • SNAP_NAME: snap name
    • SNAP_INSTANCE_NAME: snap instance name incl. instance key if one is set (snapd 2.36+)
    • SNAP_INSTANCE_KEY: instance key if any (snapd 2.36+)
    • SNAP_REVISION: store revision of the snap
    • SNAP_USER_DATA: per-user writable area for a particular revision of the snap
    • SNAP_USER_COMMON: per-user writable area common across all revisions of the snap
    • SNAP_VERSION: snap version (from snap.yaml)
  • When hardware is assigned to the snap, sets up a device cgroup with default devices (eg, /dev/null, /dev/urandom, etc) and any devices that are assigned to this snap. Hardware is assigned via interface connections.
  • Sets up a private mount namespace shared across all commands of the snap
  • Sets up a private /tmp using a per-snap private mount namespace and mounting a per-snap directory on /tmp
  • Sets up a per-command devpts new instance
  • Sets up the seccomp filter for the command
  • Executes the command under the command-specific AppArmor profile under a default nice value

This combination of restrictive AppArmor profiles (which mediate file access, application execution, Linux capabilities(7), mount, ptrace, IPC, signals, coarse-grained networking), clearly defined application-specific filesystem areas, whitelist syscall filtering via seccomp, private /tmp, new instance devpts and device cgroups provides for strong application confinement and isolation.

AppArmor

Upon snap install, the snap metadata is examined and AppArmor profiles are generated for each command to have the appropriate security label and command-specific AppArmor rules. As mentioned, each command runs under an app-specific default policy that may be extended through declared interfaces which are expressed in the metadata as plugs and slots. AppArmor policy violations in strict mode snaps will be denied access and typically have errno set to EACCES. The violation will be logged…

Seccomp

Similar to AppArmor, upon snap install, the snap metadata is examined and seccomp filters are generated for each command to run under a default seccomp filter that may be extended through declared interfaces which are expressed in the metadata as plugs and slots. Processes with seccomp policy violations will be denied access to the system call with errno set to EPERM (snapd releases prior to 2.32 receive SIGSYS) and the violation is logged.

Device cgroup

Similar to AppArmor and Seccomp, upon snap install, the snap metadata is examined and udev rules are generated for each command to tag devices so they may be added/removed to the command’s device cgroup. By default, no devices are tagged and the device cgroup is not used and only AppArmor is used to mediate access. A device cgroup may be used in addition to AppArmor depending on the declared interfaces which are expressed in the metadata as plugs and slots. Processes accessing devices not in the snap-specific device cgroup will be denied access with errno set to EPERM. Access violations are typically not logged.

Traditional permissions

Traditional file permissions (owner, group and other as well as file ACLs) are also enforced with snaps. Processes trying to access resources which the traditional file permissions do not allow are denied access with errno typically set to EACCES (see the man page for the operation for specifics). Access violations are typically not logged.

Working with snap security policy

The snap metadata need not specify anything for default confinement and may optionally specify plugs and slots to declare additional interfaces to use. When an interface is connected, the snap’s security policy will be updated to allow access to use the interface. See the snap format and interfaces for details.

The AppArmor policy is deny by default and snaps are restricted to their app-specific directories, libraries, etc (enforcing ro, rw, etc). The seccomp filter is also deny by default and the default filter allows enough safe syscalls so that snaps using the default security policy should work.

For example, consider the following:

name: foo
version: 1.0
apps:
  bar:
    command: bar
  baz:
    command: baz
    daemon: simple
    plugs: [network]

then:

  • the security label for bar is snap.foo.bar. It uses only the default policy
  • the security label for baz is snap.foo.baz. It uses the default policy plus the network interface security policy as provided by the core snap

Security policies and store policies work together to provide flexibility, speed and safety. Because of this, use of some interfaces may trigger a manual review in the official Ubuntu store and/or may need to be connected by the user or gadget snap developer.

The interfaces available on the system and those used by snaps can be seen with the snap connections command. Eg:

$ snap connections
Interface           Plug                                Slot               Notes
home                wormhole:home                       :home              -
log-observe         gnome-logs:log-observe              :log-observe       -
mount-observe       gnome-system-monitor:mount-observe  :mount-observe     -
network             xkcd-webserver:network              :network           -
network-bind        xkcd-webserver:network-bind         :network-bind      -
[...]

In the above it can be seen that the gnome-logs snap has the log-observe interface connected (and therefore the security policy from log-observe is added to it) and the xkcd-webserver has the network and network-bind interfaces connected. An interesting quality of interfaces is that they may be either declared per-command or per-snap. If declared per-snap, all the commands within the snap have the interface security policy added to the command’s security policy when the interface is connected. If declared per-command, only the commands within the snap that declare use of the interface have the interface security policy added to them.

Snappy may auto-connect the requested interfaces upon install or may require the user to manually connect them. Interface connections and disconnections are performed via the snap connect and snap disconnect commands. See
interfaces for details.

Developer mode

Sometimes it is helpful when developing a snap to not have to worry about the security sandbox in order to focus on developing the snap. To support this, snappy allows installing the snap in developer mode which puts the security policy in complain mode (where violations against security policy are logged, but permitted). For example:

$ sudo snap install --devmode mysnap

Debugging

To check if you have any policy violations:

$ sudo grep audit /var/log/syslog

An AppArmor violation will look something like:

audit: type=1400 audit(1431384420.408:319): apparmor="DENIED" operation="mkdir" profile="snap.foo.bar" name="/var/lib/foo" pid=637 comm="bar" requested_mask="c" denied_mask="c" fsuid=0 ouid=0

If there are no AppArmor denials, AppArmor shouldn’t be blocking the snap.

A seccomp violation will look something like:

audit: type=1326 audit(1430766107.122:16): auid=1000 uid=1000 gid=1000 ses=15 pid=1491 comm="env" exe="/bin/bash" sig=31 arch=40000028 syscall=983045 compat=0 ip=0xb6fb0bd6 code=0x0

The syscall=983045 can be resolved by running the scmp_sys_resolver command on a system of the same architecture as the one with the seccomp violation:

$ scmp_sys_resolver 983045
set_tls

If there are no seccomp violations, seccomp isn’t blocking the snap. If you notice compat=1 in the seccomp denial, then specify the correct compatibility architecture to scmp_sys_resolver with -a <arch>. Eg, if on an amd64 system, use scmp_sys_resolver -a x86 191 (use -a arm on arm64 systems).

The snappy-debug snap can be used to help with policy violations. To use it:

$ sudo snap install snappy-debug
$ sudo /snap/bin/snappy-debug.security scanlog foo

This will:

  • disable kernel log rate limiting (important to not miss policy denials. Can disable manually with sudo sysctl -w kernel.printk_ratelimit=0)
  • follow /var/log/syslog looking for policy violations for foo
  • resolve syscall names (considering compat)
  • make recommendations on how to fix violations

See snappy-debug.security help for details.

Traditional file permissions are enforced but denied accesses will not be logged.

Device cgroups may also block access but denied accesses will not be logged. You can see if device cgroups are in effect by:

  • seeing if there are any snapd-generated udev rules in /etc/udev/rules.d/70-snap.$SNAPNAME.rules
  • if rules are defined, use udevadm info /dev/$DEVICE to see if the snap shows up in TAGS or see if the /run/udev/tags/snap_$SNAPNAME_$COMMAND directory exists
  • examine if the /sys/fs/cgroup/snap.$SNAPNAME.$COMMAND directory exists and if the device is listed in /sys/fs/cgroup/snap.$SNAPNAME.$COMMAND/devices.allow (eg, /dev/kmsg would have ‘c 1:11 rwm’ since /dev/kmsg is a character device with MAJOR:MINOR as 1:11 (see ls -l /dev/kmsg))

If you believe there is a bug in the security policy or want to request and/or contribute a new interface, please file a bug, adding the snapd-interface tag.

Interface development and security policy

When participating in snappy development and implementing new interfaces for others to use, you will almost always need to write security policy for both the slots and the plugs side of the interface but keep in mind you are not expected to write perfect security policy on the first try. The review process for snapd includes a security review of the interface security policy and it is expected that the security policy will be iterated on during the review process (in other words, if you are stuck on writing security policy but the interface otherwise works, feel free to submit the interface and ask for help).

Tips

When fine-tuning AppArmor policy, it is often easiest to install the snap in strict mode then modify the AppArmor policy in place on the target system, then copying it back. Eg, these steps might be:

  1. build your snap
  2. copy your snap to your target device and install it (or use snap try)
  3. use the snap (perhaps using snap run --shell <name>.<command>), monitoring /var/log/syslog for denials
  4. modifying /var/lib/snapd/apparmor/profiles/snap.<name>.<command> as needed (eg, adding rules before the final '}')and running sudo apparmor_parser -r /var/lib/snapd/apparmor/profiles/snap.<name>.<command> to load the policy into the kernel
  5. use sudo service snap.<name>.<command> stop/start/etc as needed for daemons
  6. repeat until satisfied

The same process as above holds for seccomp except the seccomp policy is in /var/lib/snapd/seccomp/bpf/snap.<name>.<command>.src and you must generate the bpf with sudo /usr/lib/snapd/snap-seccomp compile /var/lib/snapd/seccomp/bpf/snap.<name>.<command>.src /var/lib/snapd/seccomp/bpf/snap.<name>.<command>.bin. The snap-confine command will load the bpf in the .bin file for the command when you (re)launch the command or snap run --shell. The seccomp policy language is considerably simpler and is essentially a list of allowed syscalls.

When done, copy any changes you make to /var/lib/snapd/apparmor/profiles/snap.<name>.<command> or /var/lib/snapd/seccomp/bpf/snap.<name>.<command>.src to your interface code.

For device cgroups, create or modify /etc/udev/rules.d/70-snap.$SNAPNAME.rules as necessary (eg, KERNEL=="kmsg" TAGS+="snap_$YOURSNAPNAME_$YOURCOMMAND" would tag /dev/kmsg for your snap), then run sudo udevadm trigger --action=change. To undo the access, remove the file and run the udevadm command again. When done, update the interfaces code based on your changes.

In addition to the above, here are some other useful techniques when debugging/developing policy:

  • temporarily specify @unrestricted in the seccomp policy and this will allow all syscalls
  • temporarily use a combination of bare apparmor rules to focus on only the parts you want. Eg:
    file,
    capability,
    network,
    mount,
    remount,
    pivot_root,
    umount,
    dbus,
    signal,
    ptrace,
    unix,
    
  • look at existing policy in interfaces/apparmor/template.go, interfaces/seccomp/template.go and interfaces/builtin/* for examples of the policy language
  • stracing snaps. In addition to simply stracing the app, it can also be helpful to strace the app in both devmode and strict confinement and comparing the results.
  • when testing new versions of snappy-app-dev, if re-exec is enabled you will need to copy the new version to the location udev expects it (eg, /lib/udev) and then bind mount it over where the re-exec’d snap-confine expects it (eg, mount --bind /lib/udev/snappy-app-dev /snap/core/<version>/lib/udev/snappy-app-dev)

Installing in devmode and developing policy can also be done; you will simply focus on getting rid of logged (but otherwise allowed) policy violations.

References


The desktop interfaces
Classic confinement for pcsxr-2017
Create Security documentation
Snaping MusicBrainz Picard & problems with gconf
Sanitisation of snaps' requested interfaces
Porting ubuntu core to IMX6/am335x issue?
Systemd-journald use high CPU
Snappy-debug improvements
Snapping CUPS Printing Stack: Avahi support, system users/groups
Snap rejected because of use of browser-support
How to move from devmode to strict confinement?
How to move from devmode to strict confinement?
Confinement policy inheritence
Strict mode seccomp policy violations now set errno to EPERM instead of killing the process
Segfault with older glib
Unable to change/modify/write app data
Classic confinement for paraemu snap
Udev backend in snapd
MysteryMentor: Request for classic confinement
#2

Content moved out of the wiki.