SnappyConfinement

Differences between revisions 1 and 51 (spanning 50 versions)
Revision 1 as of 2014-12-10 05:09:46
Size: 13935
Editor: jdstrand
Comment:
Revision 51 as of 2015-12-03 23:58:34
Size: 21334
Editor: jdstrand
Comment:
Deletions are marked like this. Additions are marked like this.
Line 5: Line 5:
 * '''Contributors''': [[LaunchpadHome:jdstrand|Jamie Strandboge]], [[LaunchpadHome:mdeslaur|Marc Deslauriers]]
 * '''Packages affected''': apparmor, apparmor-easyprof-ubuntu-snappy, click-apparmor, snappy-systemd
 * '''Status''': Beta
 * '''Contributors''': [[LaunchpadHome:jdstrand|Jamie Strandboge]], [[LaunchpadHome:tyhicks|Tyler Hicks]], [[LaunchpadHome:mdeslaur|Marc Deslauriers]]
 * '''Packages affected''': apparmor, libseccomp, ubuntu-core-security, ubuntu-snappy, ubuntu-core-launcher (historically, click-apparmor)
 * '''Status''': Production
Line 10: Line 10:
Snappy confinement is an evolution of the [[https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement|security model for Ubuntu Touch]]. The basic concepts for confined applications and the !AppStore model pertain to snappy applications as well. In short, applications are confined using AppArmor by default and this is achieved through a simple template-based system where policy are extended through the use of policy groups.

Please familiarize yourself with the following before proceeding:
 * http://www.ubuntu.com/snappy#core-tour
 * http://developer.ubuntu.com/snappy/filesystem-layout/
 * http://developer.ubuntu.com/snappy/packaging-format-for-apps/
Snappy confinement is an evolution of the [[https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement|security model for Ubuntu Touch]]. The basic concepts for confined applications and the !AppStore model pertain to snappy applications as well. In short, applications are confined by default through the use of various technologies and this is achieved through a simple template-based system where policy is extended through the use of caps (aka policy groups).

It will be most helpful if you are familiar with:
 * https://developer.ubuntu.com/en/snappy/tutorials/using-snappy
 * https://developer.ubuntu.com/en/snappy/guides/filesystem-layout
 * https://developer.ubuntu.com/en/snappy/guides/packaging-format-apps
Line 18: Line 18:
Snappy uses a simple packaging format that is an evolution of [[https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement/Manifest|click packaging]]. Snappy packages use a declarative [[http://developer.ubuntu.com/snappy/packaging-format-for-apps/|yaml syntax]] and defaults to using `default` !AppArmor template and the "networking" policy group. Apps may customize the default behavior in a number of ways via the yaml syntax.

Under the hood, the [[https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement/Manifest|security manifest]] is used to describe the confinement of the app. Most apps do not need to specify anything for confinement and snappy will create a security manifest like the following:{{{
{
  "policy_vendor": "ubuntu-snappy",
  "policy_version": 1.3,
  "template": "default",
  "policy_groups": [
    "networking"
  ]
}
}}}

The defaults provided may change as the confinement needs evolve. The template policy is found in `/usr/share/apparmor/easyprof/ubuntu-snappy/templates/1.3/default` and the `networking` policy is found in `/usr/share/apparmor/easyprof/ubuntu-snappy/policy-groups/1.3/networking`.

=== "apparmor" hook ===
The `apparmor` hook is used to generate an !AppArmor profile based on what is in the snappy packaging yaml. Importantly, the profile name is unique to the application binary/writer and is achieved by using the concept of an ApplicationId. The APP_ID is the composition of the package name, the service/binary name and package version. The APP_ID takes the form of `<pkgname>_<appname>_<version>`. For example, if this is in packaging yaml:{{{
Snappy uses a simple packaging format that is an evolution of [[https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement/Manifest|click packaging]]. Snappy packages use a declarative [[https://developer.ubuntu.com/en/snappy/guides/packaging-format-apps/|yaml syntax]] and defaults to using `default` !AppArmor template and the "network-client" cap. Apps will be able to customize the default behavior in a number of ways via the yaml syntax.

Snappy developers will likely want to read the following:
 * https://developer.ubuntu.com/en/snappy/guides/filesystem-layout/
 * https://developer.ubuntu.com/en/snappy/guides/packaging-format-apps/
 * https://developer.ubuntu.com/en/snappy/guides/package-metadata/
 * https://developer.ubuntu.com/en/snappy/guides/security-policy/
 * https://developer.ubuntu.com/en/snappy/guides/oem/
 * https://developer.ubuntu.com/en/snappy/guides/frameworks/

=== Native snap format ===

==== Security policy ====
Applications are tracked by the system by using the concept of an ApplicationId. The `APP_ID` is the composition of the package name, the app's origin namespace from the store (if applicable-- only snaps of type: app (the default) have an origin namespace as part of their `APP_ID`), the service/binary name and package version. The `APP_ID` takes the form of `<pkgname>.<namespace>_<appname>_<version>`. For example, if this is in package.yaml:{{{
Line 43: Line 40:
Then the APP_ID for the 'bar' service is `foo_bar_0.1`.

Applications are launched under confinement by:
 * If unspecified, `snappy` will choose the `default` profile and `networking` policy group
 * Apps may choose to specify an alternate confinement binaries and services by specifying `security-template` and/or `security-policy-groups` in the yaml. Eg:{{{
...
services:
  - name: bar
    start: bin/bar
and the app was uploaded to the myorigin namespace in the store, then the `APP_ID` for the bar service is `foo.myorigin_bar_0.1`. The `APP_ID` is used throughout the system including in the enforcement of security policy by the app launcher.

The app launcher:
 * sets up cgroups (device, network (future), memory (future)*)
 * sets up iptables (for internal app access, future)
 * drops privileges to uid of service (future)
 * sets up a private /tmp (private mount namespace with app-specific directory mounted on /tmp in the namespace)
 * sets up seccomp
 * sets up various environment variables (eg, SNAP_APP_PATH, SNAP_APP_DATA_PATH, SNAP_APP_USER_DATA_PATH, SNAP_APP_TMPDIR, SNAP_OLD_PWD, SNAP_APP_ARCH and HOME).
 * chdirs to SNAP_APP_PATH (the install directory)
 * execs the app under apparmor profile under default nice value

The launcher is used when lauching both services and using CLI binaries. The default policy allows ELF executables, python, perl and shell (with selected corresponding utilities from /bin and /usr/bin), disallows capabilities(7) and enforces application isolation as per the [[https://developer.ubuntu.com/en/snappy/guides/filesystem-layout/|snappy FHS]].


===== AppArmor =====
Upon snap package install, the yaml is examined and `apparmor` profiles are generated for each service and binary in `/var/lib/snappy/apparmor/profiles` and have names based on the APP_ID. `apparmor` profiles are template based and may be extended through caps (policy groups), which are expressed in the yaml as `caps`.
 * If unspecified in the packaging yaml, `snappy` will choose the `default` template and `network-client` cap (this may change as snappy involves)
 * Apps may choose to specify an alternate confinement for binaries and services by specifying `caps` and/or `security-template` in the yaml. Eg:{{{
...
services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
Line 53: Line 68:
    security-policy-groups: networking, something, etc
}}}
 * For special circumstances, the packaging yaml may also use `apparmor` in its 'integration' section:{{{
...
services:
  - name: bar
    start: bin/bar
integration:
  bar:
    apparmor: meta/bar.apparmor
}}}
 * `aa-clickhook` is run on package install and places the resulting profile in `/var/lib/apparmor/profiles`.
 * If the binary is a service, the snappy-systemd hook makes sure `/etc/systemd/system/<service>.service` has `AppArmorProfile=<profilename>` (in the above example, `AppArmorProfile=foo_bar_0.1`) such that the service starts under confinement.
 * If the binary is a cli binary, it will be launched under confinement via the '`bin-path` hook (see below)
 * apparmor policy templates and policy groups are usually shipped via the apparmor-easyprof-ubuntu-snappy package

The default template allows ELF binaries, python, perl and shell (with selected corresponding utilities from /bin and /usr/bin), disallows capabilities(7) and enforces application isolation as per the [[|snappy FHS]].

=== "apparmor-profile" hook ===
In addition to the above, a new `apparmor-profile` hook is provided to facilitate specialized, hand-crafted confinement. This hook is not allowed to !AppStore apps, but is provided for snappy framework and trusted snappy applications.

The `aa-profile-hook` works similarly to `aa-clickhook`:
 * The snappy packaging specifies to use 'apparmor-profile' in its 'integration' section, which specifies the profile. Eg:{{{
...
services:
  - name: bar
    start: bin/bar
integration:
  bar:
    apparmor-profile: meta/bar.profile
}}}
 * the profile should reference the !AppArmor variables for CLICK_DIR, APP_PKGNAME, APP_APPNAME, APP_VERSION and for profile attachment, just like the `apparmor` template does with click-apparmor
 * aa-profile-hook is run on install and places the profile in `/var/lib/apparmor/profiles` (prefixed with 'profile_')
 * If the binary is a service, the snappy-systemd hook makes sure `/etc/systemd/system/<service>.service` has `AppArmorProfile=<profilename>` (in the above example, `AppArmorProfile=foo_bar_0.1`) such that the service starts under confinement.
 * `aa-profile-hook` is shipped in click-apparmor
 * use of `apparmor-profile` requires manual review via the store.

To improve maintenace, the profile author doesn't have to worry about updating the profile name, the app name, where it is installed or package version and may instead use AppArmor variables in the policy. Example profile:{{{
    caps:
      - network-client
      - something-else
}}}

`apparmor` policy templates and caps (policy groups) are shipped on the snappy system and also via framework snaps. Apps that depend on a particular framework may reference the framework snap's policy templates and/or groups.

Apps may also optionally specify `security-override` to specify high level overrides to use when `security-template’ and `caps’ are not sufficient. The path specified by `security-override` is a custom [[https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement/Manifest|security manifest]]. Use of this will trigger manual review in the Ubuntu store. Eg:{{{
services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-override:
      read-paths:
        - /path/to/file
        - /path/to/dir/
      write-paths:
        - /path/to/file
      abstractions:
        - apparmor-abstraction-name
}}}

then upon install the `default` security policy is generated but with the specified read-paths, write-paths and abstractions added. Specifying a directory to `read-paths` or `write-paths` gives permission to that directory and all files in that directory and its subdirectories.

Note: Ubuntu Core 15.04 used a [[https://github.com/ubuntu-core/snappy/blob/15.04/docs/security.md|different method]] for specifying `security-override`.

Furthermore, apps may also specify `security-policy` instead of using the template based policy to use hand-crafted `apparmor` policy. Use of this will trigger a manual review in the Ubuntu store. Eg:{{{
services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-policy:
      apparmor: meta/apparmor.profile
      seccomp: ...
}}}

When using `security-policy`, the profile author can use !AppArmor variables to avoid worrying about updating the profile name, the app name, where the app is installed or knowing the package version. Example `meta/apparmor.profile`:{{{
Line 120: Line 140:
Example yaml to use the profile:{{{
name: foo
version: 0.1
...
services:
  - name: bar
    start: bin/bar
integration:
  bar:
    apparmor-profile: meta/bar.profile
}}}

You may not specify the `apparmor` and `apparmor-profile` hooks for the same <appname> (eg, specifying `"apparmor-profile": "meta/appname.profile"` and `"apparmor": "meta/appname.apparmor"` is an error).

=== "apparmor-policy" hook ===
The `apparmor-policy` hook is provided for frameworks to extend security policy (specifically, to add templates and/or policy groups) so that apps that specify the framework may then reference these template/policy groups to access any services/binaries that the framework exposes.

Key ideas for snappy frameworks:
 * Frameworks extend Ubuntu Core in useful ways for apps to use
 * Frameworks are delivered via snaps
 * Because frameworks require additional privilege and are considered trusted (how much depends on what the framework offers), framework authors must partner with Canonical on framework design, security policy verification, acceptance in the store, etc
 * Frameworks are expected to provide significant functionality that is useful to a wide range of apps
 * Multiple snappy frameworks may be installed on the same system
 * The number of snappy frameworks is expected to be relatively low

See the SnappyFrameworkConfinement for more details.

=== "bin-path" hook ===
If the yaml specifies 'binaries' then the `snappy-bin-path` hook will expose these binaries to the shell and other apps by launching apps via a specialized launcher that does the following:
 * chdir()s to the app install directory
 * sets up any environment
 * launches the app under confinement

It does this by placing a symlink in `~/snappy-bin` pointing to the launcher. The launcher then queries the system to obtain the pkgname, version and profile name to launch the app under. The launcher will also `~/snappy-bin` is included as part of the user's PATH. The `snappy-bin-path` hook will create a very small shell script in `~/snappy-bin` to setup the environment, adjust the working directory and launch the binary under its !AppArmor profile. For example, consider the following snappy packaging yaml:{{{
name: foo
version: 0.1
...
binaries:
  - name: bin/bar
}}}

The `snappy-bin-path` hook will create the ~/snappy-bin/foo-bar symlink (notice this is <name>-<basename of binary name>. When executing that symlink, the launcher will:
 * chdir() to /apps/foo/current
 * set TMPDIR="/var/lib/apps/foo/0.1", etc
 * run the app under the `foo_bar_0.1` profile

Note, as of 2014-12-09, a shell script is temporarily being used instead of a symlink.
}}}
Up to date templates can be found in the [[http://bazaar.launchpad.net/~snappy-dev/snappy-hub/snappy-examples/files/head:/framework-template/meta/|snappy-examples bzr tree]]. Use of `security-policy` for seccomp requires the use of `security-policy` for apparmor.

===== Seccomp =====
Like with `apparmor` (see above), on snap package install the yaml is examined and seccomp filter lists are generated for each service and binary in `/var/lib/snappy/seccomp/profiles`. These seccomp filter lists are template based and may be extended through caps (policy groups), which are expressed in the yaml as `caps`. For simplicity, the seccomp caps (policy groups) and templates will have the same names as the corresponding `apparmor` caps and templates such that:
 * If unspecified in the packaging yaml, `snappy` will choose the `default` filter template and `network-client` cap (policy group) (this may change as snappy involves)
 * Apps may choose to specify an alternate confinement for binaries and services by specifying `caps` and/or `security-template`

Apps may also optionally specify `security-override` to specify high level overrides to use when `security-template` and `caps` are not sufficient. The path specified by `security-override` is a custom seccomp filter manifest (this uses yaml-syntax and can be used to specify `security-template`, `caps`, `syscalls`, `policy-vendor` and `policy-version`). Use of this will trigger manual review in the Ubuntu store. Eg:{{{
services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-override:
      syscalls:
        - clock_adjtime
}}}

then upon install the `default` security policy is used but with the `clock_adjtime` syscall added.

Note: Ubuntu Core 15.04 used a [[https://github.com/ubuntu-core/snappy/blob/15.04/docs/security.md|different method]] for specifying `security-override`.

Furthermore, apps may also specify `security-policy` instead of using the template based policy to use hand-crafted seccomp filters. Use of this will trigger a manual review in the Ubuntu store. Eg:{{{
services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-policy:
      seccomp: meta/seccomp-filter.list
      apparmor: ...
}}}

The format of the seccomp filter is simply a list of syscalls specified one per line. If an app attempts to use a syscall not found in its seccomp filter, the process will be sent the SIGKILL signal. Use of `security-policy` for seccomp requires the use of `security-policy` for apparmor.

'''IMPORTANT''': there are several things to keep in mind when running an app under a seccomp sandbox:
 * The ubuntu-core-launcher uses libseccomp's `seccomp_load()` which will call `prctl` with `PR_SET_NO_NEW_PRIVS`
 * When a process is running under seccomp it may alter its seccomp policy (if the seccomp filter allows it) to be more strict, but not more lenient
 * When a process performs a fork/exec, the child will inherit the parent's seccomp policy

The combination of all of these means that processes running under seccomp are not allowed to elevate their privileges at any point (including executing a setuid program). If this affects your program, you will have to alter your program to work within the seccomp sandbox.

===== Cgroups =====
As described above, snaps normally run confined and therefore have limited access to devices. Snappy has the concept of assigning hardware to specific (usually framework) snaps. To achieve this, OEM snaps may provide udev rule snippets via the `assign` yaml and upon install, udev rules are generated which add app-specific tags and properties to matching hardware (the `snappy hw-assign` developer command does something similar for the specified devices, but the udev tags/properties won't persist across reboots). Upon app launch, the launcher queries udev to see which devices have tags that match the app, then adds those devices (along with a few common devices such as /dev/null, /dev/random, etc) to a default deny, app-specific device cgroup (`/sys/fs/cgroup/devices/snappy.<pkgname>`). When the app is executed by the launcher, apps will be able to see all devices, but the device cgroup, DAC and !AppArmor^1^ will determine if the app can access it.

 0. '''Note:''' because device names are not always static and due to limitations in !AppArmor (Bug:1350598, Bug:1444679), the device cgroup mechanism is only used when hardware is assigned to a snap, at which point a general write rule for '/dev/**' and a read rule for '/run/udev/data/*' is added to the !AppArmor profile (via the .additional mechanism) for the app and device access is mediated by the app-specific device cgroup and traditional UNIX permissions. Conversely, when no hardware is assigned to the app, then the strict !AppArmor rules are in effect and an app-specific cgroup is not used.

In the future a net cgroup will be added to tag network traffic and possibly a memory cgroup will be added.

===== Namespaces =====
Initially snappy setup an app-specific directory in /tmp (eg, `/tmp/snaps/<pkgname>/<version>`) and set TMPDIR to point to that. However it was found that too many applications did not properly honor TMPDIR. To help developers, snappy now uses Linux namespaces to setup a private /tmp. This is achieved by creating a private mount namespace then bind mounting an app-specific directory on /tmp in the private namespace.

===== Privilege dropping =====
TBD: a mechanism will be provided for apps to drop privileges

==== Available policy ====
A given Snappy system will have the following security policy:
 * Default policy shipped on the system. This policy contains `security-templates` and `caps` that is always be available to apps for a particular release. For example, a 15.04 based Ubuntu Core system will always have `ubuntu-core/15.04` `security-templates` and `caps`.
 * Optional [[https://wiki.ubuntu.com/SecurityTeam/Specifications/SnappyConfinement/DevelopingFrameworkPolicy|framework policy]] that is installed by [[https://developer.ubuntu.com/en/snappy/guides/frameworks/|framework]] snaps that extends the security policy available to apps and allows apps that depend on this given framework to interact with the framework. A framework may ship `security-templates`, `caps` or a combination of both. A system with no frameworks installed will not have any framework policy.

The Ubuntu store contains the `snappy-debug` tool to see what policy is available on the system. See `snappy-debug.security -h` for details. Eg: {{{
$ sudo snappy install snappy-debug
...
$ snappy-debug.security list -i
System policy:
 Policy vendor: ubuntu-core
 Policy version: 15.04
 Templates:
  default
  - Description: Allows access to app-specific directories and basic runtime
  - Usage: common
  unconfined
  - Description: Allows unrestricted access to the system
  - Usage: reserved
 Caps:
  network-admin
  - Description: Can configure networking
  - Usage: reserved
  network-client
  - Description: Can access the network as a client
  - Usage: common
  network-firewall
  - Description: Can configure firewall
  - Usage: reserved
  network-service
  - Description: Can access the network as a server
  - Usage: common
  network-status
  - Description: Can query network status information
  - Usage: reserved
  networking
  - Description: Can access the network as a client
  - Usage: common
  snapd
  - Description: Can use snapd
  - Usage: reserved
Framework policy:
 Templates:
 Caps:
}}}

Note: the `unconfined` template provides unrestricted access to the system (with the exception that it doesn’t allow transitioning into another app’s security policy). This template is useful for debugging and development but should not generally be used in production. Store policies may trigger a manual review for uploads of snaps specifying this template.

=== Click compat ===
Historically (no longer on 16.04 and higher) snappy used `click-apparmor` to generate security policy for !AppArmor (seccomp always used the native format, above). When debugging on a 15.04 system the following may be useful:
 * click security manifests for apparmor are installed to `/var/lib/apparmor/clicks` for templated policy and `/var/lib/apparmor/snappy/profiles` for hand-crafted policy
 * apparmor profiles are generated in `/var/lib/apparmor/profiles`
 * use `aa-clickhook -f` to regenerate all templated policy
 * use `aa-profile-hook -f` to regenerate all custom policy
Line 177: Line 261:
    start: bin/owncloud
  - name: baz
    start: bin/owncloud
    start: bin/bar
  - name: baz
    start: bin/baz
Line 181: Line 265:
    start: bin/owncloud     start: bin/norf
Line 190: Line 274:
While the snappy packaging yaml is intentionally simple and straightforward for app developers, it can also quite flexible for those who need it. For example, consider the following yaml ('''TBD'''):{{{ While the snappy packaging yaml is intentionally simple and straightforward for app developers, it can also quite flexible for those who need it. For example, consider the following yaml:{{{
Line 199: Line 283:
    security-policy-groups: networking, extra     caps:
      -
network-client
      -
extra
Line 206: Line 292:
    security-policy-groups: networking, extra
}}}

snappy will generate the following security manifests:
 * normal-service:{{{
{
  "policy_vendor": "ubuntu-snappy",
  "policy_version": 1.3,
  "template": "default",
  "policy_groups": [
    "networking"
  ]
}
}}}
 * extra-policy-group-service:{{{
{
  "policy_vendor": "ubuntu-snappy",
  "policy_version": 1.3,
  "template": "default",
  "policy_groups": [
    "networking",
    "extra"
  ]
}
}}}
 * non-default-template-service:{{{
{
  "policy_vendor": "ubuntu-snappy",
  "policy_version": 1.3,
  "template": "non-default",
  "policy_groups": [
    "networking"
  ]
}
}}}
 * normal-binary:{{{
{
  "policy_vendor": "ubuntu-snappy",
  "policy_version": 1.3,
  "template": "default",
  "policy_groups": [
    "networking"
  ]
}
}}}
 * extra-policy-group-binary:{{{
{
  "policy_vendor": "ubuntu-snappy",
  "policy_version": 1.3,
  "template": "default",
  "policy_groups": [
    "networking",
    "extra"
  ]
}
}}}

Which in turn creates the following !AppArmor profiles:
    caps:
      - network-client
      - extra
}}}

Which in turn creates the following !AppArmor profiles (using the specified `security-template` and/or `caps`:
Line 271: Line 305:
To check to see if you have any denials:{{{
$ sudo journalctl --no-pager -k | grep DEN
}}}

A denial will look something like:{{{
When debugging policy issues, the `snappy-debug.security` tool can help. Use `sudo snappy install snappy-debug` and then simply launch it to have it follow the logs and provide suggestions:{{{
$ sudo snappy-debug.security scanlog
...
}}}

`snappy-debug.security scanlog` will report both !AppArmor and seccomp denials.

Alternatively you can use the lowlevel tools to check to see if you have any denials:{{{
$ sudo journalctl --no-pager -k | grep audit
}}}

An !AppArmor denial will look something like:{{{
Line 279: Line 320:
If there are no denials, !AppArmor shouldn't be blocking the app.

If there are denials, you can unblock yourself by:
 * modifying the profile in /var/lib/apparmor/profiles that corresponds to your app
 * reload the profile with:{{{
If there are no !AppArmor denials, !AppArmor shouldn't be blocking the app.

If there are !AppArmor denials, you can unblock yourself by:
 * modifying the profile in /var/lib/snappy/apparmor/profiles (/var/lib/apparmor/profiles on 15.04) that corresponds to your app
 * reload the profile with:
  * 16.04:{{{
$ sudo apparmor_parser -r /var/lib/snappy/apparmor/profiles/<profile>
}}}
  * 15.04:{{{
Line 287: Line 332:
A seccomp denial 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 with the `scmp_sys_resolver` command (you may also use the `sc-logresolve` command). Eg:{{{
$ scmp_sys_resolver 983045
set_tls
}}}

In general, if there are no seccomp denials, it shouldn't be blocking the app, however do keep in mind that if the app is somehow trying to elevate its privileges (eg, via a setuid executable) the app may receive a `Permission denied` error with no denial (see `PR_SET_NO_NEW_PRIVS` discussion above). If there are seccomp denials, you can unblock yourself by modifying the seccomp file in /var/lib/snappy/seccomp/profiles, then launch your app like normal (the launcher will pick up the change on app invocation).
Line 289: Line 345:
=== Helpful commands ===
 * `sudo aa-clickhook -f` will regenerate all the `apparmor` profiles in /var/lib/apparmor/profiles
=== Helpful degugging commands ===
 * `sudo sysctl -w kernel.printk_ratelimit=0` will disable kernel rate limitingof denials
 * `snappy-debug.security scanlog`: follow /var/log/syslog` and show !AppArmor and seccompg denial
 * `snappy-debug.security disable-rate-limiting`: disable kernel rate limiting
 * `snappy-debug.security reload [<name>.<origin>]`: reloads apparmor security policy into the kernel
 * `snappy-debug.security regenerate [<name>.<origin>]`: regenerate apparmor security policy from snappy packaging
Line 292: Line 352:
 * `sudo aa-status` will show you the profiles loaded in the kernel and what processes are running under them
 * `ps Z`, `ps Z <pid>` and `ps auxwwZ` will show you normal ps output, but with the apparmor label the profile is running under
 * `sudo systemctl stop <app>.service` and `sudo systemctl start <app>.service` to stop and start services from /etc/systemd/system (ie, where the snappy-systemd hook puts the service files)
 * `sudo snappy service stop <snap name>` and `sudo snappy service start <snap name>` to stop and start services
Line 296: Line 354:
  * In one terminal launch (tail kernel log for !AppArmor denials):{{{
$ sudo journalctl -f -k | grep DEN
}}}
  * In another (service name is the filename in /etc/systemd/system):{{{
$ sudo journalctl -f -u <service name>
}}}
  * Then launch the app with systemctl like above, or launch manually under confinement with:{{{
  * In one terminal launch `snappy-debug.security scanlog`:{{{
$ sudo snappy-debug.security scanlog
}}}
  * In another:{{{
$ sudo snappy service logs <snap name>
}}}
  * Then launch the app with `snappy service start` like above, or launch manually under confinement with:{{{
Line 306: Line 364:
= Future work =
 * Confining snappy apps (particularly processes requiring root) may use !AppArmor-wrapped user namespaces.
----
CategorySpec

Introduction

Snappy confinement is an evolution of the security model for Ubuntu Touch. The basic concepts for confined applications and the AppStore model pertain to snappy applications as well. In short, applications are confined by default through the use of various technologies and this is achieved through a simple template-based system where policy is extended through the use of caps (aka policy groups).

It will be most helpful if you are familiar with:

Implementation summary

Snappy uses a simple packaging format that is an evolution of click packaging. Snappy packages use a declarative yaml syntax and defaults to using default AppArmor template and the "network-client" cap. Apps will be able to customize the default behavior in a number of ways via the yaml syntax.

Snappy developers will likely want to read the following:

Native snap format

Security policy

Applications are tracked by the system by using the concept of an ApplicationId. The APP_ID is the composition of the package name, the app's origin namespace from the store (if applicable-- only snaps of type: app (the default) have an origin namespace as part of their APP_ID), the service/binary name and package version. The APP_ID takes the form of <pkgname>.<namespace>_<appname>_<version>. For example, if this is in package.yaml:

name: foo
version: 0.1
...
services:
  - name: bar
    start: bin/bar

and the app was uploaded to the myorigin namespace in the store, then the APP_ID for the bar service is foo.myorigin_bar_0.1. The APP_ID is used throughout the system including in the enforcement of security policy by the app launcher.

The app launcher:

  • sets up cgroups (device, network (future), memory (future)*)
  • sets up iptables (for internal app access, future)
  • drops privileges to uid of service (future)
  • sets up a private /tmp (private mount namespace with app-specific directory mounted on /tmp in the namespace)
  • sets up seccomp
  • sets up various environment variables (eg, SNAP_APP_PATH, SNAP_APP_DATA_PATH, SNAP_APP_USER_DATA_PATH, SNAP_APP_TMPDIR, SNAP_OLD_PWD, SNAP_APP_ARCH and HOME).
  • chdirs to SNAP_APP_PATH (the install directory)
  • execs the app under apparmor profile under default nice value

The launcher is used when lauching both services and using CLI binaries. The default policy allows ELF executables, python, perl and shell (with selected corresponding utilities from /bin and /usr/bin), disallows capabilities(7) and enforces application isolation as per the snappy FHS.

AppArmor

Upon snap package install, the yaml is examined and apparmor profiles are generated for each service and binary in /var/lib/snappy/apparmor/profiles and have names based on the APP_ID. apparmor profiles are template based and may be extended through caps (policy groups), which are expressed in the yaml as caps.

  • If unspecified in the packaging yaml, snappy will choose the default template and network-client cap (this may change as snappy involves)

  • Apps may choose to specify an alternate confinement for binaries and services by specifying caps and/or security-template in the yaml. Eg:

    ...
    services:
      - name: bar
        start: bin/bar
        caps:
          - network-client
      - name: baz
        start: bin/baz
        security-template: nondefault
        caps:
          - network-client
          - something-else

apparmor policy templates and caps (policy groups) are shipped on the snappy system and also via framework snaps. Apps that depend on a particular framework may reference the framework snap's policy templates and/or groups.

Apps may also optionally specify security-override to specify high level overrides to use when security-template’ and caps’ are not sufficient. The path specified by security-override is a custom security manifest. Use of this will trigger manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-override:
      read-paths:
        - /path/to/file
        - /path/to/dir/
      write-paths:
        - /path/to/file
      abstractions:
        - apparmor-abstraction-name

then upon install the default security policy is generated but with the specified read-paths, write-paths and abstractions added. Specifying a directory to read-paths or write-paths gives permission to that directory and all files in that directory and its subdirectories.

Note: Ubuntu Core 15.04 used a different method for specifying security-override.

Furthermore, apps may also specify security-policy instead of using the template based policy to use hand-crafted apparmor policy. Use of this will trigger a manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-policy:
      apparmor: meta/apparmor.profile
      seccomp: ...

When using security-policy, the profile author can use AppArmor variables to avoid worrying about updating the profile name, the app name, where the app is installed or knowing the package version. Example meta/apparmor.profile:

#include <tunables/global>

# Specified profile variables
###VAR###

###PROFILEATTACH### (attach_disconnected) {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Read-only for the install directory
  @{CLICK_DIR}/@{APP_PKGNAME}/                   r,
  @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/    r,
  @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/**  mrklix,

  # Writable home area
  owner @{HOMEDIRS}/apps/@{APP_PKGNAME}/   rw,
  owner @{HOMEDIRS}/apps/@{APP_PKGNAME}/** mrwklix,

  # Read-only system area for other versions
  /var/lib/apps/@{APP_PKGNAME}/   r,
  /var/lib/apps/@{APP_PKGNAME}/** mrkix,

  # Writable system area only for this version.
  /var/lib/apps/@{APP_PKGNAME}/@{APP_VERSION}/   w,
  /var/lib/apps/@{APP_PKGNAME}/@{APP_VERSION}/** wl,

  ... specialized confinement ...

Up to date templates can be found in the snappy-examples bzr tree. Use of security-policy for seccomp requires the use of security-policy for apparmor.

Seccomp

Like with apparmor (see above), on snap package install the yaml is examined and seccomp filter lists are generated for each service and binary in /var/lib/snappy/seccomp/profiles. These seccomp filter lists are template based and may be extended through caps (policy groups), which are expressed in the yaml as caps. For simplicity, the seccomp caps (policy groups) and templates will have the same names as the corresponding apparmor caps and templates such that:

  • If unspecified in the packaging yaml, snappy will choose the default filter template and network-client cap (policy group) (this may change as snappy involves)

  • Apps may choose to specify an alternate confinement for binaries and services by specifying caps and/or security-template

Apps may also optionally specify security-override to specify high level overrides to use when security-template and caps are not sufficient. The path specified by security-override is a custom seccomp filter manifest (this uses yaml-syntax and can be used to specify security-template, caps, syscalls, policy-vendor and policy-version). Use of this will trigger manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-override:
      syscalls:
        - clock_adjtime

then upon install the default security policy is used but with the clock_adjtime syscall added.

Note: Ubuntu Core 15.04 used a different method for specifying security-override.

Furthermore, apps may also specify security-policy instead of using the template based policy to use hand-crafted seccomp filters. Use of this will trigger a manual review in the Ubuntu store. Eg:

services:
  - name: bar
    start: bin/bar
    caps:
      - network-client
  - name: baz
    start: bin/baz
    security-policy:
      seccomp: meta/seccomp-filter.list
      apparmor: ...

The format of the seccomp filter is simply a list of syscalls specified one per line. If an app attempts to use a syscall not found in its seccomp filter, the process will be sent the SIGKILL signal. Use of security-policy for seccomp requires the use of security-policy for apparmor.

IMPORTANT: there are several things to keep in mind when running an app under a seccomp sandbox:

  • The ubuntu-core-launcher uses libseccomp's seccomp_load() which will call prctl with PR_SET_NO_NEW_PRIVS

  • When a process is running under seccomp it may alter its seccomp policy (if the seccomp filter allows it) to be more strict, but not more lenient
  • When a process performs a fork/exec, the child will inherit the parent's seccomp policy

The combination of all of these means that processes running under seccomp are not allowed to elevate their privileges at any point (including executing a setuid program). If this affects your program, you will have to alter your program to work within the seccomp sandbox.

Cgroups

As described above, snaps normally run confined and therefore have limited access to devices. Snappy has the concept of assigning hardware to specific (usually framework) snaps. To achieve this, OEM snaps may provide udev rule snippets via the assign yaml and upon install, udev rules are generated which add app-specific tags and properties to matching hardware (the snappy hw-assign developer command does something similar for the specified devices, but the udev tags/properties won't persist across reboots). Upon app launch, the launcher queries udev to see which devices have tags that match the app, then adds those devices (along with a few common devices such as /dev/null, /dev/random, etc) to a default deny, app-specific device cgroup (/sys/fs/cgroup/devices/snappy.<pkgname>). When the app is executed by the launcher, apps will be able to see all devices, but the device cgroup, DAC and AppArmor1 will determine if the app can access it.

  1. Note: because device names are not always static and due to limitations in AppArmor (1350598, 1444679), the device cgroup mechanism is only used when hardware is assigned to a snap, at which point a general write rule for '/dev/**' and a read rule for '/run/udev/data/*' is added to the AppArmor profile (via the .additional mechanism) for the app and device access is mediated by the app-specific device cgroup and traditional UNIX permissions. Conversely, when no hardware is assigned to the app, then the strict AppArmor rules are in effect and an app-specific cgroup is not used.

In the future a net cgroup will be added to tag network traffic and possibly a memory cgroup will be added.

Namespaces

Initially snappy setup an app-specific directory in /tmp (eg, /tmp/snaps/<pkgname>/<version>) and set TMPDIR to point to that. However it was found that too many applications did not properly honor TMPDIR. To help developers, snappy now uses Linux namespaces to setup a private /tmp. This is achieved by creating a private mount namespace then bind mounting an app-specific directory on /tmp in the private namespace.

Privilege dropping

TBD: a mechanism will be provided for apps to drop privileges

Available policy

A given Snappy system will have the following security policy:

  • Default policy shipped on the system. This policy contains security-templates and caps that is always be available to apps for a particular release. For example, a 15.04 based Ubuntu Core system will always have ubuntu-core/15.04 security-templates and caps.

  • Optional framework policy that is installed by framework snaps that extends the security policy available to apps and allows apps that depend on this given framework to interact with the framework. A framework may ship security-templates, caps or a combination of both. A system with no frameworks installed will not have any framework policy.

The Ubuntu store contains the snappy-debug tool to see what policy is available on the system. See snappy-debug.security -h for details. Eg:

$ sudo snappy install snappy-debug
...
$ snappy-debug.security list -i
System policy:
 Policy vendor: ubuntu-core
 Policy version: 15.04
 Templates:
  default
  - Description: Allows access to app-specific directories and basic runtime
  - Usage: common
  unconfined
  - Description: Allows unrestricted access to the system
  - Usage: reserved
 Caps:
  network-admin
  - Description: Can configure networking
  - Usage: reserved
  network-client
  - Description: Can access the network as a client
  - Usage: common
  network-firewall
  - Description: Can configure firewall
  - Usage: reserved
  network-service
  - Description: Can access the network as a server
  - Usage: common
  network-status
  - Description: Can query network status information
  - Usage: reserved
  networking
  - Description: Can access the network as a client
  - Usage: common
  snapd
  - Description: Can use snapd
  - Usage: reserved
Framework policy:
 Templates:
 Caps:

Note: the unconfined template provides unrestricted access to the system (with the exception that it doesn’t allow transitioning into another app’s security policy). This template is useful for debugging and development but should not generally be used in production. Store policies may trigger a manual review for uploads of snaps specifying this template.

Click compat

Historically (no longer on 16.04 and higher) snappy used click-apparmor to generate security policy for AppArmor (seccomp always used the native format, above). When debugging on a 15.04 system the following may be useful:

  • click security manifests for apparmor are installed to /var/lib/apparmor/clicks for templated policy and /var/lib/apparmor/snappy/profiles for hand-crafted policy

  • apparmor profiles are generated in /var/lib/apparmor/profiles

  • use aa-clickhook -f to regenerate all templated policy

  • use aa-profile-hook -f to regenerate all custom policy

Normal usage

As stated, the snappy packaging yaml does not have to do anything to specify the default confinement. Eg, the following yaml:

name: foo
version: 0.1
...
services:
  - name: bar
    start: bin/bar
  - name: baz
    start: bin/baz
  - name: norf
    start: bin/norf

will create the following AppArmor profiles with default confinement:

  • foo_bar_0.1
  • foo_baz_0.1
  • foo_norf_0.1

Advanced usage

While the snappy packaging yaml is intentionally simple and straightforward for app developers, it can also quite flexible for those who need it. For example, consider the following yaml:

name: foo
version: 0.1
...
services:
  - name: normal-service
    start: bin/normal-service
  - name: extra-policy-group-service
    start: bin/extra-policy-group-service
    caps:
      - network-client
      - extra
  - name: non-default-template-service
    start: bin/non-default-template-service
    security-template: non-default
binaries:
  - name: bin/normal-binary
  - name: extra-policy-group-binary
    caps:
      - network-client
      - extra

Which in turn creates the following AppArmor profiles (using the specified security-template and/or caps:

  • foo_normal-service_0.1
  • foo_extra-policy-group-service_0.1
  • foo_non-default-template-service_0.1
  • foo_normal-binary_0.1
  • foo_extra-policy-group-binary_0.1

Debugging

When debugging policy issues, the snappy-debug.security tool can help. Use sudo snappy install snappy-debug and then simply launch it to have it follow the logs and provide suggestions:

$ sudo snappy-debug.security scanlog
...

snappy-debug.security scanlog will report both AppArmor and seccomp denials.

Alternatively you can use the lowlevel tools to check to see if you have any denials:

$ sudo journalctl --no-pager -k | grep audit

An AppArmor denial will look something like:

apparmor="DENIED" operation="mkdir" profile="foo_bar_0.1" 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 app.

If there are AppArmor denials, you can unblock yourself by:

  • modifying the profile in /var/lib/snappy/apparmor/profiles (/var/lib/apparmor/profiles on 15.04) that corresponds to your app
  • reload the profile with:
    • 16.04:

      $ sudo apparmor_parser -r /var/lib/snappy/apparmor/profiles/<profile>
    • 15.04:

      $ sudo apparmor_parser -r /var/lib/apparmor/profiles/<profile>

A seccomp denial 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 with the scmp_sys_resolver command (you may also use the sc-logresolve command). Eg:

$ scmp_sys_resolver 983045
set_tls

In general, if there are no seccomp denials, it shouldn't be blocking the app, however do keep in mind that if the app is somehow trying to elevate its privileges (eg, via a setuid executable) the app may receive a Permission denied error with no denial (see PR_SET_NO_NEW_PRIVS discussion above). If there are seccomp denials, you can unblock yourself by modifying the seccomp file in /var/lib/snappy/seccomp/profiles, then launch your app like normal (the launcher will pick up the change on app invocation).

Do note that the local modification will not be preserved on package update. If you believe you have found a bug, please file a bug against: https://bugs.launchpad.net/ubuntu/+source/apparmor/+filebug

Helpful degugging commands

  • sudo sysctl -w kernel.printk_ratelimit=0 will disable kernel rate limitingof denials

  • snappy-debug.security scanlog: follow /var/log/syslog` and show AppArmor and seccompg denial

  • snappy-debug.security disable-rate-limiting: disable kernel rate limiting

  • snappy-debug.security reload [<name>.<origin>]: reloads apparmor security policy into the kernel

  • snappy-debug.security regenerate [<name>.<origin>]: regenerate apparmor security policy from snappy packaging

  • sudo aa-profile-hook -f will regenerate all the apparmor-profile profiles in /var/lib/apparmor/profiles

  • sudo snappy service stop <snap name> and sudo snappy service start <snap name> to stop and start services

  • This is often helpful when developing your app or policy for it:
    • In one terminal launch snappy-debug.security scanlog:

      $ sudo snappy-debug.security scanlog
    • In another:

      $ sudo snappy service logs <snap name>
    • Then launch the app with snappy service start like above, or launch manually under confinement with:

      $ aa-exec -p <profile name> -- /apps/<pkgname>/<version>/...


CategorySpec

SecurityTeam/Specifications/SnappyConfinement (last edited 2016-11-17 16:29:21 by jdstrand)