This is establish the basic concepts for #35685, in the hope to get this
merged first.
This defines a special, fixed 64K UID range that is supposed to be used
by directory container images on disk, that is mapped to a dynamic UID
range at runtime (via idmapped mounts).
This enables a world where each container can run with a dynamic UID
range, but this in no way leaks onto the disk, thus making supposedly
dynamic, transient UID range assignments persistent.
This is infrastructure later used for the primary part of #35685: unpriv
container execution with directory images inside user's home dirs, that
are assigned to this special "foreign UID range".
This PR only defines the ranges, synthesizes NSS records for them via
userdb, and then exposes them in a new "systemd-dissect --shift" command
that can re-chown a container directory tree into this range (and in
fact any range).
This comes with docs. But no tests. There are tests in #35685 that cover
all this, but they are more comprehensive and also test nspawn's hook-up
with this, hence are excluded from this PR.
If we unexpectly disconnect from the bus, systemd would end up dropping
the list of subscribers, which breaks the ability of clients like logind
to monitor the state of units.
Stash the list of subscribers into the deserialized state in the event
of a disconnect so that when we recover we can renew the broken
subscriptions.
---
Fixes: #8672#26744
If we unexpectly disconnect from the bus, systemd would end up dropping
the list of subscribers, which breaks the ability of clients like logind
to monitor the state of units.
Stash the list of subscribers into the deserialized state in the event
of a disconnect so that when we recover we can renew the broken
subscriptions.
This makes the UID range configurable via build time options, but of
course it really shouldn't be changed. The default range I picked is
outside even of IPAs current (ridiculously large) allocation ranges,
hence hopefully minimizes conflicts.
When running unprivileged containers, we run into a scenario where an
unpriv owned cgroup has a subcgroup delegated to another user (i.e. the
container's own UIDs). When the owner of that cgroup dies without
cleaning it up then the unpriv service manager might encounter a cgroup
it cannot delete anymore.
Let's address that: let's expose a method call on the service manager
(primarly in PID1) that can be used to delete a subcgroup of a unit one
owns. This would then allow the unpriv service manager to ask the priv
service manager to get rid of such a cgroup.
This commit only adds the method call, the next commit then adds the
code that makes use of this.
Let's liberalize process migration a bit. Previously, PID 1 would only
allow you to move processes into your own cgroups, if those processes
are owned by you too. This is now slightly relaxed: it's now also OK if
the processes are in a userns owned by you.
This makes process migration more useful in context of unpriv userns.
unit_file_add_dependency() asserts, so check before calling it that the
type is expected, or return EINVAL to the caller.
root@localhost:~# busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager AddDependencyUnitFiles "asssbb" 0 uwhatm8 After 1 1
Broadcast message from systemd-journald@localhost (Mon 2025-01-06 18:12:14 UTC):
systemd[1]: Caught <ABRT>, from our own process.
Fixes https://github.com/systemd/systemd/issues/35882
Supersedes #35308 (cherry-picked one commit and replaced the rest)
(I left a few comments that's folded by GitHub. Please make sure to
check them too.)
- Make fd_is_namespace() take NamespaceType
- Drop support for kernel without NS_GET_NSTYPE (< 4.11)
- Port is_our_namespace() to namespace_open_by_type()
(preparation for later commits, where the latter
would go by pidfd if available, avoiding procfs)
If bus_add_match_full() is called without install callback and we failed
to install the signal match e.g. by timeout, then add_match_callback()
will disconnect from the bus. Let's use a custom install handler and
handle failures gracefully.
This does not *solve* the root cause of issue
https://github.com/systemd/systemd/issues/30573, but should improve the
situation when the issue is triggered.
Prior to this commit, if no Exec*= is defined for socket,
and the unit was in SOCKET_FAILED state, failure of socket_open_fds()
would induce state transition SOCKET_FAILED -> SOCKET_FAILED,
and OnFailure= deps get unexpectedly skipped. Let's introduce
an intermediate state, so that during unit start we enter
UNIT_ACTIVATING at least once.
Fixes#35635
- Log about errors in socket_open_fds()
- Open parent dir fd and operate under it, instead of constructing paths
everywhere
- Replace scandir() with readdir_all()
Follow-up for c968d76a38
We'd only transition to SOCKET_CLEAN state if previously
inactive (see socket_clean()), hence no fds shall be persisted
in this state.
If bus_add_match_full() is called without install callback and we failed
to install the signal match e.g. by timeout, then add_match_callback()
will disconnect from the bus.
Let's use a custom install handler and handle failures gracefully.
This does not *solve* the root cause of issue #30573, but should improve
the situation when the issue is triggered.
When I added support for unprivileged credentials I apparently never
hooked them up to service management correctly. Let's fix that.
Fixes: #33796#33318
If an enumerated device has ID_PROCESSING=1 property, and the service
manager does not know if the device has been processed by udevd
previously (that is, Device.deserialized_found does not have
DEVICE_FOUND_UDEV), then drop DEVICE_FOUND_UDEV flag from the device and
make the device not enter the active state.
Follow-up for 405be62f05, which was
reverted by c4fc22c4de.
Now that we only support serialization into a memfd (rather than a file
in /run/) there's no point to check the free space in /run/. Let's drop it.
One error scenario gone. Yay.
These new calls will do three things:
1. in case of FILE* stuff: flush any pending bytes onto the fd, just in
case
2. seal the backing memfd
3. seek back to the beginning.
Note that this adds sealing to serialization: once we serialized fully,
we'll seal the thing off for further modifications, before we pass the
fd over to the target process. This should add a bit of robustness, and
maybe finds a bug or two one day, if we accidentally write to a
serialization that is complete.
Let's drop support systems lacking memfds, i.e. pre kernel 3.17 systems.
This allows us to drastically simplify the "data fd" concept, so far
that we can remove it entirely.
This replaces acquire_data_fd() with a specialized call to
memfd_new_and_seal(), not that memfds can be the only implementation of
the concept.
Let's bump the kernel baseline a bit to 4.3 and thus require ambient
caps.
This allows us to remove support for a variety of special casing, most
importantly the ExecStart=!! hack.
To be able to run systemd in a Type=notify transient unit, the notify
socket can't be bind mounted to /run/systemd/notify as systemd in the
transient unit wants to use that as its own notify socket which
conflicts with systemd on the host.
Instead, for sandboxed units, let's bind mount the notify socket to
/run/host/notify as documented in the container interface. Since we
don't guarantee a stable location for the notify socket and insist users
use $NOTIFY_SOCKET to get its path, this is safe to do.
To be able to run systemd in a Type=notify transient unit, the notify
socket can't be bind mounted to /run/systemd/notify as systemd in the
transient unit wants to use that as its own notify socket which conflicts
with systemd on the host.
Instead, for sandboxed units, let's bind mount the notify socket to
/run/host/notify as documented in the container interface. Since we don't
guarantee a stable location for the notify socket and insist users use
$NOTIFY_SOCKET to get its path, this is safe to do.
Recently, PrivateUsers=identity was added to support mapping the first
65536 UIDs/GIDs from parent to the child namespace and mapping the other
UID/GIDs to the nobody user.
However, there are use cases where users have UIDs/GIDs > 65536 and need
to do a similar identity mapping. Moreover, in some of those cases,
users want a full identity mapping from 0 -> UID_MAX.
To support this, we add PrivateUsers=full that does identity mapping for
all available UID/GIDs.
Note to differentiate ourselves from the init user namespace, we need to
set up the uid_map/gid_map like:
```
0 0 1
1 1 UINT32_MAX - 1
```
as the init user namedspace uses `0 0 UINT32_MAX` and some applications
- like systemd itself - determine if its a non-init user namespace based
on uid_map/gid_map files.
Note systemd will remove this heuristic in running_in_userns() in
version 258 (https://github.com/systemd/systemd/pull/35382) and uses
namespace inode. But some users may be running a container image with
older systemd < 258 so we keep this hack until version 259 for version
N-1 compatibility.
In addition to mapping the whole UID/GID space, we also set
/proc/pid/setgroups to "allow". While we usually set "deny" to avoid
security issues with dropping supplementary groups
(https://lwn.net/Articles/626665/), this ends up breaking dbus-broker
when running /sbin/init in full OS containers.
Fixes: #35168Fixes: #35425
When trying to run dbus-broker in a systemd unit with PrivateUsers=full,
we see dbus-broker fails with EPERM at `util_audit_drop_permissions`.
The root cause is dbus-broker calls the setgroups() system call and this
is disallowed via systemd's implementation of PrivateUsers= by setting
/proc/pid/setgroups = deny. This is done to remediate potential privilege
escalation vulnerabilities in user namespaces where an attacker can remove
supplementary groups and gain access to resources where those groups are
restricted.
However, for OS-like containers, setgroups() is a pretty common API and
disabling it is not feasible. So we allow setgroups() by setting
/proc/pid/setgroups to allow in PrivateUsers=full. Note security conscious
users can still use SystemCallFilter= to disable setgroups() if they want
to specifically prevent this system call.
Fixes: #35425
This introduces a new unit condition check: that matches if a specific
kmod module is allowed. This should be generally useful, but there's one
usecase in particular: we can optimize modprobe@.service with this and
avoid forking out a bunch of modprobe requests during boot for the same
kmods.
Checking if a kernel module is loaded is more complicated than just
checking if /sys/module/$MODULE/ exists, since kernel modules typically
take a while to initialize and we must check that this is complete (by
checking if the sysfs attr "initstate" is "live").