verb_chid(), verb_blame() and verb_identify_tpm2() all render their
output via table_print_with_pager() with arg_json_format_flags, so JSON
output has been implemented for them since each verb was introduced.
None of them was ever added to the verb allowlist
that gates --json=, so e.g. systemd-analyze chid --json=pretty failed
with:
Option --json= is only supported for security, inspect-elf,
dlopen-metadata, ...
This adds them to the allowlist and the error message. After a sweep of
src/analyze/ for arg_json_format_flags, these are the only remaining
verbs that render through it but were not allowlisted.
has-tpm2 is deliberately not included: verb_has_tpm2() defers to
verb_has_tpm2_generic(), which writes plain text via printf() and has no
JSON support, so accepting --json= there would silently ignore it. The
table_print_with_pager() call in analyze-has-tpm2.c
belongs to verb_identify_tpm2(), which is what got allowlisted instead.
The chid gap went unnoticed because its TEST-65-ANALYZE coverage is
guarded behind a check for SMBIOS board_vendor and a non-container
environment, so it only runs on bare-metal/VM runners.
Teach the harness a --coco=tdx|sev-snp|any flag: it gates the test on
host coco capability (exit 77 otherwise), binds /dev/kvm and the coco
device(s) into the boot-mode container, and passes the resolved type to
the test via $COCO_TYPE.
TEST-94-COCO boots the mkosi image as a confidential guest with
systemd-vmspawn --coco= (using the /vm-images bind) and checks the guest
reports the expected type via systemd-detect-virt --cvm. Runs only on
coco-capable bare-metal hosts, skips elsewhere.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
Integration tests that boot a nested VM (e.g. with systemd-vmspawn) need
the mkosi-built images, which are build outputs rather than installed
test artifacts. Add a --vm-images wrapper flag and 'vm-images' test
option that bind-mount the mkosi output directory read-only into the
boot-mode container at /work/vm-images, instead of exposing the whole
build tree via RuntimeBuildSources.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
This line matches only if svn is the first field, but DMI modaliases
normally begin with BIOS fields such as bvn/bvr/bd. Use a wildcard like
other similar entries in the file.
Follow-up-for: 0aaea4b376
I'm not sure if these leading zeros matter or not, but the description
at the top of the file clearly says that the numbers are 4-digit. The
author of this entry also mentions evdev:input:b0003v1020p0501* in the
PR that added it.
Follow-up-for: 4ae4470ad9
Both verbs render through arg_json_format_flags but --json= was rejected for
them until the previous commit, and nothing in TEST-65-ANALYZE exercised it.
Add coverage in line with the other JSON-capable verbs.
identify-tpm2 is only invoked when has-tpm2 reports full support, as it
fails with EOPNOTSUPP when built without TPM2 support.
Several verbs in the STANDALONE bucket accept --json=, but that bucket only
offers --help --version --system --user --global --no-pager, so completion
never suggested the option for them.
Split those verbs out into a new STANDALONE_JSON bucket that additionally
offers --json=off/pretty/short. The verbs are moved rather than copied, as
${VERBS[*]} is used to complete the verb list itself.
verb_chid(), verb_blame() and verb_identify_tpm2() all render their output
via table_print_with_pager() with arg_json_format_flags, so JSON output has
been implemented for them since they were introduced. However, none of them
was ever added to the verb allowlist that gates --json=, so e.g.
"systemd-analyze chid --json=pretty" failed with:
Option --json= is only supported for security, inspect-elf, ...
Add them to the allowlist (and the error message) so the option is accepted.
These are the only remaining verbs that render through arg_json_format_flags
but were not allowlisted.
Note that has-tpm2 is deliberately not included: verb_has_tpm2() defers to
verb_has_tpm2_generic(), which writes plain text via printf() and has no JSON
support, so accepting --json= there would silently ignore it.
This went unnoticed for chid because the TEST-65-ANALYZE coverage for it is
guarded behind a check for SMBIOS board_vendor and a non-container
environment, so it only runs on bare-metal/VM runners.
SetLinkDomains() tracks whether the configured search domain list changed
so it can skip redundant persistence. The route_only flag is part of the
D-Bus API and is persisted as a ~ prefix, but flipping an existing domain
between search and route-only only updated in-memory state and left
changed false. The method then skipped link_save_user() and
manager_write_resolv_conf(), leaving stale state until restart.
Reproduced on Ubuntu with systemd 259:
sudo resolvectl domain enp1s0 route-only-repro.test
sudo resolvectl domain enp1s0 '~route-only-repro.test'
resolvectl domain enp1s0
grep '^DOMAINS=' /run/systemd/resolve/netif/$(cat /sys/class/net/enp1s0/ifindex)
grep -H route-only-repro.test /run/systemd/resolve/*.conf
Before, resolvectl showed ~route-only-repro.test, but the persisted state
file still showed DOMAINS=route-only-repro.test, and the generated
resolv.conf files still contained search route-only-repro.test.
After this change, the state file is updated to
DOMAINS=~route-only-repro.test and the generated resolv.conf files are
refreshed so the route-only domain is not emitted as a search domain.
Follow-up for: cbf23f3853
The managed-namespace state lives only in the pinned BPF maps, which
survive a plain restart. But an upgrade from a version whose map schema
differs replaces those maps (they are pinned under a new directory), so
they come up empty, and nothing repopulated them: every user namespace that
was already running would silently lose its write restriction (and, for
"self" allocations, its setgroups() denial) until it exited.
Re-add every live registry entry to the BPF maps during manager_startup(),
in the same pass that reaps the dead ones. userns_managed is reconstructed
for all entries; the setgroups() denial is reconstructed from a flag now
persisted in the registry, since it applies only to "self" allocations,
which the map alone cannot tell apart. The update is idempotent when the
pinned maps already carry the entries (the common restart case), and
load-bearing after an upgrade that replaced them with empty ones.
Ordering matters here: the programs are loaded but left unattached until
the maps have been repopulated, and only then does the previous version's
pin directory get removed, which is what detaches its programs. So the old
policy keeps enforcing right up to the moment the new one takes over, and
the new programs never run against a managed set that is still empty. Doing
it the other way around would leave every pre-upgrade namespace failing
open for the duration of the registry sweep, which is long enough for a
client looping over create() to land a transient owned inode on a
persistent file system.
Entries written before that flag existed are exactly the ones the first
such upgrade finds, so they get no answer from it. Recognize those by what
the flag mirrors: a "self" allocation maps the UID of the caller rather
than a transient range, i.e. it is the single UID entry whose start UID is
its owner.
This does not help a downgrade to a version without this change: such a
version cannot clean up the newer pin directory it knows nothing about, so
those programs stay attached until the next reboot clears the tmpfs-backed
bpffs, with enforcement reverting to the older policy in the meantime.
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.
nsresourced hands out transient, recyclable UID/GID ranges to
unprivileged user namespaces. So that a client cannot gain persistence by
leaving inodes owned by its transient range on a file system that outlives
the namespace -- where a later, unrelated client that recycles the range
would inherit them -- a BPF-LSM policy vetoes inode creation and chown()
that would end up owned by a transient ID.
The policy this replaces keyed its decision on properties of the mount a
write targeted. Those can be forged: a single unshare(CLONE_NEWNS) hands
the namespace clones of every mount it can see, each backed by the very
same superblock but no longer recognisable by the original check, and the
mount namespace's owning user namespace is attacker-chosen as well.
Decide instead from two questions about the object the write would actually
create, both answered from fields the kernel stamps itself and that survive
mount cloning and cannot be forged with unshare():
a) Does the file system die with the user namespace? A superblock records
the user namespace it was mounted in (sb->s_user_ns); if that sits at
or below the managed namespace, nothing it carries can outlive the
transient range.
b) Will the transient ID actually reach the disk? An idmapped mount
(vfsmount->mnt_idmap) may translate it into something else first, in
which case nothing recyclable is recorded. This mirrors the kernel's
own from_vfsuid()/from_vfsgid().
Walking the user-namespace chain upwards, rather than looking only at the
exact namespace, is what keeps a nested namespace from shaking off the
policy.
The map schema changed incompatibly (the old mount-id allowlist became the
flat userns_managed inode set), so the BPF objects are pinned under a fresh
directory, /sys/fs/bpf/systemd/userns-restrict-v2, and the obsolete
unsuffixed tree from previous versions is removed on install. Removing the
old link pins detaches the previous, bypassable programs, so they do not
linger attached next to the new ones.
Alibaba Cloud ECS instances expose "Alibaba Cloud ECS" in DMI
product_name/bios_vendor and "Alibaba Cloud" in sys_vendor. None of
these match dmi_vendor_table[], so systemd-detect-virt --vm reports
"none" on these machines, even though they run on a KVM-based
hypervisor. This is especially visible on ARM64 instances, where the
CPUID fallback does not exist (x86 only) and /proc/device-tree is
not populated.
Add "Alibaba Cloud ECS" to dmi_vendor_table[], mapping to
VIRTUALIZATION_KVM. The narrower prefix avoids matching the bare
"Alibaba Cloud" sys_vendor value, reducing the false-positive
surface for Alibaba's bare-metal (X-Dragon) instances compared to
the original broader prefix. bios_vendor also carries the same
string on ECS VMs though, so the protection is not complete —
fully resolving the VM-vs-bare-metal ambiguity would need the
SMBIOS BIOS-Characteristics bit check that the Amazon EC2 entry
uses.
The existing "back to DMI" fallback path in detect_vm() reports
"kvm" on both x86 and ARM64.
When stdio is passed as file descriptors, the provider of those
descriptors remains responsible for their ownership and access mode.
This keeps run0 in a pipeline from changing the caller's TTY ownership
to the target user, which can otherwise break later terminal access from
the original user.
Fixes#43336
--cvm is a mode switch, so it must not stop option parsing.
Keep parsing after it to honor later options and validate arguments.
Before: --cvm --quiet printed none; --cvm unexpected-argument printed none.
After: --cvm --quiet prints nothing; extra arguments are rejected.
Follow-up for: 5e0c61f64d
tries should be line-oriented like the other --print= modes.
Terminate its counter output with a newline to avoid glued CLI output.
Before: output bytes were 2b 32 2d 33.
After: output bytes are 2b 32 2d 33 0a.
Follow-up for: 9e61ed1115
- pcrextend: Fix use-after-free/double-free error in pcrextend_pcr_now
With SignInitrdPCRs=disabled in mkosi the TPM/PCR services are broken
which triggers the error path in the varlink call which means that we
get parameters set in the response. The wrong ownership assumption when
looking at the reply with _cleanup_ set caused a memory corruption that
surfaced in systemd-sysext with an ABRT.
This is a common mistake done with the API, fix it by not setting
_cleanup_(sd_json_variant_unrefp).
- pkcs7: Correct error handling for pkcs7_extract_signers
The lack of error handling for sym_i2d_X509_NAME/sym_i2d_ASN1_INTEGER
means that we get a NULL iovec that will cause an assertion failure
later. The use of regular free for OpenSSL is probably okay but since
the allocator can differ, one should use OPENSSL_free.
Add error handling and do an explicit copy instead of ownership transfer
between possibly different allocators.
This was introduced all the way back in 2019 where this particular test
case was the last one in the file, so it worked as intended.
Unfortunately, since then we added new tests after this one, which means
this particular `set +e` masked fails in any of them (except for
assert_*() calls, which call exit on fail).
This was introduced all the way back in 2019 where this particular test
case was the last one in the file, so it worked as intended.
Unfortunately, since then we added new tests after this one, which means
this particular `set +e` masked fails in any of them (except for
assert_*() calls, which call exit on fail).
Stay `set +e` in the test masked that this test has apparently never
worked in the first place: when a unit is killed with a signal, any
bound unit is stopped retroactively via
retroactively_stop_dependencies() but it's not restarted again, as the
restart for the bound unit is queued as TRY_RESTART in a following
transaction - but at this point the bound unit is already inactive, so
the TRY_RESTART job collapses into a NOP.
Let's account for this and also add a couple of test cases for
RestartMode= with an explicit restart, which should trigger the bound
unit as well (assuming it's active).
Follow-up for 9b42c58a2b.
RestrictFileSystemAccess= currently denies execution from any overlayfs
mount, even when every layer sits on a signed dm-verity device. The
enforcement hooks compare file->f_inode->i_sb->s_dev against the map of
trusted devices and for a file on an overlay that's the overlay's own
anonymous device, never the backing block device. So the fairly common
image based setup where a signed DDI is the lower layer and a writable
upper is stacked on top of it is out.
Kernel v7.2 grew the bpf_real_data_inode() kfunc which resolves a file
to
the inode hosting its data. Use it from the two enforcement hooks that
get
to see the union-level file. Execution through such an overlay then
works
while anything whose data lives in an untrusted upper layer is still
denied.
All of it is gated on kernel support. The kfunc is a __weak __ksym and
every call sits behind a const volatile .rodata flag that PID1 only sets
when the kernel BTF advertises the function, so the object still loads
on
older kernels and keeps denying union filesystems exactly like before.
If
loading with resolution enabled fails anyway the loader retries with the
flag off. A setup failure here is fatal to PID1 startup and I'd rather
degrade than not boot.
Two things worth pointing out. The mmap_file hook matters more than it
looks like. binfmt_elf maps the PT_INTERP interpreter via vm_mmap() so
ld.so is only ever checked there and never by bprm_check_security. And
file_mprotect deliberately doesn't use the kfunc. It runs under
mmap_write_lock where sleeping in d_real() would invert the documented
i_rwsem -> mmap_lock order, and it doesn't need it anyway since
ovl_mmap()
has already installed the backing file into vma->vm_file by then.
Known limitations:
* With metacopy=on an untrusted upper layer can still own a trusted
binary's mode, ownership and setuid bits since the resolution only
follows the data. Documented in the man page, just don't build that.
* composefs with a separate data directory isn't covered. That needs
fs-verity digests and bpf_get_fsverity_digest() takes a struct file
and gets the overlay inode, so it doesn't compose with this today.
* erofs page cache sharing is denied. The inode comes from a pseudo
filesystem whose s_dev is never in the verity map.
* Multi-device filesystems like btrfs stay unsupported, as before.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
With SignInitrdPCRs=disabled in mkosi the TPM/PCR services are broken
which triggers the error path in the varlink call which means that we
get parameters set in the response. The wrong ownership assumption when
looking at the reply with _cleanup_ set caused a memory corruption that
surfaced in systemd-sysext with an ABRT.
This is a common mistake done with the API, fix it by not setting
_cleanup_(sd_json_variant_unrefp).
The lack of error handling for sym_i2d_X509_NAME/sym_i2d_ASN1_INTEGER
means that we get a NULL iovec that will cause an assertion failure
later. The use of regular free for OpenSSL is probably okay but since
the allocator can differ, one should use OPENSSL_free.
Add error handling and do an explicit copy instead of ownership transfer
between possibly different allocators.
On many-CPU hosts, the CPU% column in systemd-cgtop can exceed the
hardcoded %6.1f format width (e.g. 12769.4% on a 128-CPU host),
causing column overflow and table misalignment.
Follow the existing maxtcpu pattern for CPU Time: dynamically compute
the maximum width of CPU percentage values and use it for both the
header and data rows.
Fixes#39819
The centos-fedora, opensuse and postmarketos image configurations all
install bpftool but the Debian/Ubuntu ones never did. Only their build
image gets it via the packaging's Build-Depends. So the
TEST-90-RESTRICT-FSACCESS guard and overlay subtests, both gated on
'command -v bpftool', just silently skip there.
Mirror what the build image does. Plain bpftool everywhere except Ubuntu
jammy and noble which still bundle it in linux-tools-generic.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Replace the paragraph that claims overlayfs is always blocked. Mention
that only the file data is checked, keep the btrfs note and mention the
new behavior in NEWS.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Have the attach helper print whether the loader enabled union filesystem
resolution and assert that this agrees with the kernel BTF.
Then mount the signed minimal image as an overlayfs lower layer with a
tmpfs upper. On kernels with bpf_real_data_inode() executing through the
overlay works while a file created in the upper layer or copied up into
it is denied. Without the kfunc execution through the overlay is denied
outright.
The overlay uses metacopy=off so that the copy-up test really does move
the data over to the untrusted upper layer.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Probe the kernel BTF for bpf_real_data_inode() and set the BPF program's
.rodata flag accordingly. The flag has to be written for every open of
the skeleton because .rodata is frozen once the object is loaded, so fold
the two open and load attempts into a common helper.
A positive BTF probe isn't a guarantee. It tells us that the function
exists, not that it is registered as an LSM kfunc with the signature we
expect. So if loading with resolution enabled fails anyway, retry with
the flag off. Files on union filesystems are then denied like they were
before which is a lot better than turning kernel drift into a boot
failure.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Add bpf_kernel_has_kfunc() which checks whether the kernel's vmlinux BTF
advertises a function of a given name.
The three btf__* symbols it needs are optional in the dlopen table.
btf__load_vmlinux_btf() only exists since libbpf 0.5 and a libbpf that
lacks any of them should degrade this probe, not fail dlopen_bpf() and
with it all of BPF. Note, libbpf < 1.0 returns error pointers instead of
NULL, so the result is checked with bpf_get_error_translated().
Finding the name doesn't tell us all that much though. It tells us that
the function exists, not that it is registered as a kfunc for the program
type we care about. So callers still have to be prepared for the program
load to fail.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Execution from an overlayfs mount is currently denied even when every
layer sits on a signed dm-verity device. The hooks compare
file->f_inode->i_sb->s_dev against the map of trusted devices and for a
file on an overlay that's the overlay's own anonymous device, never the
backing block device.
Kernel v7.2 grew the bpf_real_data_inode() kfunc which resolves a file to
the inode hosting its data. Use it from bprm_check_security and mmap_file
which both get to see the union-level file. They become lsm.s/ programs
because the kfunc is sleepable. That doesn't raise our kernel
requirements, both have been in sleepable_lsm_hooks since v5.11.
The file_mprotect hook stays non-sleepable. It runs under mmap_write_lock
where sleeping in d_real() would invert the documented i_rwsem ->
mmap_lock order, and it doesn't need the kfunc anyway. By the time a vma
exists ovl_mmap() has already installed the backing file into
vma->vm_file.
The initramfs trust window keeps comparing the mount device. If the
initrd is itself a union mount then userspace only ever recorded the
union's anonymous s_dev. The verity map is keyed by block device so that
one gets compared against the resolved device.
The kfunc is declared __weak __ksym and every call is gated on a const
volatile .rodata flag that PID1 only sets when the kernel provides it. So
the object still loads on pre-7.2 kernels where files on union
filesystems are denied as before. The flag stays off until the loader
learns to set it in a followup.
The file argument of bpf_lsm_mmap_file() is annotated __nullable since
v7.0 so the verifier won't let us hand it to the kfunc unless we branch
on it first.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The bprm_check_security and file_mprotect hooks currently fetch the file
pointer with BPF_CORE_READ_INTO(), i.e. bpf_probe_read_kernel(). That
hands us back a plain scalar and since 1a5c01d2508a ("bpf: Make
KF_TRUSTED_ARGS the default for all kfuncs") in v7.0 such a pointer can't
be passed to a kfunc at all.
Dereference the fields directly instead. Both bprm->file and vma->vm_file
are in the verifier's BTF_TYPE_SAFE_TRUSTED(_OR_NULL) lists so the loads
give us PTR_TRUSTED registers, and vmlinux.h pushes preserve_access_index
for all records so we don't lose CO-RE.
No functional changes. This is just so we can hand the pointers to the
bpf_real_data_inode() kfunc in the next patch.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Syncing acpi/pnp data from uefi.org has been broken for a while due to
changes to the server trying to block scrapers. Set custom user agent
string to be allowed to connect.
The BlackShark V3 Pro wireless dongle (1532:0577) exposes a sound card
that is not detected as a headset, since neither the model name nor the
USB interfaces provide a usable hint. Add a quirk so the form factor is
reported correctly.
vmspawn's positional arguments are extra kernel command line arguments
rather than a command to execute, so unlike nspawn or run it has nothing
to shield from the option parser: a kernel command line argument never
begins with a dash. Stopping at the first one only means an option that
follows one is silently taken for another kernel command line argument,
with no diagnostic:
systemd-vmspawn -i image.raw console=ttyS0 --set-credential=foo:bar
boots with "--set-credential=foo:bar" appended to the kernel command
line instead of setting a credential.
The stop was right when it was written. In 9de3cc1484, vmspawn's first
commit, the trailing arguments were appended to the QEMU command line
directly, so they really were options that had to reach QEMU unparsed.
4291f4461e turned them into extra kernel command line arguments passed
through SMBIOS three weeks later, and updated the man page to say so,
but left the "+" in the optstring behind; 4c778c51c0 and 5ff0ccaf8b then
carried it into the new option parser as
OPTION_PARSER_STOP_AT_FIRST_NONOPTION.
Use the default parser mode, which permutes options ahead of the
arguments; "--" still ends option parsing for an argument that does look
like an option. test-options gains the case this turns on, an option
after a positional argument in the default mode, beside the existing one
for the mode vmspawn no longer uses.
When --json is requested, resolution errors returned by resolved still went
through text logging. That made scripts parse stderr even though the
varlink error already carried structured fields such as rcode and EDE.
Add a small JSON error dumper for resolvectl query errors. It outputs the
queried name and varlink error id alongside the original varlink parameters
so DNSError and DNSSECValidationFailed expose rcode, extendedDNSErrorCode,
extendedDNSErrorMessage, queryString, and result without duplicating the
schema in resolvectl.
Keep non-JSON text output and silent warn_missing NXDOMAIN handling
unchanged. Document the JSON resolution error object in resolvectl(1),
including the distinction between resolved errors and local invocation or
connection errors.
Closes#42904
For some of us building image based operating systems with
`systemd-sysupdate` it's useful to have UKI filenames that encode
`IMAGE_VERSION` (and architecture). For example
`SimonOS_45.20260805.0_x86-64.efi`.
Currently to do this I have to manually post-process around the `.efi`
produced by `60-ukify` because it is named
`$ENTRY_TOKEN-$KERNEL_VERSION.efi`. It'd be very useful if I could skip
that step and instead have the image built directly without this
post-processing step; then extract the UKI and /usr partitions and plop
them onto my update server. It makes the build much more "install RPMs
into a tree and you're done" (barring other things `60-ukify` currently
can't do but I have follow-ups for those in mind).
This PR adds a knob to `install.conf` to let users specify a custom
format for the output filename for both UKI's and BLS entries produced
by `kernel-install`.
It accepts the following specifiers:
- `%e`: entry token
- `%v`: kernel version being installed
- Several common specifiers
The default is %e-%v, which is the current behavior. With
`entry_name_format=%M_%A_%a`, an OS with `IMAGE_ID=SimonOS` and
`IMAGE_VERSION=1.0` would get moved to `SimonOS_1.0_x86-64.efi`.
The resolved name is exported as `$KERNEL_INSTALL_ENTRY_NAME` for shell
plugins. Both `90-uki-copy.install` and `90-loaderentry.install` are
updated to use it, with a fallback to `$ENTRY_TOKEN-$KERNEL_VERSION` for
compatibility with older kernel-install versions.
compare_boot_ids() (from 262299dccb) skips
the tail entry realtime timestamp ordering when the compared locations
are from two different machines (and hence have two different machine
IDs) - in that case we fall through to a realtime comparison between the
two locations. This works fine except for journals that were imported
via systemd-journal-remote - the machine ID in the journal header is set
to the receiving machine's ID instead of the original one; but the
original machine ID is kept in the _MACHINE_ID= field.
This, however, defeats the check in compare_boot_ids() so we treat the
remote journals as local ones and try to compare the boots based on
their tail entry realtime timestamps, which results in grouping the
entries by their boot ID instead of interleaving them by their realtime
timestamp.
Given that we (transitively) call journal_file_read_tail_timestamp()
from compare_boot_ids(), where we both (re)set the newest_machine_id
field to the machine ID from the journal header and have the tail entry
at hand, let's try to get the _MACHINE_ID= field from the tail entry and
set newest_machine_id to that if it's present and valid. This way
compare_boot_ids() will correctly determine that the entries are from
two different machines and compare_locations() eventually falls through
to the entry realtime check, so we end up with properly interleaved
entries instead of entries grouped by boot ID.
The performance impact should be pretty negligible in vast majority of
cases, as we'd do this only when:
- opening a new file
- comparing entries across different boots
- advancing to a next location
The first case is a one-time thing only per journal. The second and
third case might call this a bit more frequently, but we fall through to
the _MACHINE_ID= parsing only if the journal file got a new entry since
the last check. The most significant impact would be on journals that
are being actively written into, but even then iterating through the
handful of typical fields is negligible compared to what the rest of the
function does. Also, since 8f8ab4bf98 the
journal_file_read_tail_timestamp() calls are ratelimited when walking
through the journal, lessening the impact even further.
Resolves: #34169
RestrictSUIDSGID uses seccomp to block setting SUID/SGID bits.
openat2() passes these mode bits indirectly via struct open_how.
Document that such calls are blocked regardless of requested mode.
Closes#43314
TEST-92-TPM2-SWTPM requires about 15 minutes on GitHub,
if it starts at the very end, the total CI run simply waits for
the test finished, and wastes the last 15 minutes.
Starting earlier should slightly improve the performance.
tar_c() keeps a database of already seen inodes so it can emit hardlinks
rather than storing the same content repeatedly. It prefers to keep that
database on a tmpfs superblock of its own, which is created lazily on the
first hardlinkable inode and which requires CAP_SYS_ADMIN in the caller's
user namespace. Failing that it falls back to a temporary directory under
/var/tmp/.
In the tar child forked off by import_fork_tar_c() the tmpfs branch can
never succeed: the child joins the user namespace, then drops all
capabilities but CAP_DAC_OVERRIDE, and only afterwards runs tar_c(), by
which point CAP_SYS_ADMIN is long gone. Every export therefore silently
took the /var/tmp/ fallback.
That fallback is a problem beyond being the slower path. The child runs
with the transient UID range assigned to the user namespace, so the
directory it creates is owned by a recyclable UID on a file system that
outlives the namespace, which is exactly what the nsresourced BPF-LSM
policy exists to prevent. With that policy in effect the mkdtemp() is
refused and the export fails with EPERM.
Create the database up front, in the window after the user namespace has
been joined and before the capabilities are dropped, and pass the
resulting fd into tar_c(). The database then lives on a superblock that
dies together with the namespace, no persistent file system is touched,
and the sandbox around tar is unchanged.
tar_c() keeps its lazy fallback for the callers that do not pass an fd,
i.e. systemd-dissect and the test, which do not run under a transient
range. The fd is borrowed, not consumed.
Signed-off-by: Daan De Meyer <daan@amutable.com>