Merge pull request #26826 from poettering/mntfsd
unprivileged DDI mounts + dynamic userns range allocation via IPC
This commit is contained in:
@@ -268,6 +268,18 @@ Features:
|
||||
* use udev rule networkd ownership property to take ownership of network
|
||||
interfaces nspawn creates
|
||||
|
||||
* mountfsd/nsresourced
|
||||
- userdb: maybe allow callers to map one uid to their own uid
|
||||
- bpflsm: allow writes if resulting UID on disk would be userns' owner UID
|
||||
- make encrypted DDIs work (password…)
|
||||
- add API for creating a new file system from scratch (together with some
|
||||
dm-integrity/HMAC key). Should probably work using systemd-repart (access
|
||||
via varlink).
|
||||
- add api to make an existing file "trusted" via dm-integry/HMAC key
|
||||
- port: portabled
|
||||
- port: tmpfiles, sysusers and similar
|
||||
- lets see if we can make runtime bind mounts into unpriv nspawn work
|
||||
|
||||
* add a kernel cmdline switch (and cred?) for marking a system to be
|
||||
"headless", in which case we never open /dev/console for reading, only for
|
||||
writing. This would then mean: systemd-firstboot would process creds but not
|
||||
|
||||
@@ -676,3 +676,21 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as
|
||||
service. Takes a file system path: if specified the tool will listen on an
|
||||
`AF_UNIX` stream socket on the specified path in addition to whatever else it
|
||||
would listen on.
|
||||
|
||||
`systemd-mountfsd`:
|
||||
|
||||
* `$SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES` – takes a boolean argument. If true
|
||||
disk images from the usual disk image directories (`/var/lib/machines/`,
|
||||
`/var/lib/confexts/`, …) will be considered "trusted", i.e. are validated
|
||||
with a more relaxed image policy (typically not requiring Verity signature
|
||||
checking) than those from other directories (where Verity signature checks
|
||||
are mandatory). If false all images are treated the same, regardless if
|
||||
placed in the usual disk image directories or elsewhere. If not set defaults
|
||||
to a compile time setting.
|
||||
|
||||
* `$SYSTEMD_MOUNTFSD_IMAGE_POLICY_TRUSTED`,
|
||||
`$SYSTEMD_MOUNTFSD_IMAGE_POLICY_UNTRUSTED` – the default image policy to
|
||||
apply to trusted and untrusted disk images. An image is considered trusted if
|
||||
placed in a trusted disk image directory (see above), or if suitable polkit
|
||||
authentication was acquired. See `systemd.image-policy(7)` for the valid
|
||||
syntax for image policy strings.
|
||||
|
||||
@@ -999,6 +999,7 @@ manpages = [
|
||||
['systemd-measure', '1', [], 'HAVE_TPM2 HAVE_BLKID HAVE_OPENSSL'],
|
||||
['systemd-modules-load.service', '8', ['systemd-modules-load'], 'HAVE_KMOD'],
|
||||
['systemd-mount', '1', ['systemd-umount'], ''],
|
||||
['systemd-mountfsd.service', '8', ['systemd-mountfsd'], 'ENABLE_MOUNTFSD'],
|
||||
['systemd-network-generator.service', '8', ['systemd-network-generator'], ''],
|
||||
['systemd-networkd-wait-online.service',
|
||||
'8',
|
||||
@@ -1007,6 +1008,10 @@ manpages = [
|
||||
['systemd-networkd.service', '8', ['systemd-networkd'], 'ENABLE_NETWORKD'],
|
||||
['systemd-notify', '1', [], ''],
|
||||
['systemd-nspawn', '1', [], ''],
|
||||
['systemd-nsresourced.service',
|
||||
'8',
|
||||
['systemd-nsresourced'],
|
||||
'ENABLE_NSRESOURCED'],
|
||||
['systemd-oomd.service', '8', ['systemd-oomd'], 'ENABLE_OOMD'],
|
||||
['systemd-path', '1', [], ''],
|
||||
['systemd-pcrlock',
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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-mountfsd.service" conditional='ENABLE_MOUNTFSD'>
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-mountfsd.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-mountfsd.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-mountfsd.service</refname>
|
||||
<refname>systemd-mountfsd</refname>
|
||||
<refpurpose>Disk Image File System Mount Service</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>systemd-mountfsd.service</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-mountfsd</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-mountfsd</command> is a system service that dissects disk images, and returns mount
|
||||
file descriptors for the file systems contained therein to clients, via a Varlink IPC API.</para>
|
||||
|
||||
<para>The disk images provided must contain a raw file system image or must follow the <ulink
|
||||
url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification/">Discoverable
|
||||
Partitions Specification</ulink>. Before mounting any file systems authenticity of the disk image is
|
||||
established in one or a combination of the following ways:</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem><para>If the disk image is located in a regular file in one of the directories
|
||||
<filename>/var/lib/machines/</filename>, <filename>/var/lib/portables/</filename>,
|
||||
<filename>/var/lib/extensions/</filename>, <filename>/var/lib/confexts/</filename> or their
|
||||
counterparts in the <filename>/etc/</filename>, <filename>/run/</filename>,
|
||||
<filename>/usr/lib/</filename> it is assumed to be trusted.</para></listitem>
|
||||
|
||||
<listitem><para>If the disk image contains a Verity enabled disk image, along with a signature
|
||||
partition with a key in the kernel keyring or in <filename>/etc/verity.d/</filename> (and related
|
||||
directories) the disk image is considered trusted.</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>This service provides one <ulink url="https://varlink.org/">Varlink</ulink> service:
|
||||
<constant>io.systemd.MountFileSystem</constant> which accepts a file descriptor to a regular file or
|
||||
block device, and returns a number of file descriptors referring to an <function>fsmount()</function>
|
||||
file descriptor the client may then attach to a path of their choice.</para>
|
||||
|
||||
<para>The returned mounts are automatically allowlisted in the per-user-namespace allowlist maintained by
|
||||
<citerefentry><refentrytitle>systemd-nsresourced.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>The file systems are automatically fsck'ed before mounting.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-nsresourced.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
@@ -118,6 +118,28 @@
|
||||
keeps track of running containers, and provides programming interfaces to interact with them.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Unprivileged Operation</title>
|
||||
|
||||
<para><command>systemd-nspawn</command> may be invoked with or without privileges. The full functionality
|
||||
is currently only available when invoked with privileges. When invoked without privileges, various
|
||||
limitations apply, including, but not limited to:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>Only disk image based containers are supported (i.e. <option>--image=</option>).
|
||||
Directory based ones (i.e. <option>--directory=</option>) are not supported.</para></listitem>
|
||||
|
||||
<listitem><para>Machine registration via <option>--machine=</option> is not supported.</para></listitem>
|
||||
|
||||
<listitem><para>Only <option>--private-network</option> and <option>--network-veth</option> networking modes are supported.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>When running in unprivileged mode, some needed functionality is provided via
|
||||
<citerefentry><refentrytitle>systemd-mountfsd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
and
|
||||
<citerefentry><refentrytitle>systemd-nsresourced.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
@@ -1910,6 +1932,8 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
|
||||
<member><citerefentry><refentrytitle>systemd.slice</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>importctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-mountfsd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-nsresourced.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project='url'><refentrytitle url='https://btrfs.readthedocs.io/en/latest/btrfs.html'>btrfs</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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 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 ensures that user namespaces set up
|
||||
this way can only write to file systems they allocate themselves or that are explicitly allowlisted via
|
||||
<command>systemd-nsresourced</command>.</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 service provides API calls to allowlist 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>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-mountfsd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>user_namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
+81
@@ -272,6 +272,8 @@ conf.set_quoted('SYSTEMD_TEST_DATA', testdata_dir)
|
||||
conf.set_quoted('SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH', bindir / 'systemd-tty-ask-password-agent')
|
||||
conf.set_quoted('SYSTEMD_UPDATE_HELPER_PATH', libexecdir / 'systemd-update-helper')
|
||||
conf.set_quoted('SYSTEMD_USERWORK_PATH', libexecdir / 'systemd-userwork')
|
||||
conf.set_quoted('SYSTEMD_MOUNTWORK_PATH', libexecdir / 'systemd-mountwork')
|
||||
conf.set_quoted('SYSTEMD_NSRESOURCEWORK_PATH', libexecdir / 'systemd-nsresourcework')
|
||||
conf.set_quoted('SYSTEMD_VERITYSETUP_PATH', libexecdir / 'systemd-veritysetup')
|
||||
conf.set_quoted('SYSTEM_CONFIG_UNIT_DIR', pkgsysconfdir / 'system')
|
||||
conf.set_quoted('SYSTEM_DATA_UNIT_DIR', systemunitdir)
|
||||
@@ -1597,6 +1599,8 @@ conf.set10('ENABLE_REMOTE', have)
|
||||
feature = get_option('vmspawn').disable_auto_if(conf.get('BUILD_MODE_DEVELOPER') == 0)
|
||||
conf.set10('ENABLE_VMSPAWN', feature.allowed())
|
||||
|
||||
conf.set10('DEFAULT_MOUNTFSD_TRUSTED_DIRECTORIES', get_option('default-mountfsd-trusted-directories'))
|
||||
|
||||
foreach term : ['analyze',
|
||||
'backlight',
|
||||
'binfmt',
|
||||
@@ -1617,8 +1621,10 @@ foreach term : ['analyze',
|
||||
'localed',
|
||||
'logind',
|
||||
'machined',
|
||||
'mountfsd',
|
||||
'networkd',
|
||||
'nscd',
|
||||
'nsresourced',
|
||||
'nss-myhostname',
|
||||
'nss-systemd',
|
||||
'oomd',
|
||||
@@ -1839,6 +1845,79 @@ conf.set10('ENABLE_UKIFY', want_ukify)
|
||||
#####################################################################
|
||||
|
||||
check_efi_alignment_py = find_program('tools/check-efi-alignment.py')
|
||||
|
||||
#####################################################################
|
||||
|
||||
use_provided_vmlinux_h = false
|
||||
use_generated_vmlinux_h = false
|
||||
provided_vmlinux_h_path = get_option('vmlinux-h-path')
|
||||
|
||||
# For the more complex BPF programs we really want a vmlinux.h (which is arch
|
||||
# specific, but only somewhat bound to kernel version). Ideally the kernel
|
||||
# development headers would ship that, but right now they don't. Hence address
|
||||
# this in two ways:
|
||||
#
|
||||
# 1. Provide a vmlinux.h at build time
|
||||
# 2. Generate the file on the fly where possible (which requires /sys/ to be mounted)
|
||||
#
|
||||
# We generally prefer the former (to support reproducible builds), but will
|
||||
# fallback to the latter.
|
||||
|
||||
if conf.get('BPF_FRAMEWORK') == 1
|
||||
enable_vmlinux_h = get_option('vmlinux-h')
|
||||
|
||||
if enable_vmlinux_h == 'auto'
|
||||
if provided_vmlinux_h_path != ''
|
||||
use_provided_vmlinux_h = true
|
||||
elif fs.exists('/sys/kernel/btf/vmlinux') and \
|
||||
bpftool.found() and \
|
||||
(host_machine.cpu_family() == build_machine.cpu_family()) and \
|
||||
host_machine.cpu_family() in ['x86_64', 'aarch64']
|
||||
|
||||
# We will only generate a vmlinux.h from the running
|
||||
# kernel if the host and build machine are of the same
|
||||
# family. Also for now we focus on x86_64 and aarch64,
|
||||
# since other archs don't seem to be ready yet.
|
||||
|
||||
use_generated_vmlinux_h = true
|
||||
endif
|
||||
elif enable_vmlinux_h == 'provided'
|
||||
use_provided_vmlinux_h = true
|
||||
elif enable_vmlinux_h == 'generated'
|
||||
if not fs.exists('/sys/kernel/btf/vmlinux')
|
||||
error('BTF data from kernel not available (/sys/kernel/btf/vmlinux missing), cannot generate vmlinux.h, but was asked to.')
|
||||
endif
|
||||
if not bpftool.found()
|
||||
error('bpftool not available, cannot generate vmlinux.h, but was asked to.')
|
||||
endif
|
||||
use_generated_vmlinux_h = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if use_provided_vmlinux_h
|
||||
if not fs.exists(provided_vmlinux_h_path)
|
||||
error('Path to provided vmlinux.h does not exist.')
|
||||
endif
|
||||
vmlinux_h_dependency = []
|
||||
bpf_o_unstripped_cmd += ['-I' + fs.parent(provided_vmlinux_h_path)]
|
||||
message('Using provided @0@'.format(provided_vmlinux_h_path))
|
||||
elif use_generated_vmlinux_h
|
||||
vmlinux_h_dependency = custom_target(
|
||||
'vmlinux.h',
|
||||
output: 'vmlinux.h',
|
||||
command : [ bpftool, 'btf', 'dump', 'file', '/sys/kernel/btf/vmlinux', 'format', 'c' ],
|
||||
capture : true)
|
||||
|
||||
bpf_o_unstripped_cmd += ['-I' + fs.parent(vmlinux_h_dependency.full_path())]
|
||||
message('Using generated @0@'.format(vmlinux_h_dependency.full_path()))
|
||||
else
|
||||
message('Using neither provided nor generated vmlinux.h, some features will not be available.')
|
||||
endif
|
||||
|
||||
conf.set10('HAVE_VMLINUX_H', use_provided_vmlinux_h or use_generated_vmlinux_h)
|
||||
|
||||
#####################################################################
|
||||
|
||||
check_version_history_py = find_program('tools/check-version-history.py')
|
||||
elf2efi_py = find_program('tools/elf2efi.py')
|
||||
export_dbus_interfaces_py = find_program('tools/dbus_exporter.py')
|
||||
@@ -2201,11 +2280,13 @@ subdir('src/locale')
|
||||
subdir('src/login')
|
||||
subdir('src/machine')
|
||||
subdir('src/machine-id-setup')
|
||||
subdir('src/mountfsd')
|
||||
subdir('src/modules-load')
|
||||
subdir('src/mount')
|
||||
subdir('src/network')
|
||||
subdir('src/notify')
|
||||
subdir('src/nspawn')
|
||||
subdir('src/nsresourced')
|
||||
subdir('src/nss-myhostname')
|
||||
subdir('src/nss-mymachines')
|
||||
subdir('src/nss-resolve')
|
||||
|
||||
+13
-2
@@ -124,6 +124,8 @@ option('portabled', type : 'boolean',
|
||||
description : 'install the systemd-portabled stack')
|
||||
option('sysext', type : 'boolean',
|
||||
description : 'install the systemd-sysext stack')
|
||||
option('mountfsd', type : 'boolean',
|
||||
description : 'install the systemd-mountfsd stack')
|
||||
option('userdb', type : 'boolean',
|
||||
description : 'install the systemd-userdbd stack')
|
||||
option('homed', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
|
||||
@@ -140,6 +142,8 @@ option('remote', type : 'feature', deprecated : { 'true' : 'enabled', 'false' :
|
||||
description : 'support for "journal over the network"')
|
||||
option('create-log-dirs', type : 'boolean',
|
||||
description : 'create /var/log/journal{,/remote}')
|
||||
option('nsresourced', type : 'boolean',
|
||||
description : 'install the systemd-nsresourced stack')
|
||||
option('nss-myhostname', type : 'boolean',
|
||||
description : 'install nss-myhostname module')
|
||||
option('nss-mymachines', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
|
||||
@@ -519,6 +523,13 @@ option('analyze', type: 'boolean', value: true,
|
||||
description : 'install systemd-analyze')
|
||||
|
||||
option('bpf-compiler', type : 'combo', choices : ['clang', 'gcc'],
|
||||
description: 'compiler used to build BPF programs')
|
||||
description : 'compiler used to build BPF programs')
|
||||
option('bpf-framework', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
|
||||
description: 'build BPF programs from source code in restricted C')
|
||||
description : 'build BPF programs from source code in restricted C')
|
||||
option('vmlinux-h', type : 'combo', choices : ['auto', 'provided', 'generated', 'disabled'],
|
||||
description : 'which vmlinux.h to use')
|
||||
option('vmlinux-h-path', type : 'string', value : '',
|
||||
description : 'path to vmlinux.h to use')
|
||||
|
||||
option('default-mountfsd-trusted-directories', type : 'boolean', value: false,
|
||||
description : 'controls whether mountfsd should apply a relaxed policy on DDIs in system DDI directories')
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
#
|
||||
# This config file is installed as part of systemd.
|
||||
# It may be freely copied and edited (following the MIT No Attribution license).
|
||||
#
|
||||
# To make local modifications, one of the following methods may be used:
|
||||
# 1. add a drop-in file that extends this file by creating the
|
||||
# /etc/systemd/network/80-namespace-ns.network.d/ directory and creating a
|
||||
# new .conf file there.
|
||||
# 2. copy this file into /etc/systemd/network or one of the other paths checked
|
||||
# by systemd-networkd and edit it there.
|
||||
# This file should not be edited in place, because it'll be overwritten on upgrades.
|
||||
|
||||
# This network file matches the host-side of the virtual Ethernet link
|
||||
# created by systemd-nsresourced's network support. See systemd-nsresourced(1) for
|
||||
# details.
|
||||
|
||||
[Match]
|
||||
Kind=veth
|
||||
Name=ns-*
|
||||
|
||||
[Network]
|
||||
# Default to using a /28 prefix, giving up to 13 addresses per namespace
|
||||
Address=0.0.0.0/28
|
||||
LinkLocalAddressing=yes
|
||||
DHCPServer=yes
|
||||
IPMasquerade=both
|
||||
LLDP=yes
|
||||
EmitLLDP=customer-bridge
|
||||
IPv6AcceptRA=no
|
||||
IPv6SendRA=yes
|
||||
@@ -11,6 +11,7 @@ if conf.get('ENABLE_NETWORKD') == 1
|
||||
'80-container-ve.link',
|
||||
'80-container-vz.network',
|
||||
'80-container-vz.link',
|
||||
'80-namespace-ns.network',
|
||||
'80-vm-vt.network',
|
||||
'80-vm-vt.link',
|
||||
'80-wifi-adhoc.network',
|
||||
|
||||
@@ -23,9 +23,11 @@ enable systemd-homed.service
|
||||
enable systemd-homed-activate.service
|
||||
enable systemd-homed-firstboot.service
|
||||
enable systemd-journald-audit.socket
|
||||
enable systemd-mountfsd.socket
|
||||
enable systemd-network-generator.service
|
||||
enable systemd-networkd.service
|
||||
enable systemd-networkd-wait-online.service
|
||||
enable systemd-nsresourced.socket
|
||||
enable systemd-pstore.service
|
||||
enable systemd-resolved.service
|
||||
enable systemd-sysext.service
|
||||
|
||||
+48
-1
@@ -22,6 +22,7 @@
|
||||
#include "log.h"
|
||||
#include "login-util.h"
|
||||
#include "macro.h"
|
||||
#include "missing_fs.h"
|
||||
#include "missing_magic.h"
|
||||
#include "missing_threads.h"
|
||||
#include "mkdir.h"
|
||||
@@ -39,6 +40,38 @@
|
||||
#include "user-util.h"
|
||||
#include "xattr-util.h"
|
||||
|
||||
int cg_path_open(const char *controller, const char *path) {
|
||||
_cleanup_free_ char *fs = NULL;
|
||||
int r;
|
||||
|
||||
r = cg_get_path(controller, path, /* item=*/ NULL, &fs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC));
|
||||
}
|
||||
|
||||
int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) {
|
||||
_cleanup_close_ int fsfd = -EBADF;
|
||||
|
||||
if (cgroupfs_fd < 0) {
|
||||
fsfd = open("/sys/fs/cgroup", O_CLOEXEC|O_DIRECTORY);
|
||||
if (fsfd < 0)
|
||||
return -errno;
|
||||
|
||||
cgroupfs_fd = fsfd;
|
||||
}
|
||||
|
||||
cg_file_handle fh = CG_FILE_HANDLE_INIT;
|
||||
CG_FILE_HANDLE_CGROUPID(fh) = id;
|
||||
|
||||
int fd = open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int cg_enumerate_items(const char *controller, const char *path, FILE **ret, const char *item) {
|
||||
_cleanup_free_ char *fs = NULL;
|
||||
FILE *f;
|
||||
@@ -1404,7 +1437,7 @@ int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
|
||||
|
||||
int cg_path_get_cgroupid(const char *path, uint64_t *ret) {
|
||||
cg_file_handle fh = CG_FILE_HANDLE_INIT;
|
||||
int mnt_id = -1;
|
||||
int mnt_id;
|
||||
|
||||
assert(path);
|
||||
assert(ret);
|
||||
@@ -1418,6 +1451,20 @@ int cg_path_get_cgroupid(const char *path, uint64_t *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cg_fd_get_cgroupid(int fd, uint64_t *ret) {
|
||||
cg_file_handle fh = CG_FILE_HANDLE_INIT;
|
||||
int mnt_id = -1;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
if (name_to_handle_at(fd, "", &fh.file_handle, &mnt_id, AT_EMPTY_PATH) < 0)
|
||||
return -errno;
|
||||
|
||||
*ret = CG_FILE_HANDLE_CGROUPID(fh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cg_path_get_session(const char *path, char **ret_session) {
|
||||
_cleanup_free_ char *unit = NULL;
|
||||
char *start, *end;
|
||||
|
||||
+10
-1
@@ -180,6 +180,9 @@ typedef enum CGroupUnified {
|
||||
* generate paths with multiple adjacent / removed.
|
||||
*/
|
||||
|
||||
int cg_path_open(const char *controller, const char *path);
|
||||
int cg_cgroupid_open(int fsfd, uint64_t id);
|
||||
|
||||
int cg_enumerate_processes(const char *controller, const char *path, FILE **ret);
|
||||
int cg_read_pid(FILE *f, pid_t *ret);
|
||||
int cg_read_pidref(FILE *f, PidRef *ret);
|
||||
@@ -267,6 +270,7 @@ int cg_is_empty_recursive(const char *controller, const char *path);
|
||||
int cg_get_root_path(char **path);
|
||||
|
||||
int cg_path_get_cgroupid(const char *path, uint64_t *ret);
|
||||
int cg_fd_get_cgroupid(int fd, uint64_t *ret);
|
||||
int cg_path_get_session(const char *path, char **ret_session);
|
||||
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid);
|
||||
int cg_path_get_unit(const char *path, char **ret_unit);
|
||||
@@ -352,5 +356,10 @@ typedef union {
|
||||
uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)];
|
||||
} cg_file_handle;
|
||||
|
||||
#define CG_FILE_HANDLE_INIT { .file_handle.handle_bytes = sizeof(uint64_t) }
|
||||
#define CG_FILE_HANDLE_INIT \
|
||||
(cg_file_handle) { \
|
||||
.file_handle.handle_bytes = sizeof(uint64_t), \
|
||||
.file_handle.handle_type = FILEID_KERNFS, \
|
||||
}
|
||||
|
||||
#define CG_FILE_HANDLE_CGROUPID(fh) (*(uint64_t*) (fh).file_handle.f_handle)
|
||||
|
||||
@@ -17,7 +17,7 @@ static inline int make_lock_file(const char *p, int operation, LockFile *ret) {
|
||||
int make_lock_file_for(const char *p, int operation, LockFile *ret);
|
||||
void release_lock_file(LockFile *f);
|
||||
|
||||
#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF }
|
||||
#define LOCK_FILE_INIT (LockFile) { .dir_fd = -EBADF, .fd = -EBADF }
|
||||
|
||||
/* POSIX locks with the same interface as flock(). */
|
||||
int posix_lock(int fd, int operation);
|
||||
|
||||
@@ -111,3 +111,8 @@ assert_cc(FS_PROJINHERIT_FL == 0x20000000);
|
||||
#else
|
||||
assert_cc(FS_KEY_DESCRIPTOR_SIZE == 8);
|
||||
#endif
|
||||
|
||||
/* linux/exportfs.h */
|
||||
#ifndef FILEID_KERNFS
|
||||
#define FILEID_KERNFS 0xfe
|
||||
#endif
|
||||
|
||||
@@ -34,6 +34,14 @@ const struct namespace_info namespace_info[] = {
|
||||
|
||||
#define pid_namespace_path(pid, type) procfs_file_alloca(pid, namespace_info[type].proc_path)
|
||||
|
||||
static NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) {
|
||||
for (NamespaceType t = 0; t < _NAMESPACE_TYPE_MAX; t++)
|
||||
if (((namespace_info[t].clone_flag ^ clone_flag) & (CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWUTS|CLONE_NEWTIME)) == 0)
|
||||
return t;
|
||||
|
||||
return _NAMESPACE_TYPE_INVALID;
|
||||
}
|
||||
|
||||
int namespace_open(
|
||||
pid_t pid,
|
||||
int *ret_pidns_fd,
|
||||
@@ -214,6 +222,88 @@ int detach_mount_namespace(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int detach_mount_namespace_harder(uid_t target_uid, gid_t target_gid) {
|
||||
int r;
|
||||
|
||||
/* Tried detach_mount_namespace() first. If that doesn't work due to permissions, opens up an
|
||||
* unprivileged user namespace with a mapping of the originating UID/GID to the specified target
|
||||
* UID/GID. Then, tries detach_mount_namespace() again.
|
||||
*
|
||||
* Or in other words: tries much harder to get a mount namespace, making use of unprivileged user
|
||||
* namespaces if need be.
|
||||
*
|
||||
* Note that after this function completed:
|
||||
*
|
||||
* → if we had privs, afterwards uids/gids on files and processes are as before
|
||||
*
|
||||
* → if we had no privs, our own id and all our files will show up owned by target_uid/target_gid,
|
||||
* and everything else owned by nobody.
|
||||
*
|
||||
* Yes, that's quite a difference. */
|
||||
|
||||
if (!uid_is_valid(target_uid))
|
||||
return -EINVAL;
|
||||
if (!gid_is_valid(target_gid))
|
||||
return -EINVAL;
|
||||
|
||||
r = detach_mount_namespace();
|
||||
if (r != -EPERM)
|
||||
return r;
|
||||
|
||||
if (unshare(CLONE_NEWUSER) < 0)
|
||||
return log_debug_errno(errno, "Failed to acquire user namespace: %m");
|
||||
|
||||
r = write_string_filef("/proc/self/uid_map", 0,
|
||||
UID_FMT " " UID_FMT " 1\n", target_uid, getuid());
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write uid map: %m");
|
||||
|
||||
r = write_string_file("/proc/self/setgroups", "deny", 0);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write setgroups file: %m");
|
||||
|
||||
r = write_string_filef("/proc/self/gid_map", 0,
|
||||
GID_FMT " " GID_FMT " 1\n", target_gid, getgid());
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write gid map: %m");
|
||||
|
||||
return detach_mount_namespace();
|
||||
}
|
||||
|
||||
int detach_mount_namespace_userns(int userns_fd) {
|
||||
int r;
|
||||
|
||||
assert(userns_fd >= 0);
|
||||
|
||||
if (setns(userns_fd, CLONE_NEWUSER) < 0)
|
||||
return log_debug_errno(errno, "Failed to join user namespace: %m");
|
||||
|
||||
r = reset_uid_gid();
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to become root in user namespace: %m");
|
||||
|
||||
return detach_mount_namespace();
|
||||
}
|
||||
|
||||
int userns_acquire_empty(void) {
|
||||
_cleanup_(sigkill_waitp) pid_t pid = 0;
|
||||
_cleanup_close_ int userns_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
/* Child. We do nothing here, just freeze until somebody kills us. */
|
||||
freeze();
|
||||
|
||||
r = namespace_open(pid, NULL, NULL, NULL, &userns_fd, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open userns fd: %m");
|
||||
|
||||
return TAKE_FD(userns_fd);
|
||||
}
|
||||
|
||||
int userns_acquire(const char *uid_map, const char *gid_map) {
|
||||
char path[STRLEN("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1];
|
||||
_cleanup_(sigkill_waitp) pid_t pid = 0;
|
||||
@@ -344,3 +434,50 @@ int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_r
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int namespace_open_by_type(NamespaceType type) {
|
||||
const char *p;
|
||||
int fd;
|
||||
|
||||
assert(type >= 0);
|
||||
assert(type < _NAMESPACE_TYPE_MAX);
|
||||
|
||||
p = pid_namespace_path(0, type);
|
||||
|
||||
fd = RET_NERRNO(open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC));
|
||||
if (fd == -ENOENT && proc_mounted() == 0)
|
||||
return -ENOSYS;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int is_our_namespace(int fd, NamespaceType request_type) {
|
||||
int clone_flag;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
clone_flag = ioctl(fd, NS_GET_NSTYPE);
|
||||
if (clone_flag < 0)
|
||||
return -errno;
|
||||
|
||||
NamespaceType found_type = clone_flag_to_namespace_type(clone_flag);
|
||||
if (found_type < 0)
|
||||
return -EBADF; /* Uh? Unknown namespace type? */
|
||||
|
||||
if (request_type >= 0 && request_type != found_type) /* It's a namespace, but not of the right type? */
|
||||
return -EUCLEAN;
|
||||
|
||||
struct stat st_fd, st_ours;
|
||||
if (fstat(fd, &st_fd) < 0)
|
||||
return -errno;
|
||||
|
||||
const char *p = pid_namespace_path(0, found_type);
|
||||
if (stat(p, &st_ours) < 0) {
|
||||
if (errno == ENOENT)
|
||||
return proc_mounted() == 0 ? -ENOSYS : -ENOENT;
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return stat_inode_same(&st_ours, &st_fd);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
|
||||
int fd_is_ns(int fd, unsigned long nsflag);
|
||||
|
||||
int detach_mount_namespace(void);
|
||||
int detach_mount_namespace_harder(uid_t target_uid, gid_t target_gid);
|
||||
int detach_mount_namespace_userns(int userns_fd);
|
||||
|
||||
static inline bool userns_shift_range_valid(uid_t shift, uid_t range) {
|
||||
/* Checks that the specified userns range makes sense, i.e. contains at least one UID, and the end
|
||||
@@ -50,8 +52,15 @@ static inline bool userns_shift_range_valid(uid_t shift, uid_t range) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int userns_acquire_empty(void);
|
||||
int userns_acquire(const char *uid_map, const char *gid_map);
|
||||
|
||||
int netns_acquire(void);
|
||||
|
||||
int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type);
|
||||
|
||||
int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range);
|
||||
|
||||
int namespace_open_by_type(NamespaceType type);
|
||||
|
||||
int is_our_namespace(int fd, NamespaceType type);
|
||||
|
||||
+110
-3
@@ -10,6 +10,7 @@
|
||||
#include "format-util.h"
|
||||
#include "macro.h"
|
||||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
#include "sort-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "uid-range.h"
|
||||
@@ -204,7 +205,7 @@ int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_rang
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_load_userns(UIDRange **ret, const char *path) {
|
||||
int uid_range_load_userns(UIDRange **ret, const char *path, UIDRangeUsernsMode mode) {
|
||||
_cleanup_(uid_range_freep) UIDRange *range = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
int r;
|
||||
@@ -216,9 +217,11 @@ int uid_range_load_userns(UIDRange **ret, const char *path) {
|
||||
* To simplify things this will modify the passed array in case of later failure. */
|
||||
|
||||
assert(ret);
|
||||
assert(mode >= 0);
|
||||
assert(mode < _UID_RANGE_USERNS_MODE_MAX);
|
||||
|
||||
if (!path)
|
||||
path = "/proc/self/uid_map";
|
||||
path = IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "/proc/self/uid_map" : "/proc/self/gid_map";
|
||||
|
||||
f = fopen(path, "re");
|
||||
if (!f) {
|
||||
@@ -243,7 +246,11 @@ int uid_range_load_userns(UIDRange **ret, const char *path) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = uid_range_add_internal(&range, uid_base, uid_range, /* coalesce = */ false);
|
||||
r = uid_range_add_internal(
|
||||
&range,
|
||||
IN_SET(mode, UID_RANGE_USERNS_INSIDE, GID_RANGE_USERNS_INSIDE) ? uid_base : uid_shift,
|
||||
uid_range,
|
||||
/* coalesce = */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -253,3 +260,103 @@ int uid_range_load_userns(UIDRange **ret, const char *path) {
|
||||
*ret = TAKE_PTR(range);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret) {
|
||||
_cleanup_(close_pairp) int pfd[2] = EBADF_PAIR;
|
||||
_cleanup_(sigkill_waitp) pid_t pid = 0;
|
||||
ssize_t n;
|
||||
char x;
|
||||
int r;
|
||||
|
||||
assert(userns_fd >= 0);
|
||||
assert(mode >= 0);
|
||||
assert(mode < _UID_RANGE_USERNS_MODE_MAX);
|
||||
assert(ret);
|
||||
|
||||
if (pipe2(pfd, O_CLOEXEC) < 0)
|
||||
return -errno;
|
||||
|
||||
r = safe_fork_full(
|
||||
"(sd-mkuserns)",
|
||||
/* stdio_fds= */ NULL,
|
||||
(int[]) { pfd[1], userns_fd }, 2,
|
||||
FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL,
|
||||
&pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* Child. */
|
||||
|
||||
if (setns(userns_fd, CLONE_NEWUSER) < 0) {
|
||||
log_debug_errno(errno, "Failed to join userns: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
userns_fd = safe_close(userns_fd);
|
||||
|
||||
n = write(pfd[1], &(const char) { 'x' }, 1);
|
||||
if (n < 0) {
|
||||
log_debug_errno(errno, "Failed to write to fifo: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
assert(n == 1);
|
||||
|
||||
freeze();
|
||||
}
|
||||
|
||||
pfd[1] = safe_close(pfd[1]);
|
||||
|
||||
n = read(pfd[0], &x, 1);
|
||||
if (n < 0)
|
||||
return -errno;
|
||||
if (n == 0)
|
||||
return -EPROTO;
|
||||
assert(n == 1);
|
||||
assert(x == 'x');
|
||||
|
||||
const char *p = procfs_file_alloca(
|
||||
pid,
|
||||
IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "uid_map" : "gid_map");
|
||||
|
||||
return uid_range_load_userns(ret, p, mode);
|
||||
}
|
||||
|
||||
bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr) {
|
||||
|
||||
if (!range)
|
||||
return false;
|
||||
|
||||
/* Avoid overflow */
|
||||
if (start > UINT32_MAX - nr)
|
||||
nr = UINT32_MAX - start;
|
||||
|
||||
if (nr == 0)
|
||||
return false;
|
||||
|
||||
FOREACH_ARRAY(entry, range->entries, range->n_entries)
|
||||
if (start < entry->start + entry->nr &&
|
||||
start + nr >= entry->start)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool uid_range_equal(const UIDRange *a, const UIDRange *b) {
|
||||
if (a == b)
|
||||
return true;
|
||||
|
||||
if (!a || !b)
|
||||
return false;
|
||||
|
||||
if (a->n_entries != b->n_entries)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < a->n_entries; i++) {
|
||||
if (a->entries[i].start != b->entries[i].start)
|
||||
return false;
|
||||
if (a->entries[i].nr != b->entries[i].nr)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+43
-1
@@ -33,4 +33,46 @@ static inline bool uid_range_contains(const UIDRange *range, uid_t uid) {
|
||||
|
||||
int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range);
|
||||
|
||||
int uid_range_load_userns(UIDRange **ret, const char *path);
|
||||
static inline size_t uid_range_entries(const UIDRange *range) {
|
||||
return range ? range->n_entries : 0;
|
||||
}
|
||||
|
||||
static inline unsigned uid_range_size(const UIDRange *range) {
|
||||
if (!range)
|
||||
return 0;
|
||||
|
||||
unsigned n = 0;
|
||||
|
||||
FOREACH_ARRAY(e, range->entries, range->n_entries)
|
||||
n += e->nr;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static inline bool uid_range_is_empty(const UIDRange *range) {
|
||||
|
||||
if (!range)
|
||||
return true;
|
||||
|
||||
FOREACH_ARRAY(e, range->entries, range->n_entries)
|
||||
if (e->nr > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uid_range_equal(const UIDRange *a, const UIDRange *b);
|
||||
|
||||
typedef enum UIDRangeUsernsMode {
|
||||
UID_RANGE_USERNS_INSIDE,
|
||||
UID_RANGE_USERNS_OUTSIDE,
|
||||
GID_RANGE_USERNS_INSIDE,
|
||||
GID_RANGE_USERNS_OUTSIDE,
|
||||
_UID_RANGE_USERNS_MODE_MAX,
|
||||
_UID_RANGE_USERNS_MODE_INVALID = -EINVAL,
|
||||
} UIDRangeUsernsMode;
|
||||
|
||||
int uid_range_load_userns(UIDRange **ret, const char *path, UIDRangeUsernsMode mode);
|
||||
int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret);
|
||||
|
||||
bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr);
|
||||
|
||||
+1
-1
@@ -4805,7 +4805,7 @@ static int short_uid_range(const char *path) {
|
||||
/* Taint systemd if we the UID range assigned to this environment doesn't at least cover 0…65534,
|
||||
* i.e. from root to nobody. */
|
||||
|
||||
r = uid_range_load_userns(&p, path);
|
||||
r = uid_range_load_userns(&p, path, UID_RANGE_USERNS_INSIDE);
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return false;
|
||||
if (r < 0)
|
||||
|
||||
+55
-35
@@ -2114,6 +2114,7 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) {
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
|
||||
_cleanup_strv_free_ char **hierarchies = NULL;
|
||||
_cleanup_(mount_list_done) MountList ml = {};
|
||||
_cleanup_close_ int userns_fd = -EBADF;
|
||||
bool require_prefix = false;
|
||||
const char *root;
|
||||
DissectImageFlags dissect_image_flags =
|
||||
@@ -2150,40 +2151,57 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) {
|
||||
|
||||
SET_FLAG(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE, p->verity && p->verity->data_path);
|
||||
|
||||
r = loop_device_make_by_path(
|
||||
p->root_image,
|
||||
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&loop_device);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to create loop device for root image: %m");
|
||||
if (p->runtime_scope == RUNTIME_SCOPE_SYSTEM) {
|
||||
/* In system mode we mount directly */
|
||||
|
||||
r = dissect_loop_device(
|
||||
loop_device,
|
||||
p->verity,
|
||||
p->root_image_options,
|
||||
p->root_image_policy,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to dissect image: %m");
|
||||
r = loop_device_make_by_path(
|
||||
p->root_image,
|
||||
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&loop_device);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to create loop device for root image: %m");
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(
|
||||
dissected_image,
|
||||
loop_device->fd,
|
||||
p->verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = dissect_loop_device(
|
||||
loop_device,
|
||||
p->verity,
|
||||
p->root_image_options,
|
||||
p->root_image_policy,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to dissect image: %m");
|
||||
|
||||
r = dissected_image_decrypt(
|
||||
dissected_image,
|
||||
NULL,
|
||||
p->verity,
|
||||
dissect_image_flags);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
|
||||
r = dissected_image_load_verity_sig_partition(
|
||||
dissected_image,
|
||||
loop_device->fd,
|
||||
p->verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_decrypt(
|
||||
dissected_image,
|
||||
NULL,
|
||||
p->verity,
|
||||
dissect_image_flags);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
|
||||
} else {
|
||||
userns_fd = namespace_open_by_type(NAMESPACE_USER);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to open our own user namespace: %m");
|
||||
|
||||
r = mountfsd_mount_image(
|
||||
p->root_image,
|
||||
userns_fd,
|
||||
p->root_image_policy,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (p->root_directory)
|
||||
@@ -2547,16 +2565,18 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) {
|
||||
root,
|
||||
/* uid_shift= */ UID_INVALID,
|
||||
/* uid_range= */ UID_INVALID,
|
||||
/* userns_fd= */ -EBADF,
|
||||
userns_fd,
|
||||
dissect_image_flags);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to mount root image: %m");
|
||||
|
||||
/* Now release the block device lock, so that udevd is free to call BLKRRPART on the device
|
||||
* if it likes. */
|
||||
r = loop_device_flock(loop_device, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to release lock on loopback block device: %m");
|
||||
if (loop_device) {
|
||||
r = loop_device_flock(loop_device, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to release lock on loopback block device: %m");
|
||||
}
|
||||
|
||||
r = dissected_image_relinquish(dissected_image);
|
||||
if (r < 0)
|
||||
|
||||
+135
-63
@@ -31,10 +31,12 @@
|
||||
#include "log.h"
|
||||
#include "loop-util.h"
|
||||
#include "main-func.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "mkdir.h"
|
||||
#include "mount-util.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "namespace-util.h"
|
||||
#include "nsresource.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -91,6 +93,7 @@ static char **arg_argv = NULL;
|
||||
static char *arg_loop_ref = NULL;
|
||||
static ImagePolicy *arg_image_policy = NULL;
|
||||
static bool arg_mtree_hash = true;
|
||||
static bool arg_via_service = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
@@ -704,6 +707,18 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
r = getenv_bool("SYSTEMD_USE_MOUNTFSD");
|
||||
if (r < 0) {
|
||||
if (r != -ENXIO)
|
||||
return log_error_errno(r, "Failed to parse $SYSTEMD_USE_MOUNTFSD: %m");
|
||||
} else
|
||||
arg_via_service = r;
|
||||
|
||||
if (!IN_SET(arg_action, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_DISCOVER, ACTION_VALIDATE) && geteuid() != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root.");
|
||||
|
||||
SET_FLAG(arg_flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH, isatty(STDIN_FILENO));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -841,7 +856,11 @@ static int get_extension_scopes(DissectedImage *m, ImageClass class, char ***ret
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int action_dissect(DissectedImage *m, LoopDevice *d) {
|
||||
static int action_dissect(
|
||||
DissectedImage *m,
|
||||
LoopDevice *d,
|
||||
int userns_fd) {
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
_cleanup_free_ char *bn = NULL;
|
||||
@@ -849,7 +868,6 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(d);
|
||||
|
||||
r = path_extract_filename(arg_image, &bn);
|
||||
if (r < 0)
|
||||
@@ -875,7 +893,7 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
r = dissected_image_acquire_metadata(m, 0);
|
||||
r = dissected_image_acquire_metadata(m, userns_fd, /* extra_flags= */ 0);
|
||||
if (r == -ENXIO)
|
||||
return log_error_errno(r, "No root partition discovered.");
|
||||
if (r == -EUCLEAN)
|
||||
@@ -1087,7 +1105,6 @@ static int action_mount(DissectedImage *m, LoopDevice *d) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(d);
|
||||
assert(arg_action == ACTION_MOUNT);
|
||||
|
||||
r = dissected_image_mount_and_warn(
|
||||
@@ -1100,9 +1117,11 @@ static int action_mount(DissectedImage *m, LoopDevice *d) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = loop_device_flock(d, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlock loopback block device: %m");
|
||||
if (d) {
|
||||
r = loop_device_flock(d, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlock loopback block device: %m");
|
||||
}
|
||||
|
||||
r = dissected_image_relinquish(m);
|
||||
if (r < 0)
|
||||
@@ -1390,8 +1409,8 @@ static int archive_item(
|
||||
}
|
||||
#endif
|
||||
|
||||
static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d) {
|
||||
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
||||
static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d, int userns_fd) {
|
||||
_cleanup_(umount_and_freep) char *mounted_dir = NULL;
|
||||
_cleanup_free_ char *t = NULL;
|
||||
const char *root;
|
||||
int r;
|
||||
@@ -1400,12 +1419,16 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD
|
||||
|
||||
if (arg_image) {
|
||||
assert(m);
|
||||
assert(d);
|
||||
|
||||
r = detach_mount_namespace();
|
||||
if (userns_fd < 0)
|
||||
r = detach_mount_namespace_harder(0, 0);
|
||||
else
|
||||
r = detach_mount_namespace_userns(userns_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to detach mount namespace: %m");
|
||||
|
||||
/* Create a place we can mount things onto soon. We use a fixed path shared by all invocations. Given
|
||||
* the mounts are done in a mount namespace there's not going to be a collision here */
|
||||
r = get_common_dissect_directory(&t);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed generate private mount directory: %m");
|
||||
@@ -1422,9 +1445,11 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD
|
||||
|
||||
mounted_dir = TAKE_PTR(t);
|
||||
|
||||
r = loop_device_flock(d, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlock loopback block device: %m");
|
||||
if (d) {
|
||||
r = loop_device_flock(d, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlock loopback block device: %m");
|
||||
}
|
||||
|
||||
r = dissected_image_relinquish(m);
|
||||
if (r < 0)
|
||||
@@ -1433,6 +1458,8 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD
|
||||
|
||||
root = mounted_dir ?: arg_root;
|
||||
|
||||
dissected_image_close(m);
|
||||
|
||||
switch (arg_action) {
|
||||
|
||||
case ACTION_COPY_FROM: {
|
||||
@@ -1716,7 +1743,6 @@ static int action_with(DissectedImage *m, LoopDevice *d) {
|
||||
int r, rcode;
|
||||
|
||||
assert(m);
|
||||
assert(d);
|
||||
assert(arg_action == ACTION_WITH);
|
||||
|
||||
r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
|
||||
@@ -1745,9 +1771,11 @@ static int action_with(DissectedImage *m, LoopDevice *d) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
|
||||
|
||||
r = loop_device_flock(d, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlock loopback block device: %m");
|
||||
if (d) {
|
||||
r = loop_device_flock(d, LOCK_UN);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlock loopback block device: %m");
|
||||
}
|
||||
|
||||
rcode = safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, NULL);
|
||||
if (rcode == 0) {
|
||||
@@ -1788,14 +1816,16 @@ static int action_with(DissectedImage *m, LoopDevice *d) {
|
||||
}
|
||||
|
||||
/* Let's manually detach everything, to make things synchronous */
|
||||
r = loop_device_flock(d, LOCK_SH);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m");
|
||||
if (d) {
|
||||
r = loop_device_flock(d, LOCK_SH);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m");
|
||||
}
|
||||
|
||||
r = umount_recursive(mounted_dir, 0);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to unmount '%s', ignoring: %m", mounted_dir);
|
||||
else
|
||||
else if (d)
|
||||
loop_device_unrelinquish(d); /* Let's try to destroy the loopback device */
|
||||
|
||||
created_dir = TAKE_PTR(mounted_dir);
|
||||
@@ -1982,8 +2012,8 @@ static int action_validate(void) {
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
|
||||
uint32_t loop_flags;
|
||||
int open_flags, r;
|
||||
_cleanup_close_ int userns_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
@@ -2015,7 +2045,7 @@ static int run(int argc, char *argv[]) {
|
||||
return action_discover();
|
||||
|
||||
default:
|
||||
/* All other actions need the image dissected */
|
||||
/* All other actions need the image dissected (except for ACTION_VALIDATE, see below) */
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2031,50 +2061,92 @@ static int run(int argc, char *argv[]) {
|
||||
* hence if there's external Verity data
|
||||
* available we turn off partition table
|
||||
* support */
|
||||
}
|
||||
|
||||
if (arg_action == ACTION_VALIDATE)
|
||||
return action_validate();
|
||||
if (arg_action == ACTION_VALIDATE)
|
||||
return action_validate();
|
||||
|
||||
open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
|
||||
loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
|
||||
if (arg_in_memory)
|
||||
r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
|
||||
else
|
||||
r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
|
||||
if (arg_image) {
|
||||
/* First try locally, if we are allowed to */
|
||||
if (!arg_via_service) {
|
||||
uint32_t loop_flags;
|
||||
int open_flags;
|
||||
|
||||
if (arg_loop_ref) {
|
||||
r = loop_device_set_filename(d, arg_loop_ref);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref);
|
||||
open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
|
||||
loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
|
||||
|
||||
if (arg_in_memory)
|
||||
r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
|
||||
else
|
||||
r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
|
||||
if (r < 0) {
|
||||
if (!ERRNO_IS_PRIVILEGE(r) || !IN_SET(arg_action, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO))
|
||||
return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
|
||||
|
||||
log_debug_errno(r, "Lacking permissions to set up loopback block device for %s, using service: %m", arg_image);
|
||||
arg_via_service = true;
|
||||
} else {
|
||||
if (arg_loop_ref) {
|
||||
r = loop_device_set_filename(d, arg_loop_ref);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref);
|
||||
}
|
||||
|
||||
r = dissect_loop_device_and_warn(
|
||||
d,
|
||||
&arg_verity_settings,
|
||||
/* mount_options= */ NULL,
|
||||
arg_image_policy,
|
||||
arg_flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_action == ACTION_ATTACH)
|
||||
return action_attach(m, d);
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(
|
||||
m,
|
||||
d->fd,
|
||||
&arg_verity_settings);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load verity signature partition: %m");
|
||||
|
||||
if (arg_action != ACTION_DISSECT) {
|
||||
r = dissected_image_decrypt_interactively(
|
||||
m, NULL,
|
||||
&arg_verity_settings,
|
||||
arg_flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r = dissect_loop_device_and_warn(
|
||||
d,
|
||||
&arg_verity_settings,
|
||||
/* mount_options= */ NULL,
|
||||
arg_image_policy,
|
||||
arg_flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
/* Try via service */
|
||||
if (arg_via_service) {
|
||||
if (arg_in_memory)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--in-memory= not supported when operating via systemd-mountfsd.");
|
||||
|
||||
if (arg_action == ACTION_ATTACH)
|
||||
return action_attach(m, d);
|
||||
if (arg_loop_ref)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--loop-ref= not supported when operating via systemd-mountfsd.");
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(
|
||||
m,
|
||||
d->fd,
|
||||
&arg_verity_settings);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load verity signature partition: %m");
|
||||
if (verity_settings_set(&arg_verity_settings))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Externally configured verity settings not supported when operating via systemd-mountfsd.");
|
||||
|
||||
if (arg_action != ACTION_DISSECT) {
|
||||
r = dissected_image_decrypt_interactively(
|
||||
m, NULL,
|
||||
&arg_verity_settings,
|
||||
arg_flags);
|
||||
/* Don't run things in private userns, if the mount shall be attached to the host */
|
||||
if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH)) {
|
||||
userns_fd = nsresource_allocate_userns(/* name= */ NULL, UINT64_C(0x10000)); /* allocate 64K users by default */
|
||||
if (userns_fd < 0)
|
||||
return log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
|
||||
}
|
||||
|
||||
r = mountfsd_mount_image(
|
||||
arg_image,
|
||||
userns_fd,
|
||||
arg_image_policy,
|
||||
arg_flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -2083,7 +2155,7 @@ static int run(int argc, char *argv[]) {
|
||||
switch (arg_action) {
|
||||
|
||||
case ACTION_DISSECT:
|
||||
return action_dissect(m, d);
|
||||
return action_dissect(m, d, userns_fd);
|
||||
|
||||
case ACTION_MOUNT:
|
||||
return action_mount(m, d);
|
||||
@@ -2093,7 +2165,7 @@ static int run(int argc, char *argv[]) {
|
||||
case ACTION_COPY_FROM:
|
||||
case ACTION_COPY_TO:
|
||||
case ACTION_MAKE_ARCHIVE:
|
||||
return action_list_or_mtree_or_copy_or_make_archive(m, d);
|
||||
return action_list_or_mtree_or_copy_or_make_archive(m, d, userns_fd);
|
||||
|
||||
case ACTION_WITH:
|
||||
return action_with(m, d);
|
||||
|
||||
@@ -473,7 +473,7 @@ static bool check_sender_uid(sd_device_monitor *m, uid_t uid) {
|
||||
return true;
|
||||
|
||||
if (!m->mapped_userns_uid_range) {
|
||||
r = uid_range_load_userns(&m->mapped_userns_uid_range, NULL);
|
||||
r = uid_range_load_userns(&m->mapped_userns_uid_range, NULL, UID_RANGE_USERNS_INSIDE);
|
||||
if (r < 0)
|
||||
log_monitor_errno(m, r, "Failed to load UID ranges mapped to the current user namespace, ignoring: %m");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
|
||||
<!--
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
This file is part of systemd.
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
-->
|
||||
|
||||
<policyconfig>
|
||||
|
||||
<vendor>The systemd Project</vendor>
|
||||
<vendor_url>https://systemd.io</vendor_url>
|
||||
|
||||
<!-- Allow mounting DDIs into the host user namespace -->
|
||||
<action id="io.systemd.mount-file-system.mount-image">
|
||||
<!-- This action is generally checked first: we'll first try to mount the image with
|
||||
signature checks on. If that fails, we'll retry with the untrusted action below. -->
|
||||
<description gettext-domain="systemd">Allow mounting of file system image</description>
|
||||
<message gettext-domain="systemd">Authentication is required for an application to mount a file system image.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.mount-file-system.mount-untrusted-image">
|
||||
<!-- If the image cannot be mounted via the regular action because it is not signed by a
|
||||
recognized key, we'll try this action. -->
|
||||
<description gettext-domain="systemd">Allow mounting of untrusted file system image</description>
|
||||
<message gettext-domain="systemd">Authentication is required for an application to mount a cryptographically unsigned file system image or an image whose cryptographic signature is not recognized.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
|
||||
<annotate key="org.freedesktop.policykit.imply">io.systemd.mount-file-system.mount-image</annotate>
|
||||
</action>
|
||||
|
||||
<!-- Allow mounting DDIs into a private user namespace -->
|
||||
<action id="io.systemd.mount-file-system.mount-image-privately">
|
||||
<description gettext-domain="systemd">Allow private mounting of trusted file system image</description>
|
||||
<message gettext-domain="systemd">Authentication is required for an application to privately mount a file system image or an image whose cryptographic signature is recognized.</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.mount-file-system.mount-untrusted-image-privately">
|
||||
<description gettext-domain="systemd">Allow private mounting of untrusted file system image</description>
|
||||
<message gettext-domain="systemd">Authentication is required for an application to privately mount a cryptographically unsigned file system image or an image whose cryptographic signature is not recognized.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
|
||||
<annotate key="org.freedesktop.policykit.imply">io.systemd.mount-file-system.mount-image-privately</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
||||
@@ -0,0 +1,28 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
systemd_mountwork_sources = files(
|
||||
'mountwork.c',
|
||||
)
|
||||
|
||||
systemd_mountfsd_sources = files(
|
||||
'mountfsd.c',
|
||||
'mountfsd-manager.c',
|
||||
)
|
||||
|
||||
executables += [
|
||||
libexec_template + {
|
||||
'name' : 'systemd-mountfsd',
|
||||
'conditions' : ['ENABLE_MOUNTFSD'],
|
||||
'sources' : systemd_mountfsd_sources,
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-mountwork',
|
||||
'conditions' : ['ENABLE_MOUNTFSD'],
|
||||
'sources' : systemd_mountwork_sources,
|
||||
'link_with' : common_libs,
|
||||
'dependencies' : common_deps,
|
||||
},
|
||||
]
|
||||
|
||||
install_data('io.systemd.mount-file-system.policy',
|
||||
install_dir : polkitpolicydir)
|
||||
@@ -0,0 +1,277 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "sd-daemon.h"
|
||||
|
||||
#include "build-path.h"
|
||||
#include "common-signal.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "mountfsd-manager.h"
|
||||
#include "process-util.h"
|
||||
#include "set.h"
|
||||
#include "signal-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "umask-util.h"
|
||||
|
||||
#define LISTEN_TIMEOUT_USEC (25 * USEC_PER_SEC)
|
||||
|
||||
static int start_workers(Manager *m, bool explicit_request);
|
||||
|
||||
static size_t manager_current_workers(Manager *m) {
|
||||
assert(m);
|
||||
|
||||
return set_size(m->workers_fixed) + set_size(m->workers_dynamic);
|
||||
}
|
||||
|
||||
static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(s);
|
||||
|
||||
assert_se(!set_remove(m->workers_dynamic, s) != !set_remove(m->workers_fixed, s));
|
||||
sd_event_source_disable_unref(s);
|
||||
|
||||
if (si->si_code == CLD_EXITED) {
|
||||
if (si->si_status == EXIT_SUCCESS)
|
||||
log_debug("Worker " PID_FMT " exited successfully.", si->si_pid);
|
||||
else
|
||||
log_warning("Worker " PID_FMT " died with a failure exit status %i, ignoring.", si->si_pid, si->si_status);
|
||||
} else if (si->si_code == CLD_KILLED)
|
||||
log_warning("Worker " PID_FMT " was killed by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
|
||||
else if (si->si_code == CLD_DUMPED)
|
||||
log_warning("Worker " PID_FMT " dumped core by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
|
||||
else
|
||||
log_warning("Got unexpected exit code via SIGCHLD, ignoring.");
|
||||
|
||||
(void) start_workers(m, /* explicit_request= */ false); /* Fill up workers again if we fell below the low watermark */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
(void) start_workers(m, /* explicit_request= */ true); /* Workers told us there's more work, let's add one more worker as long as we are below the high watermark */
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
|
||||
event_source_hash_ops,
|
||||
sd_event_source,
|
||||
(void (*)(const sd_event_source*, struct siphash*)) trivial_hash_func,
|
||||
(int (*)(const sd_event_source*, const sd_event_source*)) trivial_compare_func,
|
||||
sd_event_source_disable_unref);
|
||||
|
||||
int manager_new(Manager **ret) {
|
||||
_cleanup_(manager_freep) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
m = new(Manager, 1);
|
||||
if (!m)
|
||||
return -ENOMEM;
|
||||
|
||||
*m = (Manager) {
|
||||
.listen_fd = -EBADF,
|
||||
.worker_ratelimit = {
|
||||
.interval = 5 * USEC_PER_SEC,
|
||||
.burst = 50,
|
||||
},
|
||||
};
|
||||
|
||||
r = sd_event_new(&m->event);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_set_signal_exit(m->event, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m");
|
||||
|
||||
r = sd_event_set_watchdog(m->event, true);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to enable watchdog handling, ignoring: %m");
|
||||
|
||||
r = sd_event_add_signal(m->event, NULL, SIGUSR2|SD_EVENT_SIGNAL_PROCMASK, on_sigusr2, m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Manager* manager_free(Manager *m) {
|
||||
if (!m)
|
||||
return NULL;
|
||||
|
||||
set_free(m->workers_fixed);
|
||||
set_free(m->workers_dynamic);
|
||||
|
||||
/* Note: we rely on PR_DEATHSIG to kill the workers for us */
|
||||
|
||||
sd_event_unref(m->event);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
|
||||
static int start_one_worker(Manager *m) {
|
||||
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *source = NULL;
|
||||
bool fixed;
|
||||
pid_t pid;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
fixed = set_size(m->workers_fixed) < MOUNTFS_WORKERS_MIN;
|
||||
|
||||
r = safe_fork_full(
|
||||
"(sd-worker)",
|
||||
/* stdio_fds= */ NULL,
|
||||
&m->listen_fd, 1,
|
||||
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_CLOSE_ALL_FDS,
|
||||
&pid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to fork new worker child: %m");
|
||||
if (r == 0) {
|
||||
char pids[DECIMAL_STR_MAX(pid_t)];
|
||||
/* Child */
|
||||
|
||||
if (m->listen_fd == 3) {
|
||||
r = fd_cloexec(3, false);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to turn off O_CLOEXEC for fd 3: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if (dup2(m->listen_fd, 3) < 0) { /* dup2() creates with O_CLOEXEC off */
|
||||
log_error_errno(errno, "Failed to move listen fd to 3: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
safe_close(m->listen_fd);
|
||||
}
|
||||
|
||||
xsprintf(pids, PID_FMT, pid);
|
||||
if (setenv("LISTEN_PID", pids, 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setenv("LISTEN_FDS", "1", 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $LISTEN_FDS: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setenv("MOUNTFS_FIXED_WORKER", one_zero(fixed), 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $MOUNTFS_FIXED_WORKER: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = setenv_systemd_log_level();
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to set $SYSTEMD_LOG_LEVEL: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = invoke_callout_binary(SYSTEMD_MOUNTWORK_PATH, STRV_MAKE("systemd-mountwork", "xxxxxxxxxxxxxxxx")); /* With some extra space rename_process() can make use of */
|
||||
log_error_errno(r, "Failed start worker process: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = sd_event_add_child(m->event, &source, pid, WEXITED, on_worker_exit, m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to watch child " PID_FMT ": %m", pid);
|
||||
|
||||
r = set_ensure_put(
|
||||
fixed ? &m->workers_fixed : &m->workers_dynamic,
|
||||
&event_source_hash_ops,
|
||||
source);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add child process to set: %m");
|
||||
|
||||
TAKE_PTR(source);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_workers(Manager *m, bool explicit_request) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
for (;;) {
|
||||
size_t n;
|
||||
|
||||
n = manager_current_workers(m);
|
||||
|
||||
log_debug("%zu workers running.", n);
|
||||
|
||||
if (n >= MOUNTFS_WORKERS_MIN && (!explicit_request || n >= MOUNTFS_WORKERS_MAX))
|
||||
break;
|
||||
|
||||
if (!ratelimit_below(&m->worker_ratelimit)) {
|
||||
/* If we keep starting workers too often, let's fail the whole daemon, something is wrong */
|
||||
sd_event_exit(m->event, EXIT_FAILURE);
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Worker threads requested too frequently, something is wrong.");
|
||||
}
|
||||
|
||||
r = start_one_worker(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
explicit_request = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_startup(Manager *m) {
|
||||
int n;
|
||||
|
||||
assert(m);
|
||||
assert(m->listen_fd < 0);
|
||||
|
||||
n = sd_listen_fds(false);
|
||||
if (n < 0)
|
||||
return log_error_errno(n, "Failed to determine number of passed file descriptors: %m");
|
||||
if (n > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one listening fd, got %i.", n);
|
||||
if (n == 1)
|
||||
m->listen_fd = SD_LISTEN_FDS_START;
|
||||
else {
|
||||
static const union sockaddr_union sockaddr = {
|
||||
.un.sun_family = AF_UNIX,
|
||||
.un.sun_path = "/run/systemd/io.systemd.MountFileSystem",
|
||||
};
|
||||
|
||||
m->listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||
if (m->listen_fd < 0)
|
||||
return log_error_errno(errno, "Failed to bind on socket: %m");
|
||||
|
||||
(void) sockaddr_un_unlink(&sockaddr.un);
|
||||
|
||||
WITH_UMASK(0000)
|
||||
if (bind(m->listen_fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0)
|
||||
return log_error_errno(errno, "Failed to bind socket: %m");
|
||||
|
||||
if (listen(m->listen_fd, SOMAXCONN) < 0)
|
||||
return log_error_errno(errno, "Failed to listen on socket: %m");
|
||||
}
|
||||
|
||||
/* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be
|
||||
* GC'ed on idle */
|
||||
if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, TIMEVAL_STORE(LISTEN_TIMEOUT_USEC), sizeof(struct timeval)) < 0)
|
||||
return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m");
|
||||
|
||||
return start_workers(m, /* explicit_request= */ false);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-event.h"
|
||||
|
||||
typedef struct Manager Manager;
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "ratelimit.h"
|
||||
|
||||
#define MOUNTFS_WORKERS_MIN 3
|
||||
#define MOUNTFS_WORKERS_MAX 4096
|
||||
|
||||
struct Manager {
|
||||
sd_event *event;
|
||||
|
||||
Set *workers_fixed; /* Workers 0…MOUNTFS_WORKERS_MIN */
|
||||
Set *workers_dynamic; /* Workers MOUNTFS_WORKERS_MIN+1…MOUNTFS_WORKERS_MAX */
|
||||
|
||||
int listen_fd;
|
||||
|
||||
RateLimit worker_ratelimit;
|
||||
};
|
||||
|
||||
int manager_new(Manager **ret);
|
||||
Manager* manager_free(Manager *m);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
|
||||
|
||||
int manager_startup(Manager *m);
|
||||
@@ -0,0 +1,43 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "daemon-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "mountfsd-manager.h"
|
||||
#include "signal-util.h"
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_unused_ _cleanup_(notify_on_cleanup) const char *notify_stop = NULL;
|
||||
_cleanup_(manager_freep) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
umask(0022);
|
||||
|
||||
if (argc != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
|
||||
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
|
||||
|
||||
r = manager_new(&m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not create manager: %m");
|
||||
|
||||
r = manager_startup(m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to start up daemon: %m");
|
||||
|
||||
notify_stop = notify_start(NOTIFY_READY, NOTIFY_STOPPING);
|
||||
|
||||
r = sd_event_loop(m->event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Event loop failed: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
@@ -0,0 +1,703 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-daemon.h"
|
||||
|
||||
#include "argv-util.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "chase.h"
|
||||
#include "discover-image.h"
|
||||
#include "dissect-image.h"
|
||||
#include "env-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "io-util.h"
|
||||
#include "main-func.h"
|
||||
#include "missing_loop.h"
|
||||
#include "namespace-util.h"
|
||||
#include "nsresource.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "os-util.h"
|
||||
#include "process-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "user-util.h"
|
||||
#include "varlink.h"
|
||||
#include "varlink-io.systemd.MountFileSystem.h"
|
||||
|
||||
#define ITERATIONS_MAX 64U
|
||||
#define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE)
|
||||
#define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC)
|
||||
#define LISTEN_IDLE_USEC (90 * USEC_PER_SEC)
|
||||
|
||||
static const ImagePolicy image_policy_untrusted = {
|
||||
.n_policies = 2,
|
||||
.policies = {
|
||||
{ PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
|
||||
{ PARTITION_USR, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
|
||||
},
|
||||
.default_flags = PARTITION_POLICY_IGNORE,
|
||||
};
|
||||
|
||||
static int json_dispatch_image_policy(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
_cleanup_(image_policy_freep) ImagePolicy *q = NULL;
|
||||
ImagePolicy **p = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
|
||||
if (json_variant_is_null(variant)) {
|
||||
*p = image_policy_free(*p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!json_variant_is_string(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
|
||||
|
||||
r = image_policy_from_string(json_variant_string(variant), &q);
|
||||
if (r < 0)
|
||||
return json_log(variant, flags, r, "JSON field '%s' is not a valid image policy.", strna(name));
|
||||
|
||||
image_policy_free(*p);
|
||||
*p = TAKE_PTR(q);
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct MountImageParameters {
|
||||
unsigned image_fd_idx;
|
||||
unsigned userns_fd_idx;
|
||||
int read_only;
|
||||
int growfs;
|
||||
char *password;
|
||||
ImagePolicy *image_policy;
|
||||
} MountImageParameters;
|
||||
|
||||
static void mount_image_parameters_done(MountImageParameters *p) {
|
||||
assert(p);
|
||||
|
||||
p->password = erase_and_free(p->password);
|
||||
p->image_policy = image_policy_free(p->image_policy);
|
||||
}
|
||||
|
||||
static int validate_image_fd(int fd, MountImageParameters *p) {
|
||||
int r, fl;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(p);
|
||||
|
||||
r = fd_verify_regular(fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fl = fd_verify_safe_flags(fd);
|
||||
if (fl < 0)
|
||||
return log_debug_errno(fl, "Image file descriptor has unsafe flags set: %m");
|
||||
|
||||
switch (fl & O_ACCMODE) {
|
||||
|
||||
case O_RDONLY:
|
||||
p->read_only = true;
|
||||
break;
|
||||
|
||||
case O_RDWR:
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verify_trusted_image_fd_by_path(int fd) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
struct stat sta;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
r = secure_getenv_bool("SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES");
|
||||
if (r == -ENXIO) {
|
||||
if (!DEFAULT_MOUNTFSD_TRUSTED_DIRECTORIES) {
|
||||
log_debug("Trusted directory mechanism disabled at compile time.");
|
||||
return false;
|
||||
}
|
||||
} else if (r < 0) {
|
||||
log_debug_errno(r, "Failed to parse $SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES environment variable, not trusting any image.");
|
||||
return false;
|
||||
} else if (!r) {
|
||||
log_debug("Trusted directory mechanism disabled via $SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES environment variable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
r = fd_get_path(fd, &p);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to get path of passed image file descriptor: %m");
|
||||
if (fstat(fd, &sta) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat() passed image file descriptor: %m");
|
||||
|
||||
log_debug("Checking if image '%s' is in trusted directories.", p);
|
||||
|
||||
for (ImageClass c = 0; c < _IMAGE_CLASS_MAX; c++)
|
||||
NULSTR_FOREACH(s, image_search_path[c]) {
|
||||
_cleanup_close_ int dir_fd = -EBADF, inode_fd = -EBADF;
|
||||
_cleanup_free_ char *q = NULL;
|
||||
struct stat stb;
|
||||
const char *e;
|
||||
|
||||
r = chase(s, NULL, CHASE_SAFE, &q, &dir_fd);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to resolve search path '%s', ignoring: %m", s);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check that the inode refers to a file immediately inside the image directory,
|
||||
* i.e. not the image directory itself, and nothing further down the tree */
|
||||
e = path_startswith(p, q);
|
||||
if (isempty(e))
|
||||
continue;
|
||||
|
||||
e += strspn(e, "/");
|
||||
if (!filename_is_valid(e))
|
||||
continue;
|
||||
|
||||
r = chaseat(dir_fd, e, CHASE_SAFE, NULL, &inode_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Couldn't verify that specified image '%s' is in search path '%s': %m", p, s);
|
||||
|
||||
if (fstat(inode_fd, &stb) < 0)
|
||||
return log_error_errno(errno, "Failed to stat image file '%s/%s': %m", q, e);
|
||||
|
||||
if (stat_inode_same(&sta, &stb)) {
|
||||
log_debug("Image '%s' is *in* trusted directories.", p);
|
||||
return true; /* Yay */
|
||||
}
|
||||
}
|
||||
|
||||
log_debug("Image '%s' is *not* in trusted directories.", p);
|
||||
return false;
|
||||
}
|
||||
|
||||
static int determine_image_policy(
|
||||
int image_fd,
|
||||
bool trusted,
|
||||
ImagePolicy *client_policy,
|
||||
ImagePolicy **ret) {
|
||||
|
||||
_cleanup_(image_policy_freep) ImagePolicy *envvar_policy = NULL;
|
||||
const ImagePolicy *default_policy;
|
||||
const char *envvar, *e;
|
||||
int r;
|
||||
|
||||
assert(image_fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
if (trusted) {
|
||||
envvar = "SYSTEMD_MOUNTFSD_IMAGE_POLICY_TRUSTED";
|
||||
default_policy = &image_policy_allow;
|
||||
} else {
|
||||
envvar = "SYSTEMD_MOUNTFSD_IMAGE_POLICY_UNTRUSTED";
|
||||
default_policy = &image_policy_untrusted;
|
||||
}
|
||||
|
||||
e = secure_getenv(envvar);
|
||||
if (e) {
|
||||
r = image_policy_from_string(e, &envvar_policy);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse image policy supplied via $%s: %m", envvar);
|
||||
|
||||
default_policy = envvar_policy;
|
||||
}
|
||||
|
||||
return image_policy_intersect(default_policy, client_policy, ret);
|
||||
}
|
||||
|
||||
static int validate_userns(Varlink *link, int *userns_fd) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(userns_fd);
|
||||
|
||||
if (*userns_fd < 0)
|
||||
return 0;
|
||||
|
||||
r = fd_verify_safe_flags(*userns_fd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "User namespace file descriptor has unsafe flags set: %m");
|
||||
|
||||
r = fd_is_ns(*userns_fd, CLONE_NEWUSER);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
|
||||
|
||||
/* Our own host user namespace? Then close the fd, and handle it as if none was specified. */
|
||||
r = is_our_namespace(*userns_fd, NAMESPACE_USER);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to determine if user namespace provided by client is our own.");
|
||||
if (r > 0) {
|
||||
log_debug("User namespace provided by client is our own.");
|
||||
*userns_fd = safe_close(*userns_fd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vl_method_mount_image(
|
||||
Varlink *link,
|
||||
JsonVariant *parameters,
|
||||
VarlinkMethodFlags flags,
|
||||
void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "imageFileDescriptor", JSON_VARIANT_UNSIGNED, json_dispatch_uint, offsetof(MountImageParameters, image_fd_idx), JSON_MANDATORY },
|
||||
{ "userNamespaceFileDescriptor", JSON_VARIANT_UNSIGNED, json_dispatch_uint, offsetof(MountImageParameters, userns_fd_idx), 0 },
|
||||
{ "readOnly", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(MountImageParameters, read_only), 0 },
|
||||
{ "growFileSystems", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(MountImageParameters, growfs), 0 },
|
||||
{ "password", JSON_VARIANT_STRING, json_dispatch_string, offsetof(MountImageParameters, password), 0 },
|
||||
{ "imagePolicy", JSON_VARIANT_STRING, json_dispatch_image_policy, offsetof(MountImageParameters, image_policy), 0 },
|
||||
VARLINK_DISPATCH_POLKIT_FIELD,
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
_cleanup_(mount_image_parameters_done) MountImageParameters p = {
|
||||
.image_fd_idx = UINT_MAX,
|
||||
.userns_fd_idx = UINT_MAX,
|
||||
.read_only = -1,
|
||||
.growfs = -1,
|
||||
};
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *di = NULL;
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *aj = NULL;
|
||||
_cleanup_close_ int image_fd = -EBADF, userns_fd = -EBADF;
|
||||
_cleanup_(image_policy_freep) ImagePolicy *use_policy = NULL;
|
||||
Hashmap **polkit_registry = ASSERT_PTR(userdata);
|
||||
_cleanup_free_ char *ps = NULL;
|
||||
bool image_is_trusted = false;
|
||||
uid_t peer_uid;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(parameters);
|
||||
|
||||
json_variant_sensitive(parameters); /* might contain passwords */
|
||||
|
||||
r = varlink_get_peer_uid(link, &peer_uid);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to get client UID: %m");
|
||||
|
||||
r = varlink_dispatch(link, parameters, dispatch_table, &p);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (p.image_fd_idx != UINT_MAX) {
|
||||
image_fd = varlink_peek_dup_fd(link, p.image_fd_idx);
|
||||
if (image_fd < 0)
|
||||
return log_debug_errno(image_fd, "Failed to peek image fd from client: %m");
|
||||
}
|
||||
|
||||
if (p.userns_fd_idx != UINT_MAX) {
|
||||
userns_fd = varlink_peek_dup_fd(link, p.userns_fd_idx);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to peek user namespace fd from client: %m");
|
||||
}
|
||||
|
||||
r = validate_image_fd(image_fd, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = validate_userns(link, &userns_fd);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = verify_trusted_image_fd_by_path(image_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
image_is_trusted = r;
|
||||
|
||||
const char *polkit_details[] = {
|
||||
"read_only", one_zero(p.read_only > 0),
|
||||
NULL,
|
||||
};
|
||||
|
||||
const char *polkit_action, *polkit_untrusted_action;
|
||||
PolkitFlags polkit_flags;
|
||||
if (userns_fd < 0) {
|
||||
/* Mount into the host user namespace */
|
||||
polkit_action = "io.systemd.mount-file-system.mount-image";
|
||||
polkit_untrusted_action = "io.systemd.mount-file-system.mount-untrusted-image";
|
||||
polkit_flags = 0;
|
||||
} else {
|
||||
/* Mount into a private user namespace */
|
||||
polkit_action = "io.systemd.mount-file-system.mount-image-privately";
|
||||
polkit_untrusted_action = "io.systemd.mount-file-system.mount-untrusted-image-privately";
|
||||
|
||||
/* If polkit is not around, let's allow mounting authenticated images by default */
|
||||
polkit_flags = POLKIT_DEFAULT_ALLOW;
|
||||
}
|
||||
|
||||
/* Let's definitely acquire the regular action privilege, for mounting properly signed images */
|
||||
r = varlink_verify_polkit_async_full(
|
||||
link,
|
||||
/* bus= */ NULL,
|
||||
polkit_action,
|
||||
polkit_details,
|
||||
/* good_user= */ UID_INVALID,
|
||||
polkit_flags,
|
||||
polkit_registry);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
/* Generate the commmon dissection directory here. We are not going to use it, but the clients might,
|
||||
* and they likely are unprivileged, hence cannot create it themselves. Hence let's jsut create it
|
||||
* here, if it is missing. */
|
||||
r = get_common_dissect_directory(NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = loop_device_make(
|
||||
image_fd,
|
||||
p.read_only == 0 ? O_RDONLY : O_RDWR,
|
||||
0,
|
||||
UINT64_MAX,
|
||||
UINT32_MAX,
|
||||
LO_FLAGS_PARTSCAN,
|
||||
LOCK_EX,
|
||||
&loop);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
DissectImageFlags dissect_flags =
|
||||
(p.read_only == 0 ? DISSECT_IMAGE_READ_ONLY : 0) |
|
||||
(p.growfs != 0 ? DISSECT_IMAGE_GROWFS : 0) |
|
||||
DISSECT_IMAGE_DISCARD_ANY |
|
||||
DISSECT_IMAGE_FSCK |
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
|
||||
|
||||
/* Let's see if we have acquired the privilege to mount untrusted images already */
|
||||
bool polkit_have_untrusted_action =
|
||||
varlink_has_polkit_action(link, polkit_untrusted_action, polkit_details, polkit_registry);
|
||||
|
||||
for (;;) {
|
||||
use_policy = image_policy_free(use_policy);
|
||||
ps = mfree(ps);
|
||||
|
||||
/* We use the image policy for trusted images if either the path is below a trusted
|
||||
* directory, or if we have already acquired a PK authentication that tells us that untrusted
|
||||
* images are OK */
|
||||
bool use_trusted_policy =
|
||||
image_is_trusted ||
|
||||
polkit_have_untrusted_action;
|
||||
|
||||
r = determine_image_policy(
|
||||
image_fd,
|
||||
use_trusted_policy,
|
||||
p.image_policy,
|
||||
&use_policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = image_policy_to_string(use_policy, /* simplify= */ true, &ps);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("Using image policy: %s", ps);
|
||||
|
||||
r = dissect_loop_device(
|
||||
loop,
|
||||
&verity,
|
||||
/* mount_options= */ NULL,
|
||||
use_policy,
|
||||
dissect_flags,
|
||||
&di);
|
||||
if (r == -ENOPKG)
|
||||
return varlink_error(link, "io.systemd.MountFileSystem.IncompatibleImage", NULL);
|
||||
if (r == -ENOTUNIQ)
|
||||
return varlink_error(link, "io.systemd.MountFileSystem.MultipleRootPartitionsFound", NULL);
|
||||
if (r == -ENXIO)
|
||||
return varlink_error(link, "io.systemd.MountFileSystem.RootPartitionNotFound", NULL);
|
||||
if (r == -ERFKILL) {
|
||||
/* The image policy refused this, let's retry after trying to get PolicyKit */
|
||||
|
||||
if (!polkit_have_untrusted_action) {
|
||||
log_debug("Denied by image policy. Trying a stronger polkit authentication before continuing.");
|
||||
r = varlink_verify_polkit_async_full(
|
||||
link,
|
||||
/* bus= */ NULL,
|
||||
polkit_untrusted_action,
|
||||
polkit_details,
|
||||
/* good_user= */ UID_INVALID,
|
||||
/* flags= */ 0, /* NB: the image cannot be authenticated, hence unless PK is around to allow this anyway, fail! */
|
||||
polkit_registry);
|
||||
if (r <= 0 && !ERRNO_IS_NEG_PRIVILEGE(r))
|
||||
return r;
|
||||
if (r > 0) {
|
||||
/* Try again, now that we know the client has enough privileges. */
|
||||
log_debug("Denied by image policy, retrying after polkit authentication.");
|
||||
polkit_have_untrusted_action = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return varlink_error(link, "io.systemd.MountFileSystem.DeniedByImagePolicy", NULL);
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Success */
|
||||
break;
|
||||
}
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(
|
||||
di,
|
||||
loop->fd,
|
||||
&verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_decrypt(
|
||||
di,
|
||||
p.password,
|
||||
&verity,
|
||||
dissect_flags);
|
||||
if (r == -ENOKEY) /* new dm-verity userspace returns ENOKEY if the dm-verity signature key is not in
|
||||
* key chain. That's great. */
|
||||
return varlink_error(link, "io.systemd.MountFileSystem.KeyNotFound", NULL);
|
||||
if (r == -EBUSY) /* DM kernel subsystem is shit with returning useful errors hence we keep retrying
|
||||
* under the assumption that some errors are transitional. Which the errors might
|
||||
* not actually be. After all retries failed we return EBUSY. Let's turn that into a
|
||||
* generic Verity error. It's not very helpful, could mean anything, but at least it
|
||||
* gives client a clear idea that this has to do with Verity. */
|
||||
return varlink_error(link, "io.systemd.MountFileSystem.VerityFailure", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_mount(
|
||||
di,
|
||||
/* where= */ NULL,
|
||||
/* uid_shift= */ UID_INVALID,
|
||||
/* uid_range= */ UID_INVALID,
|
||||
userns_fd,
|
||||
dissect_flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *pj = NULL;
|
||||
DissectedPartition *pp = di->partitions + d;
|
||||
int fd_idx;
|
||||
|
||||
if (!pp->found)
|
||||
continue;
|
||||
|
||||
if (pp->fsmount_fd < 0)
|
||||
continue;
|
||||
|
||||
if (userns_fd >= 0) {
|
||||
r = nsresource_add_mount(userns_fd, pp->fsmount_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
fd_idx = varlink_push_fd(link, pp->fsmount_fd);
|
||||
if (fd_idx < 0)
|
||||
return fd_idx;
|
||||
|
||||
TAKE_FD(pp->fsmount_fd);
|
||||
|
||||
r = json_build(&pj,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("designator", JSON_BUILD_STRING(partition_designator_to_string(d))),
|
||||
JSON_BUILD_PAIR("writable", JSON_BUILD_BOOLEAN(pp->rw)),
|
||||
JSON_BUILD_PAIR("growFileSystem", JSON_BUILD_BOOLEAN(pp->growfs)),
|
||||
JSON_BUILD_PAIR_CONDITION(pp->partno > 0, "partitionNumber", JSON_BUILD_INTEGER(pp->partno)),
|
||||
JSON_BUILD_PAIR_CONDITION(pp->architecture > 0, "architecture", JSON_BUILD_STRING(architecture_to_string(pp->architecture))),
|
||||
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(pp->uuid), "partitionUuid", JSON_BUILD_UUID(pp->uuid)),
|
||||
JSON_BUILD_PAIR("fileSystemType", JSON_BUILD_STRING(dissected_partition_fstype(pp))),
|
||||
JSON_BUILD_PAIR_CONDITION(pp->label, "partitionLabel", JSON_BUILD_STRING(pp->label)),
|
||||
JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(pp->size)),
|
||||
JSON_BUILD_PAIR("offset", JSON_BUILD_INTEGER(pp->offset)),
|
||||
JSON_BUILD_PAIR("mountFileDescriptor", JSON_BUILD_INTEGER(fd_idx))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_variant_append_array(&aj, pj);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
loop_device_relinquish(loop);
|
||||
|
||||
r = varlink_replyb(link, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("partitions", JSON_BUILD_VARIANT(aj)),
|
||||
JSON_BUILD_PAIR("imagePolicy", JSON_BUILD_STRING(ps)),
|
||||
JSON_BUILD_PAIR("imageSize", JSON_BUILD_INTEGER(di->image_size)),
|
||||
JSON_BUILD_PAIR("sectorSize", JSON_BUILD_INTEGER(di->sector_size)),
|
||||
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(di->image_uuid), "imageUuid", JSON_BUILD_UUID(di->image_uuid))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int process_connection(VarlinkServer *server, int _fd) {
|
||||
_cleanup_close_ int fd = TAKE_FD(_fd); /* always take possesion */
|
||||
_cleanup_(varlink_close_unrefp) Varlink *vl = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
int r;
|
||||
|
||||
r = sd_event_new(&event);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = varlink_server_attach_event(server, event, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach Varlink server to event loop: %m");
|
||||
|
||||
r = varlink_server_add_connection(server, fd, &vl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add connection: %m");
|
||||
|
||||
TAKE_FD(fd);
|
||||
vl = varlink_ref(vl);
|
||||
|
||||
r = varlink_set_allow_fd_passing_input(vl, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable fd passing for read: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_output(vl, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable fd passing for write: %m");
|
||||
|
||||
r = sd_event_loop(event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run event loop: %m");
|
||||
|
||||
r = varlink_server_detach_event(server);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to detach Varlink server from event loop: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY;
|
||||
_cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL;
|
||||
_cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL;
|
||||
_cleanup_(pidref_done) PidRef parent = PIDREF_NULL;
|
||||
unsigned n_iterations = 0;
|
||||
int m, listen_fd, r;
|
||||
|
||||
log_setup();
|
||||
|
||||
m = sd_listen_fds(false);
|
||||
if (m < 0)
|
||||
return log_error_errno(m, "Failed to determine number of listening fds: %m");
|
||||
if (m == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No socket to listen on received.");
|
||||
if (m > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Worker can only listen on a single socket at a time.");
|
||||
|
||||
listen_fd = SD_LISTEN_FDS_START;
|
||||
|
||||
r = fd_nonblock(listen_fd, false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to turn off non-blocking mode for listening socket: %m");
|
||||
|
||||
r = varlink_server_new(&server, VARLINK_SERVER_INHERIT_USERDATA);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate server: %m");
|
||||
|
||||
r = varlink_server_add_interface(server, &vl_interface_io_systemd_MountFileSystem);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add MountFileSystem interface to varlink server: %m");
|
||||
|
||||
r = varlink_server_bind_method_many(
|
||||
server,
|
||||
"io.systemd.MountFileSystem.MountImage",vl_method_mount_image);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind methods: %m");
|
||||
|
||||
varlink_server_set_userdata(server, &polkit_registry);
|
||||
|
||||
r = varlink_server_set_exit_on_idle(server, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable exit-on-idle mode: %m");
|
||||
|
||||
r = getenv_bool("MOUNTFS_FIXED_WORKER");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse MOUNTFSD_FIXED_WORKER: %m");
|
||||
listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC;
|
||||
|
||||
r = pidref_set_parent(&parent);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire pidfd of parent process: %m");
|
||||
|
||||
start_time = now(CLOCK_MONOTONIC);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
usec_t n;
|
||||
|
||||
/* Exit the worker in regular intervals, to flush out all memory use */
|
||||
if (n_iterations++ > ITERATIONS_MAX) {
|
||||
log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations);
|
||||
break;
|
||||
}
|
||||
|
||||
n = now(CLOCK_MONOTONIC);
|
||||
if (n >= usec_add(start_time, RUNTIME_MAX_USEC)) {
|
||||
log_debug("Exiting worker, ran for %s, that's enough.",
|
||||
FORMAT_TIMESPAN(usec_sub_unsigned(n, start_time), 0));
|
||||
break;
|
||||
}
|
||||
|
||||
if (last_busy_usec == USEC_INFINITY)
|
||||
last_busy_usec = n;
|
||||
else if (listen_idle_usec != USEC_INFINITY && n >= usec_add(last_busy_usec, listen_idle_usec)) {
|
||||
log_debug("Exiting worker, been idle for %s.",
|
||||
FORMAT_TIMESPAN(usec_sub_unsigned(n, last_busy_usec), 0));
|
||||
break;
|
||||
}
|
||||
|
||||
(void) rename_process("systemd-mountwork: waiting...");
|
||||
fd = RET_NERRNO(accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC));
|
||||
(void) rename_process("systemd-mountwork: processing...");
|
||||
|
||||
if (fd == -EAGAIN)
|
||||
continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected
|
||||
* after a while, let's check if it's time to exit though. */
|
||||
if (fd == -EINTR)
|
||||
continue; /* Might be that somebody attached via strace, let's just continue in that
|
||||
* case */
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to accept() from listening socket: %m");
|
||||
|
||||
if (now(CLOCK_MONOTONIC) <= usec_add(n, PRESSURE_SLEEP_TIME_USEC)) {
|
||||
/* We only slept a very short time? If so, let's see if there are more sockets
|
||||
* pending, and if so, let's ask our parent for more workers */
|
||||
|
||||
r = fd_wait_for_event(listen_fd, POLLIN, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to test for POLLIN on listening socket: %m");
|
||||
|
||||
if (FLAGS_SET(r, POLLIN)) {
|
||||
r = pidref_kill(&parent, SIGUSR2);
|
||||
if (r == -ESRCH)
|
||||
return log_error_errno(r, "Parent already died?");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to send SIGUSR2 signal to parent. %m");
|
||||
}
|
||||
}
|
||||
|
||||
(void) process_connection(server, TAKE_FD(fd));
|
||||
last_busy_usec = USEC_INFINITY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
+52
-35
@@ -13,6 +13,7 @@
|
||||
#include "mountpoint-util.h"
|
||||
#include "nspawn-cgroup.h"
|
||||
#include "nspawn-mount.h"
|
||||
#include "nsresource.h"
|
||||
#include "path-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "string-util.h"
|
||||
@@ -46,38 +47,6 @@ static int chown_cgroup_path(const char *path, uid_t uid_shift) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int chown_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift) {
|
||||
_cleanup_free_ char *path = NULL, *fs = NULL;
|
||||
int r;
|
||||
|
||||
r = cg_pid_get_path(NULL, pid, &path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get container cgroup path: %m");
|
||||
|
||||
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get file system path for container cgroup: %m");
|
||||
|
||||
r = chown_cgroup_path(fs, uid_shift);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chown() cgroup %s: %m", fs);
|
||||
|
||||
if (unified_requested == CGROUP_UNIFIED_SYSTEMD || (unified_requested == CGROUP_UNIFIED_NONE && cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0)) {
|
||||
_cleanup_free_ char *lfs = NULL;
|
||||
/* Always propagate access rights from unified to legacy controller */
|
||||
|
||||
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path, NULL, &lfs);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get file system path for container cgroup: %m");
|
||||
|
||||
r = chown_cgroup_path(lfs, uid_shift);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chown() cgroup %s: %m", lfs);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift) {
|
||||
_cleanup_free_ char *cgroup = NULL;
|
||||
char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1];
|
||||
@@ -142,7 +111,14 @@ finish:
|
||||
return r;
|
||||
}
|
||||
|
||||
int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested) {
|
||||
int create_subcgroup(
|
||||
pid_t pid,
|
||||
bool keep_unit,
|
||||
CGroupUnified unified_requested,
|
||||
uid_t uid_shift,
|
||||
int userns_fd,
|
||||
bool privileged) {
|
||||
|
||||
_cleanup_free_ char *cgroup = NULL, *payload = NULL;
|
||||
CGroupMask supported;
|
||||
char *e;
|
||||
@@ -185,13 +161,54 @@ int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested)
|
||||
if (!payload)
|
||||
return log_oom();
|
||||
|
||||
r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, payload, pid);
|
||||
if (privileged)
|
||||
r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, payload, pid);
|
||||
else
|
||||
r = cg_create(SYSTEMD_CGROUP_CONTROLLER, payload);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create %s subcgroup: %m", payload);
|
||||
|
||||
if (privileged) {
|
||||
_cleanup_free_ char *fs = NULL;
|
||||
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, payload, NULL, &fs);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get file system path for container cgroup: %m");
|
||||
|
||||
r = chown_cgroup_path(fs, uid_shift);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chown() cgroup %s: %m", fs);
|
||||
|
||||
} else if (userns_fd >= 0) {
|
||||
_cleanup_close_ int cgroup_fd = -EBADF;
|
||||
|
||||
cgroup_fd = cg_path_open(SYSTEMD_CGROUP_CONTROLLER, payload);
|
||||
if (cgroup_fd < 0)
|
||||
return log_error_errno(cgroup_fd, "Failed to open cgroup %s: %m", payload);
|
||||
|
||||
r = cg_fd_attach(cgroup_fd, pid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add process " PID_FMT " to cgroup %s: %m", pid, payload);
|
||||
|
||||
r = nsresource_add_cgroup(userns_fd, cgroup_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add cgroup %s to userns: %m", payload);
|
||||
}
|
||||
|
||||
if (unified_requested == CGROUP_UNIFIED_SYSTEMD || (unified_requested == CGROUP_UNIFIED_NONE && cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0)) {
|
||||
_cleanup_free_ char *lfs = NULL;
|
||||
/* Always propagate access rights from unified to legacy controller */
|
||||
|
||||
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER_LEGACY, payload, NULL, &lfs);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get file system path for container cgroup: %m");
|
||||
|
||||
r = chown_cgroup_path(lfs, uid_shift);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chown() cgroup %s: %m", lfs);
|
||||
}
|
||||
|
||||
if (keep_unit) {
|
||||
_cleanup_free_ char *supervisor = NULL;
|
||||
|
||||
supervisor = path_join(cgroup, "supervisor");
|
||||
if (!supervisor)
|
||||
return log_oom();
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
|
||||
#include "cgroup-util.h"
|
||||
|
||||
int chown_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift);
|
||||
int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift);
|
||||
int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested);
|
||||
int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested, uid_t uid_shift, int userns_fd, bool privileged);
|
||||
|
||||
int mount_cgroups(const char *dest, CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, bool use_cgns);
|
||||
int mount_systemd_cgroup_writable(const char *dest, CGroupUnified unified_requested);
|
||||
|
||||
+46
-24
@@ -444,22 +444,38 @@ int tmpfs_patch_options(
|
||||
}
|
||||
|
||||
int mount_sysfs(const char *dest, MountSettingsMask mount_settings) {
|
||||
const char *full, *top;
|
||||
int r;
|
||||
_cleanup_free_ char *top = NULL, *full = NULL;;
|
||||
unsigned long extra_flags = 0;
|
||||
int r;
|
||||
|
||||
top = prefix_roota(dest, "/sys");
|
||||
r = path_is_fs_type(top, SYSFS_MAGIC);
|
||||
top = path_join(dest, "/sys");
|
||||
if (!top)
|
||||
return log_oom();
|
||||
|
||||
r = path_is_mount_point(top);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top);
|
||||
/* /sys might already be mounted as sysfs by the outer child in the
|
||||
* !netns case. In this case, it's all good. Don't touch it because we
|
||||
* don't have the right to do so, see https://github.com/systemd/systemd/issues/1555.
|
||||
*/
|
||||
if (r > 0)
|
||||
return 0;
|
||||
return log_error_errno(r, "Failed to determine if '%s' is a mountpoint: %m", top);
|
||||
if (r == 0) {
|
||||
/* If this is not a mount point yet, then mount a tmpfs there */
|
||||
r = mount_nofollow_verbose(LOG_ERR, "tmpfs", top, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0555" TMPFS_LIMITS_SYS);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
r = path_is_fs_type(top, SYSFS_MAGIC);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top);
|
||||
|
||||
full = prefix_roota(top, "/full");
|
||||
/* /sys/ might already be mounted as sysfs by the outer child in the !netns case. In this case, it's
|
||||
* all good. Don't touch it because we don't have the right to do so, see
|
||||
* https://github.com/systemd/systemd/issues/1555.
|
||||
*/
|
||||
if (r > 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
full = path_join(top, "/full");
|
||||
if (!full)
|
||||
return log_oom();
|
||||
|
||||
(void) mkdir(full, 0755);
|
||||
|
||||
@@ -501,10 +517,11 @@ int mount_sysfs(const char *dest, MountSettingsMask mount_settings) {
|
||||
if (rmdir(full) < 0)
|
||||
return log_error_errno(errno, "Failed to remove %s: %m", full);
|
||||
|
||||
/* Create mountpoint for cgroups. Otherwise we are not allowed since we
|
||||
* remount /sys read-only.
|
||||
*/
|
||||
const char *x = prefix_roota(top, "/fs/cgroup");
|
||||
/* Create mountpoint for cgroups. Otherwise we are not allowed since we remount /sys/ read-only. */
|
||||
_cleanup_free_ char *x = path_join(top, "/fs/cgroup");
|
||||
if (!x)
|
||||
return log_oom();
|
||||
|
||||
(void) mkdir_p(x, 0755);
|
||||
|
||||
return mount_nofollow_verbose(LOG_ERR, NULL, top, NULL,
|
||||
@@ -541,7 +558,7 @@ int mount_all(const char *dest,
|
||||
} MountPoint;
|
||||
|
||||
static const MountPoint mount_table[] = {
|
||||
/* First we list inner child mounts (i.e. mounts applied *after* entering user namespacing) */
|
||||
/* First we list inner child mounts (i.e. mounts applied *after* entering user namespacing when we are privileged) */
|
||||
{ "proc", "/proc", "proc", NULL, PROC_DEFAULT_MOUNT_FLAGS,
|
||||
MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_MKDIR|MOUNT_FOLLOW_SYMLINKS }, /* we follow symlinks here since not following them requires /proc/ already being mounted, which we don't have here. */
|
||||
|
||||
@@ -575,15 +592,15 @@ int mount_all(const char *dest,
|
||||
{ "mqueue", "/dev/mqueue", "mqueue", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
MOUNT_IN_USERNS|MOUNT_MKDIR },
|
||||
|
||||
/* Then we list outer child mounts (i.e. mounts applied *before* entering user namespacing) */
|
||||
/* Then we list outer child mounts (i.e. mounts applied *before* entering user namespacing when we are privileged) */
|
||||
{ "tmpfs", "/tmp", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME,
|
||||
MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR },
|
||||
{ "tmpfs", "/sys", "tmpfs", "mode=0555" TMPFS_LIMITS_SYS, MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR },
|
||||
MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR|MOUNT_PRIVILEGED },
|
||||
{ "sysfs", "/sys", "sysfs", NULL, SYS_DEFAULT_MOUNT_FLAGS,
|
||||
MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR }, /* skipped if above was mounted */
|
||||
MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* skipped if above was mounted */
|
||||
{ "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
MOUNT_FATAL|MOUNT_MKDIR }, /* skipped if above was mounted */
|
||||
MOUNT_FATAL|MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* skipped if above was mounted */
|
||||
{ "tmpfs", "/dev", "tmpfs", "mode=0755" TMPFS_LIMITS_PRIVATE_DEV, MS_NOSUID|MS_STRICTATIME,
|
||||
MOUNT_FATAL|MOUNT_MKDIR },
|
||||
{ "tmpfs", "/dev/shm", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME,
|
||||
@@ -604,11 +621,11 @@ int mount_all(const char *dest,
|
||||
MOUNT_FATAL|MOUNT_IN_USERNS },
|
||||
#if HAVE_SELINUX
|
||||
{ "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND,
|
||||
MOUNT_MKDIR }, /* Bind mount first (mkdir/chown the mount point in case /sys/ is mounted as minimal skeleton tmpfs) */
|
||||
MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* Bind mount first (mkdir/chown the mount point in case /sys/ is mounted as minimal skeleton tmpfs) */
|
||||
{ NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT,
|
||||
0 }, /* Then, make it r/o (don't mkdir/chown the mount point here, the previous entry already did that) */
|
||||
MOUNT_PRIVILEGED }, /* Then, make it r/o (don't mkdir/chown the mount point here, the previous entry already did that) */
|
||||
{ NULL, "/sys/fs/selinux", NULL, NULL, MS_PRIVATE,
|
||||
0 }, /* Turn off propagation (we only want that for the mount propagation tunnel dir) */
|
||||
MOUNT_PRIVILEGED }, /* Turn off propagation (we only want that for the mount propagation tunnel dir) */
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -617,6 +634,7 @@ int mount_all(const char *dest,
|
||||
bool ro = FLAGS_SET(mount_settings, MOUNT_APPLY_APIVFS_RO);
|
||||
bool in_userns = FLAGS_SET(mount_settings, MOUNT_IN_USERNS);
|
||||
bool tmpfs_tmp = FLAGS_SET(mount_settings, MOUNT_APPLY_TMPFS_TMP);
|
||||
bool privileged = FLAGS_SET(mount_settings, MOUNT_PRIVILEGED);
|
||||
int r;
|
||||
|
||||
for (size_t k = 0; k < ELEMENTSOF(mount_table); k++) {
|
||||
@@ -624,6 +642,10 @@ int mount_all(const char *dest,
|
||||
bool fatal = FLAGS_SET(mount_table[k].mount_settings, MOUNT_FATAL);
|
||||
const char *o;
|
||||
|
||||
/* If we are not privileged but the entry is marked as privileged and to be mounted outside the user namespace, then skip it */
|
||||
if (!privileged && FLAGS_SET(mount_table[k].mount_settings, MOUNT_PRIVILEGED) && !FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS))
|
||||
continue;
|
||||
|
||||
if (in_userns != FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS))
|
||||
continue;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ typedef enum MountSettingsMask {
|
||||
MOUNT_TOUCH = 1 << 9, /* if set, touch file to mount over first */
|
||||
MOUNT_PREFIX_ROOT = 1 << 10,/* if set, prefix the source path with the container's root directory */
|
||||
MOUNT_FOLLOW_SYMLINKS = 1 << 11,/* if set, we'll follow symlinks for the mount target */
|
||||
MOUNT_PRIVILEGED = 1 << 12,/* if set, we'll only mount this in in the outer child if we are running in privileged mode */
|
||||
} MountSettingsMask;
|
||||
|
||||
typedef enum CustomMountType {
|
||||
|
||||
+396
-181
@@ -84,6 +84,7 @@
|
||||
#include "nspawn-stub-pid1.h"
|
||||
#include "nspawn-util.h"
|
||||
#include "nspawn.h"
|
||||
#include "nsresource.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "os-util.h"
|
||||
#include "pager.h"
|
||||
@@ -237,6 +238,7 @@ static char *arg_settings_filename = NULL;
|
||||
static Architecture arg_architecture = _ARCHITECTURE_INVALID;
|
||||
static ImagePolicy *arg_image_policy = NULL;
|
||||
static char *arg_background = NULL;
|
||||
static bool arg_privileged = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
|
||||
@@ -518,6 +520,12 @@ static int detect_unified_cgroup_hierarchy_from_environment(void) {
|
||||
static int detect_unified_cgroup_hierarchy_from_image(const char *directory) {
|
||||
int r;
|
||||
|
||||
if (!arg_privileged) {
|
||||
/* We only support the unified mode when running unprivileged */
|
||||
arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_ALL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Let's inherit the mode to use from the host system, but let's take into consideration what systemd
|
||||
* in the image actually supports. */
|
||||
r = cg_all_unified();
|
||||
@@ -619,7 +627,6 @@ static int parse_mount_settings_env(void) {
|
||||
e = getenv("SYSTEMD_NSPAWN_API_VFS_WRITABLE");
|
||||
if (streq_ptr(e, "network"))
|
||||
arg_mount_settings |= MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_APIVFS_NETNS;
|
||||
|
||||
else if (e) {
|
||||
r = parse_boolean(e);
|
||||
if (r < 0)
|
||||
@@ -1653,6 +1660,21 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
static int verify_arguments(void) {
|
||||
int r;
|
||||
|
||||
SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged);
|
||||
|
||||
if (!arg_privileged) {
|
||||
/* machined is not accessible to unpriv clients */
|
||||
if (arg_register) {
|
||||
log_notice("Automatically implying --register=no, since machined is not accessible to unprivileged clients.");
|
||||
arg_register = false;
|
||||
}
|
||||
|
||||
if (!arg_private_network) {
|
||||
log_notice("Automatically implying --private-network, since mounting /sys/ in an unprivileged user namespaces requires network namespacing.");
|
||||
arg_private_network = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (arg_start_mode == START_PID2 && arg_unified_cgroup_hierarchy == CGROUP_UNIFIED_UNKNOWN) {
|
||||
/* If we are running the stub init in the container, we don't need to look at what the init
|
||||
* in the container supports, because we are not using it. Let's immediately pick the right
|
||||
@@ -2692,6 +2714,9 @@ static int reset_audit_loginuid(void) {
|
||||
if ((arg_clone_ns_flags & CLONE_NEWPID) == 0)
|
||||
return 0;
|
||||
|
||||
if (!arg_privileged)
|
||||
return 0;
|
||||
|
||||
r = read_one_line_file("/proc/self/loginuid", &p);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
@@ -2721,6 +2746,11 @@ static int mount_tunnel_dig(const char *root) {
|
||||
const char *p, *q;
|
||||
int r;
|
||||
|
||||
if (!arg_privileged) {
|
||||
log_debug("Not digging mount tunnel, because running unprivileged.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void) mkdir_p("/run/systemd/nspawn/", 0755);
|
||||
(void) mkdir_p("/run/systemd/nspawn/propagate", 0600);
|
||||
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
|
||||
@@ -2749,6 +2779,11 @@ static int mount_tunnel_dig(const char *root) {
|
||||
static int mount_tunnel_open(void) {
|
||||
int r;
|
||||
|
||||
if (!arg_privileged) {
|
||||
log_debug("Not opening up mount tunnel, because running unprivileged.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = mount_follow_verbose(LOG_ERR, NULL, NSPAWN_MOUNT_TUNNEL, NULL, MS_SLAVE, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -3270,20 +3305,32 @@ static int inner_child(
|
||||
return r;
|
||||
|
||||
if (!arg_network_namespace_path && arg_private_network) {
|
||||
r = unshare(CLONE_NEWNET);
|
||||
_cleanup_close_ int netns_fd = -EBADF;
|
||||
|
||||
if (arg_privileged) {
|
||||
if (unshare(CLONE_NEWNET) < 0)
|
||||
return log_error_errno(errno, "Failed to unshare network namespace: %m");
|
||||
}
|
||||
|
||||
netns_fd = namespace_open_by_type(NAMESPACE_NET);
|
||||
if (netns_fd < 0)
|
||||
return log_error_errno(netns_fd, "Failed to open newly allocate network namespace: %m");
|
||||
|
||||
r = send_one_fd(fd_inner_socket, netns_fd, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to unshare network namespace: %m");
|
||||
return log_error_errno(r, "Failed to send network namespace to supervisor: %m");
|
||||
|
||||
/* Tell the parent that it can setup network interfaces. */
|
||||
(void) barrier_place(barrier); /* #3 */
|
||||
}
|
||||
|
||||
r = mount_sysfs(NULL, arg_mount_settings);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (arg_privileged) {
|
||||
r = mount_sysfs(NULL, arg_mount_settings);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Wait until we are cgroup-ified, so that we
|
||||
* can mount the right cgroup path writable */
|
||||
/* Wait until we are cgroup-ified, so that we can mount the right cgroup path writable */
|
||||
if (!barrier_place_and_sync(barrier)) /* #4 */
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
|
||||
"Parent died too early");
|
||||
@@ -3584,11 +3631,11 @@ static int inner_child(
|
||||
return log_error_errno(errno, "execv(%s) failed: %m", exec_target);
|
||||
}
|
||||
|
||||
static int setup_notify_child(void) {
|
||||
static int setup_notify_child(const void *directory) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
static const union sockaddr_union sa = {
|
||||
_cleanup_free_ char *j = NULL;
|
||||
union sockaddr_union sa = {
|
||||
.un.sun_family = AF_UNIX,
|
||||
.un.sun_path = NSPAWN_NOTIFY_SOCKET_PATH,
|
||||
};
|
||||
int r;
|
||||
|
||||
@@ -3596,7 +3643,17 @@ static int setup_notify_child(void) {
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to allocate notification socket: %m");
|
||||
|
||||
(void) mkdir_parents(NSPAWN_NOTIFY_SOCKET_PATH, 0755);
|
||||
if (directory) {
|
||||
j = path_join(directory, NSPAWN_NOTIFY_SOCKET_PATH);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = sockaddr_un_set_path(&sa.un, j ?: NSPAWN_NOTIFY_SOCKET_PATH);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set AF_UNIX path to %s: %m", j ?: NSPAWN_NOTIFY_SOCKET_PATH);
|
||||
|
||||
(void) mkdir_parents(sa.un.sun_path, 0755);
|
||||
(void) sockaddr_un_unlink(&sa.un);
|
||||
|
||||
WITH_UMASK(0577) { /* only set "w" bit, which is all that's necessary for connecting from the container */
|
||||
@@ -3605,7 +3662,7 @@ static int setup_notify_child(void) {
|
||||
return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m");
|
||||
}
|
||||
|
||||
r = userns_lchown(NSPAWN_NOTIFY_SOCKET_PATH, 0, 0);
|
||||
r = userns_lchown(sa.un.sun_path, 0, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chown " NSPAWN_NOTIFY_SOCKET_PATH ": %m");
|
||||
|
||||
@@ -3621,6 +3678,11 @@ static int setup_unix_export_dir_outside(char **ret) {
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (!arg_privileged) {
|
||||
log_debug("Not digging socket tunnel, because running unprivileged.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *p = NULL;
|
||||
p = path_join("/run/systemd/nspawn/unix-export", arg_machine);
|
||||
if (!p)
|
||||
@@ -3672,6 +3734,10 @@ static int setup_unix_export_host_inside(const char *directory, const char *unix
|
||||
int r;
|
||||
|
||||
assert(directory);
|
||||
|
||||
if (!arg_privileged)
|
||||
return 0;
|
||||
|
||||
assert(unix_export_path);
|
||||
|
||||
r = make_run_host(directory);
|
||||
@@ -3714,10 +3780,16 @@ static int setup_unix_export_host_inside(const char *directory, const char *unix
|
||||
|
||||
static DissectImageFlags determine_dissect_image_flags(void) {
|
||||
return
|
||||
DISSECT_IMAGE_GENERIC_ROOT |
|
||||
DISSECT_IMAGE_REQUIRE_ROOT |
|
||||
DISSECT_IMAGE_RELAX_VAR_CHECK |
|
||||
DISSECT_IMAGE_USR_NO_ROOT |
|
||||
DISSECT_IMAGE_DISCARD_ON_LOOP |
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
|
||||
(arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS) |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
|
||||
(arg_console_mode == CONSOLE_INTERACTIVE ? DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH : 0);
|
||||
}
|
||||
|
||||
static int outer_child(
|
||||
@@ -4093,47 +4165,59 @@ static int outer_child(
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Mark everything as shared so our mounts get propagated down. This is required to make new bind
|
||||
* mounts available in systemd services inside the container that create a new mount namespace. See
|
||||
* https://github.com/systemd/systemd/issues/3860 Further submounts (such as /dev) done after this
|
||||
* will inherit the shared propagation mode.
|
||||
*
|
||||
* IMPORTANT: Do not overmount the root directory anymore from now on to enable moving the root
|
||||
* directory mount to root later on.
|
||||
* https://github.com/systemd/systemd/issues/3847#issuecomment-562735251
|
||||
*/
|
||||
r = mount_switch_root(directory, MS_SHARED);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to move root directory: %m");
|
||||
/* We have different codepaths here for privileged and non-privileged mode. In privileged mode we'll
|
||||
* now switch into the target directory, and then do the final setup from there. If a user namespace
|
||||
* is then allocated for the container, the root mount and everything else will be out of reach for
|
||||
* it. For unprivileged containers we cannot do that however, since we couldn't mount a sysfs and
|
||||
* procfs then anymore, since that only works if there's an unobstructed instance currently
|
||||
* visible. Hence there we do it the other way round: we first allocate a new set set of namespaces
|
||||
* (and fork for it) for which we then mount sysfs/procfs, and only then switch root. */
|
||||
|
||||
/* We finished setting up the rootfs which is a shared mount. The mount tunnel needs to be a
|
||||
* dependent mount otherwise we can't MS_MOVE mounts that were propagated from the host into
|
||||
* the container. */
|
||||
r = mount_tunnel_open();
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (arg_privileged) {
|
||||
/* Mark everything as shared so our mounts get propagated down. This is required to make new
|
||||
* bind mounts available in systemd services inside the container that create a new mount
|
||||
* namespace. See https://github.com/systemd/systemd/issues/3860 Further submounts (such as
|
||||
* /dev/) done after this will inherit the shared propagation mode.
|
||||
*
|
||||
* IMPORTANT: Do not overmount the root directory anymore from now on to enable moving the root
|
||||
* directory mount to root later on.
|
||||
* https://github.com/systemd/systemd/issues/3847#issuecomment-562735251
|
||||
*/
|
||||
r = mount_switch_root(directory, MS_SHARED);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to move root directory: %m");
|
||||
|
||||
if (arg_userns_mode != USER_NAMESPACE_NO) {
|
||||
/* In order to mount procfs and sysfs in an unprivileged container the kernel
|
||||
* requires that a fully visible instance is already present in the target mount
|
||||
* namespace. Mount one here so the inner child can mount its own instances. Later
|
||||
* we umount the temporary instances created here before we actually exec the
|
||||
* payload. Since the rootfs is shared the umount will propagate into the container.
|
||||
* Note, the inner child wouldn't be able to unmount the instances on its own since
|
||||
* it doesn't own the originating mount namespace. IOW, the outer child needs to do
|
||||
* this. */
|
||||
r = pin_fully_visible_fs();
|
||||
/* We finished setting up the rootfs which is a shared mount. The mount tunnel needs to be a
|
||||
* dependent mount otherwise we can't MS_MOVE mounts that were propagated from the host into
|
||||
* the container. */
|
||||
r = mount_tunnel_open();
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
fd = setup_notify_child();
|
||||
if (arg_userns_mode != USER_NAMESPACE_NO) {
|
||||
/* In order to mount procfs and sysfs in an unprivileged container the kernel
|
||||
* requires that a fully visible instance is already present in the target mount
|
||||
* namespace. Mount one here so the inner child can mount its own instances. Later
|
||||
* we umount the temporary instances created here before we actually exec the
|
||||
* payload. Since the rootfs is shared the umount will propagate into the container.
|
||||
* Note, the inner child wouldn't be able to unmount the instances on its own since
|
||||
* it doesn't own the originating mount namespace. IOW, the outer child needs to do
|
||||
* this. */
|
||||
r = pin_fully_visible_fs();
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
fd = setup_notify_child(NULL);
|
||||
} else
|
||||
fd = setup_notify_child(directory);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
pid = raw_clone(SIGCHLD|CLONE_NEWNS|
|
||||
arg_clone_ns_flags |
|
||||
(arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0));
|
||||
(arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0) |
|
||||
((arg_private_network && !arg_privileged) ? CLONE_NEWNET : 0));
|
||||
if (pid < 0)
|
||||
return log_error_errno(errno, "Failed to fork inner child: %m");
|
||||
if (pid == 0) {
|
||||
@@ -4152,6 +4236,26 @@ static int outer_child(
|
||||
return log_error_errno(r, "Failed to join network namespace: %m");
|
||||
}
|
||||
|
||||
if (!arg_privileged) {
|
||||
/* In unprivileged operation, sysfs + procfs are special, we'll have to mount them
|
||||
* inside the inner namespaces, but before we switch root. Hence do so here. */
|
||||
_cleanup_free_ char *j = path_join(directory, "/proc");
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
r = mount_follow_verbose(LOG_ERR, "proc", j, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = mount_sysfs(directory, arg_mount_settings);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = mount_switch_root(directory, MS_SHARED);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to move root directory: %m");
|
||||
}
|
||||
|
||||
r = inner_child(barrier, fd_inner_socket, fds, os_release_pairs);
|
||||
if (r < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
@@ -4433,6 +4537,9 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r
|
||||
static int setup_notify_parent(sd_event *event, int fd, pid_t *inner_child_pid, sd_event_source **notify_event_source) {
|
||||
int r;
|
||||
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
|
||||
r = sd_event_add_io(event, notify_event_source, fd, EPOLLIN, nspawn_dispatch_notify_fd, inner_child_pid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate notify event source: %m");
|
||||
@@ -4812,26 +4919,28 @@ static int load_settings(void) {
|
||||
return 0;
|
||||
|
||||
/* We first look in the admin's directories in /etc and /run */
|
||||
FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") {
|
||||
_cleanup_free_ char *j = NULL;
|
||||
if (arg_privileged) {
|
||||
FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") {
|
||||
_cleanup_free_ char *j = NULL;
|
||||
|
||||
j = path_join(i, arg_settings_filename);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
j = path_join(i, arg_settings_filename);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
f = fopen(j, "re");
|
||||
if (f) {
|
||||
p = TAKE_PTR(j);
|
||||
f = fopen(j, "re");
|
||||
if (f) {
|
||||
p = TAKE_PTR(j);
|
||||
|
||||
/* By default, we trust configuration from /etc and /run */
|
||||
if (arg_settings_trusted < 0)
|
||||
arg_settings_trusted = true;
|
||||
/* By default, we trust configuration from /etc and /run */
|
||||
if (arg_settings_trusted < 0)
|
||||
arg_settings_trusted = true;
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
if (errno != ENOENT)
|
||||
return log_error_errno(errno, "Failed to open %s: %m", j);
|
||||
}
|
||||
|
||||
if (errno != ENOENT)
|
||||
return log_error_errno(errno, "Failed to open %s: %m", j);
|
||||
}
|
||||
|
||||
if (!f) {
|
||||
@@ -4891,10 +5000,14 @@ static int load_oci_bundle(void) {
|
||||
|
||||
static int run_container(
|
||||
DissectedImage *dissected_image,
|
||||
int userns_fd,
|
||||
FDSet *fds,
|
||||
char veth_name[IFNAMSIZ], bool *veth_created,
|
||||
char veth_name[IFNAMSIZ],
|
||||
bool *veth_created,
|
||||
struct ExposeArgs *expose_args,
|
||||
int *master, pid_t *pid, int *ret) {
|
||||
int *master,
|
||||
pid_t *pid,
|
||||
int *ret) {
|
||||
|
||||
static const struct sigaction sa = {
|
||||
.sa_handler = nop_signal_handler,
|
||||
@@ -4979,11 +5092,44 @@ static int run_container(
|
||||
"Path %s doesn't refer to a network namespace, refusing.", arg_network_namespace_path);
|
||||
}
|
||||
|
||||
*pid = raw_clone(SIGCHLD|CLONE_NEWNS);
|
||||
if (*pid < 0)
|
||||
return log_error_errno(errno, "clone() failed%s: %m",
|
||||
errno == EINVAL ?
|
||||
", do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in)" : "");
|
||||
if (arg_privileged) {
|
||||
assert(userns_fd < 0);
|
||||
|
||||
/* If we have no user namespace then we'll clone and create a new mount namepsace right-away. */
|
||||
|
||||
*pid = raw_clone(SIGCHLD|CLONE_NEWNS);
|
||||
if (*pid < 0)
|
||||
return log_error_errno(errno, "clone() failed%s: %m",
|
||||
errno == EINVAL ?
|
||||
", do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in)" : "");
|
||||
} else {
|
||||
assert(userns_fd >= 0);
|
||||
|
||||
/* If we have a user namespace then we'll clone() first, and then join the user namespace,
|
||||
* and then open the mount namespace, so that it is owned by the user namespace */
|
||||
|
||||
*pid = raw_clone(SIGCHLD);
|
||||
if (*pid < 0)
|
||||
return log_error_errno(errno, "clone() failed: %m");
|
||||
|
||||
if (*pid == 0) {
|
||||
if (setns(userns_fd, CLONE_NEWUSER) < 0) {
|
||||
log_error_errno(errno, "Failed to join allocate user namespace: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = reset_uid_gid();
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to reset UID/GID to root: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (unshare(CLONE_NEWNS) < 0) {
|
||||
log_error_errno(errno, "Failed to unshare file system namespace: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*pid == 0) {
|
||||
/* The outer child only has a file system namespace. */
|
||||
@@ -5120,19 +5266,13 @@ static int run_container(
|
||||
/* Wait until the child has unshared its network namespace. */
|
||||
if (!barrier_place_and_sync(&barrier)) /* #3 */
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Child died too early");
|
||||
}
|
||||
|
||||
if (child_netns_fd < 0) {
|
||||
/* Make sure we have an open file descriptor to the child's network
|
||||
* namespace so it stays alive even if the child exits. */
|
||||
r = namespace_open(*pid,
|
||||
/* ret_pidns_fd = */ NULL,
|
||||
/* ret_mntns_fd = */ NULL,
|
||||
&child_netns_fd,
|
||||
/* ret_userns_fd = */ NULL,
|
||||
/* ret_root_fd = */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open child network namespace: %m");
|
||||
/* Make sure we have an open file descriptor to the child's network namespace so it
|
||||
* stays alive even if the child exits. */
|
||||
assert(child_netns_fd < 0);
|
||||
child_netns_fd = receive_one_fd(fd_inner_socket_pair[0], 0);
|
||||
if (child_netns_fd < 0)
|
||||
return log_error_errno(r, "Failed to receive child network namespace: %m");
|
||||
}
|
||||
|
||||
r = move_network_interfaces(child_netns_fd, arg_network_interfaces);
|
||||
@@ -5140,12 +5280,29 @@ static int run_container(
|
||||
return r;
|
||||
|
||||
if (arg_network_veth) {
|
||||
r = setup_veth(arg_machine, *pid, veth_name,
|
||||
arg_network_bridge || arg_network_zone, &arg_network_provided_mac);
|
||||
if (r < 0)
|
||||
return r;
|
||||
else if (r > 0)
|
||||
ifi = r;
|
||||
if (arg_privileged) {
|
||||
r = setup_veth(arg_machine, *pid, veth_name,
|
||||
arg_network_bridge || arg_network_zone, &arg_network_provided_mac);
|
||||
if (r < 0)
|
||||
return r;
|
||||
else if (r > 0)
|
||||
ifi = r;
|
||||
} else {
|
||||
_cleanup_free_ char *host_ifname = NULL;
|
||||
|
||||
r = nsresource_add_netif(userns_fd, child_netns_fd, /* namespace_ifname= */ NULL, &host_ifname, /* ret_namespace_ifname= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add network interface to container: %m");
|
||||
|
||||
ifi = if_nametoindex(host_ifname);
|
||||
if (ifi == 0)
|
||||
return log_error_errno(errno, "Failed to resolve interface '%s': %m", host_ifname);
|
||||
|
||||
if (strlen(host_ifname) >= IFNAMSIZ)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Host interface name too long?");
|
||||
|
||||
strcpy(veth_name, host_ifname);
|
||||
}
|
||||
|
||||
if (arg_network_bridge) {
|
||||
/* Add the interface to a bridge */
|
||||
@@ -5184,9 +5341,12 @@ static int run_container(
|
||||
}
|
||||
|
||||
if (arg_register || !arg_keep_unit) {
|
||||
r = sd_bus_default_system(&bus);
|
||||
if (arg_privileged)
|
||||
r = sd_bus_default_system(&bus);
|
||||
else
|
||||
r = sd_bus_default_user(&bus);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open system bus: %m");
|
||||
return log_error_errno(r, "Failed to open bus: %m");
|
||||
|
||||
r = sd_bus_set_close_on_exit(bus, false);
|
||||
if (r < 0)
|
||||
@@ -5247,7 +5407,13 @@ static int run_container(
|
||||
} else if (arg_slice || arg_property)
|
||||
log_notice("Machine and scope registration turned off, --slice= and --property= settings will have no effect.");
|
||||
|
||||
r = create_subcgroup(*pid, arg_keep_unit, arg_unified_cgroup_hierarchy);
|
||||
r = create_subcgroup(
|
||||
*pid,
|
||||
arg_keep_unit,
|
||||
arg_unified_cgroup_hierarchy,
|
||||
arg_uid_shift,
|
||||
userns_fd,
|
||||
arg_privileged);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -5255,14 +5421,8 @@ static int run_container(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = chown_cgroup(*pid, arg_unified_cgroup_hierarchy, arg_uid_shift);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Notify the child that the parent is ready with all
|
||||
* its setup (including cgroup-ification), and that
|
||||
* the child can now hand over control to the code to
|
||||
* run inside the container. */
|
||||
/* Notify the child that the parent is ready with all its setup (including cgroup-ification), and
|
||||
* that the child can now hand over control to the code to run inside the container. */
|
||||
(void) barrier_place(&barrier); /* #4 */
|
||||
|
||||
/* Block SIGCHLD here, before notifying child.
|
||||
@@ -5428,7 +5588,7 @@ static int run_container(
|
||||
|
||||
fd_kmsg_fifo = safe_close(fd_kmsg_fifo);
|
||||
|
||||
if (arg_private_network) {
|
||||
if (arg_private_network && arg_privileged) {
|
||||
r = move_back_network_interfaces(child_netns_fd, arg_network_interfaces);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -5569,6 +5729,10 @@ static int cant_be_in_netns(void) {
|
||||
if (r == -ENOENT || ERRNO_IS_NEG_DISCONNECT(r))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Sorry, but --image= requires access to the host's /run/ hierarchy, since we need access to udev.");
|
||||
if (ERRNO_IS_NEG_PRIVILEGE(r)) {
|
||||
log_debug_errno(r, "Can't connect to udev control socket, assuming we are in same netns.");
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect socket to udev control socket: %m");
|
||||
|
||||
@@ -5587,7 +5751,7 @@ static int cant_be_in_netns(void) {
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
bool remove_directory = false, remove_image = false, veth_created = false, remove_tmprootdir = false;
|
||||
_cleanup_close_ int master = -EBADF;
|
||||
_cleanup_close_ int master = -EBADF, userns_fd = -EBADF;
|
||||
_cleanup_fdset_free_ FDSet *fds = NULL;
|
||||
int r, n_fd_passed, ret = EXIT_SUCCESS;
|
||||
char veth_name[IFNAMSIZ] = "";
|
||||
@@ -5602,17 +5766,12 @@ static int run(int argc, char *argv[]) {
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
arg_privileged = getuid() == 0;
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
goto finish;
|
||||
|
||||
if (geteuid() != 0) {
|
||||
r = log_warning_errno(SYNTHETIC_ERRNO(EPERM),
|
||||
argc >= 2 ? "Need to be root." :
|
||||
"Need to be root (and some arguments are usually required).\nHint: try --help");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = cant_be_in_netns();
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
@@ -5643,7 +5802,7 @@ static int run(int argc, char *argv[]) {
|
||||
if (!arg_private_network && arg_userns_mode != USER_NAMESPACE_NO && arg_uid_shift > 0)
|
||||
arg_caps_retain &= ~(UINT64_C(1) << CAP_NET_BIND_SERVICE);
|
||||
|
||||
r = cg_unified();
|
||||
r = cg_unified(); /* initialize cache early */
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m");
|
||||
goto finish;
|
||||
@@ -5664,6 +5823,16 @@ static int run(int argc, char *argv[]) {
|
||||
/* Reapply environment settings. */
|
||||
(void) detect_unified_cgroup_hierarchy_from_environment();
|
||||
|
||||
if (!arg_privileged) {
|
||||
r = cg_all_unified();
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to determine if we are in unified cgroupv2 mode: %m");
|
||||
goto finish;
|
||||
}
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unprivileged operation only supported in unified cgroupv2 mode.");
|
||||
}
|
||||
|
||||
/* Ignore SIGPIPE here, because we use splice() on the ptyfwd stuff and that will generate SIGPIPE if
|
||||
* the result is closed. Note that the container payload child will reset signal mask+handler anyway,
|
||||
* so just turning this off here means we only turn it off in nspawn itself, not any children. */
|
||||
@@ -5683,9 +5852,21 @@ static int run(int argc, char *argv[]) {
|
||||
* the child. Functions like copy_devnodes() change the umask temporarily. */
|
||||
umask(0022);
|
||||
|
||||
if (arg_console_mode < 0)
|
||||
arg_console_mode = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) ?
|
||||
CONSOLE_INTERACTIVE : CONSOLE_READ_ONLY;
|
||||
|
||||
if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */
|
||||
arg_quiet = true;
|
||||
|
||||
if (arg_directory) {
|
||||
assert(!arg_image);
|
||||
|
||||
if (!arg_privileged) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Invoking container from plain directory tree is currently not supported if called without privileges.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Safety precaution: let's not allow running images from the live host OS image, as long as
|
||||
* /var from the host will propagate into container dynamically (because bad things happen if
|
||||
* two systems write to the same /var). Let's allow it for the special cases where /var is
|
||||
@@ -5722,7 +5903,11 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
/* We take an exclusive lock on this image, since it's our private, ephemeral copy
|
||||
* only owned by us and no one else. */
|
||||
r = image_path_lock(np, LOCK_EX|LOCK_NB, &tree_global_lock, &tree_local_lock);
|
||||
r = image_path_lock(
|
||||
np,
|
||||
LOCK_EX|LOCK_NB,
|
||||
arg_privileged ? &tree_global_lock : NULL,
|
||||
&tree_local_lock);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to lock %s: %m", np);
|
||||
goto finish;
|
||||
@@ -5754,7 +5939,11 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock);
|
||||
r = image_path_lock(
|
||||
arg_directory,
|
||||
(arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB,
|
||||
arg_privileged ? &tree_global_lock : NULL,
|
||||
&tree_local_lock);
|
||||
if (r == -EBUSY) {
|
||||
log_error_errno(r, "Directory tree %s is currently busy.", arg_directory);
|
||||
goto finish;
|
||||
@@ -5846,15 +6035,12 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
} else {
|
||||
DissectImageFlags dissect_image_flags =
|
||||
DISSECT_IMAGE_GENERIC_ROOT |
|
||||
DISSECT_IMAGE_REQUIRE_ROOT |
|
||||
DISSECT_IMAGE_RELAX_VAR_CHECK |
|
||||
DISSECT_IMAGE_USR_NO_ROOT |
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_PIN_PARTITION_DEVICES;
|
||||
determine_dissect_image_flags();
|
||||
|
||||
assert(arg_image);
|
||||
assert(!arg_template);
|
||||
|
||||
|
||||
r = chase_and_update(&arg_image, 0);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
@@ -5869,7 +6055,11 @@ static int run(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
/* Always take an exclusive lock on our own ephemeral copy. */
|
||||
r = image_path_lock(np, LOCK_EX|LOCK_NB, &tree_global_lock, &tree_local_lock);
|
||||
r = image_path_lock(
|
||||
np,
|
||||
LOCK_EX|LOCK_NB,
|
||||
arg_privileged ? &tree_global_lock : NULL,
|
||||
&tree_local_lock);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to create image lock: %m");
|
||||
goto finish;
|
||||
@@ -5894,7 +6084,11 @@ static int run(int argc, char *argv[]) {
|
||||
free_and_replace(arg_image, np);
|
||||
remove_image = true;
|
||||
} else {
|
||||
r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock);
|
||||
r = image_path_lock(
|
||||
arg_image,
|
||||
(arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB,
|
||||
arg_privileged ? &tree_global_lock : NULL,
|
||||
&tree_local_lock);
|
||||
if (r == -EBUSY) {
|
||||
log_error_errno(r, "Disk image %s is currently busy.", arg_image);
|
||||
goto finish;
|
||||
@@ -5929,57 +6123,81 @@ static int run(int argc, char *argv[]) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = loop_device_make_by_path(
|
||||
arg_image,
|
||||
arg_read_only ? O_RDONLY : O_RDWR,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&loop);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to set up loopback block device: %m");
|
||||
goto finish;
|
||||
if (arg_privileged) {
|
||||
r = loop_device_make_by_path(
|
||||
arg_image,
|
||||
arg_read_only ? O_RDONLY : O_RDWR,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&loop);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to set up loopback block device: %m");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = dissect_loop_device_and_warn(
|
||||
loop,
|
||||
&arg_verity_settings,
|
||||
/* mount_options=*/ NULL,
|
||||
arg_image_policy ?: &image_policy_container,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r == -ENOPKG) {
|
||||
/* dissected_image_and_warn() already printed a brief error message. Extend on that with more details */
|
||||
log_notice("Note that the disk image needs to\n"
|
||||
" a) either contain only a single MBR partition of type 0x83 that is marked bootable\n"
|
||||
" b) or contain a single GPT partition of type 0FC63DAF-8483-4772-8E79-3D69D8477DE4\n"
|
||||
" c) or follow https://uapi-group.org/specifications/specs/discoverable_partitions_specification\n"
|
||||
" d) or contain a file system without a partition table\n"
|
||||
"in order to be bootable with systemd-nspawn.");
|
||||
goto finish;
|
||||
}
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(
|
||||
dissected_image,
|
||||
loop->fd,
|
||||
&arg_verity_settings);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (dissected_image->has_verity && !arg_verity_settings.root_hash && !dissected_image->has_verity_sig)
|
||||
log_notice("Note: image %s contains verity information, but no root hash specified and no embedded "
|
||||
"root hash signature found! Proceeding without integrity checking.", arg_image);
|
||||
|
||||
r = dissected_image_decrypt_interactively(
|
||||
dissected_image,
|
||||
NULL,
|
||||
&arg_verity_settings,
|
||||
dissect_image_flags);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
} else {
|
||||
_cleanup_free_ char *userns_name = strjoin("nspawn-", arg_machine);
|
||||
if (!userns_name) {
|
||||
r = log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* if we are unprivileged, let's allocate a 64K userns first */
|
||||
userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000));
|
||||
if (userns_fd < 0) {
|
||||
r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = mountfsd_mount_image(
|
||||
arg_image,
|
||||
userns_fd,
|
||||
arg_image_policy,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = dissect_loop_device_and_warn(
|
||||
loop,
|
||||
&arg_verity_settings,
|
||||
/* mount_options=*/ NULL,
|
||||
arg_image_policy ?: &image_policy_container,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r == -ENOPKG) {
|
||||
/* dissected_image_and_warn() already printed a brief error message. Extend on that with more details */
|
||||
log_notice("Note that the disk image needs to\n"
|
||||
" a) either contain only a single MBR partition of type 0x83 that is marked bootable\n"
|
||||
" b) or contain a single GPT partition of type 0FC63DAF-8483-4772-8E79-3D69D8477DE4\n"
|
||||
" c) or follow https://uapi-group.org/specifications/specs/discoverable_partitions_specification\n"
|
||||
" d) or contain a file system without a partition table\n"
|
||||
"in order to be bootable with systemd-nspawn.");
|
||||
goto finish;
|
||||
}
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(
|
||||
dissected_image,
|
||||
loop->fd,
|
||||
&arg_verity_settings);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (dissected_image->has_verity && !arg_verity_settings.root_hash && !dissected_image->has_verity_sig)
|
||||
log_notice("Note: image %s contains verity information, but no root hash specified and no embedded "
|
||||
"root hash signature found! Proceeding without integrity checking.", arg_image);
|
||||
|
||||
r = dissected_image_decrypt_interactively(
|
||||
dissected_image,
|
||||
NULL,
|
||||
&arg_verity_settings,
|
||||
0);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
/* Now that we mounted the image, let's try to remove it again, if it is ephemeral */
|
||||
if (remove_image && unlink(arg_image) >= 0)
|
||||
remove_image = false;
|
||||
@@ -5992,13 +6210,6 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (arg_console_mode < 0)
|
||||
arg_console_mode = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) ?
|
||||
CONSOLE_INTERACTIVE : CONSOLE_READ_ONLY;
|
||||
|
||||
if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */
|
||||
arg_quiet = true;
|
||||
|
||||
if (!arg_quiet) {
|
||||
const char *t = arg_image ?: arg_directory;
|
||||
_cleanup_free_ char *u = NULL;
|
||||
@@ -6029,11 +6240,13 @@ static int run(int argc, char *argv[]) {
|
||||
expose_args.fw_ctx = fw_ctx;
|
||||
}
|
||||
for (;;) {
|
||||
r = run_container(dissected_image,
|
||||
fds,
|
||||
veth_name, &veth_created,
|
||||
&expose_args, &master,
|
||||
&pid, &ret);
|
||||
r = run_container(
|
||||
dissected_image,
|
||||
userns_fd,
|
||||
fds,
|
||||
veth_name, &veth_created,
|
||||
&expose_args, &master,
|
||||
&pid, &ret);
|
||||
if (r <= 0)
|
||||
break;
|
||||
}
|
||||
@@ -6075,7 +6288,7 @@ finish:
|
||||
log_debug_errno(errno, "Can't remove temporary root directory '%s', ignoring: %m", tmprootdir);
|
||||
}
|
||||
|
||||
if (arg_machine) {
|
||||
if (arg_machine && arg_privileged) {
|
||||
const char *p;
|
||||
|
||||
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
|
||||
@@ -6089,9 +6302,11 @@ finish:
|
||||
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4);
|
||||
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET6, &expose_args.address6);
|
||||
|
||||
if (veth_created)
|
||||
(void) remove_veth_links(veth_name, arg_network_veth_extra);
|
||||
(void) remove_bridge(arg_network_zone);
|
||||
if (arg_privileged) {
|
||||
if (veth_created)
|
||||
(void) remove_veth_links(veth_name, arg_network_veth_extra);
|
||||
(void) remove_bridge(arg_network_zone);
|
||||
}
|
||||
|
||||
custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts);
|
||||
expose_port_free_all(arg_expose_ports);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
if conf.get('HAVE_VMLINUX_H') != 1
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
userns_restrict_bpf_o_unstripped = custom_target(
|
||||
'userns-restrict.bpf.unstripped.o',
|
||||
input : 'userns-restrict.bpf.c',
|
||||
output : 'userns-restrict.bpf.unstripped.o',
|
||||
command : bpf_o_unstripped_cmd,
|
||||
depends : vmlinux_h_dependency)
|
||||
|
||||
userns_restrict_bpf_o = custom_target(
|
||||
'userns-restrict.bpf.o',
|
||||
input : userns_restrict_bpf_o_unstripped,
|
||||
output : 'userns-restrict.bpf.o',
|
||||
command : bpf_o_cmd)
|
||||
|
||||
userns_restrict_skel_h = custom_target(
|
||||
'userns-restrict.skel.h',
|
||||
input : userns_restrict_bpf_o,
|
||||
output : 'userns-restrict.skel.h',
|
||||
command : skel_h_cmd,
|
||||
capture : true)
|
||||
@@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
/* The SPDX header above is actually correct in claiming this was
|
||||
* LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
|
||||
* compatible with GPL we will claim this to be GPL however, which should be
|
||||
* fine given that LGPL-2.1-or-later downgrades to GPL if needed.
|
||||
*/
|
||||
|
||||
#include "bpf-dlopen.h"
|
||||
|
||||
/* libbpf is used via dlopen(), so rename symbols */
|
||||
#define bpf_object__attach_skeleton sym_bpf_object__attach_skeleton
|
||||
#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton
|
||||
#define bpf_object__load_skeleton sym_bpf_object__load_skeleton
|
||||
#define bpf_object__open_skeleton sym_bpf_object__open_skeleton
|
||||
|
||||
#include "bpf/userns_restrict/userns-restrict.skel.h"
|
||||
@@ -0,0 +1,174 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
/* The SPDX header above is actually correct in claiming this was
|
||||
* LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
|
||||
* compatible with GPL we will claim this to be GPL however, which should be
|
||||
* fine given that LGPL-2.1-or-later downgrades to GPL if needed.
|
||||
*/
|
||||
|
||||
/* If offsetof() is implemented via __builtin_offset() then it doesn't work on current compilers, since the
|
||||
* built-ins do not understand CO-RE. Let's undefine any such macros here, to force bpf_helpers.h to define
|
||||
* its own definitions for this. (In new versions it will do so automatically, but at least in libbpf 1.1.0
|
||||
* it does not.) */
|
||||
#undef offsetof
|
||||
#undef container_of
|
||||
|
||||
#include "vmlinux.h"
|
||||
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <errno.h>
|
||||
|
||||
void *bpf_rdonly_cast(void *, __u32) __ksym;
|
||||
|
||||
/* BPF module that implements an allowlist of mounts (identified by mount ID) for user namespaces (identified
|
||||
* by their inode number in nsfs) that restricts creation of inodes (which would inherit the callers UID/GID)
|
||||
* or changing of ownership (similar).
|
||||
*
|
||||
* This hooks into the varius path-based LSM entrypoints that control inode creation as well as chmod(), and
|
||||
* then looks up the calling process' user namespace in a global map of namespaces, which points us to
|
||||
* another map that is simply a list of allowed mnt_ids. */
|
||||
|
||||
// FIXME: ACL adjustments are currently not blocked. There's no path-based LSM hook available in the kernel
|
||||
// for setting xattrs or ACLs, hence we cannot easily block them, even though we want that. We can get away
|
||||
// with ignoring this for now, as ACLs never define ownership, but purely access: i.e. ACLs never allow
|
||||
// taking possession of an object, but only control access to it. Thus, things like suid access modes should
|
||||
// not be reachable through it. It still sucks though that a user can persistently add an ACL entry to a file
|
||||
// with their transient UIDs/GIDs.
|
||||
|
||||
/* kernel currently enforces a maximum usernamespace nesting depth of 32, see create_user_ns() in the kernel sources */
|
||||
#define USER_NAMESPACE_DEPTH_MAX 32U
|
||||
|
||||
struct mnt_id_map {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 1); /* placeholder, configured otherwise by nsresourced */
|
||||
__type(key, int);
|
||||
__type(value, int);
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH_OF_MAPS);
|
||||
__uint(max_entries, 1); /* placeholder, configured otherwise by nsresourced */
|
||||
__type(key, unsigned); /* userns inode */
|
||||
__array(values, struct mnt_id_map);
|
||||
} userns_mnt_id_hash SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 4096);
|
||||
} userns_ringbuf SEC(".maps");
|
||||
|
||||
static inline struct mount *real_mount(struct vfsmount *mnt) {
|
||||
return container_of(mnt, struct mount, mnt);
|
||||
}
|
||||
|
||||
static int validate_inode_on_mount(struct inode *inode, struct vfsmount *v) {
|
||||
struct user_namespace *mount_userns, *task_userns, *p;
|
||||
unsigned task_userns_inode;
|
||||
struct task_struct *task;
|
||||
void *mnt_id_map;
|
||||
struct mount *m;
|
||||
int mnt_id;
|
||||
|
||||
/* Get user namespace from vfsmount */
|
||||
m = bpf_rdonly_cast(real_mount(v), bpf_core_type_id_kernel(struct mount));
|
||||
mount_userns = m->mnt_ns->user_ns;
|
||||
|
||||
/* Get user namespace from task */
|
||||
task = (struct task_struct*) bpf_get_current_task_btf();
|
||||
task_userns = task->cred->user_ns;
|
||||
|
||||
/* Is the file on a mount that belongs to our own user namespace or a child of it? If so, say
|
||||
* yes immediately. */
|
||||
p = mount_userns;
|
||||
for (unsigned i = 0; i < USER_NAMESPACE_DEPTH_MAX; i++) {
|
||||
if (p == task_userns)
|
||||
return 0; /* our task's user namespace (or a child thereof) owns this superblock: allow! */
|
||||
|
||||
p = p->parent;
|
||||
if (!p)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Hmm, something is fishy if there's more than 32 levels of namespaces involved. Let's better be
|
||||
* safe than sorry, and refuse. */
|
||||
if (p)
|
||||
return -EPERM;
|
||||
|
||||
/* This is a mount foreign to our task's user namespace, let's consult our allow list */
|
||||
task_userns_inode = task_userns->ns.inum;
|
||||
|
||||
mnt_id_map = bpf_map_lookup_elem(&userns_mnt_id_hash, &task_userns_inode);
|
||||
if (!mnt_id_map) /* No rules installed for this userns? Then say yes, too! */
|
||||
return 0;
|
||||
|
||||
mnt_id = m->mnt_id;
|
||||
|
||||
/* Otherwise, say yes if the mount ID is allowlisted */
|
||||
if (bpf_map_lookup_elem(mnt_id_map, &mnt_id))
|
||||
return 0;
|
||||
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
static int validate_path(const struct path *path, int ret) {
|
||||
struct inode *inode;
|
||||
struct vfsmount *v;
|
||||
|
||||
if (ret != 0) /* propagate earlier error */
|
||||
return ret;
|
||||
|
||||
inode = path->dentry->d_inode;
|
||||
v = path->mnt;
|
||||
|
||||
return validate_inode_on_mount(inode, v);
|
||||
}
|
||||
|
||||
SEC("lsm/path_chown")
|
||||
int BPF_PROG(userns_restrict_path_chown, struct path *path, void* uid, void *gid, int ret) {
|
||||
return validate_path(path, ret);
|
||||
}
|
||||
|
||||
SEC("lsm/path_mkdir")
|
||||
int BPF_PROG(userns_restrict_path_mkdir, struct path *dir, struct dentry *dentry, umode_t mode, int ret) {
|
||||
return validate_path(dir, ret);
|
||||
}
|
||||
|
||||
SEC("lsm/path_mknod")
|
||||
int BPF_PROG(userns_restrict_path_mknod, const struct path *dir, struct dentry *dentry, umode_t mode, unsigned int dev, int ret) {
|
||||
return validate_path(dir, ret);
|
||||
}
|
||||
|
||||
SEC("lsm/path_symlink")
|
||||
int BPF_PROG(userns_restrict_path_symlink, const struct path *dir, struct dentry *dentry, const char *old_name, int ret) {
|
||||
return validate_path(dir, ret);
|
||||
}
|
||||
|
||||
SEC("lsm/path_link")
|
||||
int BPF_PROG(userns_restrict_path_link, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry, int ret) {
|
||||
return validate_path(new_dir, ret);
|
||||
}
|
||||
|
||||
SEC("kprobe/free_user_ns")
|
||||
void BPF_KPROBE(userns_restrict_free_user_ns, struct work_struct *work) {
|
||||
struct user_namespace *userns;
|
||||
unsigned inode;
|
||||
void *mnt_id_map;
|
||||
|
||||
/* Inform userspace that a user namespace just went away. I wish there was a nicer way to hook into
|
||||
* user namespaces being deleted than using kprobes, but couldn't find any. */
|
||||
|
||||
userns = bpf_rdonly_cast(container_of(work, struct user_namespace, work),
|
||||
bpf_core_type_id_kernel(struct user_namespace));
|
||||
|
||||
inode = userns->ns.inum;
|
||||
|
||||
mnt_id_map = bpf_map_lookup_elem(&userns_mnt_id_hash, &inode);
|
||||
if (!mnt_id_map) /* No rules installed for this userns? Then send no notification. */
|
||||
return;
|
||||
|
||||
bpf_ringbuf_output(&userns_ringbuf, &inode, sizeof(inode), 0);
|
||||
}
|
||||
|
||||
static const char _license[] SEC("license") = "GPL";
|
||||
@@ -0,0 +1,48 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
subdir('bpf/userns_restrict')
|
||||
|
||||
systemd_nsresourcework_sources = files(
|
||||
'nsresourcework.c',
|
||||
'userns-restrict.c',
|
||||
'userns-registry.c',
|
||||
)
|
||||
|
||||
systemd_nsresourced_sources = files(
|
||||
'nsresourced-manager.c',
|
||||
'nsresourced.c',
|
||||
'userns-restrict.c',
|
||||
'userns-registry.c',
|
||||
)
|
||||
|
||||
userns_restrict_include = include_directories('.')
|
||||
|
||||
if conf.get('HAVE_VMLINUX_H') == 1
|
||||
systemd_nsresourcework_sources += userns_restrict_skel_h
|
||||
systemd_nsresourced_sources += userns_restrict_skel_h
|
||||
|
||||
executables += [
|
||||
test_template + {
|
||||
'sources' : files('test-userns-restrict.c', 'userns-restrict.c') + userns_restrict_skel_h,
|
||||
'conditions' : ['ENABLE_NSRESOURCED', 'HAVE_VMLINUX_H'],
|
||||
'include_directories' : [ includes, userns_restrict_include ],
|
||||
},
|
||||
]
|
||||
endif
|
||||
|
||||
executables += [
|
||||
libexec_template + {
|
||||
'name' : 'systemd-nsresourcework',
|
||||
'conditions' : ['ENABLE_NSRESOURCED'],
|
||||
'sources' : systemd_nsresourcework_sources,
|
||||
'dependencies' : threads,
|
||||
'include_directories' : [ includes, userns_restrict_include ],
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-nsresourced',
|
||||
'conditions' : ['ENABLE_NSRESOURCED'],
|
||||
'sources' : systemd_nsresourced_sources,
|
||||
'dependencies' : threads,
|
||||
'include_directories' : [ includes, userns_restrict_include ],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,647 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/mount.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "sd-daemon.h"
|
||||
|
||||
#include "bpf-dlopen.h"
|
||||
#include "build-path.h"
|
||||
#include "common-signal.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "nsresourced-manager.h"
|
||||
#include "parse-util.h"
|
||||
#include "process-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "set.h"
|
||||
#include "signal-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "strv.h"
|
||||
#include "umask-util.h"
|
||||
#include "unaligned.h"
|
||||
#include "user-util.h"
|
||||
#include "userns-registry.h"
|
||||
#include "userns-restrict.h"
|
||||
|
||||
#define LISTEN_TIMEOUT_USEC (25 * USEC_PER_SEC)
|
||||
|
||||
static int start_workers(Manager *m, bool explicit_request);
|
||||
|
||||
static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(s);
|
||||
|
||||
assert_se(!set_remove(m->workers_dynamic, s) != !set_remove(m->workers_fixed, s));
|
||||
sd_event_source_disable_unref(s);
|
||||
|
||||
if (si->si_code == CLD_EXITED) {
|
||||
if (si->si_status == EXIT_SUCCESS)
|
||||
log_debug("Worker " PID_FMT " exited successfully.", si->si_pid);
|
||||
else
|
||||
log_warning("Worker " PID_FMT " died with a failure exit status %i, ignoring.", si->si_pid, si->si_status);
|
||||
} else if (si->si_code == CLD_KILLED)
|
||||
log_warning("Worker " PID_FMT " was killed by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
|
||||
else if (si->si_code == CLD_DUMPED)
|
||||
log_warning("Worker " PID_FMT " dumped core by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
|
||||
else
|
||||
log_warning("Got unexpected exit code via SIGCHLD, ignoring.");
|
||||
|
||||
(void) start_workers(m, /* explicit_request= */ false); /* Fill up workers again if we fell below the low watermark */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(s);
|
||||
|
||||
(void) start_workers(m, /* explicit_request=*/ true); /* Workers told us there's more work, let's add one more worker as long as we are below the high watermark */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_deferred_start_worker(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(s);
|
||||
|
||||
m->deferred_start_worker_event_source = sd_event_source_unref(m->deferred_start_worker_event_source);
|
||||
|
||||
(void) start_workers(m, /* explicit_request=*/ false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
|
||||
event_source_hash_ops,
|
||||
sd_event_source,
|
||||
(void (*)(const sd_event_source*, struct siphash*)) trivial_hash_func,
|
||||
(int (*)(const sd_event_source*, const sd_event_source*)) trivial_compare_func,
|
||||
sd_event_source_disable_unref);
|
||||
|
||||
int manager_new(Manager **ret) {
|
||||
_cleanup_(manager_freep) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
m = new(Manager, 1);
|
||||
if (!m)
|
||||
return -ENOMEM;
|
||||
|
||||
*m = (Manager) {
|
||||
.listen_fd = -EBADF,
|
||||
.worker_ratelimit = {
|
||||
.interval = 2 * USEC_PER_SEC,
|
||||
.burst = 250,
|
||||
},
|
||||
.registry_fd = -EBADF,
|
||||
};
|
||||
|
||||
r = sd_event_new(&m->event);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_set_signal_exit(m->event, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m");
|
||||
|
||||
r = sd_event_set_watchdog(m->event, true);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to enable watchdog handling, ignoring: %m");
|
||||
|
||||
r = sd_event_add_signal(m->event, NULL, SIGUSR2|SD_EVENT_SIGNAL_PROCMASK, on_sigusr2, m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Manager* manager_free(Manager *m) {
|
||||
if (!m)
|
||||
return NULL;
|
||||
|
||||
set_free(m->workers_fixed);
|
||||
set_free(m->workers_dynamic);
|
||||
|
||||
m->deferred_start_worker_event_source = sd_event_source_unref(m->deferred_start_worker_event_source);
|
||||
|
||||
safe_close(m->listen_fd);
|
||||
|
||||
#if HAVE_VMLINUX_H
|
||||
sd_event_source_disable_unref(m->userns_restrict_bpf_ring_buffer_event_source);
|
||||
if (m->userns_restrict_bpf_ring_buffer)
|
||||
sym_ring_buffer__free(m->userns_restrict_bpf_ring_buffer);
|
||||
userns_restrict_bpf_free(m->userns_restrict_bpf);
|
||||
#endif
|
||||
|
||||
safe_close(m->registry_fd);
|
||||
|
||||
sd_event_unref(m->event);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
|
||||
static size_t manager_current_workers(Manager *m) {
|
||||
assert(m);
|
||||
|
||||
return set_size(m->workers_fixed) + set_size(m->workers_dynamic);
|
||||
}
|
||||
|
||||
static int start_one_worker(Manager *m) {
|
||||
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *source = NULL;
|
||||
bool fixed;
|
||||
pid_t pid;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
fixed = set_size(m->workers_fixed) < NSRESOURCE_WORKERS_MIN;
|
||||
|
||||
r = safe_fork_full(
|
||||
"(sd-worker)",
|
||||
/* stdio_fds= */ NULL,
|
||||
&m->listen_fd, 1,
|
||||
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_CLOSE_ALL_FDS,
|
||||
&pid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to fork new worker child: %m");
|
||||
if (r == 0) {
|
||||
char pids[DECIMAL_STR_MAX(pid_t)];
|
||||
/* Child */
|
||||
|
||||
if (m->listen_fd == 3) {
|
||||
r = fd_cloexec(3, false);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to turn off O_CLOEXEC for fd 3: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if (dup2(m->listen_fd, 3) < 0) { /* dup2() creates with O_CLOEXEC off */
|
||||
log_error_errno(errno, "Failed to move listen fd to 3: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
safe_close(m->listen_fd);
|
||||
}
|
||||
|
||||
xsprintf(pids, PID_FMT, pid);
|
||||
if (setenv("LISTEN_PID", pids, 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setenv("LISTEN_FDS", "1", 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $LISTEN_FDS: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setenv("NSRESOURCE_FIXED_WORKER", one_zero(fixed), 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $NSRESOURCE_FIXED_WORKER: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#if HAVE_VMLINUX_H
|
||||
bool supported = m->userns_restrict_bpf;
|
||||
#else
|
||||
bool supported = false;
|
||||
#endif
|
||||
|
||||
/* Tell the workers whether to enable the userns API */
|
||||
if (setenv("NSRESOURCE_API", one_zero(supported), 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $NSRESOURCE_API: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = setenv_systemd_log_level();
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to set $SYSTEMD_LOG_LEVEL: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = invoke_callout_binary(SYSTEMD_NSRESOURCEWORK_PATH, STRV_MAKE("systemd-nsresourcework", "xxxxxxxxxxxxxxxx")); /* With some extra space rename_process() can make use of */
|
||||
log_error_errno(r, "Failed start worker process: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = sd_event_add_child(m->event, &source, pid, WEXITED, on_worker_exit, m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to watch child " PID_FMT ": %m", pid);
|
||||
|
||||
r = set_ensure_put(
|
||||
fixed ? &m->workers_fixed : &m->workers_dynamic,
|
||||
&event_source_hash_ops,
|
||||
source);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add child process to set: %m");
|
||||
|
||||
TAKE_PTR(source);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_workers(Manager *m, bool explicit_request) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
for (;;) {
|
||||
size_t n;
|
||||
|
||||
n = manager_current_workers(m);
|
||||
if (n >= NSRESOURCE_WORKERS_MIN && (!explicit_request || n >= NSRESOURCE_WORKERS_MAX))
|
||||
break;
|
||||
|
||||
if (!ratelimit_below(&m->worker_ratelimit)) {
|
||||
|
||||
/* If we keep starting workers too often but none sticks, let's fail the whole
|
||||
* daemon, something is wrong */
|
||||
if (n == 0) {
|
||||
sd_event_exit(m->event, EXIT_FAILURE);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Worker threads requested too frequently, but worker count is zero, something is wrong.");
|
||||
}
|
||||
|
||||
/* Otherwise, let's stop spawning more for a while. */
|
||||
log_warning("Worker threads requested too frequently, not starting new ones for a while.");
|
||||
|
||||
if (!m->deferred_start_worker_event_source) {
|
||||
r = sd_event_add_time(
|
||||
m->event,
|
||||
&m->deferred_start_worker_event_source,
|
||||
CLOCK_MONOTONIC,
|
||||
ratelimit_end(&m->worker_ratelimit),
|
||||
/* accuracy_usec= */ 0,
|
||||
on_deferred_start_worker,
|
||||
m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate deferred start worker event source: %m");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
r = start_one_worker(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
explicit_request = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void manager_release_userns_bpf(Manager *m, uint64_t inode) {
|
||||
#if HAVE_VMLINUX_H
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
if (inode == 0)
|
||||
return;
|
||||
|
||||
assert(m->userns_restrict_bpf);
|
||||
|
||||
r = userns_restrict_reset_by_inode(m->userns_restrict_bpf, inode);
|
||||
if (r < 0)
|
||||
return (void) log_warning_errno(r, "Failed to remove namespace inode from BPF map, ignoring: %m");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void manager_release_userns_fds(Manager *m, uint64_t inode) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(inode != 0);
|
||||
|
||||
r = sd_notifyf(/* unset_environment= */ false,
|
||||
"FDSTOREREMOVE=1\n"
|
||||
"FDNAME=userns-%" PRIu64 "\n", inode);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to send fd store removal message, ignoring: %m");
|
||||
}
|
||||
|
||||
static void manager_release_userns_by_inode(Manager *m, uint64_t inode) {
|
||||
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
|
||||
_cleanup_close_ int lock_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(inode != 0);
|
||||
|
||||
lock_fd = userns_registry_lock(m->registry_fd);
|
||||
if (lock_fd < 0)
|
||||
return (void) log_error_errno(lock_fd, "Failed to lock registry: %m");
|
||||
|
||||
r = userns_registry_load_by_userns_inode(m->registry_fd, inode, &userns_info);
|
||||
if (r < 0)
|
||||
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
|
||||
"Failed to find userns for inode %" PRIu64 ", ignoring: %m", inode);
|
||||
|
||||
if (userns_info && uid_is_valid(userns_info->start))
|
||||
log_debug("Removing user namespace mapping %" PRIu64 " for UID " UID_FMT ".", inode, userns_info->start);
|
||||
else
|
||||
log_debug("Removing user namespace mapping %" PRIu64 ".", inode);
|
||||
|
||||
/* Remove the BPF rules */
|
||||
manager_release_userns_bpf(m, inode);
|
||||
|
||||
/* Remove the resources from the fdstore */
|
||||
manager_release_userns_fds(m, inode);
|
||||
|
||||
/* And finally remove the resources file from disk */
|
||||
if (userns_info) {
|
||||
/* Remove the cgroups of this userns */
|
||||
r = userns_info_remove_cgroups(userns_info);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to remove cgroups of user namespace: %m");
|
||||
|
||||
r = userns_registry_remove(m->registry_fd, userns_info);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to remove user namespace '%s', ignoring.", userns_info->name);
|
||||
}
|
||||
}
|
||||
|
||||
static int manager_scan_registry(Manager *m, Set **registry_inodes) {
|
||||
_cleanup_free_ DirectoryEntries *de = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(registry_inodes);
|
||||
assert(m->registry_fd >= 0);
|
||||
|
||||
r = readdir_all(m->registry_fd, RECURSE_DIR_IGNORE_DOT, &de);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate registry.");
|
||||
|
||||
for (size_t i = 0; i < de->n_entries; i++) {
|
||||
struct dirent *dentry = de->entries[i];
|
||||
_cleanup_free_ char *u = NULL;
|
||||
const char *e, *p;
|
||||
uint64_t inode;
|
||||
|
||||
p = startswith(dentry->d_name, "i");
|
||||
if (!p)
|
||||
continue;
|
||||
|
||||
e = endswith(p, ".userns");
|
||||
if (!e)
|
||||
continue;
|
||||
|
||||
u = strndup(p, e - p);
|
||||
if (!u)
|
||||
return log_oom();
|
||||
|
||||
r = safe_atou64(u, &inode);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to parse userns inode number from '%s', skipping: %m", dentry->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inode > UINT32_MAX) { /* namespace inode numbers are 23bit only right now */
|
||||
log_warning("userns inode number outside of 32bit range, skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (set_ensure_put(registry_inodes, NULL, UINT32_TO_PTR(inode)) < 0)
|
||||
return log_oom();
|
||||
|
||||
log_debug("Found user namespace %" PRIu64 " in registry directory", inode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_make_listen_socket(Manager *m) {
|
||||
static const union sockaddr_union sockaddr = {
|
||||
.un.sun_family = AF_UNIX,
|
||||
.un.sun_path = "/run/systemd/io.systemd.NamespaceResource",
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
if (m->listen_fd >= 0)
|
||||
return 0;
|
||||
|
||||
m->listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||
if (m->listen_fd < 0)
|
||||
return log_error_errno(errno, "Failed to bind on socket: %m");
|
||||
|
||||
(void) sockaddr_un_unlink(&sockaddr.un);
|
||||
|
||||
WITH_UMASK(0000)
|
||||
if (bind(m->listen_fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0)
|
||||
return log_error_errno(errno, "Failed to bind socket: %m");
|
||||
|
||||
r = mkdir_p("/run/systemd/userdb", 0755);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create /run/systemd/userdb: %m");
|
||||
|
||||
r = symlink_idempotent("../io.systemd.NamespaceResource", "/run/systemd/userdb/io.systemd.NamespaceResource", /* make_relative= */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to symlink userdb socket: %m");
|
||||
|
||||
if (listen(m->listen_fd, SOMAXCONN) < 0)
|
||||
return log_error_errno(errno, "Failed to listen on socket: %m");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int manager_scan_listen_fds(Manager *m, Set **fdstore_inodes) {
|
||||
_cleanup_strv_free_ char **names = NULL;
|
||||
int n, r;
|
||||
|
||||
assert(m);
|
||||
assert(fdstore_inodes);
|
||||
|
||||
n = sd_listen_fds_with_names(/* unset_environment= */ true, &names);
|
||||
if (n < 0)
|
||||
return log_error_errno(n, "Failed to determine number of passed file descriptors: %m");
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
_cleanup_close_ int fd = SD_LISTEN_FDS_START + i; /* Take possession */
|
||||
const char *e;
|
||||
|
||||
/* If this is a BPF allowlist related fd, just close it, but remember which start UIDs this covers */
|
||||
e = startswith(names[i], "userns-");
|
||||
if (e) {
|
||||
uint64_t inode;
|
||||
|
||||
r = safe_atou64(e, &inode);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to parse UID from fd name '%s', ignoring: %m", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inode > UINT32_MAX) {
|
||||
log_warning("Inode number outside of 32bit range, ignoring");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (set_ensure_put(fdstore_inodes, NULL, UINT32_TO_PTR(inode)) < 0)
|
||||
return log_oom();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We don't check the name for the stream socket, for compatibility with older versions */
|
||||
r = sd_is_socket(fd, AF_UNIX, SOCK_STREAM, 1);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to detect if passed file descriptor is a socket: %m");
|
||||
if (r > 0) {
|
||||
if (m->listen_fd >= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Passed more than one AF_UNIX/SOCK_STREAM socket, refusing.");
|
||||
|
||||
m->listen_fd = TAKE_FD(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
log_warning("Closing passed file descriptor %i (%s) we don't recognize.", fd, names[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_VMLINUX_H
|
||||
static int ringbuf_event(void *userdata, void *data, size_t size) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
size_t n;
|
||||
|
||||
if ((size % sizeof(unsigned int)) != 0) /* Not multiples of "unsigned int"? */
|
||||
return -EIO;
|
||||
|
||||
n = size / sizeof(unsigned int);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const void *d;
|
||||
uint64_t inode;
|
||||
|
||||
d = (const uint8_t*) data + i * sizeof(unsigned int);
|
||||
inode = unaligned_read_ne32(d);
|
||||
|
||||
log_debug("Got BPF ring buffer notification that user namespace %" PRIu64 " is now dead.", inode);
|
||||
manager_release_userns_by_inode(m, inode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_ringbuf_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = sym_ring_buffer__poll(m->userns_restrict_bpf_ring_buffer, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Got failure reading from BPF ring buffer: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_setup_bpf(Manager *m) {
|
||||
int rb_fd = -EBADF, poll_fd = -EBADF, r;
|
||||
|
||||
assert(m);
|
||||
assert(!m->userns_restrict_bpf);
|
||||
assert(!m->userns_restrict_bpf_ring_buffer);
|
||||
assert(!m->userns_restrict_bpf_ring_buffer_event_source);
|
||||
|
||||
r = userns_restrict_install(/* pin= */ true, &m->userns_restrict_bpf);
|
||||
if (r < 0) {
|
||||
log_notice_errno(r, "Proceeding with user namespace interfaces disabled.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
rb_fd = sym_bpf_map__fd(m->userns_restrict_bpf->maps.userns_ringbuf);
|
||||
if (rb_fd < 0)
|
||||
return log_error_errno(rb_fd, "Failed to get fd of ring buffer: %m");
|
||||
|
||||
m->userns_restrict_bpf_ring_buffer = sym_ring_buffer__new(rb_fd, ringbuf_event, m, NULL);
|
||||
if (!m->userns_restrict_bpf_ring_buffer)
|
||||
return log_error_errno(errno, "Failed to allocate BPF ring buffer object: %m");
|
||||
|
||||
poll_fd = sym_ring_buffer__epoll_fd(m->userns_restrict_bpf_ring_buffer);
|
||||
if (poll_fd < 0)
|
||||
return log_error_errno(poll_fd, "Failed to get poll fd of ring buffer: %m");
|
||||
|
||||
r = sd_event_add_io(
|
||||
m->event,
|
||||
&m->userns_restrict_bpf_ring_buffer_event_source,
|
||||
poll_fd,
|
||||
EPOLLIN,
|
||||
on_ringbuf_io,
|
||||
m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate event source for BPF ring buffer: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int manager_setup_bpf(Manager *m) {
|
||||
log_notice("Not setting up BPF subsystem, as functionality has been disabled at compile time.");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int manager_startup(Manager *m) {
|
||||
_cleanup_(set_freep) Set *fdstore_inodes = NULL, *registry_inodes = NULL;
|
||||
void *p;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(m->registry_fd < 0);
|
||||
assert(m->listen_fd < 0);
|
||||
|
||||
m->registry_fd = userns_registry_open_fd();
|
||||
if (m->registry_fd < 0)
|
||||
return log_error_errno(m->registry_fd, "Failed to open registry directory: %m");
|
||||
|
||||
r = manager_setup_bpf(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_scan_listen_fds(m, &fdstore_inodes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_scan_registry(m, ®istry_inodes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If there are resources tied to UIDs not found in the registry, then release them */
|
||||
SET_FOREACH(p, fdstore_inodes) {
|
||||
uint64_t inode;
|
||||
|
||||
if (set_contains(registry_inodes, p))
|
||||
continue;
|
||||
|
||||
inode = PTR_TO_UINT32(p);
|
||||
|
||||
log_debug("Found stale fd store entry for user namespace %" PRIu64 ", removing.", inode);
|
||||
manager_release_userns_by_inode(m, inode);
|
||||
}
|
||||
|
||||
r = manager_make_listen_socket(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be
|
||||
* GC'ed on idle */
|
||||
if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, TIMEVAL_STORE(LISTEN_TIMEOUT_USEC), sizeof(struct timeval)) < 0)
|
||||
return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m");
|
||||
|
||||
r = start_workers(m, /* explicit_request= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-event.h"
|
||||
|
||||
typedef struct Manager Manager;
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "ratelimit.h"
|
||||
|
||||
#define NSRESOURCE_WORKERS_MIN 5
|
||||
#define NSRESOURCE_WORKERS_MAX 4096
|
||||
|
||||
struct Manager {
|
||||
sd_event *event;
|
||||
|
||||
Set *workers_fixed; /* Workers 0…NSRESOURCE_WORKERS_MIN */
|
||||
Set *workers_dynamic; /* Workers NSRESOURCES_WORKERS_MIN+1…NSRESOURCES_WORKERS_MAX */
|
||||
|
||||
int listen_fd;
|
||||
|
||||
RateLimit worker_ratelimit;
|
||||
|
||||
sd_event_source *deferred_start_worker_event_source;
|
||||
|
||||
#if HAVE_VMLINUX_H
|
||||
struct userns_restrict_bpf *userns_restrict_bpf;
|
||||
struct ring_buffer *userns_restrict_bpf_ring_buffer;
|
||||
sd_event_source *userns_restrict_bpf_ring_buffer_event_source;
|
||||
#endif
|
||||
|
||||
int registry_fd;
|
||||
};
|
||||
|
||||
int manager_new(Manager **ret);
|
||||
Manager* manager_free(Manager *m);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
|
||||
|
||||
int manager_startup(Manager *m);
|
||||
@@ -0,0 +1,46 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "daemon-util.h"
|
||||
#include "nsresourced-manager.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "signal-util.h"
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(manager_freep) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
umask(0022);
|
||||
|
||||
if (argc != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
|
||||
|
||||
if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.NamespaceResource", 1) < 0)
|
||||
return log_error_errno(errno, "Failed to set $SYSTEMD_BYPASS_USERDB: %m");
|
||||
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
|
||||
|
||||
r = manager_new(&m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not create manager: %m");
|
||||
|
||||
r = manager_startup(m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to start up daemon: %m");
|
||||
|
||||
_unused_ _cleanup_(notify_on_cleanup) const char *notify_stop = NULL;
|
||||
notify_stop = notify_start(NOTIFY_READY, NOTIFY_STOPPING);
|
||||
|
||||
r = sd_event_loop(m->event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Event loop failed: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,182 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "main-func.h"
|
||||
#include "missing_mount.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "namespace-util.h"
|
||||
#include "process-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "userns-restrict.h"
|
||||
|
||||
static int make_tmpfs_fsmount(void) {
|
||||
_cleanup_close_ int fsfd = -EBADF, mntfd = -EBADF;
|
||||
|
||||
fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
|
||||
assert_se(fsfd >= 0);
|
||||
assert_se(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) >= 0);
|
||||
|
||||
mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
|
||||
assert_se(mntfd >= 0);
|
||||
|
||||
return TAKE_FD(mntfd);
|
||||
}
|
||||
|
||||
static void test_works_reg(int parent_fd, const char *fname) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
fd = openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666);
|
||||
assert_se(fd >= 0);
|
||||
}
|
||||
|
||||
static void test_fails_reg(int parent_fd, const char *fname) {
|
||||
errno = 0;
|
||||
assert_se(openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666) < 0);
|
||||
assert_se(errno == EPERM);
|
||||
}
|
||||
|
||||
static void test_works_dir(int parent_fd, const char *fname) {
|
||||
assert_se(mkdirat(parent_fd, fname, 0666) >= 0);
|
||||
}
|
||||
|
||||
static void test_fails_dir(int parent_fd, const char *fname) {
|
||||
errno = 0;
|
||||
assert_se(mkdirat(parent_fd, fname, 0666) < 0);
|
||||
assert_se(errno == EPERM);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(userns_restrict_bpf_freep) struct userns_restrict_bpf *obj = NULL;
|
||||
_cleanup_close_ int userns_fd = -EBADF, host_fd1 = -EBADF, host_tmpfs = -EBADF, afd = -EBADF, bfd = -EBADF;
|
||||
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
|
||||
_cleanup_(sigkill_waitp) pid_t pid = 0;
|
||||
int r;
|
||||
|
||||
log_set_max_level(LOG_DEBUG);
|
||||
log_open();
|
||||
|
||||
r = userns_restrict_install(/* pin= */ false, &obj);
|
||||
if (ERRNO_IS_NOT_SUPPORTED(r)) {
|
||||
log_notice("Skipping test, LSM-BPF logic not supported.");
|
||||
return EXIT_TEST_SKIP;
|
||||
}
|
||||
if (ERRNO_IS_PRIVILEGE(r)) {
|
||||
log_notice("Skipping test, lacking privileges.");
|
||||
return EXIT_TEST_SKIP;
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert_se(mkdtemp_malloc(NULL, &t) >= 0);
|
||||
|
||||
host_fd1 = open(t, O_DIRECTORY|O_CLOEXEC);
|
||||
assert_se(host_fd1 >= 0);
|
||||
|
||||
host_tmpfs = make_tmpfs_fsmount();
|
||||
assert_se(host_tmpfs >= 0);
|
||||
|
||||
userns_fd = userns_acquire("0 0 1", "0 0 1");
|
||||
if (userns_fd < 0)
|
||||
return log_error_errno(userns_fd, "Failed to make user namespace: %m");
|
||||
|
||||
r = userns_restrict_put_by_fd(
|
||||
obj,
|
||||
userns_fd,
|
||||
/* replace= */ true,
|
||||
/* mount_fds= */ NULL,
|
||||
/* n_mount_fds= */ 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to restrict user namespace: %m");
|
||||
|
||||
afd = eventfd(0, EFD_CLOEXEC);
|
||||
bfd = eventfd(0, EFD_CLOEXEC);
|
||||
|
||||
assert_se(afd >= 0 && bfd >= 0);
|
||||
|
||||
r = safe_fork("(test)", FORK_DEATHSIG_SIGKILL, &pid);
|
||||
assert_se(r >= 0);
|
||||
if (r == 0) {
|
||||
_cleanup_close_ int private_tmpfs = -EBADF;
|
||||
|
||||
assert_se(setns(userns_fd, CLONE_NEWUSER) >= 0);
|
||||
assert_se(unshare(CLONE_NEWNS) >= 0);
|
||||
|
||||
/* Allocate tmpfs locally */
|
||||
private_tmpfs = make_tmpfs_fsmount();
|
||||
|
||||
/* These two host mounts should be inaccessible */
|
||||
test_fails_reg(host_fd1, "test");
|
||||
test_fails_reg(host_tmpfs, "xxx");
|
||||
test_fails_dir(host_fd1, "test2");
|
||||
test_fails_dir(host_tmpfs, "xxx2");
|
||||
|
||||
/* But this mount created locally should be fine */
|
||||
test_works_reg(private_tmpfs, "yyy");
|
||||
test_works_dir(private_tmpfs, "yyy2");
|
||||
|
||||
/* Let's sync with the parent, so that it allowlists more stuff for us */
|
||||
assert_se(eventfd_write(afd, 1) >= 0);
|
||||
uint64_t x;
|
||||
assert_se(eventfd_read(bfd, &x) >= 0);
|
||||
|
||||
/* And now we should also have access to the host tmpfs */
|
||||
test_works_reg(host_tmpfs, "zzz");
|
||||
test_works_reg(private_tmpfs, "aaa");
|
||||
test_works_dir(host_tmpfs, "zzz2");
|
||||
test_works_dir(private_tmpfs, "aaa2");
|
||||
|
||||
/* But this one should still fail */
|
||||
test_fails_reg(host_fd1, "bbb");
|
||||
test_fails_dir(host_fd1, "bbb2");
|
||||
|
||||
/* Sync again, to get more stuff allowlisted */
|
||||
assert_se(eventfd_write(afd, 1) >= 0);
|
||||
assert_se(eventfd_read(bfd, &x) >= 0);
|
||||
|
||||
/* Everything should now be allowed */
|
||||
test_works_reg(host_tmpfs, "ccc");
|
||||
test_works_reg(host_fd1, "ddd");
|
||||
test_works_reg(private_tmpfs, "eee");
|
||||
test_works_dir(host_tmpfs, "ccc2");
|
||||
test_works_reg(host_fd1, "ddd2");
|
||||
test_works_dir(private_tmpfs, "eee2");
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
uint64_t x;
|
||||
assert_se(eventfd_read(afd, &x) >= 0);
|
||||
|
||||
r = userns_restrict_put_by_fd(
|
||||
obj,
|
||||
userns_fd,
|
||||
/* replace= */ false,
|
||||
&host_tmpfs,
|
||||
1);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to loosen user namespace: %m");
|
||||
|
||||
assert_se(eventfd_write(bfd, 1) >= 0);
|
||||
|
||||
assert_se(eventfd_read(afd, &x) >= 0);
|
||||
|
||||
r = userns_restrict_put_by_fd(
|
||||
obj,
|
||||
userns_fd,
|
||||
/* replace= */ false,
|
||||
&host_fd1,
|
||||
1);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to loosen user namespace: %m");
|
||||
|
||||
assert_se(eventfd_write(bfd, 1) >= 0);
|
||||
|
||||
assert_se(wait_for_terminate_and_check("(test)", pid, WAIT_LOG) >= 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
@@ -0,0 +1,646 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "chase.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "json.h"
|
||||
#include "missing_magic.h"
|
||||
#include "path-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "rm-rf.h"
|
||||
#include "user-util.h"
|
||||
#include "userns-registry.h"
|
||||
|
||||
int userns_registry_open_fd(void) {
|
||||
int fd;
|
||||
|
||||
fd = chase_and_open(
|
||||
"/run/systemd/nsresource/registry",
|
||||
/* root= */ NULL,
|
||||
CHASE_MKDIR_0755,
|
||||
O_CLOEXEC|O_DIRECTORY|O_CREAT,
|
||||
/* ret_path= */ NULL);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(fd, "Failed to open registry dir: %m");
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int userns_registry_lock(int dir_fd) {
|
||||
_cleanup_close_ int registry_fd = -EBADF, lock_fd = -EBADF;
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
lock_fd = xopenat_lock_full(dir_fd, "lock", O_CREAT|O_RDWR|O_CLOEXEC, /* xopen_flags= */ 0, 0600, LOCK_BSD, LOCK_EX);
|
||||
if (lock_fd < 0)
|
||||
return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m");
|
||||
|
||||
return TAKE_FD(lock_fd);
|
||||
}
|
||||
|
||||
UserNamespaceInfo* userns_info_new(void) {
|
||||
UserNamespaceInfo *info = new(UserNamespaceInfo, 1);
|
||||
if (!info)
|
||||
return NULL;
|
||||
|
||||
*info = (UserNamespaceInfo) {
|
||||
.owner = UID_INVALID,
|
||||
.start = UID_INVALID,
|
||||
.target = UID_INVALID,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
UserNamespaceInfo *userns_info_free(UserNamespaceInfo *userns) {
|
||||
if (!userns)
|
||||
return NULL;
|
||||
|
||||
free(userns->cgroups);
|
||||
free(userns->name);
|
||||
|
||||
return mfree(userns);
|
||||
}
|
||||
|
||||
static int dispatch_cgroups_array(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
UserNamespaceInfo *info = ASSERT_PTR(userdata);
|
||||
_cleanup_free_ uint64_t *cgroups = NULL;
|
||||
size_t n_cgroups = 0;
|
||||
|
||||
if (json_variant_is_null(variant)) {
|
||||
info->cgroups = mfree(info->cgroups);
|
||||
info->n_cgroups = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!json_variant_is_array(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
|
||||
|
||||
cgroups = new(uint64_t, json_variant_elements(variant));
|
||||
if (!cgroups)
|
||||
return json_log_oom(variant, flags);
|
||||
|
||||
JsonVariant *e;
|
||||
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
|
||||
bool found = false;
|
||||
|
||||
if (!json_variant_is_unsigned(e))
|
||||
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a number.");
|
||||
|
||||
FOREACH_ARRAY(cg, cgroups, n_cgroups)
|
||||
if (*cg == json_variant_unsigned(e)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (found) /* suppress duplicate */
|
||||
continue;
|
||||
|
||||
cgroups[n_cgroups++] = json_variant_unsigned(e);
|
||||
}
|
||||
|
||||
assert(n_cgroups <= json_variant_elements(variant));
|
||||
|
||||
free_and_replace(info->cgroups, cgroups);
|
||||
info->n_cgroups = n_cgroups;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int userns_registry_load(int dir_fd, const char *fn, UserNamespaceInfo **ret) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "owner", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserNamespaceInfo, owner), JSON_MANDATORY },
|
||||
{ "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserNamespaceInfo, name), JSON_MANDATORY },
|
||||
{ "userns", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_inode), JSON_MANDATORY },
|
||||
{ "start", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start), 0 },
|
||||
{ "size", JSON_VARIANT_UNSIGNED, json_dispatch_uint32, offsetof(UserNamespaceInfo, size), 0 },
|
||||
{ "target", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target), 0 },
|
||||
{ "cgroups", JSON_VARIANT_ARRAY, dispatch_cgroups_array, 0, 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_close_ int registry_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
r = json_parse_file_at(NULL, dir_fd, fn, 0, &v, NULL, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
userns_info = userns_info_new();
|
||||
if (!userns_info)
|
||||
return -ENOMEM;
|
||||
|
||||
r = json_dispatch(v, dispatch_table, 0, userns_info);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (userns_info->userns_inode == 0)
|
||||
return -EBADMSG;
|
||||
if (userns_info->start == 0)
|
||||
return -EBADMSG;
|
||||
if (userns_info->size == 0) {
|
||||
if (uid_is_valid(userns_info->start) || uid_is_valid(userns_info->target))
|
||||
return -EBADMSG;
|
||||
} else {
|
||||
if (!uid_is_valid(userns_info->start) || !uid_is_valid(userns_info->target))
|
||||
return -EBADMSG;
|
||||
|
||||
if (userns_info->size > UINT32_MAX - userns_info->start ||
|
||||
userns_info->size > UINT32_MAX - userns_info->target)
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(userns_info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_registry_uid_exists(int dir_fd, uid_t start) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
|
||||
assert(dir_fd >= 0);
|
||||
|
||||
if (!uid_is_valid(start))
|
||||
return -ENOENT;
|
||||
|
||||
if (start == 0)
|
||||
return true;
|
||||
|
||||
if (asprintf(&fn, "u" UID_FMT ".userns", start) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return errno == ENOENT ? false : -errno;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int userns_registry_name_exists(int dir_fd, const char *name) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
|
||||
assert(dir_fd >= 0);
|
||||
|
||||
if (!userns_name_is_valid(name))
|
||||
return -EINVAL;
|
||||
|
||||
fn = strjoin("n", name, ".userns");
|
||||
if (!fn)
|
||||
return -ENOMEM;
|
||||
|
||||
if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return errno == ENOENT ? false : -errno;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int userns_registry_inode_exists(int dir_fd, uint64_t inode) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
|
||||
assert(dir_fd >= 0);
|
||||
|
||||
if (inode <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (asprintf(&fn, "i%" PRIu64 ".userns", inode) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return errno == ENOENT ? false : -errno;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int userns_registry_load_by_start_uid(int dir_fd, uid_t start, UserNamespaceInfo **ret) {
|
||||
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
|
||||
_cleanup_close_ int registry_fd = -EBADF;
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
int r;
|
||||
|
||||
if (!uid_is_valid(start))
|
||||
return -ENOENT;
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
if (asprintf(&fn, "u" UID_FMT ".userns", start) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
r = userns_registry_load(dir_fd, fn, &userns_info);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (userns_info->start != start)
|
||||
return -EBADMSG;
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(userns_info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_registry_load_by_userns_inode(int dir_fd, uint64_t inode, UserNamespaceInfo **ret) {
|
||||
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
|
||||
_cleanup_close_ int registry_fd = -EBADF;
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
int r;
|
||||
|
||||
if (inode == 0)
|
||||
return -ENOENT;
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
if (asprintf(&fn, "i%" PRIu64 ".userns", inode) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
r = userns_registry_load(dir_fd, fn, &userns_info);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (userns_info->userns_inode != inode)
|
||||
return -EBADMSG;
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(userns_info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_registry_load_by_name(int dir_fd, const char *name, UserNamespaceInfo **ret) {
|
||||
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
|
||||
_cleanup_close_ int registry_fd = -EBADF;
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
|
||||
if (!userns_name_is_valid(name)) /* Invalid names never exist */
|
||||
return -ENOENT;
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
fn = strjoin("n", name, ".userns");
|
||||
if (!fn)
|
||||
return -ENOMEM;
|
||||
|
||||
r = userns_registry_load(dir_fd, fn, &userns_info);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq_ptr(userns_info->name, name))
|
||||
return -EBADMSG;
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(userns_info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_registry_store(int dir_fd, UserNamespaceInfo *info) {
|
||||
_cleanup_close_ int registry_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(info);
|
||||
|
||||
if (!uid_is_valid(info->owner) ||
|
||||
!info->name ||
|
||||
info->userns_inode == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *cgroup_array = NULL;
|
||||
FOREACH_ARRAY(cg, info->cgroups, info->n_cgroups) {
|
||||
r = json_variant_append_arrayb(
|
||||
&cgroup_array,
|
||||
JSON_BUILD_UNSIGNED(*cg));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *def = NULL;
|
||||
r = json_build(&def, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("owner", JSON_BUILD_UNSIGNED(info->owner)),
|
||||
JSON_BUILD_PAIR("name", JSON_BUILD_STRING(info->name)),
|
||||
JSON_BUILD_PAIR("userns", JSON_BUILD_UNSIGNED(info->userns_inode)),
|
||||
JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "start", JSON_BUILD_UNSIGNED(info->start)),
|
||||
JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "size", JSON_BUILD_UNSIGNED(info->size)),
|
||||
JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "target", JSON_BUILD_UNSIGNED(info->target)),
|
||||
JSON_BUILD_PAIR_CONDITION(cgroup_array, "cgroups", JSON_BUILD_VARIANT(cgroup_array))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *def_buf = NULL;
|
||||
r = json_variant_format(def, 0, &def_buf);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to format userns JSON object: %m");
|
||||
|
||||
_cleanup_free_ char *reg_fn = NULL, *link1_fn = NULL, *link2_fn = NULL, *owner_fn = NULL, *uid_fn = NULL;
|
||||
if (asprintf(®_fn, "i%" PRIu64 ".userns", info->userns_inode) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
r = write_string_file_at(dir_fd, reg_fn, def_buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write userns data to '%s' in registry: %m", reg_fn);
|
||||
|
||||
link1_fn = strjoin("n", info->name, ".userns");
|
||||
if (!link1_fn) {
|
||||
r = log_oom_debug();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = linkat_replace(dir_fd, reg_fn, dir_fd, link1_fn);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to link userns data to '%s' in registry: %m", link1_fn);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (uid_is_valid(info->start)) {
|
||||
if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start) < 0) {
|
||||
r = log_oom_debug();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = linkat_replace(dir_fd, reg_fn, dir_fd, link2_fn);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to link userns data to '%s' in registry: %m", link2_fn);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (asprintf(&uid_fn, "o" UID_FMT ".owns", info->owner) < 0) {
|
||||
r = log_oom_debug();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (mkdirat(dir_fd, uid_fn, 0755) < 0 && errno != EEXIST) {
|
||||
r = log_debug_errno(errno, "Failed to create per-UID subdir '%s' of registry: %m", uid_fn);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (asprintf(&owner_fn, "%s/i%" PRIu64 ".userns", uid_fn, info->userns_inode) < 0) {
|
||||
r = log_oom_debug();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = linkat_replace(dir_fd, reg_fn, dir_fd, owner_fn);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to link userns data to '%s' in registry: %m", owner_fn);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (reg_fn)
|
||||
(void) unlinkat(dir_fd, reg_fn, /* flags= */ 0);
|
||||
if (link1_fn)
|
||||
(void) unlinkat(dir_fd, link1_fn, /* flags= */ 0);
|
||||
if (link2_fn)
|
||||
(void) unlinkat(dir_fd, link2_fn, /* flags= */ 0);
|
||||
if (owner_fn)
|
||||
(void) unlinkat(dir_fd, owner_fn, /* flags= */ 0);
|
||||
if (uid_fn)
|
||||
(void) unlinkat(dir_fd, uid_fn, AT_REMOVEDIR);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) {
|
||||
_cleanup_close_ int registry_fd = -EBADF;
|
||||
int ret = 0, r;
|
||||
|
||||
assert(info);
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *reg_fn = NULL;
|
||||
if (asprintf(®_fn, "i%" PRIu64 ".userns", info->userns_inode) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
ret = RET_NERRNO(unlinkat(dir_fd, reg_fn, 0));
|
||||
|
||||
_cleanup_free_ char *link1_fn = NULL;
|
||||
link1_fn = strjoin("n", info->name, ".userns");
|
||||
if (!link1_fn)
|
||||
return log_oom_debug();
|
||||
|
||||
RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, link1_fn, 0)));
|
||||
|
||||
if (uid_is_valid(info->start)) {
|
||||
_cleanup_free_ char *link2_fn = NULL;
|
||||
|
||||
if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, link2_fn, 0)));
|
||||
}
|
||||
|
||||
_cleanup_free_ char *uid_fn = NULL;
|
||||
if (asprintf(&uid_fn, "o" UID_FMT ".owns", info->owner) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
_cleanup_free_ char *owner_fn = NULL;
|
||||
if (asprintf(&owner_fn, "%s/i%" PRIu64 ".userns", uid_fn, info->userns_inode) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, owner_fn, 0)));
|
||||
|
||||
r = RET_NERRNO(unlinkat(dir_fd, uid_fn, AT_REMOVEDIR));
|
||||
if (r != -ENOTEMPTY)
|
||||
RET_GATHER(ret, r);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool userns_info_has_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id) {
|
||||
assert(userns);
|
||||
|
||||
FOREACH_ARRAY(i, userns->cgroups, userns->n_cgroups)
|
||||
if (*i == cgroup_id)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int userns_info_add_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id) {
|
||||
|
||||
if (userns_info_has_cgroup(userns, cgroup_id))
|
||||
return 0;
|
||||
|
||||
if (!GREEDY_REALLOC(userns->cgroups, userns->n_cgroups+1))
|
||||
return -ENOMEM;
|
||||
|
||||
userns->cgroups[userns->n_cgroups++] = cgroup_id;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int userns_destroy_cgroup(uint64_t cgroup_id) {
|
||||
_cleanup_close_ int cgroup_fd = -EBADF, parent_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
cgroup_fd = cg_cgroupid_open(/* cgroupfsfd= */ -EBADF, cgroup_id);
|
||||
if (cgroup_fd == -ESTALE) {
|
||||
log_debug_errno(cgroup_fd, "Control group %" PRIu64 " already gone, ignoring: %m", cgroup_id);
|
||||
return 0;
|
||||
}
|
||||
if (cgroup_fd < 0)
|
||||
return log_debug_errno(errno, "Failed to open cgroup %" PRIu64 ", ignoring: %m", cgroup_id);
|
||||
|
||||
_cleanup_free_ char *path = NULL;
|
||||
r = fd_get_path(cgroup_fd, &path);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to get path of cgroup %" PRIu64 ", ignoring: %m", cgroup_id);
|
||||
|
||||
const char *e = path_startswith(path, "/sys/fs/cgroup/");
|
||||
if (!e)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Got cgroup path that doesn't start with /sys/fs/cgroup/, refusing: %s", path);
|
||||
if (isempty(e))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Got root cgroup path, which can't be right, refusing.");
|
||||
|
||||
log_debug("Path of cgroup %" PRIu64 " is: %s", cgroup_id, path);
|
||||
|
||||
_cleanup_free_ char *fname = NULL;
|
||||
r = path_extract_filename(path, &fname);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract name of cgroup %" PRIu64 ", ignoring: %m", cgroup_id);
|
||||
|
||||
parent_fd = openat(cgroup_fd, "..", O_CLOEXEC|O_DIRECTORY);
|
||||
if (parent_fd < 0)
|
||||
return log_debug_errno(errno, "Failed to open parent cgroup of %" PRIu64 ", ignoring: %m", cgroup_id);
|
||||
|
||||
/* Safety check, never leave cgroupfs */
|
||||
r = fd_is_fs_type(parent_fd, CGROUP2_SUPER_MAGIC);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to determine if parent directory of cgroup %" PRIu64 " is still a cgroup, ignoring: %m", cgroup_id);
|
||||
if (!r)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Parent directory of cgroup %" PRIu64 " is not a cgroup, refusing.", cgroup_id);
|
||||
|
||||
cgroup_fd = safe_close(cgroup_fd);
|
||||
|
||||
r = rm_rf_child(parent_fd, fname, REMOVE_ONLY_DIRECTORIES|REMOVE_PHYSICAL|REMOVE_CHMOD);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to remove delegated cgroup %" PRIu64 ", ignoring: %m", cgroup_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_info_remove_cgroups(UserNamespaceInfo *userns) {
|
||||
int ret = 0;
|
||||
|
||||
assert(userns);
|
||||
|
||||
FOREACH_ARRAY(c, userns->cgroups, userns->n_cgroups)
|
||||
RET_GATHER(ret, userns_destroy_cgroup(*c));
|
||||
|
||||
userns->cgroups = mfree(userns->cgroups);
|
||||
userns->n_cgroups = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool userns_name_is_valid(const char *name) {
|
||||
|
||||
/* Checks if the specified string is suitable as user namespace name. */
|
||||
|
||||
if (strlen(name) > NAME_MAX) /* before we use alloca(), let's check for size */
|
||||
return false;
|
||||
|
||||
const char *f = strjoina("n", name, ".userns"); /* Make sure we can name our lookup symlink with this name */
|
||||
if (!filename_is_valid(f))
|
||||
return false;
|
||||
|
||||
const char *u = strjoina("ns-", name, "-65535"); /* Make sure we can turn this into valid user names */
|
||||
if (!valid_user_group_name(u, 0))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int userns_registry_per_uid(int dir_fd, uid_t owner) {
|
||||
_cleanup_close_ int registry_fd = -EBADF;
|
||||
int n = 0, r;
|
||||
|
||||
if (dir_fd < 0) {
|
||||
registry_fd = userns_registry_open_fd();
|
||||
if (registry_fd < 0)
|
||||
return registry_fd;
|
||||
|
||||
dir_fd = registry_fd;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *uid_fn = NULL;
|
||||
if (asprintf(&uid_fn, "o" UID_FMT ".owns", owner) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
_cleanup_free_ DirectoryEntries *de = NULL;
|
||||
|
||||
r = readdir_all_at(dir_fd, uid_fn, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &de);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to enumerate contents of '%s' sub-directory: %m", uid_fn);
|
||||
|
||||
FOREACH_ARRAY(i, de->entries, de->n_entries) {
|
||||
struct dirent *e = *i;
|
||||
|
||||
if (e->d_type != DT_REG)
|
||||
continue;
|
||||
|
||||
if (!startswith(e->d_name, "i") || !endswith(e->d_name, ".userns"))
|
||||
continue;
|
||||
|
||||
n++;
|
||||
|
||||
if (n == INT_MAX) /* overflow safety check, just in case */
|
||||
break;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#define USER_NAMESPACE_CGROUPS_DELEGATE_MAX 16
|
||||
|
||||
typedef struct UserNamespaceInfo {
|
||||
uid_t owner;
|
||||
char *name;
|
||||
uint64_t userns_inode;
|
||||
uid_t start;
|
||||
uint32_t size;
|
||||
uid_t target;
|
||||
uint64_t *cgroups;
|
||||
size_t n_cgroups;
|
||||
} UserNamespaceInfo;
|
||||
|
||||
UserNamespaceInfo* userns_info_new(void);
|
||||
UserNamespaceInfo* userns_info_free(UserNamespaceInfo *userns);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(UserNamespaceInfo*, userns_info_free);
|
||||
|
||||
bool userns_info_has_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id);
|
||||
int userns_info_add_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id);
|
||||
int userns_info_remove_cgroups(UserNamespaceInfo *userns);
|
||||
|
||||
bool userns_name_is_valid(const char *name);
|
||||
|
||||
int userns_registry_open_fd(void);
|
||||
int userns_registry_lock(int dir_fd);
|
||||
|
||||
int userns_registry_load_by_start_uid(int dir_fd, uid_t start, UserNamespaceInfo **ret);
|
||||
int userns_registry_load_by_userns_inode(int dir_fd, uint64_t userns, UserNamespaceInfo **ret);
|
||||
int userns_registry_load_by_name(int dir_fd, const char *name, UserNamespaceInfo **ret);
|
||||
|
||||
int userns_registry_store(int dir_fd, UserNamespaceInfo *info);
|
||||
int userns_registry_remove(int dir_fd, UserNamespaceInfo *info);
|
||||
|
||||
int userns_registry_inode_exists(int dir_fd, uint64_t inode);
|
||||
int userns_registry_name_exists(int dir_fd, const char *name);
|
||||
int userns_registry_uid_exists(int dir_fd, uid_t start);
|
||||
|
||||
int userns_registry_per_uid(int dir_fd, uid_t owner);
|
||||
@@ -0,0 +1,346 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "userns-restrict.h"
|
||||
|
||||
#if HAVE_VMLINUX_H
|
||||
|
||||
#include <sched.h>
|
||||
|
||||
#include "bpf-dlopen.h"
|
||||
#include "bpf-link.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "lsm-util.h"
|
||||
#include "missing_mount.h"
|
||||
#include "mkdir.h"
|
||||
#include "mount-util.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "namespace-util.h"
|
||||
#include "path-util.h"
|
||||
|
||||
#define USERNS_MAX (16U*1024U)
|
||||
#define MOUNTS_MAX 4096U
|
||||
|
||||
#define PROGRAM_LINK_PREFIX "/sys/fs/bpf/systemd/userns-restrict/programs"
|
||||
#define MAP_LINK_PREFIX "/sys/fs/bpf/systemd/userns-restrict/maps"
|
||||
|
||||
struct userns_restrict_bpf *userns_restrict_bpf_free(struct userns_restrict_bpf *obj) {
|
||||
(void) userns_restrict_bpf__destroy(obj); /* this call is fine with NULL */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int make_inner_hash_map(void) {
|
||||
int fd;
|
||||
|
||||
fd = compat_bpf_map_create(
|
||||
BPF_MAP_TYPE_HASH,
|
||||
NULL,
|
||||
sizeof(int),
|
||||
sizeof(uint32_t),
|
||||
MOUNTS_MAX,
|
||||
NULL);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(errno, "Failed allocate inner BPF map: %m");
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int userns_restrict_install(
|
||||
bool pin,
|
||||
struct userns_restrict_bpf **ret) {
|
||||
|
||||
_cleanup_(userns_restrict_bpf_freep) struct userns_restrict_bpf *obj = NULL;
|
||||
_cleanup_close_ int dummy_mnt_id_hash_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
r = lsm_supported("bpf");
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm not supported, can't lock down user namespace.");
|
||||
|
||||
r = dlopen_bpf();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* bpf_object__next_map() is not available in libbpf pre-0.7.0, and we want to use it. */
|
||||
if (!sym_bpf_object__next_map)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libbpf too old for locking down user namespace.");
|
||||
|
||||
obj = userns_restrict_bpf__open();
|
||||
if (!obj)
|
||||
return log_error_errno(errno, "Failed to open userns_restrict BPF object: %m");
|
||||
|
||||
if (pin) {
|
||||
struct bpf_map *map;
|
||||
|
||||
/* libbpf will only create one level of dirs. Let's create the rest */
|
||||
(void) mkdir_p(MAP_LINK_PREFIX, 0755);
|
||||
(void) mkdir_p(PROGRAM_LINK_PREFIX, 0755);
|
||||
|
||||
map = sym_bpf_object__next_map(obj->obj, NULL);
|
||||
while (map) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
|
||||
fn = path_join(MAP_LINK_PREFIX, sym_bpf_map__name(map));
|
||||
if (!fn)
|
||||
return log_oom();
|
||||
|
||||
r = sym_bpf_map__set_pin_path(map, fn);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set pin path to '%s': %m", fn);
|
||||
|
||||
map = sym_bpf_object__next_map(obj->obj, map);
|
||||
}
|
||||
}
|
||||
|
||||
r = sym_bpf_map__set_max_entries(obj->maps.userns_mnt_id_hash, USERNS_MAX);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to size userns/mnt_id hash table: %m");
|
||||
|
||||
r = sym_bpf_map__set_max_entries(obj->maps.userns_ringbuf, USERNS_MAX * sizeof(unsigned int));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to size userns ring buffer: %m");
|
||||
|
||||
/* Dummy map to satisfy the verifier */
|
||||
dummy_mnt_id_hash_fd = make_inner_hash_map();
|
||||
if (dummy_mnt_id_hash_fd < 0)
|
||||
return dummy_mnt_id_hash_fd;
|
||||
|
||||
r = sym_bpf_map__set_inner_map_fd(obj->maps.userns_mnt_id_hash, dummy_mnt_id_hash_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set inner BPF map: %m");
|
||||
|
||||
r = userns_restrict_bpf__load(obj);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load BPF object: %m");
|
||||
|
||||
for (int i = 0; i < obj->skeleton->prog_cnt; i++) {
|
||||
_cleanup_(bpf_link_freep) struct bpf_link *link = NULL;
|
||||
struct bpf_prog_skeleton *ps = obj->skeleton->progs + i;
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
bool linked = false;
|
||||
const char *e;
|
||||
|
||||
e = startswith(ps->name, "userns_restrict_");
|
||||
assert(e);
|
||||
|
||||
if (pin) {
|
||||
fn = path_join(PROGRAM_LINK_PREFIX, e);
|
||||
if (!fn)
|
||||
return log_oom();
|
||||
|
||||
link = sym_bpf_link__open(fn);
|
||||
r = sym_libbpf_get_error(link);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Unable to open pinned program link: %m");
|
||||
link = NULL;
|
||||
} else {
|
||||
linked = true;
|
||||
log_info("userns-restrict BPF-LSM program %s already attached.", ps->name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!link) {
|
||||
link = sym_bpf_program__attach(*ps->prog);
|
||||
r = sym_libbpf_get_error(link);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach LSM BPF program: %m");
|
||||
|
||||
log_info("userns-restrict BPF-LSM program %s now attached.", ps->name);
|
||||
}
|
||||
|
||||
if (pin && !linked) {
|
||||
assert(fn);
|
||||
|
||||
r = sym_bpf_link__pin(link, fn);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to pin LSM attachment: %m");
|
||||
}
|
||||
|
||||
*ps->link = TAKE_PTR(link);
|
||||
}
|
||||
|
||||
if (pin) {
|
||||
r = sym_bpf_object__pin_maps(obj->obj, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to pin BPF maps: %m");
|
||||
}
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(obj);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_restrict_put_by_inode(
|
||||
struct userns_restrict_bpf *obj,
|
||||
uint64_t userns_inode,
|
||||
bool replace,
|
||||
const int mount_fds[],
|
||||
size_t n_mount_fds) {
|
||||
|
||||
_cleanup_close_ int inner_map_fd = -EBADF;
|
||||
_cleanup_free_ int *mnt_ids = NULL;
|
||||
uint64_t ino = userns_inode;
|
||||
int r, outer_map_fd;
|
||||
|
||||
assert(obj);
|
||||
assert(userns_inode != 0);
|
||||
assert(n_mount_fds == 0 || mount_fds);
|
||||
|
||||
/* The BPF map type BPF_MAP_TYPE_HASH_OF_MAPS only supports 32bit keys, and user namespace inode
|
||||
* numbers are 32bit too, even though ino_t is 64bit these days. Should we ever run into a 64bit
|
||||
* inode let's refuse early, we can't support this with the current BPF code for now. */
|
||||
if (userns_inode > UINT32_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
mnt_ids = new(int, n_mount_fds);
|
||||
if (!mnt_ids)
|
||||
return -ENOMEM;
|
||||
|
||||
for (size_t i = 0; i < n_mount_fds; i++) {
|
||||
r = path_get_mnt_id_at(mount_fds[i], "", mnt_ids + i);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to get mount ID: %m");
|
||||
}
|
||||
|
||||
outer_map_fd = sym_bpf_map__fd(obj->maps.userns_mnt_id_hash);
|
||||
if (outer_map_fd < 0)
|
||||
return log_debug_errno(outer_map_fd, "Failed to get outer BPF map fd: %m");
|
||||
|
||||
if (replace) {
|
||||
/* Add if missing, replace if already exists */
|
||||
inner_map_fd = make_inner_hash_map();
|
||||
if (inner_map_fd < 0)
|
||||
return inner_map_fd;
|
||||
|
||||
r = sym_bpf_map_update_elem(outer_map_fd, &ino, &inner_map_fd, BPF_ANY);
|
||||
if (r < 0)
|
||||
return log_debug_errno(errno, "Failed to replace map in inode hash: %m");
|
||||
} else {
|
||||
/* Let's add an entry for this userns inode if missing. If it exists just extend the existing map. We
|
||||
* might race against each other, hence we try a couple of times */
|
||||
for (size_t n_try = 10;; n_try--) {
|
||||
uint32_t innermap_id;
|
||||
|
||||
if (n_try == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EEXIST),
|
||||
"Stillcan't create inode entry in BPF map after 10 tries.");
|
||||
|
||||
r = sym_bpf_map_lookup_elem(outer_map_fd, &ino, &innermap_id);
|
||||
if (r >= 0) {
|
||||
inner_map_fd = sym_bpf_map_get_fd_by_id(innermap_id);
|
||||
if (inner_map_fd < 0)
|
||||
return log_debug_errno(inner_map_fd, "Failed to get file descriptor for inner map: %m");
|
||||
|
||||
break;
|
||||
}
|
||||
if (errno != ENOENT)
|
||||
return log_debug_errno(errno, "Failed to look up inode hash entry: %m");
|
||||
|
||||
/* No entry for this user namespace yet. Let's create one */
|
||||
inner_map_fd = make_inner_hash_map();
|
||||
if (inner_map_fd < 0)
|
||||
return inner_map_fd;
|
||||
|
||||
r = sym_bpf_map_update_elem(outer_map_fd, &ino, &inner_map_fd, BPF_NOEXIST);
|
||||
if (r >= 0)
|
||||
break;
|
||||
if (errno != EEXIST)
|
||||
return log_debug_errno(errno, "Failed to add mount ID list to inode hash: %m");
|
||||
}
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(mntid, mnt_ids, n_mount_fds) {
|
||||
uint32_t dummy_value = 1;
|
||||
|
||||
r = sym_bpf_map_update_elem(inner_map_fd, mntid, &dummy_value, BPF_ANY);
|
||||
if (r < 0)
|
||||
return log_debug_errno(errno, "Failed to add mount ID to map: %m");
|
||||
|
||||
log_debug("Allowing mount %i on userns inode %" PRIu64, *mntid, ino);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_restrict_put_by_fd(
|
||||
struct userns_restrict_bpf *obj,
|
||||
int userns_fd,
|
||||
bool replace,
|
||||
const int mount_fds[],
|
||||
size_t n_mount_fds) {
|
||||
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
assert(obj);
|
||||
assert(userns_fd >= 0);
|
||||
assert(n_mount_fds == 0 || mount_fds);
|
||||
|
||||
r = fd_is_ns(userns_fd, CLONE_NEWUSER);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to determine if file descriptor is user namespace: %m");
|
||||
if (r == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADF), "User namespace fd is not actually a user namespace fd.");
|
||||
|
||||
if (fstat(userns_fd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to fstat() user namespace: %m");
|
||||
|
||||
return userns_restrict_put_by_inode(
|
||||
obj,
|
||||
st.st_ino,
|
||||
replace,
|
||||
mount_fds,
|
||||
n_mount_fds);
|
||||
}
|
||||
|
||||
int userns_restrict_reset_by_inode(
|
||||
struct userns_restrict_bpf *obj,
|
||||
uint64_t ino) {
|
||||
|
||||
int r, outer_map_fd;
|
||||
unsigned u;
|
||||
|
||||
assert(obj);
|
||||
assert(ino != 0);
|
||||
|
||||
if (ino > UINT32_MAX) /* inodes larger than 32bit are definitely not included in our map, exit early */
|
||||
return 0;
|
||||
|
||||
outer_map_fd = sym_bpf_map__fd(obj->maps.userns_mnt_id_hash);
|
||||
if (outer_map_fd < 0)
|
||||
return log_debug_errno(outer_map_fd, "Failed to get outer BPF map fd: %m");
|
||||
|
||||
u = (uint32_t) ino;
|
||||
|
||||
r = sym_bpf_map_delete_elem(outer_map_fd, &u);
|
||||
if (r < 0)
|
||||
return log_debug_errno(outer_map_fd, "Failed to remove entry for inode %" PRIu64 " from outer map: %m", ino);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
int userns_restrict_install(bool pin, struct userns_restrict_bpf **ret) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled.");
|
||||
}
|
||||
|
||||
struct userns_restrict_bpf *userns_restrict_bpf_free(struct userns_restrict_bpf *obj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int userns_restrict_put_by_fd(struct userns_restrict_bpf *obj, int userns_fd, bool replace, const int mount_fds[], size_t n_mount_fds) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled.");
|
||||
}
|
||||
|
||||
int userns_restrict_put_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode, bool replace, const int mount_fds[], size_t n_mount_fds) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled.");
|
||||
}
|
||||
|
||||
int userns_restrict_reset_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled.");
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "macro.h"
|
||||
|
||||
#if HAVE_VMLINUX_H
|
||||
#include "bpf/userns_restrict/userns-restrict-skel.h"
|
||||
#else
|
||||
struct userns_restrict_bpf;
|
||||
#endif
|
||||
|
||||
int userns_restrict_install(bool pin, struct userns_restrict_bpf **ret);
|
||||
struct userns_restrict_bpf *userns_restrict_bpf_free(struct userns_restrict_bpf *obj);
|
||||
|
||||
int userns_restrict_put_by_fd(struct userns_restrict_bpf *obj, int userns_fd, bool replace, const int mount_fds[], size_t n_mount_fds);
|
||||
int userns_restrict_put_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode, bool replace, const int mount_fds[], size_t n_mount_fds);
|
||||
|
||||
int userns_restrict_reset_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(struct userns_restrict_bpf*, userns_restrict_bpf_free);
|
||||
@@ -26,6 +26,7 @@ struct bpf_map_create_opts;
|
||||
* When removing this file move these back to bpf-dlopen.h */
|
||||
extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *);
|
||||
extern int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *);
|
||||
extern struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map);
|
||||
|
||||
/* compat symbols removed in libbpf 1.0 */
|
||||
extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags);
|
||||
|
||||
+49
-19
@@ -18,28 +18,41 @@
|
||||
#define MODERN_LIBBPF 0
|
||||
#endif
|
||||
|
||||
DLSYM_FUNCTION(bpf_program__attach_cgroup);
|
||||
DLSYM_FUNCTION(bpf_program__attach_lsm);
|
||||
DLSYM_FUNCTION(bpf_link__fd);
|
||||
DLSYM_FUNCTION(bpf_link__destroy);
|
||||
DLSYM_FUNCTION(bpf_link__fd);
|
||||
DLSYM_FUNCTION(bpf_link__open);
|
||||
DLSYM_FUNCTION(bpf_link__pin);
|
||||
DLSYM_FUNCTION(bpf_map__fd);
|
||||
DLSYM_FUNCTION(bpf_map__name);
|
||||
DLSYM_FUNCTION(bpf_map__set_max_entries);
|
||||
DLSYM_FUNCTION(bpf_map_update_elem);
|
||||
DLSYM_FUNCTION(bpf_map_delete_elem);
|
||||
DLSYM_FUNCTION(bpf_map__set_inner_map_fd);
|
||||
DLSYM_FUNCTION(bpf_object__open_skeleton);
|
||||
DLSYM_FUNCTION(bpf_object__load_skeleton);
|
||||
DLSYM_FUNCTION(bpf_map__set_max_entries);
|
||||
DLSYM_FUNCTION(bpf_map__set_pin_path);
|
||||
DLSYM_FUNCTION(bpf_map_delete_elem);
|
||||
DLSYM_FUNCTION(bpf_map_get_fd_by_id);
|
||||
DLSYM_FUNCTION(bpf_map_lookup_elem);
|
||||
DLSYM_FUNCTION(bpf_map_update_elem);
|
||||
DLSYM_FUNCTION(bpf_object__attach_skeleton);
|
||||
DLSYM_FUNCTION(bpf_object__detach_skeleton);
|
||||
DLSYM_FUNCTION(bpf_object__destroy_skeleton);
|
||||
DLSYM_FUNCTION(bpf_object__detach_skeleton);
|
||||
DLSYM_FUNCTION(bpf_object__load_skeleton);
|
||||
DLSYM_FUNCTION(bpf_object__name);
|
||||
DLSYM_FUNCTION(bpf_object__open_skeleton);
|
||||
DLSYM_FUNCTION(bpf_object__pin_maps);
|
||||
DLSYM_FUNCTION(bpf_program__attach);
|
||||
DLSYM_FUNCTION(bpf_program__attach_cgroup);
|
||||
DLSYM_FUNCTION(bpf_program__attach_lsm);
|
||||
DLSYM_FUNCTION(bpf_program__name);
|
||||
DLSYM_FUNCTION(libbpf_set_print);
|
||||
DLSYM_FUNCTION(libbpf_get_error);
|
||||
DLSYM_FUNCTION(libbpf_set_print);
|
||||
DLSYM_FUNCTION(ring_buffer__epoll_fd);
|
||||
DLSYM_FUNCTION(ring_buffer__free);
|
||||
DLSYM_FUNCTION(ring_buffer__new);
|
||||
DLSYM_FUNCTION(ring_buffer__poll);
|
||||
|
||||
/* new symbols available from libbpf 0.7.0 */
|
||||
int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *);
|
||||
int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *);
|
||||
struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map);
|
||||
|
||||
/* compat symbols removed in libbpf 1.0 */
|
||||
int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags);
|
||||
@@ -90,6 +103,8 @@ int dlopen_bpf(void) {
|
||||
DLSYM_ARG(bpf_probe_prog_type)
|
||||
#endif
|
||||
);
|
||||
|
||||
/* NB: we don't try to load bpf_object__next_map() on old versions */
|
||||
} else {
|
||||
log_debug("Loaded 'libbpf.so.1' via dlopen()");
|
||||
|
||||
@@ -98,11 +113,13 @@ int dlopen_bpf(void) {
|
||||
dl, LOG_DEBUG,
|
||||
#if MODERN_LIBBPF
|
||||
DLSYM_ARG(bpf_map_create),
|
||||
DLSYM_ARG(libbpf_probe_bpf_prog_type)
|
||||
DLSYM_ARG(libbpf_probe_bpf_prog_type),
|
||||
DLSYM_ARG(bpf_object__next_map)
|
||||
#else
|
||||
/* These symbols did not exist in old libbpf, hence we cannot type check them */
|
||||
DLSYM_ARG_FORCE(bpf_map_create),
|
||||
DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type)
|
||||
DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type),
|
||||
DLSYM_ARG_FORCE(bpf_object__next_map)
|
||||
#endif
|
||||
);
|
||||
}
|
||||
@@ -113,28 +130,41 @@ int dlopen_bpf(void) {
|
||||
dl, LOG_DEBUG,
|
||||
DLSYM_ARG(bpf_link__destroy),
|
||||
DLSYM_ARG(bpf_link__fd),
|
||||
DLSYM_ARG(bpf_link__open),
|
||||
DLSYM_ARG(bpf_link__pin),
|
||||
DLSYM_ARG(bpf_map__fd),
|
||||
DLSYM_ARG(bpf_map__name),
|
||||
DLSYM_ARG(bpf_map__set_max_entries),
|
||||
DLSYM_ARG(bpf_map_update_elem),
|
||||
DLSYM_ARG(bpf_map_delete_elem),
|
||||
DLSYM_ARG(bpf_map__set_inner_map_fd),
|
||||
DLSYM_ARG(bpf_object__open_skeleton),
|
||||
DLSYM_ARG(bpf_object__load_skeleton),
|
||||
DLSYM_ARG(bpf_map__set_max_entries),
|
||||
DLSYM_ARG(bpf_map__set_pin_path),
|
||||
DLSYM_ARG(bpf_map_delete_elem),
|
||||
DLSYM_ARG(bpf_map_get_fd_by_id),
|
||||
DLSYM_ARG(bpf_map_lookup_elem),
|
||||
DLSYM_ARG(bpf_map_update_elem),
|
||||
DLSYM_ARG(bpf_object__attach_skeleton),
|
||||
DLSYM_ARG(bpf_object__detach_skeleton),
|
||||
DLSYM_ARG(bpf_object__destroy_skeleton),
|
||||
DLSYM_ARG(bpf_object__detach_skeleton),
|
||||
DLSYM_ARG(bpf_object__load_skeleton),
|
||||
DLSYM_ARG(bpf_object__name),
|
||||
DLSYM_ARG(bpf_object__open_skeleton),
|
||||
DLSYM_ARG(bpf_object__pin_maps),
|
||||
#if MODERN_LIBBPF
|
||||
DLSYM_ARG(bpf_program__attach),
|
||||
DLSYM_ARG(bpf_program__attach_cgroup),
|
||||
DLSYM_ARG(bpf_program__attach_lsm),
|
||||
#else
|
||||
/* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */
|
||||
DLSYM_ARG_FORCE(bpf_program__attach),
|
||||
DLSYM_ARG_FORCE(bpf_program__attach_cgroup),
|
||||
DLSYM_ARG_FORCE(bpf_program__attach_lsm),
|
||||
#endif
|
||||
DLSYM_ARG(bpf_program__name),
|
||||
DLSYM_ARG(libbpf_get_error),
|
||||
DLSYM_ARG(libbpf_set_print),
|
||||
DLSYM_ARG(libbpf_get_error));
|
||||
DLSYM_ARG(ring_buffer__epoll_fd),
|
||||
DLSYM_ARG(ring_buffer__free),
|
||||
DLSYM_ARG(ring_buffer__new),
|
||||
DLSYM_ARG(ring_buffer__poll));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
+22
-10
@@ -9,26 +9,38 @@
|
||||
#include "bpf-compat.h"
|
||||
#include "dlfcn-util.h"
|
||||
|
||||
DLSYM_PROTOTYPE(bpf_program__attach_cgroup);
|
||||
DLSYM_PROTOTYPE(bpf_program__attach_lsm);
|
||||
DLSYM_PROTOTYPE(bpf_link__fd);
|
||||
DLSYM_PROTOTYPE(bpf_link__destroy);
|
||||
DLSYM_PROTOTYPE(bpf_link__fd);
|
||||
DLSYM_PROTOTYPE(bpf_link__open);
|
||||
DLSYM_PROTOTYPE(bpf_link__pin);
|
||||
DLSYM_PROTOTYPE(bpf_map__fd);
|
||||
DLSYM_PROTOTYPE(bpf_map__name);
|
||||
DLSYM_PROTOTYPE(bpf_map__set_max_entries);
|
||||
DLSYM_PROTOTYPE(bpf_map_update_elem);
|
||||
DLSYM_PROTOTYPE(bpf_map_delete_elem);
|
||||
DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd);
|
||||
DLSYM_PROTOTYPE(bpf_map__set_max_entries);
|
||||
DLSYM_PROTOTYPE(bpf_map__set_pin_path);
|
||||
DLSYM_PROTOTYPE(bpf_map_delete_elem);
|
||||
DLSYM_PROTOTYPE(bpf_map_get_fd_by_id);
|
||||
DLSYM_PROTOTYPE(bpf_map_lookup_elem);
|
||||
DLSYM_PROTOTYPE(bpf_map_update_elem);
|
||||
/* The *_skeleton APIs are autogenerated by bpftool, the targets can be found
|
||||
* in ./build/src/core/bpf/socket_bind/socket-bind.skel.h */
|
||||
DLSYM_PROTOTYPE(bpf_object__open_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__load_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__attach_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__detach_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__destroy_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__detach_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__load_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__name);
|
||||
DLSYM_PROTOTYPE(bpf_object__open_skeleton);
|
||||
DLSYM_PROTOTYPE(bpf_object__pin_maps);
|
||||
DLSYM_PROTOTYPE(bpf_program__attach);
|
||||
DLSYM_PROTOTYPE(bpf_program__attach_cgroup);
|
||||
DLSYM_PROTOTYPE(bpf_program__attach_lsm);
|
||||
DLSYM_PROTOTYPE(bpf_program__name);
|
||||
DLSYM_PROTOTYPE(libbpf_set_print);
|
||||
DLSYM_PROTOTYPE(libbpf_get_error);
|
||||
DLSYM_PROTOTYPE(libbpf_set_print);
|
||||
DLSYM_PROTOTYPE(ring_buffer__epoll_fd);
|
||||
DLSYM_PROTOTYPE(ring_buffer__free);
|
||||
DLSYM_PROTOTYPE(ring_buffer__new);
|
||||
DLSYM_PROTOTYPE(ring_buffer__poll);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -384,6 +384,20 @@ int cg_attach(const char *controller, const char *path, pid_t pid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cg_fd_attach(int fd, pid_t pid) {
|
||||
char c[DECIMAL_STR_MAX(pid_t) + 2];
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(pid >= 0);
|
||||
|
||||
if (pid == 0)
|
||||
pid = getpid_cached();
|
||||
|
||||
xsprintf(c, PID_FMT "\n", pid);
|
||||
|
||||
return write_string_file_at(fd, "cgroup.procs", c, WRITE_STRING_FILE_DISABLE_BUFFER);
|
||||
}
|
||||
|
||||
int cg_attach_fallback(const char *controller, const char *path, pid_t pid) {
|
||||
int r;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root);
|
||||
|
||||
int cg_create(const char *controller, const char *path);
|
||||
int cg_attach(const char *controller, const char *path, pid_t pid);
|
||||
int cg_fd_attach(int fd, pid_t pid);
|
||||
int cg_attach_fallback(const char *controller, const char *path, pid_t pid);
|
||||
int cg_create_and_attach(const char *controller, const char *path, pid_t pid);
|
||||
|
||||
|
||||
@@ -150,18 +150,9 @@ static int show_cgroup_name(
|
||||
delegate = r > 0;
|
||||
|
||||
if (FLAGS_SET(flags, OUTPUT_CGROUP_ID)) {
|
||||
cg_file_handle fh = CG_FILE_HANDLE_INIT;
|
||||
int mnt_id = -1;
|
||||
|
||||
if (name_to_handle_at(
|
||||
fd,
|
||||
"",
|
||||
&fh.file_handle,
|
||||
&mnt_id,
|
||||
AT_EMPTY_PATH) < 0)
|
||||
r = cg_fd_get_cgroupid(fd, &cgroupid);
|
||||
if (r < 0)
|
||||
log_debug_errno(errno, "Failed to determine cgroup ID of %s, ignoring: %m", path);
|
||||
else
|
||||
cgroupid = CG_FILE_HANDLE_CGROUPID(fh);
|
||||
}
|
||||
|
||||
r = path_extract_filename(path, &b);
|
||||
|
||||
+33
-20
@@ -48,7 +48,7 @@
|
||||
#include "vpick.h"
|
||||
#include "xattr-util.h"
|
||||
|
||||
static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
|
||||
const char* const image_search_path[_IMAGE_CLASS_MAX] = {
|
||||
[IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */
|
||||
"/run/machines\0" /* and here too */
|
||||
"/var/lib/machines\0" /* the main place for images */
|
||||
@@ -1295,7 +1295,12 @@ static void make_lock_dir(void) {
|
||||
(void) mkdir("/run/systemd/nspawn/locks", 0700);
|
||||
}
|
||||
|
||||
int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) {
|
||||
int image_path_lock(
|
||||
const char *path,
|
||||
int operation,
|
||||
LockFile *ret_global,
|
||||
LockFile *ret_local) {
|
||||
|
||||
_cleanup_free_ char *p = NULL;
|
||||
LockFile t = LOCK_FILE_INIT;
|
||||
struct stat st;
|
||||
@@ -1303,8 +1308,7 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(global);
|
||||
assert(local);
|
||||
assert(ret_local);
|
||||
|
||||
/* Locks an image path. This actually creates two locks: one "local" one, next to the image path
|
||||
* itself, which might be shared via NFS. And another "global" one, in /run, that uses the
|
||||
@@ -1326,7 +1330,9 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
|
||||
}
|
||||
|
||||
if (getenv_bool("SYSTEMD_NSPAWN_LOCK") == 0) {
|
||||
*local = *global = (LockFile) LOCK_FILE_INIT;
|
||||
*ret_local = LOCK_FILE_INIT;
|
||||
if (ret_global)
|
||||
*ret_global = LOCK_FILE_INIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1342,19 +1348,23 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
|
||||
if (exclusive)
|
||||
return -EBUSY;
|
||||
|
||||
*local = *global = (LockFile) LOCK_FILE_INIT;
|
||||
*ret_local = LOCK_FILE_INIT;
|
||||
if (ret_global)
|
||||
*ret_global = LOCK_FILE_INIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (stat(path, &st) >= 0) {
|
||||
if (S_ISBLK(st.st_mode))
|
||||
r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev));
|
||||
else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))
|
||||
r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino);
|
||||
else
|
||||
return -ENOTTY;
|
||||
if (r < 0)
|
||||
return -ENOMEM;
|
||||
if (ret_global) {
|
||||
if (stat(path, &st) >= 0) {
|
||||
if (S_ISBLK(st.st_mode))
|
||||
r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev));
|
||||
else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))
|
||||
r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino);
|
||||
else
|
||||
return -ENOTTY;
|
||||
if (r < 0)
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
/* For block devices we don't need the "local" lock, as the major/minor lock above should be
|
||||
@@ -1372,15 +1382,15 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
|
||||
if (p) {
|
||||
make_lock_dir();
|
||||
|
||||
r = make_lock_file(p, operation, global);
|
||||
r = make_lock_file(p, operation, ret_global);
|
||||
if (r < 0) {
|
||||
release_lock_file(&t);
|
||||
return r;
|
||||
}
|
||||
} else
|
||||
*global = (LockFile) LOCK_FILE_INIT;
|
||||
} else if (ret_global)
|
||||
*ret_global = LOCK_FILE_INIT;
|
||||
|
||||
*local = t;
|
||||
*ret_local = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1512,7 +1522,10 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_acquire_metadata(m, flags);
|
||||
r = dissected_image_acquire_metadata(
|
||||
m,
|
||||
/* userns_fd= */ -EBADF,
|
||||
flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -122,3 +122,5 @@ int image_to_json(const struct Image *i, JsonVariant **ret);
|
||||
const char *image_root_to_string(ImageClass c) _const_;
|
||||
|
||||
extern const struct hash_ops image_hash_ops;
|
||||
|
||||
extern const char* const image_search_path[_IMAGE_CLASS_MAX];
|
||||
|
||||
+238
-3
@@ -74,6 +74,7 @@
|
||||
#include "tmpfile-util.h"
|
||||
#include "udev-util.h"
|
||||
#include "user-util.h"
|
||||
#include "varlink.h"
|
||||
#include "xattr-util.h"
|
||||
|
||||
/* how many times to wait for the device nodes to appear */
|
||||
@@ -1643,6 +1644,20 @@ int dissect_image_file_and_warn(
|
||||
verity);
|
||||
}
|
||||
|
||||
void dissected_image_close(DissectedImage *m) {
|
||||
if (!m)
|
||||
return;
|
||||
|
||||
/* Closes all fds we keep open assocated with this, but nothing else */
|
||||
|
||||
FOREACH_ARRAY(p, m->partitions, _PARTITION_DESIGNATOR_MAX) {
|
||||
p->mount_node_fd = safe_close(p->mount_node_fd);
|
||||
p->fsmount_fd = safe_close(p->fsmount_fd);
|
||||
}
|
||||
|
||||
m->loop = loop_device_unref(m->loop);
|
||||
}
|
||||
|
||||
DissectedImage* dissected_image_unref(DissectedImage *m) {
|
||||
if (!m)
|
||||
return NULL;
|
||||
@@ -2937,6 +2952,7 @@ int dissected_image_decrypt(
|
||||
* > 0 → Decrypted successfully
|
||||
* -ENOKEY → There's something to decrypt but no key was supplied
|
||||
* -EKEYREJECTED → Passed key was not correct
|
||||
* -EBUSY → Generic Verity error (kernel is not very explanatory)
|
||||
*/
|
||||
|
||||
if (verity && verity->root_hash && verity->root_hash_size < sizeof(sd_id128_t))
|
||||
@@ -3386,7 +3402,10 @@ int dissected_image_load_verity_sig_partition(
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags) {
|
||||
int dissected_image_acquire_metadata(
|
||||
DissectedImage *m,
|
||||
int userns_fd,
|
||||
DissectImageFlags extra_flags) {
|
||||
|
||||
enum {
|
||||
META_HOSTNAME,
|
||||
@@ -3445,13 +3464,22 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child);
|
||||
r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &child);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
if (r == 0) {
|
||||
/* Child in a new mount namespace */
|
||||
/* Child */
|
||||
error_pipe[0] = safe_close(error_pipe[0]);
|
||||
|
||||
if (userns_fd < 0)
|
||||
r = detach_mount_namespace_harder(0, 0);
|
||||
else
|
||||
r = detach_mount_namespace_userns(userns_fd);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to detach mount namespace: %m");
|
||||
goto inner_fail;
|
||||
}
|
||||
|
||||
r = dissected_image_mount(
|
||||
m,
|
||||
t,
|
||||
@@ -4129,3 +4157,210 @@ int get_common_dissect_directory(char **ret) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_BLKID
|
||||
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_architecture, Architecture, architecture_from_string);
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_partition_designator, PartitionDesignator, partition_designator_from_string);
|
||||
|
||||
typedef struct PartitionFields {
|
||||
PartitionDesignator designator;
|
||||
bool rw;
|
||||
bool growfs;
|
||||
unsigned partno;
|
||||
Architecture architecture;
|
||||
sd_id128_t uuid;
|
||||
char *fstype;
|
||||
char *label;
|
||||
uint64_t size;
|
||||
uint64_t offset;
|
||||
unsigned fsmount_fd_idx;
|
||||
} PartitionFields;
|
||||
|
||||
static void partition_fields_done(PartitionFields *f) {
|
||||
assert(f);
|
||||
|
||||
f->fstype = mfree(f->fstype);
|
||||
f->label = mfree(f->label);
|
||||
}
|
||||
|
||||
typedef struct ReplyParameters {
|
||||
JsonVariant *partitions;
|
||||
char *image_policy;
|
||||
uint64_t image_size;
|
||||
uint32_t sector_size;
|
||||
sd_id128_t image_uuid;
|
||||
} ReplyParameters;
|
||||
|
||||
static void reply_parameters_done(ReplyParameters *p) {
|
||||
assert(p);
|
||||
|
||||
p->image_policy = mfree(p->image_policy);
|
||||
p->partitions = json_variant_unref(p->partitions);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int mountfsd_mount_image(
|
||||
const char *path,
|
||||
int userns_fd,
|
||||
const ImagePolicy *image_policy,
|
||||
DissectImageFlags flags,
|
||||
DissectedImage **ret) {
|
||||
|
||||
#if HAVE_BLKID
|
||||
_cleanup_(reply_parameters_done) ReplyParameters p = {};
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "partitions", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(struct ReplyParameters, partitions), JSON_MANDATORY },
|
||||
{ "imagePolicy", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct ReplyParameters, image_policy), 0 },
|
||||
{ "imageSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct ReplyParameters, image_size), JSON_MANDATORY },
|
||||
{ "sectorSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct ReplyParameters, sector_size), JSON_MANDATORY },
|
||||
{ "imageUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct ReplyParameters, image_uuid), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *di = NULL;
|
||||
_cleanup_close_ int image_fd = -EBADF;
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
_cleanup_free_ char *ps = NULL;
|
||||
unsigned max_fd = UINT_MAX;
|
||||
const char *error_id;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(ret);
|
||||
|
||||
r = varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to mountfsd: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_input(vl, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable varlink fd passing for read: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_output(vl, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||
|
||||
image_fd = open(path, O_RDONLY|O_CLOEXEC);
|
||||
if (image_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
||||
|
||||
r = varlink_push_dup_fd(vl, image_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to push image fd into varlink connection: %m");
|
||||
|
||||
if (userns_fd >= 0) {
|
||||
r = varlink_push_dup_fd(vl, userns_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to push image fd into varlink connection: %m");
|
||||
}
|
||||
|
||||
if (image_policy) {
|
||||
r = image_policy_to_string(image_policy, /* simplify= */ false, &ps);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed format image policy to string: %m");
|
||||
}
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
r = varlink_callb(
|
||||
vl,
|
||||
"io.systemd.MountFileSystem.MountImage",
|
||||
&reply,
|
||||
&error_id,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("imageFileDescriptor", JSON_BUILD_UNSIGNED(0)),
|
||||
JSON_BUILD_PAIR_CONDITION(userns_fd >= 0, "userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(1)),
|
||||
JSON_BUILD_PAIR("readOnly", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_READ_ONLY))),
|
||||
JSON_BUILD_PAIR("growFileSystems", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_GROWFS))),
|
||||
JSON_BUILD_PAIR_CONDITION(ps, "imagePolicy", JSON_BUILD_STRING(ps)),
|
||||
JSON_BUILD_PAIR("allowInteractiveAuthentication", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH)))));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to call MountImage() varlink call: %m");
|
||||
if (!isempty(error_id))
|
||||
return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to call MountImage() varlink call: %s", error_id);
|
||||
|
||||
r = json_dispatch(reply, dispatch_table, JSON_ALLOW_EXTENSIONS, &p);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse MountImage() reply: %m");
|
||||
|
||||
log_debug("Effective image policy: %s", p.image_policy);
|
||||
|
||||
JsonVariant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, p.partitions) {
|
||||
_cleanup_close_ int fsmount_fd = -EBADF;
|
||||
|
||||
_cleanup_(partition_fields_done) PartitionFields pp = {
|
||||
.designator = _PARTITION_DESIGNATOR_INVALID,
|
||||
.architecture = _ARCHITECTURE_INVALID,
|
||||
.size = UINT64_MAX,
|
||||
.offset = UINT64_MAX,
|
||||
.fsmount_fd_idx = UINT_MAX,
|
||||
};
|
||||
|
||||
static const JsonDispatch partition_dispatch_table[] = {
|
||||
{ "designator", JSON_VARIANT_STRING, dispatch_partition_designator, offsetof(struct PartitionFields, designator), JSON_MANDATORY },
|
||||
{ "writable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, rw), JSON_MANDATORY },
|
||||
{ "growFileSystem", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, growfs), JSON_MANDATORY },
|
||||
{ "partitionNumber", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, partno), 0 },
|
||||
{ "architecture", JSON_VARIANT_STRING, dispatch_architecture, offsetof(struct PartitionFields, architecture), 0 },
|
||||
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct PartitionFields, uuid), 0 },
|
||||
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, fstype), JSON_MANDATORY },
|
||||
{ "partitionLabel", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, label), 0 },
|
||||
{ "size", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, size), JSON_MANDATORY },
|
||||
{ "offset", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, offset), JSON_MANDATORY },
|
||||
{ "mountFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, fsmount_fd_idx), JSON_MANDATORY },
|
||||
{}
|
||||
};
|
||||
|
||||
r = json_dispatch(i, partition_dispatch_table, JSON_ALLOW_EXTENSIONS, &pp);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse partition data: %m");
|
||||
|
||||
if (pp.fsmount_fd_idx != UINT_MAX) {
|
||||
if (max_fd == UINT_MAX || pp.fsmount_fd_idx > max_fd)
|
||||
max_fd = pp.fsmount_fd_idx;
|
||||
|
||||
fsmount_fd = varlink_take_fd(vl, pp.fsmount_fd_idx);
|
||||
if (fsmount_fd < 0)
|
||||
return fsmount_fd;
|
||||
}
|
||||
|
||||
assert(pp.designator >= 0);
|
||||
|
||||
if (!di) {
|
||||
r = dissected_image_new(path, &di);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocated new dissected image structure: %m");
|
||||
}
|
||||
|
||||
if (di->partitions[pp.designator].found)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate partition data for '%s'.", partition_designator_to_string(pp.designator));
|
||||
|
||||
di->partitions[pp.designator] = (DissectedPartition) {
|
||||
.found = true,
|
||||
.rw = pp.rw,
|
||||
.growfs = pp.growfs,
|
||||
.partno = pp.partno,
|
||||
.architecture = pp.architecture,
|
||||
.uuid = pp.uuid,
|
||||
.fstype = TAKE_PTR(pp.fstype),
|
||||
.label = TAKE_PTR(pp.label),
|
||||
.mount_node_fd = -EBADF,
|
||||
.size = pp.size,
|
||||
.offset = pp.offset,
|
||||
.fsmount_fd = TAKE_FD(fsmount_fd),
|
||||
};
|
||||
}
|
||||
|
||||
di->image_size = p.image_size;
|
||||
di->sector_size = p.sector_size;
|
||||
di->image_uuid = p.image_uuid;
|
||||
|
||||
*ret = TAKE_PTR(di);
|
||||
return 0;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ typedef enum DissectImageFlags {
|
||||
DISSECT_IMAGE_ALLOW_EMPTY = 1 << 24, /* Allow that no usable partitions is present */
|
||||
DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE = 1 << 25, /* Try to mount the image beneath the specified mountpoint, rather than on top of it, and then umount the top */
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY = 1 << 26, /* Allow userspace verity keyring in /etc/verity.d/ and related dirs */
|
||||
DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH = 1 << 27, /* Allow interactive authorization when going through mountfsd */
|
||||
} DissectImageFlags;
|
||||
|
||||
struct DissectedImage {
|
||||
@@ -163,6 +164,7 @@ int dissect_image_file_and_warn(const char *path, const VeritySettings *verity,
|
||||
int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
|
||||
void dissected_image_close(DissectedImage *m);
|
||||
DissectedImage* dissected_image_unref(DissectedImage *m);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
|
||||
|
||||
@@ -171,7 +173,7 @@ int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphr
|
||||
int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
|
||||
int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
|
||||
|
||||
int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags);
|
||||
int dissected_image_acquire_metadata(DissectedImage *m, int userns_fd, DissectImageFlags extra_flags);
|
||||
|
||||
Architecture dissected_image_architecture(DissectedImage *m);
|
||||
|
||||
@@ -198,6 +200,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref);
|
||||
int dissected_image_relinquish(DissectedImage *m);
|
||||
|
||||
int verity_settings_load(VeritySettings *verity, const char *image, const char *root_hash_path, const char *root_hash_sig_path);
|
||||
|
||||
static inline bool verity_settings_set(const VeritySettings *settings) {
|
||||
return settings &&
|
||||
(settings->root_hash_size > 0 ||
|
||||
(settings->root_hash_sig_size > 0 ||
|
||||
settings->data_path));
|
||||
}
|
||||
|
||||
void verity_settings_done(VeritySettings *verity);
|
||||
|
||||
static inline bool verity_settings_data_covers(const VeritySettings *verity, PartitionDesignator d) {
|
||||
@@ -232,3 +242,5 @@ static inline const char *dissected_partition_fstype(const DissectedPartition *m
|
||||
}
|
||||
|
||||
int get_common_dissect_directory(char **ret);
|
||||
|
||||
int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
|
||||
@@ -50,6 +50,20 @@ PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags) {
|
||||
/* The reverse of partition_policy_flags_extend(): if some parts of the flags field allow all
|
||||
* possible options, let's remove it from the flags to make them shorter */
|
||||
|
||||
if (FLAGS_SET(flags, _PARTITION_POLICY_USE_MASK))
|
||||
flags &= ~_PARTITION_POLICY_USE_MASK;
|
||||
if (FLAGS_SET(flags, _PARTITION_POLICY_READ_ONLY_MASK))
|
||||
flags &= ~_PARTITION_POLICY_READ_ONLY_MASK;
|
||||
if (FLAGS_SET(flags, _PARTITION_POLICY_GROWFS_MASK))
|
||||
flags &= ~_PARTITION_POLICY_GROWFS_MASK;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
|
||||
PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;
|
||||
|
||||
@@ -676,6 +690,90 @@ int parse_image_policy_argument(const char *s, ImagePolicy **policy) {
|
||||
return free_and_replace_full(*policy, np, image_policy_free);
|
||||
}
|
||||
|
||||
static bool partition_policy_flags_has_unspecified(PartitionPolicyFlags flags) {
|
||||
|
||||
if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
|
||||
return true;
|
||||
if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
|
||||
return true;
|
||||
if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) {
|
||||
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
|
||||
|
||||
/* Calculates the intersection of the specified policies, i.e. only what is permitted in both. This
|
||||
* might fail with -ENAVAIL if the intersection is an "impossible policy". For example, if a root
|
||||
* partition my neither be used, nor be absent, nor be unused then this is considered
|
||||
* "impossible". */
|
||||
|
||||
p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
p->default_flags =
|
||||
partition_policy_flags_extend(image_policy_default(a)) &
|
||||
partition_policy_flags_extend(image_policy_default(b));
|
||||
|
||||
if (partition_policy_flags_has_unspecified(p->default_flags)) /* Intersection empty? */
|
||||
return -ENAVAIL;
|
||||
|
||||
p->default_flags = partition_policy_flags_reduce(p->default_flags);
|
||||
|
||||
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
|
||||
PartitionPolicyFlags x, y, z, df;
|
||||
|
||||
/* If this designator has no entry in either policy we don't need to include it in the intersection either. */
|
||||
if (!image_policy_bsearch(a, d) && !image_policy_bsearch(b, d))
|
||||
continue;
|
||||
|
||||
/* Expand this policy flags field to the "long" form, i.e. for each part of the flags that
|
||||
* are left unspcified add in all possible options */
|
||||
x = image_policy_get_exhaustively(a, d);
|
||||
if (x < 0)
|
||||
return x;
|
||||
|
||||
y = image_policy_get_exhaustively(b, d);
|
||||
if (y < 0)
|
||||
return y;
|
||||
|
||||
/* Mask it */
|
||||
z = x & y;
|
||||
|
||||
/* Check if the intersection is empty for this partition. If so, generate a clear error */
|
||||
if (partition_policy_flags_has_unspecified(z))
|
||||
return -ENAVAIL;
|
||||
|
||||
df = partition_policy_normalized_flags(
|
||||
&(const PartitionPolicy) {
|
||||
.flags = image_policy_default(p),
|
||||
.designator = d,
|
||||
});
|
||||
if (df < 0)
|
||||
return df;
|
||||
if (df == z) /* Same as default? then let's skip this */
|
||||
continue;
|
||||
|
||||
/* image_policy_get_exhaustively() may have extended the flags mask to include all
|
||||
* read-only/growfs flags if not set. Let's remove them again, if they are both set to
|
||||
* minimize the policy again. */
|
||||
z = partition_policy_flags_reduce(z);
|
||||
|
||||
p->policies[p->n_policies++] = (struct PartitionPolicy) {
|
||||
.designator = d,
|
||||
.flags = z,
|
||||
};
|
||||
}
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ImagePolicy image_policy_allow = {
|
||||
/* Allow policy */
|
||||
.n_policies = 0,
|
||||
|
||||
@@ -80,6 +80,7 @@ static inline size_t image_policy_n_entries(const ImagePolicy *policy) {
|
||||
}
|
||||
|
||||
PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags);
|
||||
PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags);
|
||||
|
||||
PartitionPolicyFlags partition_policy_flags_from_string(const char *s);
|
||||
int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
|
||||
@@ -95,6 +96,8 @@ bool image_policy_equiv_deny(const ImagePolicy *policy);
|
||||
bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b); /* checks if defined the same way, i.e. has literally the same ruleset */
|
||||
int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b); /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
|
||||
|
||||
int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret);
|
||||
|
||||
static inline ImagePolicy* image_policy_free(ImagePolicy *p) {
|
||||
return mfree(p);
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ shared_sources = files(
|
||||
'netif-sriov.c',
|
||||
'netif-util.c',
|
||||
'nsflags.c',
|
||||
'nsresource.c',
|
||||
'numa-util.c',
|
||||
'open-file.c',
|
||||
'openssl-util.c',
|
||||
@@ -180,6 +181,8 @@ shared_sources = files(
|
||||
'varlink-io.systemd.Hostname.c',
|
||||
'varlink-io.systemd.Journal.c',
|
||||
'varlink-io.systemd.ManagedOOM.c',
|
||||
'varlink-io.systemd.MountFileSystem.c',
|
||||
'varlink-io.systemd.NamespaceResource.c',
|
||||
'varlink-io.systemd.Network.c',
|
||||
'varlink-io.systemd.PCRExtend.c',
|
||||
'varlink-io.systemd.PCRLock.c',
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "format-util.h"
|
||||
#include "missing_sched.h"
|
||||
#include "namespace-util.h"
|
||||
#include "nsresource.h"
|
||||
#include "process-util.h"
|
||||
#include "varlink.h"
|
||||
|
||||
static int make_pid_name(char **ret) {
|
||||
char comm[TASK_COMM_LEN];
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (prctl(PR_GET_NAME, comm) < 0)
|
||||
return -errno;
|
||||
|
||||
/* So the namespace name should be 16 chars at max (because we want that it is usable in usernames,
|
||||
* which have a limit of 31 chars effectively, and the nsresourced service wants to prefix/suffix
|
||||
* some bits). But it also should be unique if we are called multiple times in a row. Hence we take
|
||||
* the "comm" name (which is 15 chars), and suffix it with the UID, possibly overriding the end. */
|
||||
assert_cc(TASK_COMM_LEN == 15 + 1);
|
||||
|
||||
char spid[DECIMAL_STR_MAX(pid_t)];
|
||||
xsprintf(spid, PID_FMT, getpid_cached());
|
||||
|
||||
assert(strlen(spid) <= 16);
|
||||
strshorten(comm, 16 - strlen(spid));
|
||||
|
||||
_cleanup_free_ char *s = strjoin(comm, spid);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = TAKE_PTR(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsresource_allocate_userns(const char *name, uint64_t size) {
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
_cleanup_close_ int userns_fd = -EBADF;
|
||||
_cleanup_free_ char *_name = NULL;
|
||||
const char *error_id;
|
||||
int r, userns_fd_idx;
|
||||
|
||||
/* Allocate a new dynamic user namespace via the userdb registry logic */
|
||||
|
||||
if (!name) {
|
||||
r = make_pid_name(&_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
name = _name;
|
||||
}
|
||||
|
||||
if (size <= 0 || size > UINT64_C(0x100000000)) /* Note: the server actually only allows allocating 1 or 64K right now */
|
||||
return -EINVAL;
|
||||
|
||||
r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_output(vl, true);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||
|
||||
userns_fd = userns_acquire_empty();
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to acquire empty user namespace: %m");
|
||||
|
||||
userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
|
||||
if (userns_fd_idx < 0)
|
||||
return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
r = varlink_callb(vl,
|
||||
"io.systemd.NamespaceResource.AllocateUserRange",
|
||||
&reply,
|
||||
&error_id,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)),
|
||||
JSON_BUILD_PAIR("size", JSON_BUILD_UNSIGNED(size)),
|
||||
JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx))));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to call AllocateUserRange() varlink call: %m");
|
||||
if (error_id)
|
||||
return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to allocate user namespace with %" PRIu64 " users: %s", size, error_id);
|
||||
|
||||
return TAKE_FD(userns_fd);
|
||||
}
|
||||
|
||||
int nsresource_register_userns(const char *name, int userns_fd) {
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
_cleanup_close_ int _userns_fd = -EBADF;
|
||||
_cleanup_free_ char *_name = NULL;
|
||||
const char *error_id;
|
||||
int r, userns_fd_idx;
|
||||
|
||||
/* Register the specified user namespace with userbd. */
|
||||
|
||||
if (!name) {
|
||||
r = make_pid_name(&_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
name = _name;
|
||||
}
|
||||
|
||||
if (userns_fd < 0) {
|
||||
_userns_fd = namespace_open_by_type(NAMESPACE_USER);
|
||||
if (_userns_fd < 0)
|
||||
return -errno;
|
||||
|
||||
userns_fd = _userns_fd;
|
||||
}
|
||||
|
||||
r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_output(vl, true);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||
|
||||
userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
|
||||
if (userns_fd_idx < 0)
|
||||
return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
r = varlink_callb(vl,
|
||||
"io.systemd.NamespaceResource.RegisterUserNamespace",
|
||||
&reply,
|
||||
&error_id,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)),
|
||||
JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx))));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to call RegisterUserNamespace() varlink call: %m");
|
||||
if (error_id)
|
||||
return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to register user namespace: %s", error_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsresource_add_mount(int userns_fd, int mount_fd) {
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
int r, userns_fd_idx, mount_fd_idx;
|
||||
const char *error_id;
|
||||
|
||||
assert(mount_fd >= 0);
|
||||
|
||||
if (userns_fd < 0) {
|
||||
int _userns_fd = namespace_open_by_type(NAMESPACE_USER);
|
||||
if (_userns_fd < 0)
|
||||
return -errno;
|
||||
|
||||
userns_fd = _userns_fd;
|
||||
}
|
||||
|
||||
r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to namespace resource manager: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_output(vl, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||
|
||||
userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
|
||||
if (userns_fd_idx < 0)
|
||||
return log_error_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
|
||||
|
||||
mount_fd_idx = varlink_push_dup_fd(vl, mount_fd);
|
||||
if (mount_fd_idx < 0)
|
||||
return log_error_errno(mount_fd_idx, "Failed to push mount fd into varlink connection: %m");
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
r = varlink_callb(vl,
|
||||
"io.systemd.NamespaceResource.AddMountToUserNamespace",
|
||||
&reply,
|
||||
&error_id,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)),
|
||||
JSON_BUILD_PAIR("mountFileDescriptor", JSON_BUILD_UNSIGNED(mount_fd_idx))));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to call AddMountToUserNamespace() varlink call: %m");
|
||||
if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) {
|
||||
log_notice("User namespace has not been allocated via namespace resource registry, not adding mount to registration.");
|
||||
return 0;
|
||||
}
|
||||
if (error_id)
|
||||
return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to mount image: %s", error_id);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nsresource_add_cgroup(int userns_fd, int cgroup_fd) {
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
_cleanup_close_ int _userns_fd = -EBADF;
|
||||
int r, userns_fd_idx, cgroup_fd_idx;
|
||||
const char *error_id;
|
||||
|
||||
assert(cgroup_fd >= 0);
|
||||
|
||||
if (userns_fd < 0) {
|
||||
_userns_fd = namespace_open_by_type(NAMESPACE_USER);
|
||||
if (_userns_fd < 0)
|
||||
return -errno;
|
||||
|
||||
userns_fd = _userns_fd;
|
||||
}
|
||||
|
||||
r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_output(vl, true);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||
|
||||
userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
|
||||
if (userns_fd_idx < 0)
|
||||
return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
|
||||
|
||||
cgroup_fd_idx = varlink_push_dup_fd(vl, cgroup_fd);
|
||||
if (cgroup_fd_idx < 0)
|
||||
return log_debug_errno(userns_fd_idx, "Failed to push cgroup fd into varlink connection: %m");
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
r = varlink_callb(vl,
|
||||
"io.systemd.NamespaceResource.AddControlGroupToUserNamespace",
|
||||
&reply,
|
||||
&error_id,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)),
|
||||
JSON_BUILD_PAIR("controlGroupFileDescriptor", JSON_BUILD_UNSIGNED(cgroup_fd_idx))));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to call AddControlGroupToUserNamespace() varlink call: %m");
|
||||
if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) {
|
||||
log_notice("User namespace has not been allocated via namespace resource registry, not adding cgroup to registration.");
|
||||
return 0;
|
||||
}
|
||||
if (error_id)
|
||||
return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add cgroup to user namespace: %s", error_id);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nsresource_add_netif(
|
||||
int userns_fd,
|
||||
int netns_fd,
|
||||
const char *namespace_ifname,
|
||||
char **ret_host_ifname,
|
||||
char **ret_namespace_ifname) {
|
||||
|
||||
_cleanup_close_ int _userns_fd = -EBADF, _netns_fd = -EBADF;
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
int r, userns_fd_idx, netns_fd_idx;
|
||||
const char *error_id;
|
||||
|
||||
if (userns_fd < 0) {
|
||||
_userns_fd = namespace_open_by_type(NAMESPACE_USER);
|
||||
if (_userns_fd < 0)
|
||||
return -errno;
|
||||
|
||||
userns_fd = _userns_fd;
|
||||
}
|
||||
|
||||
if (netns_fd < 0) {
|
||||
_netns_fd = namespace_open_by_type(NAMESPACE_NET);
|
||||
if (_netns_fd < 0)
|
||||
return -errno;
|
||||
|
||||
netns_fd = _netns_fd;
|
||||
}
|
||||
|
||||
r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
|
||||
|
||||
r = varlink_set_allow_fd_passing_output(vl, true);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||
|
||||
userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
|
||||
if (userns_fd_idx < 0)
|
||||
return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
|
||||
|
||||
netns_fd_idx = varlink_push_dup_fd(vl, netns_fd);
|
||||
if (netns_fd_idx < 0)
|
||||
return log_debug_errno(netns_fd_idx, "Failed to push netns fd into varlink connection: %m");
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
r = varlink_callb(vl,
|
||||
"io.systemd.NamespaceResource.AddNetworkToUserNamespace",
|
||||
&reply,
|
||||
&error_id,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)),
|
||||
JSON_BUILD_PAIR("networkNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(netns_fd_idx)),
|
||||
JSON_BUILD_PAIR("mode", JSON_BUILD_CONST_STRING("veth")),
|
||||
JSON_BUILD_PAIR_CONDITION(namespace_ifname, "namespaceInterfaceName", JSON_BUILD_STRING(namespace_ifname))));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to call AddNetworkToUserNamespace() varlink call: %m");
|
||||
if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) {
|
||||
log_notice("User namespace has not been allocated via namespace resource registry, not adding network to registration.");
|
||||
return 0;
|
||||
}
|
||||
if (error_id)
|
||||
return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add network to user namespace: %s", error_id);
|
||||
|
||||
_cleanup_free_ char *host_interface_name = NULL, *namespace_interface_name = NULL;
|
||||
r = json_dispatch(
|
||||
reply,
|
||||
(const JsonDispatch[]) {
|
||||
{ "hostInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&host_interface_name) },
|
||||
{ "namespaceInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&namespace_interface_name) },
|
||||
},
|
||||
JSON_ALLOW_EXTENSIONS,
|
||||
/* userdata= */ NULL);
|
||||
|
||||
if (ret_host_ifname)
|
||||
*ret_host_ifname = TAKE_PTR(host_interface_name);
|
||||
if (ret_namespace_ifname)
|
||||
*ret_namespace_ifname = TAKE_PTR(namespace_interface_name);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
int nsresource_allocate_userns(const char *name, uint64_t size);
|
||||
int nsresource_register_userns(const char *name, int userns_fd);
|
||||
int nsresource_add_mount(int userns_fd, int mount_fd);
|
||||
int nsresource_add_cgroup(int userns_fd, int cgroup_fd);
|
||||
int nsresource_add_netif(int userns_fd, int netns_fd, const char *namespace_ifname, char **ret_host_ifname, char **ret_namespace_ifname);
|
||||
@@ -0,0 +1,69 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "varlink-io.systemd.MountFileSystem.h"
|
||||
|
||||
static VARLINK_DEFINE_ENUM_TYPE(
|
||||
PartitionDesignator,
|
||||
VARLINK_DEFINE_ENUM_VALUE(root),
|
||||
VARLINK_DEFINE_ENUM_VALUE(usr),
|
||||
VARLINK_DEFINE_ENUM_VALUE(home),
|
||||
VARLINK_DEFINE_ENUM_VALUE(srv),
|
||||
VARLINK_DEFINE_ENUM_VALUE(esp),
|
||||
VARLINK_DEFINE_ENUM_VALUE(xbootldr),
|
||||
VARLINK_DEFINE_ENUM_VALUE(swap),
|
||||
VARLINK_DEFINE_ENUM_VALUE(root_verity),
|
||||
VARLINK_DEFINE_ENUM_VALUE(usr_verity),
|
||||
VARLINK_DEFINE_ENUM_VALUE(root_verity_sig),
|
||||
VARLINK_DEFINE_ENUM_VALUE(usr_verity_sig),
|
||||
VARLINK_DEFINE_ENUM_VALUE(tmp),
|
||||
VARLINK_DEFINE_ENUM_VALUE(var));
|
||||
|
||||
static VARLINK_DEFINE_STRUCT_TYPE(
|
||||
PartitionInfo,
|
||||
VARLINK_DEFINE_FIELD(designator, VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_FIELD(writable, VARLINK_BOOL, 0),
|
||||
VARLINK_DEFINE_FIELD(growFileSystem, VARLINK_BOOL, 0),
|
||||
VARLINK_DEFINE_FIELD(partitionNumber, VARLINK_INT, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_FIELD(partitionUuid, VARLINK_STRING, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_FIELD(fileSystemType, VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_FIELD(partitionLabel, VARLINK_STRING, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_FIELD(offset, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_FIELD(mountFileDescriptor, VARLINK_INT, 0));
|
||||
|
||||
static VARLINK_DEFINE_METHOD(
|
||||
MountImage,
|
||||
VARLINK_DEFINE_INPUT(imageFileDescriptor, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_INPUT(readOnly, VARLINK_BOOL, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_INPUT(growFileSystems, VARLINK_BOOL, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_INPUT(password, VARLINK_STRING, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_INPUT(imagePolicy, VARLINK_STRING, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_OUTPUT_BY_TYPE(partitions, PartitionInfo, VARLINK_ARRAY),
|
||||
VARLINK_DEFINE_OUTPUT(imagePolicy, VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_OUTPUT(imageSize, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_OUTPUT(sectorSize, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_OUTPUT(imageName, VARLINK_STRING, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_OUTPUT(imageUuid, VARLINK_STRING, VARLINK_NULLABLE));
|
||||
|
||||
static VARLINK_DEFINE_ERROR(IncompatibleImage);
|
||||
static VARLINK_DEFINE_ERROR(MultipleRootPartitionsFound);
|
||||
static VARLINK_DEFINE_ERROR(RootPartitionNotFound);
|
||||
static VARLINK_DEFINE_ERROR(DeniedByImagePolicy);
|
||||
static VARLINK_DEFINE_ERROR(KeyNotFound);
|
||||
static VARLINK_DEFINE_ERROR(VerityFailure);
|
||||
|
||||
VARLINK_DEFINE_INTERFACE(
|
||||
io_systemd_MountFileSystem,
|
||||
"io.systemd.MountFileSystem",
|
||||
&vl_type_PartitionDesignator,
|
||||
&vl_type_PartitionInfo,
|
||||
&vl_method_MountImage,
|
||||
&vl_error_IncompatibleImage,
|
||||
&vl_error_MultipleRootPartitionsFound,
|
||||
&vl_error_RootPartitionNotFound,
|
||||
&vl_error_DeniedByImagePolicy,
|
||||
&vl_error_KeyNotFound,
|
||||
&vl_error_VerityFailure);
|
||||
@@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "varlink-idl.h"
|
||||
|
||||
extern const VarlinkInterface vl_interface_io_systemd_MountFileSystem;
|
||||
@@ -0,0 +1,62 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "varlink-io.systemd.NamespaceResource.h"
|
||||
|
||||
static VARLINK_DEFINE_METHOD(
|
||||
AllocateUserRange,
|
||||
VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_INPUT(size, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_INPUT(target, VARLINK_INT, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0));
|
||||
|
||||
static VARLINK_DEFINE_METHOD(
|
||||
RegisterUserNamespace,
|
||||
VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0));
|
||||
|
||||
static VARLINK_DEFINE_METHOD(
|
||||
AddMountToUserNamespace,
|
||||
VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_INPUT(mountFileDescriptor, VARLINK_INT, 0));
|
||||
|
||||
static VARLINK_DEFINE_METHOD(
|
||||
AddControlGroupToUserNamespace,
|
||||
VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_INPUT(controlGroupFileDescriptor, VARLINK_INT, 0));
|
||||
|
||||
static VARLINK_DEFINE_METHOD(
|
||||
AddNetworkToUserNamespace,
|
||||
VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_INPUT(networkNamespaceFileDescriptor, VARLINK_INT, 0),
|
||||
VARLINK_DEFINE_INPUT(namespaceInterfaceName, VARLINK_STRING, VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_INPUT(mode, VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_OUTPUT(hostInterfaceName, VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_OUTPUT(namespaceInterfaceName, VARLINK_STRING, 0));
|
||||
|
||||
static VARLINK_DEFINE_ERROR(UserNamespaceInterfaceNotSupported);
|
||||
static VARLINK_DEFINE_ERROR(NameExists);
|
||||
static VARLINK_DEFINE_ERROR(UserNamespaceExists);
|
||||
static VARLINK_DEFINE_ERROR(DynamicRangeUnavailable);
|
||||
static VARLINK_DEFINE_ERROR(NoDynamicRange);
|
||||
static VARLINK_DEFINE_ERROR(UserNamespaceNotRegistered);
|
||||
static VARLINK_DEFINE_ERROR(UserNamespaceWithoutUserRange);
|
||||
static VARLINK_DEFINE_ERROR(TooManyControlGroups);
|
||||
static VARLINK_DEFINE_ERROR(ControlGroupAlreadyAdded);
|
||||
|
||||
VARLINK_DEFINE_INTERFACE(
|
||||
io_systemd_NamespaceResource,
|
||||
"io.systemd.NamespaceResource",
|
||||
&vl_method_AllocateUserRange,
|
||||
&vl_method_RegisterUserNamespace,
|
||||
&vl_method_AddMountToUserNamespace,
|
||||
&vl_method_AddControlGroupToUserNamespace,
|
||||
&vl_method_AddNetworkToUserNamespace,
|
||||
&vl_error_UserNamespaceInterfaceNotSupported,
|
||||
&vl_error_NameExists,
|
||||
&vl_error_UserNamespaceExists,
|
||||
&vl_error_DynamicRangeUnavailable,
|
||||
&vl_error_NoDynamicRange,
|
||||
&vl_error_UserNamespaceNotRegistered,
|
||||
&vl_error_UserNamespaceWithoutUserRange,
|
||||
&vl_error_TooManyControlGroups,
|
||||
&vl_error_ControlGroupAlreadyAdded);
|
||||
@@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "varlink-idl.h"
|
||||
|
||||
extern const VarlinkInterface vl_interface_io_systemd_NamespaceResource;
|
||||
+29
-2
@@ -2807,12 +2807,29 @@ int varlink_get_peer_uid(Varlink *v, uid_t *ret) {
|
||||
return varlink_log_errno(v, r, "Failed to acquire credentials: %m");
|
||||
|
||||
if (!uid_is_valid(v->ucred.uid))
|
||||
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer uid is invalid.");
|
||||
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid.");
|
||||
|
||||
*ret = v->ucred.uid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int varlink_get_peer_gid(Varlink *v, gid_t *ret) {
|
||||
int r;
|
||||
|
||||
assert_return(v, -EINVAL);
|
||||
assert_return(ret, -EINVAL);
|
||||
|
||||
r = varlink_acquire_ucred(v);
|
||||
if (r < 0)
|
||||
return varlink_log_errno(v, r, "Failed to acquire credentials: %m");
|
||||
|
||||
if (!gid_is_valid(v->ucred.gid))
|
||||
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid.");
|
||||
|
||||
*ret = v->ucred.gid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int varlink_get_peer_pid(Varlink *v, pid_t *ret) {
|
||||
int r;
|
||||
|
||||
@@ -3076,7 +3093,7 @@ int varlink_push_fd(Varlink *v, int fd) {
|
||||
return i;
|
||||
}
|
||||
|
||||
int varlink_dup_fd(Varlink *v, int fd) {
|
||||
int varlink_push_dup_fd(Varlink *v, int fd) {
|
||||
_cleanup_close_ int dp = -1;
|
||||
int r;
|
||||
|
||||
@@ -3124,6 +3141,16 @@ int varlink_peek_fd(Varlink *v, size_t i) {
|
||||
return v->input_fds[i];
|
||||
}
|
||||
|
||||
int varlink_peek_dup_fd(Varlink *v, size_t i) {
|
||||
int fd;
|
||||
|
||||
fd = varlink_peek_fd(v, i);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
return RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3));
|
||||
}
|
||||
|
||||
int varlink_take_fd(Varlink *v, size_t i) {
|
||||
assert_return(v, -EINVAL);
|
||||
|
||||
|
||||
@@ -156,11 +156,12 @@ int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch tab
|
||||
|
||||
/* Write outgoing fds into the socket (to be associated with the next enqueued message) */
|
||||
int varlink_push_fd(Varlink *v, int fd);
|
||||
int varlink_dup_fd(Varlink *v, int fd);
|
||||
int varlink_push_dup_fd(Varlink *v, int fd);
|
||||
int varlink_reset_fds(Varlink *v);
|
||||
|
||||
/* Read incoming fds from the socket (associated with the currently handled message) */
|
||||
int varlink_peek_fd(Varlink *v, size_t i);
|
||||
int varlink_peek_dup_fd(Varlink *v, size_t i);
|
||||
int varlink_take_fd(Varlink *v, size_t i);
|
||||
|
||||
int varlink_set_allow_fd_passing_input(Varlink *v, bool b);
|
||||
@@ -173,6 +174,7 @@ void* varlink_set_userdata(Varlink *v, void *userdata);
|
||||
void* varlink_get_userdata(Varlink *v);
|
||||
|
||||
int varlink_get_peer_uid(Varlink *v, uid_t *ret);
|
||||
int varlink_get_peer_gid(Varlink *v, gid_t *ret);
|
||||
int varlink_get_peer_pid(Varlink *v, pid_t *ret);
|
||||
int varlink_get_peer_pidref(Varlink *v, PidRef *ret);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "cgroup-setup.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
#include "string-util.h"
|
||||
@@ -129,4 +130,47 @@ TEST(cg_create) {
|
||||
assert_se(cg_rmdir(SYSTEMD_CGROUP_CONTROLLER, test_a) == 0);
|
||||
}
|
||||
|
||||
TEST(id) {
|
||||
_cleanup_free_ char *p = NULL, *p2 = NULL;
|
||||
_cleanup_close_ int fd = -EBADF, fd2 = -EBADF;
|
||||
uint64_t id, id2;
|
||||
int r;
|
||||
|
||||
r = cg_all_unified();
|
||||
if (r == 0) {
|
||||
log_tests_skipped("skipping cgroupid test, not running in unified mode");
|
||||
return;
|
||||
}
|
||||
if (IN_SET(r, -ENOMEDIUM, -ENOENT)) {
|
||||
log_tests_skipped("cgroupfs is not mounted");
|
||||
return;
|
||||
}
|
||||
assert_se(r > 0);
|
||||
|
||||
fd = cg_path_open(SYSTEMD_CGROUP_CONTROLLER, "/");
|
||||
assert_se(fd >= 0);
|
||||
|
||||
assert_se(fd_get_path(fd, &p) >= 0);
|
||||
assert_se(path_equal(p, "/sys/fs/cgroup"));
|
||||
|
||||
assert_se(cg_fd_get_cgroupid(fd, &id) >= 0);
|
||||
|
||||
fd2 = cg_cgroupid_open(fd, id);
|
||||
|
||||
if (ERRNO_IS_NEG_PRIVILEGE(fd2))
|
||||
log_notice("Skipping open-by-cgroup-id test because lacking privs.");
|
||||
else {
|
||||
assert_se(fd2 >= 0);
|
||||
|
||||
assert_se(fd_get_path(fd2, &p2) >= 0);
|
||||
assert_se(path_equal(p2, "/sys/fs/cgroup"));
|
||||
|
||||
assert_se(cg_fd_get_cgroupid(fd2, &id2) >= 0);
|
||||
|
||||
assert_se(id == id2);
|
||||
|
||||
assert_se(inode_same_at(fd, NULL, fd2, NULL, AT_EMPTY_PATH) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
||||
@@ -130,4 +130,36 @@ TEST(extend) {
|
||||
assert_se(partition_policy_flags_extend(PARTITION_POLICY_GROWFS_ON) == (PARTITION_POLICY_GROWFS_ON|_PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK));
|
||||
}
|
||||
|
||||
static void test_policy_intersect_one(const char *a, const char *b, const char *c) {
|
||||
_cleanup_(image_policy_freep) ImagePolicy *x = NULL, *y = NULL, *z = NULL, *t = NULL;
|
||||
|
||||
assert_se(image_policy_from_string(a, &x) >= 0);
|
||||
assert_se(image_policy_from_string(b, &y) >= 0);
|
||||
assert_se(image_policy_from_string(c, &z) >= 0);
|
||||
|
||||
assert_se(image_policy_intersect(x, y, &t) >= 0);
|
||||
|
||||
_cleanup_free_ char *s1 = NULL, *s2 = NULL, *s3 = NULL, *s4 = NULL;
|
||||
assert_se(image_policy_to_string(x, false, &s1) >= 0);
|
||||
assert_se(image_policy_to_string(y, false, &s2) >= 0);
|
||||
assert_se(image_policy_to_string(z, false, &s3) >= 0);
|
||||
assert_se(image_policy_to_string(t, false, &s4) >= 0);
|
||||
|
||||
log_info("%s ^ %s → %s vs. %s", s1, s2, s3, s4);
|
||||
|
||||
assert_se(image_policy_equivalent(z, t) > 0);
|
||||
}
|
||||
|
||||
TEST(image_policy_intersect) {
|
||||
test_policy_intersect_one("", "", "");
|
||||
test_policy_intersect_one("-", "-", "-");
|
||||
test_policy_intersect_one("*", "*", "*");
|
||||
test_policy_intersect_one("~", "~", "~");
|
||||
test_policy_intersect_one("root=verity+signed", "root=signed+verity", "root=verity+signed");
|
||||
test_policy_intersect_one("root=verity+signed", "root=signed", "root=signed");
|
||||
test_policy_intersect_one("root=verity+signed", "root=verity", "root=verity");
|
||||
test_policy_intersect_one("root=open", "root=verity", "root=verity");
|
||||
test_policy_intersect_one("root=open", "=verity+ignore", "root=verity+ignore:=ignore");
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
@@ -23,9 +23,15 @@ TEST(uid_range) {
|
||||
assert_se(!uid_range_covers(p, UINT32_MAX, 1));
|
||||
assert_se(!uid_range_covers(p, UINT32_MAX - 10, 11));
|
||||
|
||||
assert_se(uid_range_entries(p) == 0);
|
||||
assert_se(uid_range_size(p) == 0);
|
||||
assert_se(uid_range_is_empty(p));
|
||||
|
||||
assert_se(uid_range_add_str(&p, "500-999") >= 0);
|
||||
assert_se(p);
|
||||
assert_se(p->n_entries == 1);
|
||||
assert_se(uid_range_entries(p) == 1);
|
||||
assert_se(uid_range_size(p) == 500);
|
||||
assert_se(!uid_range_is_empty(p));
|
||||
assert_se(p->entries[0].start == 500);
|
||||
assert_se(p->entries[0].nr == 500);
|
||||
|
||||
@@ -56,19 +62,23 @@ TEST(uid_range) {
|
||||
assert_se(uid_range_next_lower(p, &search) == -EBUSY);
|
||||
|
||||
assert_se(uid_range_add_str(&p, "1000") >= 0);
|
||||
assert_se(p->n_entries == 1);
|
||||
assert_se(uid_range_entries(p) == 1);
|
||||
assert_se(p->entries[0].start == 500);
|
||||
assert_se(p->entries[0].nr == 501);
|
||||
|
||||
assert_se(uid_range_add_str(&p, "30-40") >= 0);
|
||||
assert_se(p->n_entries == 2);
|
||||
assert_se(uid_range_entries(p) == 2);
|
||||
assert_se(uid_range_size(p) == 500 + 1 + 11);
|
||||
assert_se(!uid_range_is_empty(p));
|
||||
assert_se(p->entries[0].start == 30);
|
||||
assert_se(p->entries[0].nr == 11);
|
||||
assert_se(p->entries[1].start == 500);
|
||||
assert_se(p->entries[1].nr == 501);
|
||||
|
||||
assert_se(uid_range_add_str(&p, "60-70") >= 0);
|
||||
assert_se(p->n_entries == 3);
|
||||
assert_se(uid_range_entries(p) == 3);
|
||||
assert_se(uid_range_size(p) == 500 + 1 + 11 + 11);
|
||||
assert_se(!uid_range_is_empty(p));
|
||||
assert_se(p->entries[0].start == 30);
|
||||
assert_se(p->entries[0].nr == 11);
|
||||
assert_se(p->entries[1].start == 60);
|
||||
@@ -77,21 +87,34 @@ TEST(uid_range) {
|
||||
assert_se(p->entries[2].nr == 501);
|
||||
|
||||
assert_se(uid_range_add_str(&p, "20-2000") >= 0);
|
||||
assert_se(p->n_entries == 1);
|
||||
assert_se(uid_range_entries(p) == 1);
|
||||
assert_se(uid_range_size(p) == 1981);
|
||||
assert_se(p->entries[0].start == 20);
|
||||
assert_se(p->entries[0].nr == 1981);
|
||||
|
||||
assert_se(uid_range_add_str(&p, "2002") >= 0);
|
||||
assert_se(p->n_entries == 2);
|
||||
assert_se(uid_range_entries(p) == 2);
|
||||
assert_se(uid_range_size(p) == 1982);
|
||||
assert_se(p->entries[0].start == 20);
|
||||
assert_se(p->entries[0].nr == 1981);
|
||||
assert_se(p->entries[1].start == 2002);
|
||||
assert_se(p->entries[1].nr == 1);
|
||||
|
||||
_cleanup_(uid_range_freep) UIDRange *q = NULL;
|
||||
assert_se(!uid_range_equal(p, q));
|
||||
assert_se(uid_range_add_str(&q, "20-2000") >= 0);
|
||||
assert_se(!uid_range_equal(p, q));
|
||||
assert_se(uid_range_add_str(&q, "2002") >= 0);
|
||||
assert_se(uid_range_equal(p, q));
|
||||
|
||||
assert_se(uid_range_add_str(&p, "2001") >= 0);
|
||||
assert_se(p->n_entries == 1);
|
||||
assert_se(uid_range_entries(p) == 1);
|
||||
assert_se(uid_range_size(p) == 1983);
|
||||
assert_se(p->entries[0].start == 20);
|
||||
assert_se(p->entries[0].nr == 1983);
|
||||
|
||||
assert_se(uid_range_add_str(&q, "2001") >= 0);
|
||||
assert_se(uid_range_equal(p, q));
|
||||
}
|
||||
|
||||
TEST(load_userns) {
|
||||
@@ -100,7 +123,7 @@ TEST(load_userns) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
int r;
|
||||
|
||||
r = uid_range_load_userns(&p, NULL);
|
||||
r = uid_range_load_userns(&p, NULL, UID_RANGE_USERNS_INSIDE);
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return;
|
||||
|
||||
@@ -123,7 +146,7 @@ TEST(load_userns) {
|
||||
|
||||
p = uid_range_free(p);
|
||||
|
||||
assert_se(uid_range_load_userns(&p, fn) >= 0);
|
||||
assert_se(uid_range_load_userns(&p, fn, UID_RANGE_USERNS_INSIDE) >= 0);
|
||||
|
||||
assert_se(uid_range_contains(p, 0));
|
||||
assert_se(uid_range_contains(p, 19));
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
#include "varlink-io.systemd.Credentials.h"
|
||||
#include "varlink-io.systemd.Journal.h"
|
||||
#include "varlink-io.systemd.ManagedOOM.h"
|
||||
#include "varlink-io.systemd.MountFileSystem.h"
|
||||
#include "varlink-io.systemd.NamespaceResource.h"
|
||||
#include "varlink-io.systemd.Network.h"
|
||||
#include "varlink-io.systemd.PCRExtend.h"
|
||||
#include "varlink-io.systemd.PCRLock.h"
|
||||
#include "varlink-io.systemd.Resolve.Monitor.h"
|
||||
#include "varlink-io.systemd.Resolve.h"
|
||||
#include "varlink-io.systemd.Resolve.Monitor.h"
|
||||
#include "varlink-io.systemd.UserDatabase.h"
|
||||
#include "varlink-io.systemd.oom.h"
|
||||
#include "varlink-io.systemd.service.h"
|
||||
@@ -129,6 +131,8 @@ TEST(parse_format) {
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_UserDatabase);
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_NamespaceResource);
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_Journal);
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_Resolve);
|
||||
@@ -137,6 +141,8 @@ TEST(parse_format) {
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_ManagedOOM);
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_MountFileSystem);
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_Network);
|
||||
print_separator();
|
||||
test_parse_format_one(&vl_interface_io_systemd_oom);
|
||||
|
||||
@@ -428,7 +428,7 @@ static int display_user(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(uid_range_freep) UIDRange *uid_range = NULL;
|
||||
int boundary_lines, uid_map_lines;
|
||||
|
||||
r = uid_range_load_userns(&uid_range, "/proc/self/uid_map");
|
||||
r = uid_range_load_userns(&uid_range, "/proc/self/uid_map", UID_RANGE_USERNS_INSIDE);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to load /proc/self/uid_map, ignoring: %m");
|
||||
|
||||
@@ -731,7 +731,7 @@ static int display_group(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(uid_range_freep) UIDRange *gid_range = NULL;
|
||||
int boundary_lines, gid_map_lines;
|
||||
|
||||
r = uid_range_load_userns(&gid_range, "/proc/self/gid_map");
|
||||
r = uid_range_load_userns(&gid_range, "/proc/self/gid_map", UID_RANGE_USERNS_INSIDE);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to load /proc/self/gid_map, ignoring: %m");
|
||||
|
||||
|
||||
@@ -12,6 +12,15 @@ TEST_INSTALL_VERITY_MINIMAL=1
|
||||
# shellcheck source=test/test-functions
|
||||
. "${TEST_BASE_DIR:?}/test-functions"
|
||||
|
||||
# On Ubuntu the BPF LSM is not enabled by default, so we need to do it via the
|
||||
# kernel command line on boot
|
||||
if [ "$LOOKS_LIKE_UBUNTU" = "yes" ]; then
|
||||
KERNEL_OPTIONS=(
|
||||
"lsm=lockdown,capability,landlock,yama,apparmor,bpf"
|
||||
)
|
||||
KERNEL_APPEND+=" ${KERNEL_OPTIONS[*]}"
|
||||
fi
|
||||
|
||||
test_require_bin mksquashfs veritysetup sfdisk
|
||||
|
||||
test_append_files() {
|
||||
|
||||
@@ -39,6 +39,8 @@ os_release=$(test -e /etc/os-release && echo /etc/os-release || echo /usr/lib/os
|
||||
# shellcheck source=/dev/null
|
||||
source "$os_release"
|
||||
[[ "$ID" == "debian" || " $ID_LIKE " == *" debian "* ]] && LOOKS_LIKE_DEBIAN=yes || LOOKS_LIKE_DEBIAN=no
|
||||
# shellcheck disable=SC2034
|
||||
[[ "$ID" == "ubuntu" ]] && LOOKS_LIKE_UBUNTU=yes || LOOKS_LIKE_UBUNTU=no
|
||||
[[ "$ID" == "arch" || " $ID_LIKE " == *" arch "* ]] && LOOKS_LIKE_ARCH=yes || LOOKS_LIKE_ARCH=no
|
||||
[[ "$ID" == "fedora" ]] && LOOKS_LIKE_FEDORA=yes || LOOKS_LIKE_FEDORA=no
|
||||
[[ " $ID_LIKE " == *" suse "* ]] && LOOKS_LIKE_SUSE=yes || LOOKS_LIKE_SUSE=no
|
||||
@@ -733,6 +735,7 @@ install_verity_minimal() {
|
||||
BASICTOOLS=(
|
||||
bash
|
||||
cat
|
||||
echo
|
||||
grep
|
||||
mount
|
||||
sleep
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
export SYSTEMD_LOG_LEVEL=debug
|
||||
|
||||
cleanup_image_dir() {
|
||||
@@ -798,6 +801,62 @@ if command -v mksquashfs >/dev/null 2>&1; then
|
||||
(! test -f /usr/waldo)
|
||||
fi
|
||||
|
||||
if test -f /usr/lib/systemd/system/systemd-mountfsd.socket -a -f /usr/lib/systemd/system/systemd-nsresourced.socket && \
|
||||
command -v command -v mksquashfs >/dev/null 2>&1 && \
|
||||
grep -q bpf /sys/kernel/security/lsm && \
|
||||
test "$(find /usr/lib* -name libbpf.so.1 2>/dev/null)" != "" ; then
|
||||
|
||||
cleanunprivfiles() {
|
||||
umount -R /tmp/unpriv/mount
|
||||
rmdir /tmp/unpriv
|
||||
rm -f /tmp/test-50-unpriv-privkey.key /tmp/test-50-unpriv-cert.crt /run/verity.d/test-50-unpriv-cert.crt
|
||||
rm -f /var/tmp/unpriv.raw /tmp/unpriv.raw.mtree /tmp/unpriv2.raw.mtree
|
||||
rm -f /tmp/unpriv.out /tmp/unpriv.out2 /tmp/unpriv.out3
|
||||
}
|
||||
|
||||
trap cleanunprivfiles EXIT
|
||||
|
||||
systemctl start systemd-mountfsd.socket systemd-nsresourced.socket
|
||||
|
||||
openssl req -config "$OPENSSL_CONFIG" -subj="/CN=waldo" -x509 -sha256 -nodes -days 365 -newkey rsa:4096 -keyout /tmp/test-50-unpriv-privkey.key -out /tmp/test-50-unpriv-cert.crt
|
||||
|
||||
systemd-dissect --mkdir --mount "${image}.raw" /tmp/unpriv/mount
|
||||
SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs systemd-repart -P -s /tmp/unpriv/mount --certificate=/tmp/test-50-unpriv-cert.crt --private-key=/tmp/test-50-unpriv-privkey.key /var/tmp/unpriv.raw
|
||||
systemd-dissect --rmdir --umount /tmp/unpriv/mount
|
||||
|
||||
systemd-dissect --image-policy='root=unprotected:=absent+unused' /var/tmp/unpriv.raw
|
||||
systemd-dissect --image-policy='root=unprotected:=absent+unused' --mtree /var/tmp/unpriv.raw | tee /tmp/unpriv.raw.mtree
|
||||
|
||||
# Run unpriv, should fail due to lack of privs
|
||||
(! runas testuser systemd-dissect /var/tmp/unpriv.raw )
|
||||
(! runas testuser systemd-dissect --mtree /var/tmp/unpriv.raw )
|
||||
|
||||
# Install key in keychain
|
||||
cp /tmp/test-50-unpriv-cert.crt /run/verity.d
|
||||
|
||||
# Now run unpriv again, should be OK now.
|
||||
runas testuser systemd-dissect /var/tmp/unpriv.raw
|
||||
runas testuser systemd-dissect --mtree /var/tmp/unpriv.raw | tee /tmp/unpriv2.raw.mtree
|
||||
|
||||
# Check that unpriv and priv run yielded same results
|
||||
cmp /tmp/unpriv.raw.mtree /tmp/unpriv2.raw.mtree
|
||||
|
||||
# Make sure nspawn works unpriv, too (for now do not nest)
|
||||
if ! systemd-detect-virt -c ; then
|
||||
systemd-nspawn --pipe -i /var/tmp/unpriv.raw --read-only echo thisisatest > /tmp/unpriv.out
|
||||
echo thisisatest | cmp /tmp/unpriv.out -
|
||||
|
||||
# The unpriv user has no rights to lock the image or write to it. Let's
|
||||
# turn off both for this test, so that we don't have to copy the image
|
||||
# around.
|
||||
systemd-run -M testuser@ --user --pipe -p Environment=SYSTEMD_NSPAWN_LOCK=0 -p Delegate=1 -p DelegateSubgroup=supervisor -p Environment=SYSTEMD_LOG_LEVEL=debug --wait systemd-nspawn --keep-unit -i /var/tmp/unpriv.raw --read-only --pipe echo thisisatest > /tmp/unpriv.out2
|
||||
echo thisisatest | cmp /tmp/unpriv.out2 -
|
||||
fi
|
||||
|
||||
systemd-run -M testuser@ --user --pipe -p RootImage=/var/tmp/unpriv.raw -p PrivateUsers=1 --wait echo thisisatest > /tmp/unpriv.out3
|
||||
echo thisisatest | cmp /tmp/unpriv.out3 -
|
||||
fi
|
||||
|
||||
# Sneak in a couple of expected-to-fail invocations to cover
|
||||
# https://github.com/systemd/systemd/issues/29610
|
||||
(! systemd-run -P -p MountImages="/this/should/definitely/not/exist.img:/run/img2\:3:nosuid" false)
|
||||
|
||||
@@ -732,6 +732,22 @@ units = [
|
||||
'file' : 'systemd-userdbd.socket',
|
||||
'conditions' : ['ENABLE_USERDB'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-mountfsd.service.in',
|
||||
'conditions' : ['ENABLE_MOUNTFSD'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-mountfsd.socket',
|
||||
'conditions' : ['ENABLE_MOUNTFSD'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-nsresourced.service.in',
|
||||
'conditions' : ['ENABLE_NSRESOURCED'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-nsresourced.socket',
|
||||
'conditions' : ['ENABLE_NSRESOURCED'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-vconsole-setup.service.in',
|
||||
'conditions' : ['ENABLE_VCONSOLE'],
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=DDI File System Mounter
|
||||
Documentation=man:systemd-mountfsd.service(8)
|
||||
Requires=systemd-mountfsd.socket
|
||||
After=systemd-mountfsd.socket
|
||||
Conflicts=shutdown.target
|
||||
Before=sysinit.target shutdown.target
|
||||
DefaultDependencies=no
|
||||
|
||||
[Service]
|
||||
#CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_RESOURCE CAP_BPF CAP_PERFMON CAP_SETGID CAP_SETUID
|
||||
ExecStart={{LIBEXECDIR}}/systemd-mountfsd
|
||||
IPAddressDeny=any
|
||||
LimitNOFILE={{HIGH_RLIMIT_NOFILE}}
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectProc=invisible
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service @mount
|
||||
Type=notify
|
||||
NotifyAccess=all
|
||||
FileDescriptorStoreMax=4096
|
||||
{{SERVICE_WATCHDOG}}
|
||||
|
||||
[Install]
|
||||
Also=systemd-mountfsd.socket
|
||||
@@ -0,0 +1,22 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=DDI File System Mounter Socket
|
||||
Documentation=man:systemd-mountfsd.service(8)
|
||||
DefaultDependencies=no
|
||||
Conflicts=shutdown.target
|
||||
Before=sockets.target shutdown.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/io.systemd.MountFileSystem
|
||||
SocketMode=0666
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -0,0 +1,47 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Namespace Resource Manager
|
||||
Documentation=man:systemd-nsresourced.service(8)
|
||||
Requires=systemd-nsresourced.socket
|
||||
After=systemd-nsresourced.socket
|
||||
Conflicts=shutdown.target
|
||||
Before=sysinit.target shutdown.target
|
||||
DefaultDependencies=no
|
||||
|
||||
[Service]
|
||||
CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_RESOURCE CAP_BPF CAP_PERFMON CAP_SETGID CAP_SETUID CAP_SYS_ADMIN CAP_CHOWN CAP_FOWNER
|
||||
ExecStart={{LIBEXECDIR}}/systemd-nsresourced
|
||||
IPAddressDeny=any
|
||||
LimitNOFILE={{HIGH_RLIMIT_NOFILE}}
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
ProtectProc=invisible
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service bpf perf_event_open open_by_handle_at
|
||||
Type=notify
|
||||
NotifyAccess=all
|
||||
FileDescriptorStoreMax=4096
|
||||
{{SERVICE_WATCHDOG}}
|
||||
|
||||
[Install]
|
||||
Also=systemd-nsresourced.socket
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Namespace Resource Manager Socket
|
||||
Documentation=man:systemd-nsresourced.service(8)
|
||||
DefaultDependencies=no
|
||||
Conflicts=shutdown.target
|
||||
Before=sockets.target shutdown.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/io.systemd.NamespaceResource
|
||||
Symlinks=/run/systemd/userdb/io.systemd.NamespaceResource
|
||||
SocketMode=0666
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
Reference in New Issue
Block a user