Commit Graph
91070 Commits
Author SHA1 Message Date
Skye Soss 89fb28f1d3 include/uapi: add linux/securebits.h 2026-08-13 13:21:28 -05:00
Yu Watanabe 8f4cd7de43 resolvectl: fix query option handling and resolvconf input errors (#43370) 2026-08-14 00:35:58 +09:00
Yu Watanabe df9265b5a3 resolved: fix domain replacement and delegate DNS parsing (#43365) 2026-08-14 00:29:26 +09:00
dongshengyuan f4284e9ceb path-lookup: reject empty env path components
Only trailing empty components have special meaning:
they request appending the built-in defaults.

Share the validation between normal path lookup and
systemd-analyze verify, so verify does not compose
values that path lookup later rejects.

Propagate generator path parse errors instead of
turning them into empty generator paths.

Follow-up for: cf7d80a5fe
2026-08-14 00:27:09 +09:00
Yu Watanabe cad2c455ec fstab-filter: fix preserve fstab option escaping (#43274)
Fixes #42787.
2026-08-14 00:23:47 +09:00
guolin 9269952ab2 shell-completion: add completion for machinectl restart
The `restart` verb was added to machinectl as a convenience alias for
`reboot`, but its shell completions were never updated. Add it to both
the bash and zsh completions.
2026-08-13 15:55:03 +01:00
Luca Boccassi 4f80cc06a1 test: add TEST-94-COCO integration test for confidential VMs (#43296) 2026-08-13 13:19:15 +01:00
dongshengyuan 253b34d8ac resolvconf: reject malformed stdin without changing link state
The resolvconf compatibility parser ignored errors from
nameserver, domain, and search lines. If one of those lines was
malformed, resolvectl could still send the partially parsed
configuration to resolved and overwrite the previous link state.

Reproducer:

  IF=enp1s0
  TMP="$(mktemp -d)"

  ln -s "$(command -v resolvectl)" "$TMP/resolvconf"

  sudo resolvectl dns "$IF" 1.1.1.1
  sudo resolvectl domain "$IF" keep.example
  resolvectl domain "$IF"

  printf 'nameserver 1.1.1.1\nsearch "unterminated\n' |
    sudo "$TMP/resolvconf" -a "$IF" || true

  resolvectl domain "$IF"

Before:

  The malformed search line was ignored and the previous domain
  was cleared:

    Link 2 (enp1s0): keep.example
    Link 2 (enp1s0):

After:

  The malformed input fails before updating resolved, so
  keep.example remains configured.

Follow-up for: 097f130731
2026-08-13 17:40:50 +08:00
dongshengyuan e7223f2ab2 resolvectl: reject --raw for regular lookups
--raw only affects ResolveRecord output. Without --type= or an
RFC4501 dns: URI, query takes the ResolveHostname/ResolveAddress
paths, where --raw was silently ignored.

Reproducer:

  build/resolvectl --raw query localhost

Before:

  The command exited successfully and printed regular text output,
  such as:

    localhost: 127.0.0.1

After:

  The command fails before issuing a lookup:

    --raw may only be combined with --type= or dns: URIs.

Follow-up for: 05ea7e87b6
2026-08-13 17:40:49 +08:00
dongshengyuan e021a64391 resolvectl: avoid TXT lookup for plain SRV service queries
The service verb documents the one- and two-argument forms as
SRV lookups. They should not request TXT records by default.
Only the three-argument DNS-SD form should request TXT metadata.

Reproducer:

  Run resolvectl service _mysvc._tcp signed.test in the
  TEST-75-RESOLVED environment, where signed.test contains:

    _mysvc._tcp SRV 10 5 1234 myservice
    _mysvc._tcp TXT "This is TXT for myservice"
    myservice A 10.0.0.20

Before:

  resolvectl service _mysvc._tcp signed.test printed TXT data
  for the plain SRV form:

    _mysvc._tcp/signed.test: myservice.signed.test:1234 [priority=10, weight=5]
                             10.0.0.20
                             fd00:dead:beef:cafe::17
                             This is TXT for myservice

After:

  The plain SRV form suppresses TXT data. The DNS-SD form still
  shows it:

    resolvectl service '' _mysvc._tcp signed.test

Follow-up for: a7a4c60a25
2026-08-13 17:40:49 +08:00
Paul Meyer d66923ea07 test: fix race in TEST-07-PID1 multi-unit transaction subtest
Test failed with the following:

TEST-07-PID1.sh[20020]: + systemctl stop issue8102-nop-dep.service
TEST-07-PID1.sh[20409]: ++ systemctl show -P ActiveState issue8102-nop-dep.service
TEST-07-PID1.sh[20020]: + [[ failed == inactive ]]

systemctl start only waits for the jobs of the units named on the
command line, so the start job pulled in for issue8102-nop-dep.service
via Wants= was not waited for. When the following stop landed while its
ExecStart= was still running, the process was SIGTERMed, and since
Type=oneshot does not treat termination by signal as a clean exit, the
unit ended up failed instead of inactive:

issue8102-nop-dep.service: Changed start -> stop-sigterm
issue8102-nop-dep.service: Main process exited, code=killed, status=15/TERM
issue8102-nop-dep.service: Failed with result 'signal'.

Start both units explicitly so both jobs are waited for.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-08-13 09:58:53 +01:00
wyu 7b00f957fe strv: drop the escape_separator flag from strv_join_full()
Now that fstab_filter_options() no longer uses strv_join_full() with
escape_separator=true (it splits with EXTRACT_UNESCAPE_RELAX and joins the kept
options verbatim), the escape_separator flag has no users. Drop it from
strv_join_full() and its wrapper strv_join(), and update the remaining callers in
kernel-install and the tests accordingly.
2026-08-13 11:17:23 +08:00
wyu eb5ee83c1f fstab-filter: preserve fstab option escaping in the filtered output
fstab_filter_options() used strv_split_full(..., EXTRACT_UNESCAPE_SEPARATORS |
EXTRACT_UNESCAPE_RELAX), which unescapes "\," and "\\" while splitting, and then
strv_join_full(escape_separator=true) to re-escape the kept options. The round-trip
was lossy for invalid escapes kept verbatim by RELAX (e.g. an option value "\xff"
became "\\xff"), so the generated Options= line diverged from the input. A trailing
backslash was also collapsed: "val1\\" in the fstab became "val1\" in the split
word, which let the backslash escape the option separator in Options= (issue #42787).

Rework fstab_filter_options() to split with EXTRACT_UNESCAPE_RELAX only, so the
escape sequences of kept options are preserved verbatim and the filtered output is
assembled with the plain strv_join(); the matching option values are unescaped
manually so callers keep getting the unescaped form they had before.

Fixes a bug introduced by d4d9f034b1.
Fixes #42787.
2026-08-13 10:36:12 +08:00
wyu 609f8d0214 extract-word: also handle EXTRACT_UNESCAPE_RELAX in the escape branch
extract_first_word() handled a backslash followed by another character in the
escape-processing branch only when EXTRACT_CUNESCAPE or EXTRACT_UNESCAPE_SEPARATORS
was set. With EXTRACT_UNESCAPE_RELAX alone, the backslash was silently dropped and
only the following character kept, which is inconsistent with the documented "keep
invalid escapes verbatim" intent of RELAX. Let RELAX also enter that branch, so an
invalid escape like "\x" is preserved verbatim as "\x" rather than collapsing to
"x".

EXTRACT_UNESCAPE_RELAX is not used on its own anywhere outside the test cases —
the non-test callers always combine it with EXTRACT_CUNESCAPE, which already enters
this branch — so only the test cases that use RELAX by itself are affected, and
their expectations are updated accordingly.
2026-08-13 10:36:11 +08:00
dongshengyuan a572dd2f60 resolved: allow full replacement of link domains
SetLinkDomains() marks old link domains and then builds the replacement
list in place. The marked old domains still counted against
LINK_SEARCH_DOMAINS_MAX, so replacing a non-empty list with exactly the
maximum number of new domains failed while adding the last entry. The error
path then unlinked all domains, so a failed setter call also destroyed the
previous configuration.

Reproducer:

  resolvectl domain dnsbug1 old.example
  busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 \
    org.freedesktop.resolve1.Manager SetLinkDomains 'ia(sb)' "$ifindex" 1024 "${args[@]}"
  resolvectl domain dnsbug1

Before: the bus call failed with "Argument list too long" and the link's
domain list became empty.

Follow-up for: d2ec6608b9
2026-08-13 09:04:26 +08:00
dongshengyuan 0cfa2d4f4c resolved: keep parsing DNS delegate servers after duplicates
The DNS delegate parser tries to filter duplicate DNS= entries, but the
duplicate branch returned from the whole parser. This made a duplicate
server truncate the rest of the same DNS= line, silently dropping later
valid servers.

Reproducer:

  DNS=192.168.77.78 192.168.77.78 192.168.77.79

Before: only 192.168.77.78 appeared in the delegate server list.

Follow-up for: 7928c0e0a1
2026-08-13 09:02:52 +08:00
dongshengyuan 060b0d2f70 resolvectl: reject tlsa without a domain
The tlsa command accepts an optional service family before the domain list.
When the only argument was "tcp", "udp", or "sctp", we consumed it as the
family and returned success without issuing any query.

Reproducer:

  build/resolvectl tlsa tcp

Before: the command printed nothing and exited with status 0.

Follow-up for: ebbc70e511
2026-08-13 09:02:52 +08:00
Armaan Sandhu 32ac7af788 strv: treat newlines as hard line breaks in strv_rebreak_lines()
A newline embedded in an input entry only reset the width counter and the
leading-whitespace state, but the text collected before it was never flushed.
If nothing but whitespace followed the newline, the end-of-line path then saw
that we were still in the leading whitespace and emitted an empty line,
dropping everything before the newline: "foo\n" turned into "".

Newlines also didn't start a new output entry, so returned entries could
contain newlines and be wider than the requested width, even though callers
expect one output entry per output line.

Flush the collected line at the newline instead, reusing the same logic as at
the end of the string, and treat a trailing newline as terminating the last
line rather than adding an empty one.
2026-08-13 07:11:49 +09:00
Armaan Sandhu 5dc41f8283 options: reject an empty long option name
An argument of the form "--=VALUE" leaves an empty option name, which
startswith() treats as a prefix of every defined option. Depending on how many
long options a namespace defines that yields either an ambiguity error listing
every option, or, with exactly one option defined, a unique partial match that
silently parses "--=VALUE" as that option.
2026-08-13 07:11:37 +09:00
Yu Watanabe 3ae53ab8f6 analyze: allow --json= for the chid, blame and identify-tpm2 verbs (#43366)
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.
2026-08-13 00:06:07 +09:00
Luca Boccassi aec25f2770 Two HWDB fixes (#43367) 2026-08-12 15:12:58 +01:00
Paul Meyer b89729b16a test: add TEST-94-COCO integration test for confidential VMs
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>
2026-08-12 15:57:07 +02:00
Paul Meyer 5f34079a6a test: bind built images into boot-mode tests via a --vm-images option
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>
2026-08-12 15:57:07 +02:00
David Tardon 1d75afb798 hwdb: fix modalias
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
2026-08-12 13:30:36 +02:00
David Tardon ef3a8760cc hwdb: fix modalias
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
2026-08-12 12:57:42 +02:00
Ivan Kruglov 46a91a1867 test: cover --json= for the blame and identify-tpm2 verbs
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.
2026-08-12 03:54:33 -07:00
Ivan Kruglov b3f76af401 shell-completion: offer --json= for the standalone verbs that support it
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.
2026-08-12 03:54:10 -07:00
Ivan Kruglov 9b16161f34 analyze: allow --json= for the chid, blame and identify-tpm2 verbs
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.
2026-08-12 03:53:42 -07:00
dongshengyuan 628e8304cc resolved: notice route-only domain updates
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
2026-08-12 18:51:34 +09:00
Timon de Groot 40315add91 hwdb: add Blue Microphones Yeti X (#43363)
This microphone has vendor specific HID controls to control gain,
output, monitoring levels, polar patterns, etc.

Naming is completely based on names reported by the USB device itself
(`manufacturer` and `product`).

Information about this microphone:
- https://micpedia.com/microphone/blue-yeti-x/
- https://www.logitechg.com/en-us/shop/p/yeti-x-professional-microphone
2026-08-12 06:57:38 +09:00
zhaoshuang abffa868a8 virt: detect Alibaba Cloud ECS as KVM
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.
2026-08-12 04:12:19 +09:00
dongshengyuan e98c68104b core: do not chown fd-passed TTYs
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
2026-08-12 03:11:02 +09:00
dongshengyuan e8454eb14c detect-virt: continue parsing after --cvm
--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
2026-08-12 03:02:33 +09:00
dongshengyuan f40aa8ea1c vpick: terminate tries output with newline
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
2026-08-12 03:02:33 +09:00
Yu Watanabe 4443d0ec16 Fix error paths for pcrextend_pcr_now and pkcs7_extract_signers (#43357)
- 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.
2026-08-12 02:48:51 +09:00
Luca Boccassi 8271442983 test: drop forgotten set +e (#43359)
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).
2026-08-11 17:40:25 +01:00
Frantisek Sumsal ed0727fecd test: sync the journal before checking it for ordering cycle messages
Otherwise we might get an empty match:

TEST-03-JOBS.sh[100]: + IDS_FILE=/tmp/TEST-03-JOBS-CYCLE-IDS-30241
TEST-03-JOBS.sh[314]: + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Describe '{}'
TEST-03-JOBS.sh[315]: + jq .runtime.TransactionsWithOrderingCycle
TEST-03-JOBS.sh[316]: ++ jq length /tmp/TEST-03-JOBS-CYCLE-IDS-30241
TEST-03-JOBS.sh[100]: + [[ 20 -ge 20 ]]
TEST-03-JOBS.sh[100]: + for i in {0..19}
TEST-03-JOBS.sh[317]: ++ jq -r '.[0]' /tmp/TEST-03-JOBS-CYCLE-IDS-30241
TEST-03-JOBS.sh[100]: + journalctl -b TRANSACTION_ID=48 --grep 'cycle starting with'
TEST-03-JOBS.sh[318]: -- No entries --
[FAILED] Failed to start TEST-03-JOBS.service - TEST-03-JOBS.
[  !!  ] Exiting: unit TEST-03-JOBS.service failed

Even though the message was properly emitted:

$ journalctl --file TEST-03-JOBS-1.journal -b TRANSACTION_ID=48 --grep 'cycle starting with'
Aug 11 14:46:15 H systemd[1]: [🡕] transaction-cycle17.service: Unable to break cycle starting with transaction-cycle17.service/start
2026-08-11 16:12:42 +02:00
Frantisek Sumsal 143c284567 test: drop forgotten set +e
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).
2026-08-11 16:12:42 +02:00
Frantisek Sumsal 2c4e86c290 test: fix & enhance test for RestartMode=
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.
2026-08-11 16:11:25 +02:00
Christian Brauner 0b2d1203cb bpf-restrict-fsaccess: allow execution from overlayfs on signed dm-verity (#43223)
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>
2026-08-11 14:31:25 +02:00
Kai Lüke 76a6dc6dc0 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).
2026-08-11 20:35:57 +09:00
Kai Lüke a257de2688 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.
2026-08-11 20:31:00 +09:00
Frantisek Sumsal 2b81535f74 test: fix typo in test unit
Follow-up for bf3dfa6202.
2026-08-11 12:40:53 +02:00
Kakueeen 9b75d9bc66 cgtop: use dynamic width for CPU% column (#43286)
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
2026-08-11 00:38:17 +09:00
Christian Brauner 7849500cd9 mkosi: install bpftool in Debian/Ubuntu test images
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>
2026-08-10 16:09:06 +02:00
Christian Brauner 24bbacbe7a man: document union filesystem resolution for RestrictFileSystemAccess=
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>
2026-08-10 16:09:06 +02:00
Christian Brauner 8c199d4f1a test: cover overlayfs in TEST-90-RESTRICT-FSACCESS
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>
2026-08-10 16:09:06 +02:00
Christian Brauner e37151765f core: gate bpf_real_data_inode() use on kernel support
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>
2026-08-10 16:09:06 +02:00
Christian Brauner cc54fb8f73 bpf-util: add helper to probe the kernel for a kfunc
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>
2026-08-10 16:09:06 +02:00
Christian Brauner 43914520c1 bpf-restrict-fsaccess: resolve union filesystems to the device hosting the data
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>
2026-08-10 16:09:06 +02:00