A couple of weeks ago, we talked about snap security, taking a journey through the eyes of a developer and handing over to a user who wants to install applications from the Snap Store. We discussed concepts like application confinement, interfaces, store review, and automatic updates. Today, we will look under the hood, and examine the underlying security mechanisms, and then talk some more about Ubuntu Core.
Ubuntu hardening mechanisms
Ubuntu uses several security hardening mechanisms, including: Discretionary Access Controls (DAC), Mandatory Access Control (MAC) via AppArmor, Seccomp kernel system call filtering (limits the system calls a process may use), cgroups device access controls for hardware assignment, and pseudoterminal (PTY) functionality for login sessions via a new devpts instance per command to prevent snooping and input injection via /dev/pts. Ubuntu also uses YAMA Linux Security Module in Canonical-supported kernels, and provides ptrace scoping, symlink restrictions and hardlink restrictions.
The DHCP client runs under a restrictive AppArmor profile.
The administrative user (root) has a disabled password. All system users have disabled logins.
When installed on desktop and server, OpenSSH listens by default but it is configured with PermitRootLogin prohibit-password to disable root logins when a password is set. For Ubuntu on cloud, this is the default configuration.
Ubuntu Core Filesystem Hierarchy Standard (FHS)
The name of this subsection can be a little confusing, because the FHS applies to snaps used both on classic Ubuntu as well Ubuntu Core. The snap installation directory is read-only, with specific writable data areas. These areas correspond to environment variables that are set during runtime. Specifically:
read-only access to $SNAP (versioned install path)
write access to $SNAP_DATA (versioned path in /var)
write access to $SNAP_USER_DATA (versioned path in /home)
write access to $SNAP_COMMON (snap-specific path in /var)
write access to $SNAP_USER_COMMON (snap-specific path in /home)
write access to $XDG_RUNTIME_DIR (snap-specific path in /run/user/”uid”)
write access to shared memory files (/dev/shm/snap.SNAP_NAME.*)
read-only access to install path and data directories of previous versions
access to a subset of executables in /bin, /sbin, /usr/bin and /usr/sbin
allow processes from the same snap to communicate with each other via abstract and anonymous sockets
allow processes from the same snap to signal each other via signals
Furthermore, there are several confinement mechanisms enabled in Ubuntu (snaps):
Security policy ID – Applications are tracked by the system using the concept of identifiers. This identifier is a composition of elements from the snap’s packaging. Specifically, it consists of the package name and the command name. The security policy ID takes the form of snap.”name”.”command”.
Snap trust model – Snaps are not allowed to use crond scheduling service, change to another user, have unapproved access to hardware, add rsyslog rules, add users, ship setuid/setgid programs, change security policy, modify the system, modify kernel runtime variables, or access sensitive kernel syscalls. By default, snaps are not allowed to manipulate network interfaces, routing, QoS, network namespaces, manipulate the firewall, monitor the network, and do not run with CAP_NET_ADMIN. Snaps may request ‘network-control’, ‘firewall-control’ or ‘network-observe’ in their plugs to perform the above, but these are subject to store upload policies.
Application launching – When a snap starts, the snap run command will determine how to start the program and configure its runtime environment variables. It will execute snap-confine, which will then: configure work and temporary directories for the snap, setup device access to hardware, setup a Linux kernel system call filter via seccomp, and launch the command under an AppArmor profile.
Self-checks – On boot and during installations, the snapd service will perform integrity checks on snaps. If any problems are detected, the snaps will be disabled until refreshed from the store with an updated version.
Ubuntu Core is a lightweight, robust, transactional version of Ubuntu for IoT devices and large, scale-out container deployments. It comes with additional security hardening and restrictions compared to the snap security on classic desktop distributions.
Please note that the list below is not comprehensive, and does not cover all the security features available in Ubuntu Core, but it outlines some of the major differences, as well as security mechanisms in it.
There are a few important differences in how Ubuntu Core is designed:
Ubuntu Core is application-centric instead of distribution archive-centric.
Ubuntu Core replaces apt with the snap command – applications are packaged and delivered entirely as snaps.
The base system is a minimal system that consists of three parts: the operating system, the kernel, and gadget, which are all delivered using the snap packaging format. The OS snap is constructed from DEB packages from the Ubuntu archive. The gadget snap provides several snaps during the provisioning steps, and they will be preinstalled for the user.
There is a clean separation between the base system (base snaps) and the applications installed from the store (application snaps), as well as a clean separation between installed applications.
The root filesystem is read-only.
Ubuntu Core uses the same boot process as classic Ubuntu. However, unlike classic Ubuntu, all applications on Ubuntu Core run under an application sandbox, and they are not permitted to modify the firmware, bootloader, kernel, modules, initrd, and init, and they are not allowed to interact with the base system except in very controlled ways.
The Ubuntu Core base system contains only the following components: kernel, the init process, snapd service, and several standard Linux/UNIX tools and libraries to make application development easier.
Ubuntu Core only exposes a very small subset of the systemd specification in the snap packaging. On system install, the systemd unit file is auto-generated based on these packaging options. This prevents snaps from interacting with systemd and the system in uncontrolled ways.
As part of provisioning, one user account is setup using the Ubuntu SSO as the name of the user. By default this user has console access disabled and SSH access allowed via the SSH key stored in Launchpad for the user. This user is in the sudo group and thus capable of running commands as root. Multi-user support is limited on Ubuntu Core, but alternate user accounts can be set up using standard tools.
Security trust model
As we briefly mentioned earlier, Ubuntu Core differs from classic Ubuntu in how software is distributed, including core operating system snap, pre-installed snaps and snaps installed via the store.
Untrusted by the operating system
Application snaps are considered untrusted. We touched on this in the first article on security, whereby we mentioned that untrusted applications:
can freely access their own data.
cannot access other applications’ data.
cannot access non-application-specific user data.
cannot access privileged portions of the OS.
cannot access privileged system APIs.
may access sensitive APIs with user permission provided the API asks for permission at time of access or the permission is granted to the application outside of snap installation.
Security in action
To understand how the snap packaging, security policy and runtime restrictions all work together, let’s examine a use case. Below, we have a sample snapcraft.yaml file for a foo application. This content will be transformed into a snap and uploaded to the store. In this example, the snap is assigned revision 7 by the store.
What do we have here? We have a snap named foo, version 1.0. It has two commands in it, one called bar and one called ctl. Bar is a background service (daemon), and it requests access to qux and network interfaces. Ctl runs the bin/control executable.To make the example more complete, there is also an existing snap named baz.
The snap named baz has an application named norf. It is a D-BUS message bus type background service, and it provides a slot named qux, to snaps that request access (plug) for qux.
When foo runs, the following happens:
Security policy ID for bar is snap.foo.bar. It has the default security policy with the network interface auto-connected. The qux interface is not auto-connected, but it can be manually connected during runtime.
Security policy ID for ctl is snap.foo.ctl. It has the default security policy only.
For both of the above commands, the runtime environment variables are set to SNAP_REVISION=7, SNAP=/snap/foo/7, SNAP_DATA=/var/snap/foo/7, and SNAP_USER_DATA=$HOME/snap/foo/7.
The foo snap plugs into the qux interface. The baznorf service implements the qux slot. There is no automatic connection – it is done manually via snap connect foo:qux baz:qux. The security policies are configured to allow (by seccomp filter) bar to connect() to norf D-Bus service at org.qux (by AppArmor policy).
Snap security comprises several layers, and users often only see the top one, working and interacting with snaps. But there is a lot happening under the surface, starting with robust operating system fundamentals, and continuing with hardening policies and a strong trust model. Hopefully, this article has shed some light of the things happening behind the scenes, which should give you a better understanding and more confidence into the snap security model.
Furthermore, Ubuntu Core introduces additional measures, specifically tailored for IoT devices. With the entire system deployed as snaps, and further restrictions on what snaps can do, Ubuntu Core is a dependable choice for IoT deployments.If you have any thoughts or feedback on this article, we invite you join our discussion forum.