A write through an overlay mount does not create the object on the overlay
itself: overlayfs creates it on the upper layer via vfs_create(), which
reaches only the *inode* LSM hooks, not the *path* hooks the transient
range policy attaches to. The one path hook that does fire sees the overlay
mount, whose superblock belongs to the (managed) mounting namespace and so
looks like it dies with it -- while the upper may be a persistent file
system on which the transient IDs then persist.
Regular file system -- caught by the path hook:
openat(dir_on_persistent_fs, "f", O_CREAT)
path_openat -> ... -> security_path_mknod(path{mnt = persistent mount}) << path hook (attached)
userns_restrict_path_mknod -> validate_mount()
sb->s_user_ns = init -> not below managed (outlives the ns)
mnt_idmap does not remap the transient id (reaches disk)
=> -EPERM BLOCKED
Overlay write -- slips past the path hook (this is the hole):
openat(overlay_mnt/"f", O_CREAT)
path_openat -> ... -> security_path_mknod(path{mnt = OVERLAY mount}) << path hook (attached)
validate_mount(): overlay sb->s_user_ns == managed
=> "dies with the namespace" ALLOW
ovl_create_or_link -> vfs_create(UPPER dentry)
security_inode_create(UPPER inode) << inode hook (NOT attached)
=> no check
inode created on the UPPER fs, owned by the transient uid PERSISTS
Judge the upper at mount time instead, in lsm/sb_kern_mount:
mount("overlay", ..., upperdir=<X>, workdir=<Y>)
do_new_mount -> vfs_get_tree
ovl_fill_super
stamps work/index onto the upper via vfs_mkdir, owned by the
transient mounter -> security_inode_mkdir only (not a path hook)
security_sb_kern_mount(sb) << hook (attached)
userns_restrict_sb_kern_mount:
sb->s_magic != OVERLAYFS_SUPER_MAGIC -> return 0 (not an overlay)
find_managed_userns(sb->s_user_ns) == none -> return 0 (unmanaged mounter)
upper = ((struct ovl_fs*)sb->s_fs_info)->layers[0].mnt
upper == NULL -> return 0 (read-only, no upper)
fs_userns = upper->mnt_sb->s_user_ns
upper dies with the managed ns (e.g. a tmpfs) => return 0 ALLOW
upper persistent, idmap records no transient id => return 0 ALLOW
upper persistent, an idmap extent records a transient => -EPERM REFUSE
The upper has to be judged at mount time because overlayfs performs the
actual upper creations (both the mount-time work/index dirs and every later
copy-up) with internal vfs_create()/vfs_mkdir() calls that reach only the
inode hooks, on the upper's own superblock, where neither the overlay's
ovl_fs nor the mount idmapping is available. Refusing the mount stops every
such write. It cannot undo the work/index dirs ovl_fill_super() already
stamped onto the upper before this hook runs. Reaching those takes an
upperdir and a workdir that already exist on the same persistent,
non-idmapped file system and that the transient IDs may write to: the client
cannot create them itself, since that is what the path hooks deny.
The idmap check walks the upper mount's uid_map and gid_map and permits the
overlay only when no extent records a transient id on disk (ids the idmap
does not map fail the write, so they are fine too). This is what lets a
client back a writable overlay with a persistent but id-mapped upper, e.g.
one handed out by mountfsd, while a plain persistent upper stays refused.
The check keys on whether a writable upper exists (layers[0].mnt is
non-NULL), which is fixed for the superblock's lifetime, not on the mutable
SB_RDONLY flag: an overlay mounted read-only but with an upper can be
remounted read-write later without re-entering sb_kern_mount (remount uses a
different LSM hook), so it must be judged now. A genuinely read-only overlay
has no upper and is left alone. Reading the upper layer needs overlayfs'
private struct ovl_fs/ovl_layer, which are absent from vmlinux.h when
overlayfs is built as a module; they are declared as CO-RE type flavors and
guarded by bpf_core_type_exists(), and the service now orders itself after
modprobe@overlay.service so the module's BTF is present by the time the
program is loaded.
A managed (nsresourced-provisioned) user namespace can therefore only mount
a writable overlay whose upper dies with it (e.g. a tmpfs it set up
itself). Callers that need a writable, persistent (non-tmpfs) upperdir --
mkosi, for one -- should use a regular unprivileged user namespace for that
rather than a transient nsresourced range, since such an overlay is refused
for a managed namespace by design.
test-userns-restrict now mounts real overlays: a read-only overlay, a
writable overlay with a tmpfs upper, and a writable overlay whose upper is
an init-owned but idmapped mount are all allowed, while a writable overlay
with a plain init-owned upper is refused with EPERM. The kernel permits that
last mount without the LSM loaded, so the EPERM is the policy's doing.
109 lines
6.9 KiB
XML
109 lines
6.9 KiB
XML
<?xml version='1.0'?> <!--*-nxml-*-->
|
|
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
|
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
|
|
|
<refentry id="systemd-nsresourced.service" conditional='ENABLE_NSRESOURCED'>
|
|
|
|
<refentryinfo>
|
|
<title>systemd-nsresourced.service</title>
|
|
<productname>systemd</productname>
|
|
</refentryinfo>
|
|
|
|
<refmeta>
|
|
<refentrytitle>systemd-nsresourced.service</refentrytitle>
|
|
<manvolnum>8</manvolnum>
|
|
</refmeta>
|
|
|
|
<refnamediv>
|
|
<refname>systemd-nsresourced.service</refname>
|
|
<refname>systemd-nsresourced</refname>
|
|
<refpurpose>User Namespace Resource Delegation Service</refpurpose>
|
|
</refnamediv>
|
|
|
|
<refsynopsisdiv>
|
|
<para><filename>systemd-nsresourced.service</filename></para>
|
|
<para><filename>/usr/lib/systemd/systemd-nsresourced</filename></para>
|
|
</refsynopsisdiv>
|
|
|
|
<refsect1>
|
|
<title>Description</title>
|
|
|
|
<para><command>systemd-nsresourced</command> is a system service that permits transient delegation of a
|
|
UID/GID range to a user namespace (see <citerefentry
|
|
project='man-pages'><refentrytitle>user_namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>)
|
|
allocated by a client, via a Varlink IPC API.</para>
|
|
|
|
<para>Unprivileged clients may allocate a user namespace, and then request a UID/GID range to be assigned
|
|
to it via this service. The user namespace may then be used to run containers and other sandboxes, and/or
|
|
apply it to an id-mapped mount.</para>
|
|
|
|
<para>Allocations of UIDs/GIDs this way are transient: when a user namespace goes away, its UID/GID range
|
|
is returned to the pool of available ranges. In order to ensure that clients cannot gain persistency in
|
|
their transient UID/GID range a BPF-LSM based policy is enforced that refuses to create inodes — or
|
|
change ownership of them — that would end up owned by a transient UID/GID on a file system outliving the
|
|
user namespace the range was assigned to. Writes to file systems the user namespace allocated itself are
|
|
hence permitted, as those are torn down together with it, and so are writes through id-mapped mounts that
|
|
translate the transient range into something else before it reaches the disk. As a stacking file system
|
|
such as <literal>overlayfs</literal> writes to a separate upper layer that would not be covered by the
|
|
checks on the overlay mount itself, mounting a writable <literal>overlayfs</literal> instance is refused
|
|
for these user namespaces unless its upper layer, too, is torn down together with the user namespace, or
|
|
is an id-mapped mount that translates the transient range away before it reaches the disk.</para>
|
|
|
|
<para><command>systemd-nsresourced</command> automatically ensures that any registered UID ranges show up
|
|
in the system's NSS database via the <ulink url="https://systemd.io/USER_GROUP_API">User/Group Record
|
|
Lookup API via Varlink</ulink>.</para>
|
|
|
|
<para>Currently, only UID/GID ranges consisting of either exactly 1 or exactly 65536 UIDs/GIDs can be
|
|
registered with this service. Moreover, UIDs and GIDs are always allocated together, and
|
|
symmetrically.</para>
|
|
|
|
<para>The allocation API supports <emphasis>delegated ranges</emphasis>: additional UID/GID ranges that
|
|
are mapped 1:1 into the user namespace rather than being translated to a target UID/GID. These delegated
|
|
ranges enable nested user namespace scenarios where a container needs to create child user namespaces
|
|
with their own transient UID ranges. Normally, the kernel restricts which UIDs can be mapped into a user
|
|
namespace to those that are also mapped in the parent. Delegated ranges solve this by pre-allocating
|
|
additional ranges that are visible inside the user namespace and can be used by nested
|
|
<function>AllocateUserRange()</function> calls. Up to 16 delegated ranges can be requested per user
|
|
namespace, each of size 65536. The ranges are allocated from the container UID ranges as per
|
|
<ulink url="https://systemd.io/UIDS-GIDS">Users, Groups, UIDs and GIDs on systemd Systems</ulink>.</para>
|
|
|
|
<para>The allocation API also supports <emphasis>identity mappings</emphasis>: instead of allocating a
|
|
transient UID/GID range, the user namespace can be configured to map the caller's UID/GID to root (UID
|
|
0) inside the namespace, or to itself. Identity mappings can be combined with delegated ranges to enter
|
|
a privileged user namespace from which the container can be set up after which the container can run in
|
|
one of the delegated ranges. Identity mapped users are not subject to BPF-LSM write restrictions unlike
|
|
the transient ranges.</para>
|
|
|
|
<para>Additionally, the allocation API supports mapping the <emphasis>foreign UID range</emphasis> into
|
|
the user namespace. When this option is enabled, the foreign UID range is mapped 1:1 into the user
|
|
namespace, allowing processes inside to access and manipulate files owned by the foreign UID range.</para>
|
|
|
|
<para>The service provides API calls to delegate mounts (referenced via their mount file descriptors as
|
|
per Linux <function>fsmount()</function> API), to pass ownership of a cgroup subtree to the user
|
|
namespace and to delegate a virtual Ethernet device pair to the user namespace. When used in combination
|
|
this is sufficient to implement fully unprivileged container environments, as implemented by
|
|
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>, fully
|
|
unprivileged <varname>RootImage=</varname> (see
|
|
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>) or
|
|
fully unprivileged disk image tools such as
|
|
<citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
|
|
|
<para>This service provides one <ulink url="https://varlink.org/">Varlink</ulink> service:
|
|
<constant>io.systemd.NamespaceResource</constant> allows registering user namespaces, and assign mounts,
|
|
cgroups and network interfaces to it.</para>
|
|
</refsect1>
|
|
|
|
<refsect1>
|
|
<title>See Also</title>
|
|
<para><simplelist type="inline">
|
|
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
|
<member><citerefentry><refentrytitle>systemd-mountfsd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
|
<member><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
|
<member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
|
<member><citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
|
<member><citerefentry project='man-pages'><refentrytitle>user_namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
|
</simplelist></para>
|
|
</refsect1>
|
|
</refentry>
|