Grow TEST-94-COCO from a single inline check into the reusable structure
that can be used by multiple future subtests:
- lib.sh: code shared between subtests on the host side.
- guest-test.sh: the testcase_coco_* checks.
- guest-test-runner.sh: runs each testcase_coco_* check, ships guest
side testcase results over vsock.
Helpers live under test/units/TEST-94-COCO/. The vsock records are
best-effort detail, the aggregate exit status stays the authoritative
pass/fail.
Split the test into subtests per platform/boot flow.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
The conversion to the OPTION macros in c3f4c1b8d6 ("journalctl:
convert to OPTION macros") replaced strv_copy_unless_empty() with
strv_copy().
This is problematic because the positional arguments are now an empty
strv rather than NULL when none are given, but `--{list,dump}-catalog`
rely on that.
Instead let's hand NULL back to them when there is nothing on the
command line.
Component definitions may contain multiple Documentation= URLs, but the
components command previously displayed only the first URL in text output.
Use TABLE_STRV_WRAPPED to display all component documentation URLs in the
table, making the full documentation list visible to users.
Add integration test coverage for multiple Documentation= assignments.
The feature JSON payload switched to `id`, `isEnabled`, and `appstream`,
and made `documentation` an array of URLs.
`updatectl` still parsed legacy table-derived names and a single URL.
This broke feature JSON parsing and made output inconsistent.
Features without documentation could also cause a null dereference.
Align `updatectl` with the current schema and preserve all
documentation URLs across sysupdate, D-Bus, and Varlink payloads.
Propagate condition evaluation errors through `condition_test_list()`
and `feature_is_suggested()`, with consistent JSON error handling and
tests.
The `suggested` field is now optional and always a boolean when
present; it is omitted rather than set to an error string when
SuggestOn*= conditions cannot be evaluated.
Add `suggested` metadata and document the field transition.
Follow-up for 3e34ccf6df.
Follow-up for 75a247d4de.
Follow-up for 40e727c93b.
Fixes#43603
The json mode was returning the string `none` when unmerged and
an array for merged extensions. Return an empty array instead.
Instead of branching on the output format, always add an empty strv
for an unmerged hierarchy: the table code renders an empty strv as
the ersatz string ("-") in text mode and as an empty array in json
mode. This also changes the human-readable output from "none" to "-".
It also updated the TEST-50-DISSECT.sysext.sh test. Technically
this is a compatbility break but its also arguably a bugfix.
The PolicyPCR digest is not always the complete input to an offline
signature. PolicyAuthorize also includes the hashed policy reference.
This is now required for NVPCRs to work in the initrd.
Expose the complete data as "tbs" (to-be-signed) and use it when ukify
matches offline signatures. Keep "pol" as the compatibility fallback.
Fixes: https://github.com/systemd/systemd/issues/43635
Follow-up for f0f3687700
Fixes#43645.
`FailureAction=exit` propagates the exit status of the unit's main
process. When there is none, `emergency_action()` left `m->return_value`
at its initial 0, so the manager exited 0 for a unit that failed —
indistinguishable from an orderly shutdown to whoever supervises it.
machinectl clone --read-only ignored the requested read-only mode when
cloning a foreign-UID-owned directory because the unprivileged clone path
did not receive the read_only argument.
Pass the flag through and apply the immutable bit via the already-open
destination directory fd after copying. Keep this best-effort, matching
the ordinary directory clone fallback, so failure to set the attribute
does not turn a completed copy into a reported clone failure.
Add a regression test using foreign-0 rather than a fixed UID. Verify
the cloned image's ReadOnly property, copied contents and ownership, and
that the immutable directory rejects new entries. Run this subtest only
when file attributes and the existing rootless nspawn prerequisites are
available, leaving the other machined tests unchanged.
unprivileged_clone() returns success when mountfsd_make_directory() fails.
Consequently, callers can report a successful clone even though the
destination directory was never created.
Return the original error instead of zero so the failed clone is
reported to the caller.
Add a regression test that places a regular file without an image suffix
at the destination path. Such a file is ignored by image discovery but
prevents directory creation. Check that cloning reports failure without
changing the file, then succeeds after the conflicting file is removed.
network-generator writes generated command line configuration below
the alternate root passed with --root=, but network.* credentials
were copied to fixed host /run paths.
Build the credential target directories with arg_root before passing
them to pick_up_credentials(). This keeps normal boot behavior
unchanged while making credential output land in the requested root.
Fixes#43442.
A `.swap` unit can end up **active but unloadable, with no device**, and
stopping such a unit aborts PID 1.
### What it looks like
```
$ systemctl show 'dev-disk-byx2dpartlabel-primary.swap' -p LoadState,ActiveState,What
LoadState=not-found
ActiveState=active
What= # <-- no device
$ systemctl stop 'dev-disk-byx2dpartlabel-primary.swap'
systemd[1]: About to execute: /usr/sbin/swapoff # <-- no argument
systemd[1]: Caught <ABRT>, from our own process.
systemd[1]: Freezing execution.
```
PID 1 stays alive but frozen, so the machine stops answering `systemctl`
and never reboots.
### How it happens
1. systemd creates one `.swap` unit per udev device link of every device
in `/proc/swaps`.
2. A device link gets repointed at another device while the swap stays
on. `/proc/swaps` doesn't change, so nothing re-examines the unit — it
stays active.
3. The next `daemon-reload` drops all units and rebuilds them.
Enumeration from `/proc/swaps` no longer produces this name, so only the
deserialization brings the unit back: by name, with no fragment on disk.
4. `swap_load()` only calls `swap_add_extras()` for units that loaded a
fragment or are backed by `/proc/swaps`, so this one gets neither a
`What=` nor a patched `ExecContext`.
5. `swap_coldplug()` restores its active state anyway.
6. Shutdown stops it. `unit_stop()` deliberately permits stopping units
whose load failed, so it reaches `exec_spawn()` and trips the
`PrivateVarTmp=` assertion in `exec_context_serialize()`.
Since the D-Bus DNS-SD API was introduced in c3036641f0ed (v236),
`RegisterService()` has created an `sd_bus_track` object to destroy the
registered service when the registering client disconnects from the bus.
However, the track object is a `_cleanup_(sd_bus_track_unrefp)` local in
`bus_method_register_service()` that is never stored anywhere, so it is
destroyed again as soon as the method handler returns. The vanish
handler (`dnssd_registered_service_on_bus_track()`) has thus been dead
code for the past ~8 years.
The observable effect: services registered via D-Bus silently persist
after the client disconnects — unowned, advertised until resolved exits,
and impossible to re-register since the identifier stays taken
(`DNSSD_SERVICE_EXISTS`). At the same time, the registration doesn't
survive a resolved restart, so the current behavior is neither reliably
ephemeral nor reliably persistent. Noticed while reviewing a project
that (unknowingly relying on this bug) registers a service with a
one-shot `gdbus call` and expects it to persist.
This PR:
1. splits the goodbye/zone-removal logic out of
`bus_dnssd_method_unregister()` into a helper — the vanish path needs
the full teardown, not just `dnssd_registered_service_free()`, which
would leave the service's RRs published in the per-link mDNS zones;
2. stores the track object in `DnssdRegisteredService`, making client
tracking work as originally intended;
3. documents the lifetime semantics in `org.freedesktop.resolve1(5)`
(matching the equivalent Avahi D-Bus API, whose entry groups are
likewise bound to the client connection) and points to `*.dnssd` files
for registrations independent of a client's lifetime;
4. adds a TEST-75 case covering the connection-bound lifetime.
Note this is a behavioral change for fire-and-forget clients (one-shot
`busctl`/`gdbus` invocations). Arguably those were never working
reliably (registrations silently vanished on any resolved restart, and
stale entries blocked re-registration), but if this is deemed too risky,
the alternative would be to drop the dead tracking code and codify the
persist-until-exit behavior instead — I'd argue tracking as originally
intended is the better contract.
Verification: code compiles; the new integration test is included.
shellcheck 0.10+ reports SC2329 ("This function is never invoked") for
the per-testcase cleanup() functions in TEST-75-RESOLVED.sh, which are
only invoked indirectly via trap. The existing SC2317 markers don't
cover it, and inserting a new testcase shuffles which definitions the
heuristic fires on, making pre-existing findings show up as new in
differential CI runs. Extend the markers to SC2317,SC2329, matching
what testcase_dot_strict_per_link_verify() in this file and other test
units already do.
Verify that a DNS-SD service registered via the D-Bus RegisterService()
call is bound to the lifetime of the client's bus connection: since
busctl disconnects right after the call returns, the service must be
unregistered again shortly after. Wait for resolved's vanish handler
log message as proof that the client tracking triggered the
unregistration, check that a subsequent UnregisterService() fails
specifically because the service is not known anymore, and verify that
the identifier can be registered again — previously it stayed taken by
the vanished client's service forever, failing with
DNSSD_SERVICE_EXISTS.
A dummy link with mDNS enabled makes sure the per-scope goodbye/zone
teardown path actually runs, and since resolved would be restarted
automatically if it crashed in it, letting the D-Bus level checks pass
vacuously, verify the InvocationID stayed the same throughout.
systemd-importd runs qcow2_detect()/qcow2_convert() over images it has just
downloaded (see pull-raw.c), so the header, the L1/L2 tables and every offset
and cluster descriptor in them are attacker controlled. There is a manual test
for the happy path, but nothing that feeds it a malformed image.
The corpus is two images, a plain data cluster and a zlib-compressed one (i.e.
decompress_zlib_raw()), at the 512 byte minimum cluster size and trimmed to the
last byte the converter reads, so 2K each. A zero cluster and a QCOW3 header are
a flipped descriptor bit and a bumped version field away from these, so they are
left to the fuzzer rather than carried in the tree.
fuzz-compress only ever feeds the decompressors their own output: it compresses
the fuzzer input and then decompresses the result, so the stream the decoders
see is well formed by construction. The blobs they get in reality come out of
journal files, systemd-journal-remote streams, coredumps and downloaded images,
i.e. from wherever the data was written, which is not necessarily somewhere we
trust.
So hand them the fuzzer input directly instead, across all four entrypoints:
decompress_blob(), decompress_startswith(), decompress_stream(), and the
decompressor_detect()/decompressor_push() pair that systemd-importd feeds from
the network. The latter is pushed in chunks, so that the decoders see the same
fragmentation a real download produces. The decompressed size is capped, as an
unbounded decode of a hostile blob measures the allocator rather than the
decoder.
The corpus carries the two truncated stream headers that used to send
decompress_startswith() allocating until OOM, fixed in the previous commit.
When networkd starts or restarts, links are enumerated in ifindex
order. A slave enumerated before its master cannot register itself in
link_append_to_master(), as the master Link object does not exist yet.
The master then wrongly believes it has no slaves until the next
RTM_NEWLINK message for the slave arrives.
The stale slaves set affects operational state propagation
(degraded-carrier detection) and any code that inspects the slaves of
a master.
For instance, on restart, when networkd thinks that the master has no
slaves yet, we try to set the bond fail-over policy, which the kernel
then refuses because in reality the bond does have slaves.
To fix the issue, this updates the list of slaves after enumeration.
Co-authored-by: Yu Watanabe <watanabe.yu+github@gmail.com>
Currently `RestrictFileSystemAccess` isn't trying hard enough to enforce
W^X and thus there are still places where an executable and writable
mapping can be created:
* `/proc/<pid>/mem` `FOLL_FORCE` writes
* `PTRACE_POKE{TEXT,DATA}` `FOLL_FORCE` writes
* `MAP_PRIVATE | MAP_EXECUTABLE` memory mappings
Let's block all of these mechanisms. Do note that
`RestrictFileSystemAccess` is still in very active development and also
cannot meaningfully be used on general purpose distros and systems.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Make sure the tests can tell the difference between an actual denial and
a probe that simply cannot be run.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The custom help() is replaced by a COMMAND() description, matching the
other report-sign services. The abstract is now wrapped at 80 columns
in the help output.
Use the new copy timestamp clamping mechanism from the previous commit
to fix unreproducible wall-clock files in the generated images.
That covers both modes: online mode writes through the kernel, and
offline mode stages the tree in a temporary directory before handing it
to mkfs or mcopy. `mcopy -m` then carries the clamped mtimes into the
FAT.
This complements commit 34c3d57474 (directory times) and commit
f191ca982c (FAT insertion order).
Cover all combinations (online/offline, with and without epoch) in the
new `testcase_vfat_reproducibility()`. Update `testcase_make_symlinks()`
to cover the symlink creation/re-fix their parent directory code paths.
Sadly, images are still not reproducible byte for byte. Document
systemd-repart's $SOURCE_DATE_EPOCH handling and remaining
reproducibility issues in the manpage.
systemd-udevd is the same binary as udevadm, so the introspection data
describes both commands, from either name. The COMMAND intentionally
owns no verbs — all of udevadm's verbs are defined in udevadm.c right
after its own COMMAND, so that they are attributed to it.
The -V/--version output (GIT_VERSION) is preserved.
The custom help() is replaced by the common verb help, which derives the
synopsis, abstract, and option list from the verb declaration in
udevadm.c and the "udevadm-control" option namespace. The synopsis
makes use of the new nulstr-argspec functionality:
> udevadm [OPTION…] lock [OPTION…] COMMAND
> udevadm [OPTION…] lock [OPTION…] --print
The custom help() is replaced by the common verb help, which derives the
synopsis, abstract, and option list from the verb declaration in udevadm.c
and the "udevadm-control" option namespace. The new VERB_DEPRECATED flag
is used.
The verbs declare their subcommand option namespaces, so the
introspection data describes the options of every subcommand, nested in
the verb objects.
The bare version number output (a dracut compatibility requirement) is
preserved for --version/-V and the "version" verb.
The "lock" verb's argument specification is simplified to "COMMAND" --
other verbs do not mention their options in the argspec either, and the
subcommand help states where the options may go.
The final enablement pieces needed for coco tests in systemd CI. Runners
will be incus containers, these two commits enable running coco tests
with mkosi boot (where image is a directory), then start a confidential
guest with vmspawn as part of the test.
Add a TEST_MKOSI_ARGS environment variable whose contents are appended
to the mkosi invocation launching the test image, without affecting the
image build. This lets the environment adapt how tests are run, e.g.
pass --format=directory --output-dir=... to boot the test container
from a directory format output in environments that cannot dissect disk
images, such as unprivileged containers, where loop devices and udev
are not available.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
In a mount namespace owned by a non-initial user namespace, the kernel
only allows mounting a fresh proc or sysfs if the namespace already
contains a fully visible mount of the same filesystem type
(mount_too_revealing()). systemd-nspawn's pivoted root contains no such
mount, so booting the test container inside an unprivileged container
(e.g. a containerized CI runner) fails with EPERM when mounting /proc.
When running in a non-initial user namespace, satisfy the check by
binding a read-only instance of each into the container under /work.
Skipping the binds on regular privileged runs keeps the container
isolated from the host's proc and sysfs. Binding non-recursively keeps
foreign submounts (e.g. lxcfs overmounts) out, which would spoil full
visibility where they end up locked, and which read-only would not
cover.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
UpdateHomeEx needs to set up the home temporarily so that it can
write the refreshed identity records. Inspect and password changes use
the same setup path for metadata-oriented work.
For LUKS homes with luksDiscard=no, home_setup_luks() fully
allocates the backing image while setting it up. If diskSize=max was
previously written to a sparse image, this can fail with ENOSPC before
the user can disable auto-resize mode again.
Add a setup flag that lets metadata operations skip the full-image
fallocate step. Real create, activate, and resize paths keep the
existing allocation behavior.
Before, reproduced on Ubuntu 26.04 with /home on tmpfs:
homectl create reprohomed \
--storage=luks \
--fs-type=btrfs \
--disk-size=300M \
--auto-resize-mode=shrink-and-grow \
--luks-discard=no
homectl update reprohomed --disk-size=max
dd if=/dev/zero of=/var/tmp/homed-repro/filler bs=1M count=500
systemctl restart systemd-homed.service
homectl update reprohomed --auto-resize-mode=off
Operation on home reprohomed failed:
Not enough disk space for home reprohomed
Fixes#34527
Follow-up for: ab3b6fcb40
When a partition is deleted from a table containing multiple partitions
of the same type, filling the resulting partition-number gap can place
a new partition before an existing partition matched to an earlier
definition. This changes definition matching on the next invocation and
breaks idempotency.
Keep new partitions after existing partitions of the same type. If no
partition table slot is available after them, fall back to the first
unused slot.
Which existing partition is matched to which definition is still
determined by partition type and partition number order, as documented,
and is unchanged by this.
Fixes#43449
When an administrator installed a custom keyring under /etc,
signature verification selected it instead of the vendor keyring
under /usr. That could make vendor-signed system updates stop
verifying after adding local keys.
Collect all readable default keyrings, including the current and
legacy /etc paths, and pass temporary copies to GnuPG with
separate --keyring options. Keep SYSTEMD_OPENPGP_KEYRING as an
explicit override, add regression coverage for keyring
coexistence, override behavior, and current-path signing, and
silence the expected shellcheck false positive in the test
cleanup helper.
Closes: #40702
The transactions-with-cycles subtest starts 20 mutually cyclic
services, reads the manager's TransactionsWithOrderingCycle ids over
varlink, and then requires a journal entry matching "cycle starting
with" for every id. On a fedora rawhide CI run the entry for
transaction id 42 was missing for the entire boot and journalctl
printed "-- No entries --", failing the unit, while the manager had
correctly recorded all 20 ids. In the exported journal of that boot,
transaction 42 kept only its debug-level enqueue line
transaction-cycle12.service: Trying to enqueue job
transaction-cycle12.service/start/replace
while the other 19 transactions each also kept the three messages
that follow it ("Found ordering cycle", "Unable to break cycle
starting with", "unfixable cyclic ordering dependency"), and PID1's
next message, accepted about 2 ms after the enqueue line, shows
journald itself kept ingesting.
The messages were lost, not late. The test VM boots with
systemd.log_level=debug, and the 20 cyclic starts made PID1 emit
about 12,400 debug messages within 30 seconds, 4,100 of them in the
final second, during which journald stalled for 94 ms between two
consecutive entries. PID1 gives its journal socket a 10 ms send
timeout (src/basic/log.c), and a message whose send times out is
either dropped outright, because log_struct() ignores the sendmsg
result and falls back only on formatting errors, or, for the plain
log calls that do fall back to kmsg, reappears as _TRANSPORT=kernel
text stripped of its structured fields, where a TRANSACTION_ID=
match cannot find it. The asserted messages are log_struct() calls,
so they are in the dropped class. Either way the journalctl
assertion comes up empty. This is also why the "journalctl --sync"
added in ed0727fecd does not help here: it only flushes what journald
already received.
Run the whole section, including the daemon-reload, at log level
info, and drain what the earlier subtests queued at debug level with
one "journalctl --sync" before the first start. The daemon-reload is
itself a heavy debug emitter that returns before journald has
drained its burst, so opening the quiet window after it would leave
that backlog competing with the first cycle messages. The two
messages the subtest greps for are emitted at LOG_ERR ("Unable to
break cycle starting with") and LOG_WARNING ("Job ... deleted to
break ordering cycle starting with") in src/core/transaction.c, and
the ids are recorded into transactions_with_cycle on the same code
path directly before them, so the assertion keeps its full strength
while the debug flood that displaced it is gone.
A single at_exit handler installed at the start of the test restores
the boot-time log level when any subtest aborts, and cleans up the
transient unit the aborting subtest had in flight, guarded on the
unit file existing as in TEST-26-SYSTEMCTL.sh. It replaces the
per-subtest restart_randomized_delay_cleanup() handler, so the two
subtests that change the log level share one restore path.
systemd-shutdown parsed a bunch of options, but had no way to print
them. Add the standard --help and --version options and the CLI
introspection.
parse_argv() is moved before the PID 1 check, so that the new options
also work when the binary is invoked manually. As PID 1 we can never
return from main() (the kernel would panic), so in that case parse
errors still freeze(), and the help output is followed by a freeze()
too. Ctrl-Alt-Delete and console reopening are re-enabled before
freezing, so that the freeze can be escaped through the kernel and the
error is visible, as before.
The --help/--version/introspect-cli option names are already listed in
the kernel command line exclusion list in proc-cmdline.c.
--introspect-cli follows the same deferred pattern as --help: parse_argv()
only records the action, and it is executed in the same place where
ACTION_HELP and ACTION_VERSION are handled.
The help is looked up by explicit name: in the multicall configuration
the systemd and systemd-executor commands share one binary, so the
"first COMMAND" default would depend on link order.
The "This program takes no positional arguments." line is dropped from
the help output — the default "[OPTION…]" synopsis already says so.
(And also, this is not quite true. It does take positional args when
called as init.)
This converts systemd-homed, -hostnamed, -importd, -localed, -logind,
-machined, -networkd, -portebled, -resolved, -sysupdated, -timedated,
and -timesyncd.
The service name and description move from service_parse_argv() arguments
into a COMMAND() description that each daemon now defines. The
service_parse_argv() macro captures the calling binary's verb section,
where that COMMAND is found, while the options are still defined and
parsed in libshared, in the new "service" option namespace that each
COMMAND references. (The named namespace makes sure that options defined
by other translation units in libshared can never leak into the
services' parsers.)
The supported features are declared via option group filtering: each
COMMAND lists "Options" plus "Bus introspection" and/or "Runtime scope".
Unsupported options are now rejected by the parser as unknown options
(and are not shown in --help), replacing the runtime "This service does
not support …" errors.
The help output changes slightly: the "Bus introspection" and "Runtime
scope" option groups now get their own headings, and some descriptions
were reworded to the imperative form used elsewhere.