Make that caveat from the previous commit more obvious with a logged
warning, and document the workaround (or really, the fix) of disabling
`net.ipv4.conf.all.rp_filter` globally.
Signed-off-by: Martin Pitt <martin@amutable.com>
With CPU/I/O accounting enabled, inherited pressure parameters prevent the
user manager from setting its own threshold, producing:
systemd[807]: Failed to adjust io pressure threshold, ignoring: Device or resource busy
Use CPU/IOPressureWatch=skip, as for memory pressure, so the user manager
and capsule manager configure monitoring for their own init.scope.
Follow-up for 9d03637404
With the varlink protocol upgrade feature we can use provide
file system access via varlink easily by re-using the existing
sftp standard. SFTP is independant of ssh and provides a well
defined and widely used file transfer protocol.
So here we add two new (optional) varlink interfaces:
- io.systemd.Sftp.Root.Connect
- io.systemd.Sftp.RootReadOnly.Connect
They are behind polkit auth as io.systemd.sftp.root{,-read-only}.
Note that handing this is out is equivalent for giving root
access: even ReadOnly mode will allow to read secrets like
/etc/shadow so its inherently risky. The two actions mostly
exist to prevent accidents not as a (strong) protection
boundary. The unit description warns about this and the polkit
prompt also describes the full implications.
The use cases are:
- file access via the varlink-http-bridge [1]
- file access over AF_VSOCK
The install is optional and turned off by default.
[1] https://github.com/systemd/varlink-http-bridge
The service runs "hostnamectl tags --apply" before sysinit.target, so that
the tags declared in the machine-tags.d/ directories are in place before
most units are started and may be matched by ConditionMachineTag=. It is
skipped if no configuration exists.
This is a small service that hooks into factory-reset.target, similar to
systemd-tpm2-clear.service, and sets the LoaderFactoryResetVariables EFI
variable, asking systemd-boot to erase its EFI variables on the next
boot. Or in other words: a factory reset now resets the TPM *and* the EFI
variables owned by systemd.
The operation can be turned off via systemd.efivars_allow_clear= on the
kernel command line or $SYSTEMD_EFIVARS_ALLOW_CLEAR. With --graceful the
tool exits cleanly if the boot loader doesn't support the request.
"systemd-factory-reset cancel" now also withdraws the request, and
"bootctl remove" removes the variable too.
Allow the use of credentials (SMBIOS, ESP credential drop-ins,
--set-credential for the boot loader) to ship keys to enroll into
keyrings. This lets users avoid having to ship keys as part of the
initrd.
So accept keyring-setup.<keyring>.<name> credentials holding one PEM
certificate each. We don't enroll them directly but follow the VOA spec
instead. Before enrolling they are placed into the ephemeral load path
as:
/run/voa/<os>/kernel-keyring/<context>/x509/<name>-certificate.pem
This also makes masking, merging and deduplication apply to them like
to any other file. Only the certificate is written there, encoded anew,
so a private key that came along in the credential doesn't end up
world-readable. A credential that isn't a single certificate is bad
input.
A keyring-setup.os credential overrides the OS identifiers derived from
os-release. This is useful for generic initrds whose identity differs
from the host's.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The kernel has various keyrings that early userspace may choose to set
up. Right now it has the .fs-verity and .dm-verity keyrings and in the
next kernel it will also have a .bpf keyring.
All three keyrings accept X.509 certificates in DER form. The
.dm-verity and .bpf keyrings are inactive and restricted by default
unless the respective kernel command line options are passed that leave
them unrestricted. Both keyrings are ignored until they are restricted.
IOW, restricting them causes the kernel to use them for signature
verification.
The .fs-verity keyring has slightly different semantics. It is always
active and is taken into account as soon as it contains a key.
Add systemd-keyring-setup which provisions the keyrings based on the
VOA spec from the kernel-keyring role with the keyring as the context:
.dm-verity -> kernel-keyring/dm-verity/
.fs-verity -> kernel-keyring/fs-verity/
.bpf -> kernel-keyring/bpf/
Trust anchors are read from the trust-anchor-kernel-keyring/
counterpart. Note that the "trust-anchor-*" prefix is mandated by the
spec.
The systemd-keyring-setup binary reads
<os>/kernel-keyring/<context>/x509/*-certificate.pem and the
trust-anchor-kernel-keyring/ counterpart from all VOA load paths and
enrolls the certificates into the corresponding keyring. Afterwards it
restricts the keyring to certificates signed by an enrolled key and
drops the SetAttr permission. Hence, neither the restriction nor the
mask can be changed anymore.
The binary may run in the initrd and on the host. A second run detects
a sealed keyring by its permission mask, skips certificates that are
enrolled already and enrolls certificates that are signed by an already
enrolled key.
Any unprovisioned keyrings are sealed at the end of a run even when
empty. Failing to enroll a certificate still causes the keyring to be
sealed. We can make this more flexible in the future but I don't think
leaving a keyring open just because enrolling a key failed makes sense.
Note that a file holding more than one certificate is refused.
Note that on a second call to add_key() for a certificate with the same
subject and key the kernel silently replaces the first. We can detect
this by reading the keyring's members before and after enrollment.
The binary exits with EX_DATAERR for input that can be fixed and
EXIT_FAILURE for kernel refusals. --dry-run only shows what would be
enrolled, optionally as JSON. The service isn't enabled by default and
the tool is marked experimental for now.
systemd-keyring-setup is written in Rust on top of the systemd_shared
crate. The VOA lookup, the keyring and X.509 helpers and everything
else it uses stay C in libsystemd-shared. All lookups are anchored at
the root directory which is opened once. The program, its unit and its
man page are only built with Rust support.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The symlink for the varlink registry in /run/varlink/registry is
missing for systemd-report.socket. This looks like an oversight
(per 382f141fea it seems to qualify) so add it so that
consumers like the varlink-http-bridge can expose it automatically.
Describe the program with COMMAND() and parse the command line with the
shared helper, which adds -h/--help, --version, and --introspect-cli. The
optional namespace argument is unchanged. Superfluous arguments are now
rejected with "Too many arguments." instead of "This program takes one or
no arguments.".
The template unit is adjusted to use '-- %i' to pass the namespace name.
I'm not convinced that using a namespace name starting with a dash is a
good idea, but using '--' to disambiguate seems good in any case, so let's
do that. We might want to adjust log_namespace_name_valid() separately.
This adds systemd-report-diskhealth, which reports the key health
parameters of NVMe and ATA disks, plus basic identity information about
all disks.
For all hardware backed disks (including USB ones) size, logical and
physical sector size are reported from sysfs, and model, vendor and serial
from the udev database, preferring hwdb data, and falling back to the PCI
or USB device if the block device itself carries no model/vendor
information.
For NVMe disks the SMART / Health Information log page is read via the
NVMe admin command ioctl, and the critical warning bitmask, available
spare, percentage used and media error counter are reported. For ATA
disks with SMART enabled the overall SMART status is read via SG_IO ATA
pass-through, and for rotating ones also the reallocated, pending and
offline uncorrectable sector counts (SMART attributes 5, 197, 198), since
various SSD vendors reused these attributes with incompatible semantics.
Power-on hours and power cycle counts are reported for both
under a common metric name, for ATA disks taken from the standardized
Device Statistics log rather than from the vendor specific SMART
attributes. ATA disks in standby are not queried, in order not to spin
them up, and neither are ATA disks behind USB bridges.
The temperature is deliberately not reported, since systemd-report-hwmon
covers that already.
Add systemd-varlink-monitord, a daemon that uses BPF LSM and fentry
hooks to capture varlink protocol traffic on AF_UNIX sockets. Sockets
are classified as varlink by reading the user.varlink xattr. The daemon
exposes a varlink API with Setup/Start/Stop methods and passes a ring
buffer fd to the client for zero-copy packet delivery. Unprivileged
callers can only see their own traffic; polkit authorization grants full
access.
systemd-tpm2-setup-early.service also runs after the switch to the
root file system if the initrd doesn't include it. NvPCRs can't be
initialized there, and so far we silently skipped them. With the PCR
policy fallback, this is the only remaining reason for NvPCRs not
being initialized, and it means the initrd is incomplete. Log an
error pointing to the missing service instead.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
Set DISSECT_IMAGE_TPM2_UNLOCK where unlocking an image's LUKS partitions
via the local TPM2 is wanted and the caller is trusted:
* systemd-dissect, an admin tool operating on an image the invoking user
can read anyway;
* systemd-nspawn --image=, which already prompts for a passphrase
interactivly, so TPM2 first and passphrase second is the natural
order;
* systemd-sysext/systemd-confext, which decrypt without any passphrase,
so extension images sealed to the host can be activated without user
interaction.
For sysext/confext setting the flag at merge time is not enough: before
merging, the extension-release metadata is read via image_read_metadata()
in discover-image.c, which uses its own flag set and would already fail
with -ENOKEY on an encrypted image. Give it an extra_flags parameter and
pass the flag from there, too; the other callers (machined, portabled)
pass 0 and keep their behaviour.
Two units need adjusting for this to work when started by the service
manager: systemd-nspawn@.service has DevicePolicy=closed, so allow
/dev/tpmrm0 there, and the sysext/confext services are not ordered
against the TPM2 device showing up, so order them after tpm2.target
like the pcr* services. The initrd variants cannot be ordered the same
way: they run before local-fs-pre.target, while the software TPM fallback
only becomes available after the early ESP mount, which is ordered after
local-fs-pre.target, so a static After=tpm2.target would be an ordering
cycle with systemd.tpm2_software_fallback=1. Hence systemd-tpm2-generator
adds the ordering as a drop-in instead, unless the fallback is in use.
That drop-in also orders them after systemd-pcrphase-initrd.service:
nothing else guarantees the "enter-initrd" phase has been measured into
PCR 11 before the initrd extension units merge their images (the barrier's
Before=systemd-sysext.service is a leftover from when that unit was also
used in the initrd), so tokens bound to a signed PCR 11 policy or a
pcrlock policy would race the measurement.
Deliberately not enabled for now: RootImage=/ExtensionImages=/MountImages=
in units, portabled and gpt-auto. For those an unconditional default is
probably not wanted but a per-unit or per-image switch, which is left for
a follow-up. systemd-mountfsd, which unprivileged systemd-dissect and
systemd-nspawn delegate to, keeps its own flag set and does not honour
the flag, hence TPM2 unlocking is only available when running privileged;
the man pages say so.
Document the behaviour and $SYSTEMD_DISSECT_TPM2_UNLOCK= in the dissect,
nspawn and sysext man pages and in docs/ENVIRONMENT.md.
Currently systemd-tpm2-setup will fail if NvPCR initialization doesn't
succeed, either because the currently booted UKI carries no PCR public
key, or it carries no appropriate signed policy (one with the "initrd"
policy reference). In addition to this, systemd-pcrextend will
subsequently fail because it also fails to initialize the NvPCR it is
requested to extend.
This makes systemd-tpm2-setup return EX_CONFIG in these scenarios, with
this marked as a successful exit status in the service unit file. In
addition to this, systemd-pcrextend is updated to only attempt NvPCR
initialization from the initrd (the only place it will succceed), and
when called with --graceful, to not exit with an error code if the
target NvPCR is uninitialized, and can't be initialized because of a
missing PCR public key or appropriate signed policy. When called via
Varlink, it will return the existing "NoSuchNvPCR" error.
Note that I also removed some NvPCR initialization specific exit status
codes from systemd-tpm2-setup.service.in, as these can only be returned
via systemd-tpm2-setup-early.service.
This should address the issues in
https://github.com/systemd/systemd/issues/43848.
Replace the inotify + /run/udev/watch/ symlink machinery with fanotify inode
marks (FAN_CLOSE_WRITE). The point is not merely a different backend; fanotify
removes several structural costs that inotify imposed on udevd:
- An inotify event only identifies the watch by a watch descriptor, which udevd
had to translate back to a device. That mapping was maintained as a table of
symlinks under /run/udev/watch/. A fanotify event instead carries the watched
inode's file handle (FAN_REPORT_FID), which is resolved to the device directly
via open_by_handle_at()+fstat(). The wd->device translation, and with it
/run/udev/watch/ entirely, is no longer needed.
- Without that shared symlink store to coordinate, a worker can add and remove
its own device marks directly on the fanotify group fd. Previously the watch
state was owned by the manager, and workers had to request (un)registration
over the notification socket (sd_notify()+SIGUSR1). Dropping that round-trip
both simplifies the code and is expected to be faster.
- A fanotify mark lives on the inode and is dropped by the kernel once the inode
is gone, so the special handling for removed device nodes is no longer needed.
The group fd is handed over across restarts via the service manager's fd store
and the kernel keeps the marks, so nothing is rebuilt in-process; the fd is also
propagated to each worker. Watches left behind by an older, inotify-based udevd
under /run/udev/watch/ are migrated to fanotify marks on first start, then the
directory is removed.
Device watch falls back to inotify on kernels < v5.13, when CONFIG_SHMEM=n
(devtmpfs backed by ramfs), or when CONFIG_FANOTIFY is disabled. This fallback
can also be forced via $SYSTEMD_UDEV_USE_INOTIFY.
logind now implements io.systemd.Metrics on
/run/systemd/report/io.systemd.Login, via the new
systemd-logind-metrics.socket, and reports:
- io.systemd.Login.SessionsStarted: counter of sessions started since
boot, by session class (field class=)
- io.systemd.Login.CurrentSessions: gauge of current sessions, including
those in closing state, by session class (field class=)
- io.systemd.Login.SuspendCounter: counter of suspend cycles, as reported
by the kernel in /sys/power/suspend_stats/ (field result=success|fail)
journald now implements io.systemd.Metrics on
/run/systemd/report/io.systemd.JournalDaemon, via the new
systemd-journald-metrics.socket, and reports the message counters as
io.systemd.JournalDaemon.MessagesByPriority (field priority=) and
io.systemd.JournalDaemon.MessagesByTransport (field transport=).
Metrics requests are processed at a lower event loop priority than
incoming log messages. For now only the main journald instance provides
metrics, not the namespace instances.
Like the audit socket, the metrics socket is not listed in Sockets= of
the service, so that it can be disabled.
One of the fundamental counters we need to cover in metrics are
packet/byte counters on network interfaces. Let's add this.
We do this separately from networkd, to not unnecessarily affect
networkd operations.
This prog is still labelled as experimental and we didn't
have a way to disable its build. With the addition of this option,
all experimental components can be disabled.
Up to now there was no clear ordering of confext and sysext units.
Normally, configuration should be able to influence the behavior of
services (config files or unit drop-ins), so it makes sense to order
confext before sysext. The mounting from the initrd for the final system
makes this less important together with the fact that live merges/
refreshes can encode unit restarts now. Yet I think it's better to know
what gets merged first and if wanted one can avoid encoding restarts if
one doesn't care about later live content changes. Where it matters more
for me is consistent behavior for the refresh check during boot for
non-verity images because they get compared by file handle and mount ID
which means whether the overlay was set up or not determines the
identity and decides if we do a refresh or not and this should not
vary by boot merely due to timing or other dependencies impacting the
ordering. (Note that one extension type shipping another extension type
is not the reason for the ordering and these cycles should be avoided
because they never free the mounts and backing loop devices.)
Our primary usecase for this service is to deal with sector issues
around CDROMs. For that insisting on proper DDI layout and a root
partition is overkill. Let's hence relax the logic for this.
Fixes: #43605
The new unit is nice, but it might not be appropriate in all scenarios. It
requires a new kernel, which might be known to not be available, and also
changes sysctl kernel.core_pattern at runtime, which might not be desired.
Replace static enablement by a normal [Install] section and add the unit
to presets to enable it by default.
The awkward instructions in the man page are simplified and adjusted.
The Varlink service enumerates image targets and needs a private mount
namespace while dissecting images. Keep its sandbox in sync with
systemd-sysupdated.service.
Follow-up to 31616d00efFixes#43604
Previously we just wrote /etc/hostname and returned. The kernel hostname
was left at whatever PID 1 sethostname()'d very early in boot (before
/etc/hostname existed), which meant hostnamectl reported the transient
hostname instead of the static one until the next reboot.
Call io.systemd.Hostname.SetStaticHostname on the running system so
hostnamed writes /etc/hostname and updates the kernel hostname in one
step. If hostnamed is unreachable (masked or absent on minimal images)
fall back to writing /etc/hostname directly. --root=/--image= also keeps
writing the file directly, since hostnamed only manages the running
system.
Since kernel v6.17, we can set a path to unix socket to core_pattern
(with request mode). See also:
https://lore.kernel.org/all/20250603-work-coredump-socket-protocol-v2-0-05a5f0c18ecc@kernel.org/
This introduces a tiny service that prepares and listens on a UNIX
socket to be passed to the kernel core pattern. When a process is
crashed, the kernel connects to the socket, and sends information
about the crashed process and passes coredump through the socket.
The service accepts the connection, and forks a worker process that
receives coredump from the kernel.
nsresourced hands out transient, recyclable UID/GID ranges to
unprivileged user namespaces, and a BPF-LSM policy is what keeps a
client from leaving inodes owned by its transient range on a file system
that outlives the namespace, so that a later client which gets the same
range can't inherit them.
The old policy decided based on the mount a write went through, which is
forgeable: unshare(CLONE_NEWNS) gives you clones of every mount with
fresh mount IDs but the same superblock, and the mount namespace's owner
is yours to pick. overlayfs sidesteps it entirely. So this reworks the
check to look at where the write actually lands on disk.
The first commit (Christian Brauner's) replaces the per-mount allowlist
with two questions answered from fields the client can't forge and that
survive mount cloning: does the file system die together with the user
namespace (sb->s_user_ns at or below the managed one), and does the
transient ID actually reach the disk, or does an idmapped mount
translate it into something else first. It walks the userns chain
upwards so a nested namespace can't escape it. The map layout changed,
so the pins move to userns-restrict.v2 and the old ones are removed on
install.
The second commit handles overlayfs. A write through an overlay creates
the file on the upper layer via vfs_create(), which only trips the inode
LSM hooks, not the path hooks we attach to, and the one path hook that
does fire sees the overlay mount, which belongs to the managed namespace
and so looks like it dies with it even though the upper may be
persistent. So we judge the upper at mount time in sb_kern_mount and
refuse a writable overlay unless its upper dies with the namespace too.
Reading overlayfs' private ovl_fs/ovl_layer uses CO-RE type flavors
guarded by bpf_core_type_exists(), and the service now orders after
modprobe@overlay.service.
The third commit deals with restarts. The managed set only lives in the
pinned maps, so an upgrade that changes the map layout comes up with
empty maps and every already-running namespace silently loses its
restriction. manager_startup() now re-adds the live registry entries to
the maps in the same pass that reaps the dead ones. The setgroups()
denial is restored from a flag we now store in the registry.
One consequence: a managed namespace can only mount a writable overlay
whose upper dies with it, e.g. a tmpfs it set up itself. If you need a
writable persistent upperdir (mkosi does), use a plain unprivileged user
namespace for that, not a transient nsresourced range.
This adds an initial TPM2 implementation of the io.systemd.Report.Signer
interface, which returns a set of signed TPM attestations that also
includes the supplied report digest.
The returned report consists of multiple components:
- A signed PCR quote generated with TPM2_Quote.
- Signed NvPCR attestations generated with TPM2_NV_Certify.
- A signed audit session attestation generated with
TPM2_GetSessionAuditDigest.
Each signed report component includes the JSON encoded TPMS_ATTEST
attestation structure and corresponding signature, in both PEM encoded
form and as a JSON encoded TPMT_SIGNATURE structure.
NvPCR report components also include the readable name of the NvPCR, the
NV index public area as the JSON encoded TPMS_NV_PUBLIC structure, and some
additional authenticated data that is digested and supplied as the
qualifying data to the TPM2_NV_Certify command. This authenticated data
includes the NvPCR name and priority.
The report digest that is supplied via varlink is supplied as the
qualifying data to the audit session attestation.
The returned report also includes the event log which is retrieved from
pcrlock's varlink interface, and the public area of the signing key in
both PEM encoded form and as a JSON encoded TPMT_PUBLIC structure.
The audit session attestation is always the last component. The
preceding components are all executed with the audit session, and the
signer requires the audit session to remain exclusive whilst generating
the report. What this does is provide evidence to a verifier that the
overall report is a single, consistent snapshot of the system (ie, no
other TPM commands executed whilst generating the report) because the
audit session attestation contains a field that indicates that the
session was exclusive at the time it was attested.
The set of attested PCRs and NvPCRs are currently hardcoded. It includes
all firmware PCRs with the exception of PCR6, and all other PCRs with
the exception of the IMA one (which isn't useful on its own without the
IMA log). All defined NvPCRs are included.
A verifier can verify the signed attestation components individually by
verifying their signatures against the supplied attestation data, using
the supplied public key.
For the PCR report component, a verifier can replay the measurements in
the log in order to reconstruct the PCR values, and then use this and
the PCR selection from the attestation in order to calculate the PCR
digest, and then check that this matches the one in the attestation.
For the NvPCR report components, a verifier checks that the NV index
name in the attestation matches one that is anchored in PCR9 to be sure
that the attestation is for a valid NvPCR. A verifier can then replay
the measurements in the log to ensure that the resulting NvPCR value
matches the one supplied in the attestation. The authenticated data is a
JSON blob containing the NvPCR name and priority. A verifier can digest
this and ensure it matches the qualifying data in the attestation.
The audit session attestation includes the audit session digest and
whether the audit session was still exclusive when the attestation was
generated. A verifier should confirm exclusivity. The digest includes a
record of all TPM commands executed with the audit session, as each
command results in it being extended with the command parameter hash and
the result parameter hash. A verifier should replay the preceding report
components to compute an expected audit digest and make sure that this
digest matches the one in the attestation in order to be sure that the
overall report represents a single, consistent snapshot. The other
report components provide sufficient information for a verifier to
reconstruct the cpHash and rpHash.
When reconstructing the cpHash for the PCR and NvPCR components, the
signing scheme is the NULL scheme (they use the attestation key's
signing scheme). The required signing key public area is supplied in
the report. The supplied qualifying data is in the extra data field of
the attestation.
The PCR selection is also required to reconstruct the cpHash for the
PCR component. The PCR selection is available in the attestation.
The name of the NV index is also required to reconstruct the cpHash for
the NvPCR components. The name is available in the attestation.
The rpHash for each component can be reconstructed from the supplied
attestation blob and signature.
In the future, we might want to add more report component types. Eg, we
could add report components for ordinary NV indexes. Using the audit
session means that we could also add additional components that the TPM
can't attest directly, such as the values of interesting TPM properties
(as the TPM2_GetCapability calls will appear in the audit session digest).
The attestation signing key is stored as a JSON file in the report
signer's state directory. Different types of keys are supported:
- Ordinary keys that are protected by an existing persistent TPM key
such as the SRK or EK.
- Persistent TPM keys.
- Primary keys, which are recreated using a template on use.
When an ordinary or primary key is used, the report signer saves the
object context in its runtime directory. This context is used on future
invocations. This has the benefit that where a primary key is used, it
doesn't need to be recreated on each use. When an ordinary key is used,
we don't need to run TPM2_Load, which requires authorization with the
parent key, on each This authorization currently requires a policy
session for low-range EKs.
Regardless of the key type, it must be a restricted signing key. If no
key exists, an ordinary key is generated automatically.
The attestation key can also have an associated credential which is
attached to the signed report. The credential can be issued by a CA or
some other party during onboarding, and is presented to a verifier as
proof that the attestation key is authentic. It is considered an opaque
blob in order to not require it to be a specific format, such as a X.509
certificate. I've called this a "voucher" though because "cred" or
"credential" has other meanings in systemd.
As a follow-up to this, I plan to add a varlink interface for creating
attestation keys that can be used for report signing.
Our first resolve happens way before sockets.target, and
systemd-resolved-varlink.socket has no ordering of its own, so there's
nothing making sure the socket is listening by then. Without this the
connect fails and we quietly fall back to getaddrinfo(), which is exactly
the case the varlink path is there for.
Order after both the service and the socket since either one is enough
on its own but neither covers every install situation. For example
resolved binds the socket itself when nothing hands it one, and on
systems where only the socket is enabled the service isn't started at
boot at all.
With DNSSEC on, resolved can't validate anything until the clock is
right, and without an RTC the only thing that fixes the clock is talking
to the NTP servers we're trying to resolve. On glibc the timesyncd unit
sets SYSTEMD_NSS_RESOLVE_VALIDATE=0 and nss-resolve turns that into a
per-query flag, which breaks the chicken/egg. musl (and other systems
not using nss-resolve) have no NSS, so nothing reads it there and we're
stuck.
So talk to resolved directly when it's there, and pass
SD_RESOLVED_NO_VALIDATE until we've synced the clock ourselves.
NTP-related lookups after that are validated normally, which is better
than what we get from nss. If resolved isn't around we just keep using
getaddrinfo().
The reply handler duplicates the tail of the getaddrinfo handler, since
the two differ in how they report failures. Varlink discards what the
callback returns, so it has to move on to the next host itself.
This also drops SYSTEMD_NSS_RESOLVE_VALIDATE=0 from the unit because
nss-resolve talks to resolved over the same socket we do, so the
getaddrinfo() path is only ever taken when resolved is unreachable, and
then nothing validates anyway.
A write through an overlay mount does not create the object on the overlay
itself: overlayfs creates it on the upper layer via vfs_create(), which
reaches only the *inode* LSM hooks, not the *path* hooks the transient
range policy attaches to. The one path hook that does fire sees the overlay
mount, whose superblock belongs to the (managed) mounting namespace and so
looks like it dies with it -- while the upper may be a persistent file
system on which the transient IDs then persist.
Regular file system -- caught by the path hook:
openat(dir_on_persistent_fs, "f", O_CREAT)
path_openat -> ... -> security_path_mknod(path{mnt = persistent mount}) << path hook (attached)
userns_restrict_path_mknod -> validate_mount()
sb->s_user_ns = init -> not below managed (outlives the ns)
mnt_idmap does not remap the transient id (reaches disk)
=> -EPERM BLOCKED
Overlay write -- slips past the path hook (this is the hole):
openat(overlay_mnt/"f", O_CREAT)
path_openat -> ... -> security_path_mknod(path{mnt = OVERLAY mount}) << path hook (attached)
validate_mount(): overlay sb->s_user_ns == managed
=> "dies with the namespace" ALLOW
ovl_create_or_link -> vfs_create(UPPER dentry)
security_inode_create(UPPER inode) << inode hook (NOT attached)
=> no check
inode created on the UPPER fs, owned by the transient uid PERSISTS
Judge the upper at mount time instead, in lsm/sb_kern_mount:
mount("overlay", ..., upperdir=<X>, workdir=<Y>)
do_new_mount -> vfs_get_tree
ovl_fill_super
stamps work/index onto the upper via vfs_mkdir, owned by the
transient mounter -> security_inode_mkdir only (not a path hook)
security_sb_kern_mount(sb) << hook (attached)
userns_restrict_sb_kern_mount:
sb->s_magic != OVERLAYFS_SUPER_MAGIC -> return 0 (not an overlay)
find_managed_userns(sb->s_user_ns) == none -> return 0 (unmanaged mounter)
upper = ((struct ovl_fs*)sb->s_fs_info)->layers[0].mnt
upper == NULL -> return 0 (read-only, no upper)
fs_userns = upper->mnt_sb->s_user_ns
upper dies with the managed ns (e.g. a tmpfs) => return 0 ALLOW
upper persistent, idmap records no transient id => return 0 ALLOW
upper persistent, an idmap extent records a transient => -EPERM REFUSE
The upper has to be judged at mount time because overlayfs performs the
actual upper creations (both the mount-time work/index dirs and every later
copy-up) with internal vfs_create()/vfs_mkdir() calls that reach only the
inode hooks, on the upper's own superblock, where neither the overlay's
ovl_fs nor the mount idmapping is available. Refusing the mount stops every
such write. It cannot undo the work/index dirs ovl_fill_super() already
stamped onto the upper before this hook runs. Reaching those takes an
upperdir and a workdir that already exist on the same persistent,
non-idmapped file system and that the transient IDs may write to: the client
cannot create them itself, since that is what the path hooks deny.
The idmap check walks the upper mount's uid_map and gid_map and permits the
overlay only when no extent records a transient id on disk (ids the idmap
does not map fail the write, so they are fine too). This is what lets a
client back a writable overlay with a persistent but id-mapped upper, e.g.
one handed out by mountfsd, while a plain persistent upper stays refused.
The check keys on whether a writable upper exists (layers[0].mnt is
non-NULL), which is fixed for the superblock's lifetime, not on the mutable
SB_RDONLY flag: an overlay mounted read-only but with an upper can be
remounted read-write later without re-entering sb_kern_mount (remount uses a
different LSM hook), so it must be judged now. A genuinely read-only overlay
has no upper and is left alone. Reading the upper layer needs overlayfs'
private struct ovl_fs/ovl_layer, which are absent from vmlinux.h when
overlayfs is built as a module; they are declared as CO-RE type flavors and
guarded by bpf_core_type_exists(), and the service now orders itself after
modprobe@overlay.service so the module's BTF is present by the time the
program is loaded.
A managed (nsresourced-provisioned) user namespace can therefore only mount
a writable overlay whose upper dies with it (e.g. a tmpfs it set up
itself). Callers that need a writable, persistent (non-tmpfs) upperdir --
mkosi, for one -- should use a regular unprivileged user namespace for that
rather than a transient nsresourced range, since such an overlay is refused
for a managed namespace by design.
test-userns-restrict now mounts real overlays: a read-only overlay, a
writable overlay with a tmpfs upper, and a writable overlay whose upper is
an init-owned but idmapped mount are all allowed, while a writable overlay
with a plain init-owned upper is refused with EPERM. The kernel permits that
last mount without the LSM loaded, so the EPERM is the policy's doing.