Design
This design has been discussed with Gustavo, the snapd team and the security team. Details remain and will be discussed in this topic.
Overall design
snapd will work in conjunction with the snap store to use a ‘global ID database’. This database will provide a predictable 1 to 1 mapping of numeric ID to name. When a snap is installed using one of these IDs, snapd will consult the global database and create a group with a name that matches the global name (prefixed with snap_) and gid that matches the global ID. It then creates a user with a name that matches the global name (prefixed with snap_) and uid that matches the global ID with the primary group set to the global name. In this manner, snaps may specify these usernames/groups in their yaml and use the uid/gid in the squashfs such that the uid/gids will resolve properly via getpwnam(), getpwuid(), getgrnam(), getgrgid(), etc both inside and outside the snap.
To avoid clashes with existing uids and gids, snapd will use very high numbered IDs in the 32-bit range. Historically, early on 15-bit uids were supported and later the more common 16-bit uids. Linux and modern Unix systems now all support 32-bit uids with all but the most specialized filesystems (eg, cramfs) also supporting 32-bit uids by default (eg, ext2 and ext4 supports the non-default nouid32 option for interoperability with older (ancient) kernels). Notably, squashfs is confirmed to support 32-bit uids and gids.
To use, a snap optionally declares the username/groups it wants to use in the snap.yaml (exact yaml TBD). Eg:
name: foo
apps:
...
global-ids:
- foo
- bar
The store will issue a snap declaration assertion allowing the use of the name.
Upon installation, snapd will verify the snap declaration to see if the snap should be able to use the ID. If not, ignore. Otherwise snapd will first create the group (--extrausers is used on Core only):
$ sudo groupadd [--extrausers] \
--system \
--gid <ID from global db> \
snap_<name from global db>
then create the user with that group:
$ sudo useradd [--extrausers] \
--system \
--comment "snapd global user <name from global db>" \
--home-dir /nonexistent -M \
--shell /bin/false \
--uid <ID from global db> \
--gid <ID from global db> \
snap_<name from global db>
then snapd will generate security policy that allows use of this username and group.
At this point there are predictable uids/gids on the system that can safely be used by the squashfs, however if the snap does getent passwd foo it won’t be found because snap_foo was created. snapd should create a mapping such that inside the snap, the user doesn’t need snap_ to be prefixed, but outside it does. While the implementation details are not finalized yet, in the core/base snap, ship a glibc NSS module and add it to /etc/nsswitch.conf (will need to bind mount this on classic). This module will appropriately map the getpwnam(), getpwuid(), etc calls correctly for the snap’s processes.
Locally unasserted snap installs won’t have a snap declaration assertion telling snapd what IDs to create/use. For these, snapd will use a private ID range and will assign the username/group from this pool, locally. This range will not intersect with the global ID range. For example, if the snap.yaml had (and the snap was installed with --dangerous):
global-ids:
- foo
then snapd will take a free ID from the private range and call groupadd/useradd with this this ID.
Use case 1: System user and groups
This case is mostly handled by the current design. The snap declares what it wants to use and it can then chown, setuid, etc to the requested user/group. It does not have to prefix snap_ due to the NSS module.
What is not covered are cases like docker, lxd, libvirt, etc where the application is checking the group membership of a connecting process’ uid and upstream documentation says to add the user to a particular group (that isn’t prefixed with snap_). For example, lxd might have upstream documentation that says to run sudo adduser <user> lxd for <user> to be able to access the lxd socket. The lxd snap might have:
name: lxd
global-ids:
- lxd
which will create the snap_lxd user. Administrators then must use sudo adduser <user> snap_lxd to grant access to the lxd socket to the <user>. The question remains in whether we force upstreams to modify their documentation for snaps or to support non-snap_-prefixed users and groups. One idea would be to use system-global-ids that operates exactly like global-ids except that snap_ isn’t prefixed.
Use case 2: Device access
For things not covered by udev and systemd automatically via shipped configuration, we use setfacl to tag devices via udev rules. Eg, today serial devices are root:dialout 0660 and have no udev ACLs on them. Moving forward, we want a new backend that would be used by interfaces like the serial-port interface to specify that all serial-ports can be accessed via a particular group (eg, snap_serial-port) and the backend would add a udev rule to setfacl the serial-port devices (or use the uaccess mechanism) to ‘snap_serial-port’ such that sudo adduser <user> snap_serial-port would grant access to the serial ports for that user.
For managing the groups, there are two choices:
- put the group in the global ID database and have the backend create a group with the corresponding name and gid. Eg, have
snap_serial-port in the global ID database
- have the backend dynamically create the group specified by the interface. Eg, the serial-port interface tells the backend to create the
snap_serial-port group
Because the group isn’t needed by the snap itself, maintaining the group in the global ID database is not required and keeping them separate may provide a cleaner separation of the use cases.
Use case 3: Opt-in per-snap users/groups
This case is completely handled by the current design when shared global IDs (ie, where developers don’t mind that another snap might use the same IDs it requested) are ok. The snap declares what it wants to use and it can then chown, setuid, etc to the requested user/group. It does not have to prefix snap_ due to the NSS module.
The security team identified that a future improvement of the feature would be to support ‘private global IDs’ for security-concious developers who want guarantees that the IDs it uses are only used by the snap. For example, the snap.yaml might have:
name: example
private-ids:
- foo
- bar
which would create the snap_example_foo and snap_example_bar users which could only be used by this snap. To support squashfs files using predictable uids/gids, then this probably needs to be maintained in a private global ID list (to not expose private or brand snaps).
Use case 4: Chroot environments
This case is completely handled by the current design. The snap declares what it wants to use and the squashfs can reliably have files with uids/gids matching the requested global IDs.
Open questions
- What is the starting uid/gid number of the range. Perhaps 2147483648 (ie, 2^31)?
- What is the private uid/gid range? We don’t need many of these
- How will snapd handle clashes with existing uids/gids on the system and the global database? useradd uses
--non-unique? Panic?
- Finalize yaml declaration
- Make decision on whether or not to support users/groups not prefixed with
snap_
- Make a decision on using the global ID database or dynamically creating users for use case 2 (setfacl/uaccess)
- While not discussed above, consider exposing usernames/groups from the host in the snap’s runtime (eg, the NSS module prefixes
classic_ to uids on the host). Consider how to handle exact overlaps (should classic_ be prepended or not?), uids/gids that have different usernames/group names between the host and snap and usernames/group names that have different uids/gids between the host and snap
- Consider ‘private global IDs’ and how they might be implemented
Components to modify
- snapd
- user backend for creating users/groups
- backend for setfacl/uaccess
- snap declaration verification
- support private ID ranges
- seccomp updates to allow using the uids/gids
- store
- easily view and update the global ID database (preseeded with archive grep of users and groups created in debs via adduser/addgroup/useradd/groupadd)
- easily update snap declarations
- pass global ID database to the review tools
- review tools
- warn if using uid/gid without snap decl (ie, standard human review, like with classic confinement)
- don’t complain if not -all-root anymore, but do complain if uids, gids are not in the declared list
- complain if use IDs from the private ID range