Commit Graph
5499 Commits
Author SHA1 Message Date
Christian Brauner 9b62fd169e Extend W^X memory protection mechanism for RestrictFileSystemAccess (#43511)
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>
2026-09-01 07:12:13 +02:00
Christian Brauner 3674263071 test-bpf-restrict-fsaccess: add W^X and ptrace protection tests
Add tests for all new W^X protection mechanisms.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-31 23:33:40 +02:00
Christian Brauner 179781353a test-seccomp: seccomp_restrict_ptrace()
Test seccomp_restrict_ptrace().

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-31 23:33:40 +02:00
Christian Brauner 73e093d16d core: centralize RestrictFileSystemAccess= checks
Centralize the checks for RestrictFileSystemAccess= requirements.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-31 23:33:39 +02:00
Christian Brauner c35f061634 test-bpf-restrict-fsaccess: differentiate between a denial and an unrunnable probe
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>
2026-08-31 23:33:39 +02:00
Chris Down df3c9c9ef0 test: Add a test for Type=dbus services surviving a broker restart 2026-09-01 03:03:46 +09:00
Yu Watanabe 39cf979007 Final batch of conversions to COMMAND and additions of --introspect-cli (#43558) 2026-08-29 04:26:12 +09:00
Zbigniew Jędrzejewski-Szmek 37900a6265 tests: convert manual test binaries to VERB_COMMAND and --introspect-cli
The last five hand-written help() functions are replaced by COMMAND()
descriptions: test-homectl-prompts, test-libudev,
test-modem-manager-mock, test-chase-manual, and test-journal-append
(which open-coded the help inside the option parsing loop). None of the
binaries are installed, so no man pages are referenced and they are not
added to the introspection smoke test.
2026-08-28 14:50:10 +02:00
Zbigniew Jędrzejewski-Szmek 51636f3c39 repart: clamp copied timestamps to $SOURCE_DATE_EPOCH (#43545)
This greatly helps reproducibility, and we can actually achieve it with
https://github.com/dosfstools/dosfstools/commit/8da7bc93315c.
2026-08-28 13:22:41 +02:00
Martin Pitt edbf043333 copy: support clamping the timestamps of copied inodes
Add a `ts_clamp` argument to `copy_tree_at_full()` and
`copy_times_full()` that clamps copied inodes to a given time stamp.
This is the recommended `$SOURCE_DATE_EPOCH` semantics [1] for
reproducible builds. Their non-`_full` APIs keep existing behaviour by
clamping to `USEC_INFINITY`.

atime and mtime are clamped separately: a file that was merely read
after the epoch has to keep its mtime and still lose its atime. E.g. FAT
(EFI partition) stores an access date.

[1] https://reproducible-builds.org/specs/source-date-epoch/
2026-08-28 11:12:56 +02:00
Zbigniew Jędrzejewski-Szmek 868d22628c shared/verbs: add flag VERB_DEPRECATED
Previously, verbs were skipped in --help automatically when their
.help field was nil. But this doesn't work in two ways:
- in introspection data we want to show the option, but marked as
  deprecated
- in udevadm (and maybe other programs with verbs with own options in
  the future), we need to show help for the verb.

So add an explicit flag to mark verbs as deprecated and skip them in
help even if .help is set. If there's a footer, print it highlighted
in red automatically.
2026-08-28 09:29:04 +02:00
Zbigniew Jędrzejewski-Szmek 4aea8cedd2 shared/verbs: add a footer for verbs
This will be needed for 'udevadm hwdb' which carries a footer.
2026-08-28 09:29:04 +02:00
Zbigniew Jędrzejewski-Szmek 84d19dc8f9 shared/verbs: add flag VERB_OPTION_REQUIRED 2026-08-28 09:29:04 +02:00
Zbigniew Jędrzejewski-Szmek 3cfe456323 shared/verbs: add command_print_verb_help()
A helper to print the help of a single verb, derived entirely from the
data in the SYSTEMD_VERBS and SYSTEMD_OPTIONS sections: the synopsis is
built from the verb name and argspec, the one-line verb help serves as
the abstract, the options come from the verb's option namespace, and the
man page links from the command the verb belongs to. A flag allows
suppressing the man page links for callers which append custom sections.
2026-08-28 09:29:04 +02:00
Zbigniew Jędrzejewski-Szmek efd63ddfbf shared/verbs: turn verb argument specification into nulstr
Like the command-level CommandDescription.argspec, a verb's argspec is now
a nulstr, so that a verb may declare multiple alternative usage lines. The
verbs help table renders one synopsis line per nulstr entry in the
synopsis cell.

All verb argument specifications must gain an explicit NUL suffix. This
is a lot of churn, but I think this needs to be done. For now, there'll
be only one user of the new functionality (i.e. a verb with more than
one argspec), but I think we'll eventually want to use this in other
places too.
2026-08-28 09:29:04 +02:00
Zbigniew Jędrzejewski-Szmek 057fc4251a shared/verbs: allow verbs to declare an option namespace
Verbs like udevadm's subcommands parse their own options from a dedicated
option namespace. Allow the namespace to be declared in the verb
definition (via the new VERB_SCOPE_NS* macros), and report the options of
that namespace in the verb's CLI introspection object.

This is conformant with the CLI-Introspection Specification: verbs are
represented as nested command objects, which may carry their own "options"
array just like the top-level command object.
2026-08-28 09:29:04 +02:00
Yu Watanabe dac2b39405 compress: strictly check if stream finished on decompress
Previously, even when the compressed data was truncated, or had
trailing garbage, the decompressor might not detect it.

To fix the issue, update the check if stream is finished.
With this change, now decompressor_push() must be called with
size == 0 to finalize the stream.
2026-08-27 04:36:57 +09:00
Yu Watanabe d9b6e8663c compress: check cursor position in should_sparse()
Otherwise, the result will be broken, as skipped area by lseek() is not
cleared.
2026-08-27 04:36:57 +09:00
Yu Watanabe fb1160c588 test-compress: replace writing zeros with ftruncate() + lseek() 2026-08-27 04:34:34 +09:00
Yu Watanabe dd997b4fb8 test-compress: compare decompressed data with source 2026-08-27 04:34:34 +09:00
Zbigniew Jędrzejewski-Szmek 34d4d50d08 Another batch of conversions to COMMAND and additions of --introspect-cli (#43512) 2026-08-26 15:22:50 +02:00
Zbigniew Jędrzejewski-Szmek 4f2b0e977c core: convert to VERB_COMMAND and add --introspect-cli
--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.)
2026-08-26 12:22:15 +02:00
Zbigniew Jędrzejewski-Szmek ed595e6b67 basic/argv-util: drop unused code 2026-08-26 12:15:16 +02:00
Zbigniew Jędrzejewski-Szmek dc39dc759b Pressure metrics (#43377)
This adds a slightly extended and improved version of the patch we had
internally.
2026-08-25 14:45:16 +02:00
Yu Watanabe e036040529 test-compress: extend buffer size
Otherwise, random input cases are not tested:
```
/* test_compress_decompress_blob */
/* testing xz text blob compression/decompression */
Loaded shared library 'liblzma.so.5' via dlopen().
/* testing xz random blob compression/decompression */
compression failed: No buffer space available
/* testing lz4 text blob compression/decompression */
Loaded shared library 'liblz4.so.1' via dlopen().
/* testing lz4 random blob compression/decompression */
compression failed: No buffer space available
/* testing zstd text blob compression/decompression */
Loaded shared library 'libzstd.so.1' via dlopen().
/* testing zstd random blob compression/decompression */
compression failed: No buffer space available
/* testing gzip text blob compression/decompression */
Loaded shared library 'libz.so.1' via dlopen().
/* testing gzip random blob compression/decompression */
compression failed: No buffer space available
/* testing bzip2 text blob compression/decompression */
Loaded shared library 'libbz2.so.1' via dlopen().
/* testing bzip2 random blob compression/decompression */
compression failed: No buffer space available
```
2026-08-24 03:30:52 +09:00
Zbigniew Jędrzejewski-Szmek efa45f5f47 basic/procfs-util: add procfs_cpu_get_ticks() 2026-08-21 09:17:51 +02:00
Zbigniew Jędrzejewski-Szmek aa1045f862 test-procfs-util: convert to new ASSERT macros
Replace assert_se()/assert() with the typed ASSERT_* macros, matching the
conversions done across the rest of src/test/.
2026-08-21 09:17:51 +02:00
Chris Coulson 768b6f21ec report: Add a TPM2 signing backend
This adds an initial TPM2 implementation of the io.systemd.Report.Signer
interface, which returns a set of signed TPM attestations that also
includes the supplied report digest.

The returned report consists of multiple components:
- A signed PCR quote generated with TPM2_Quote.
- Signed NvPCR attestations generated with TPM2_NV_Certify.
- A signed audit session attestation generated with
  TPM2_GetSessionAuditDigest.

Each signed report component includes the JSON encoded TPMS_ATTEST
attestation structure and corresponding signature, in both PEM encoded
form and as a JSON encoded TPMT_SIGNATURE structure.

NvPCR report components also include the readable name of the NvPCR, the
NV index public area as the JSON encoded TPMS_NV_PUBLIC structure, and some
additional authenticated data that is digested and supplied as the
qualifying data to the TPM2_NV_Certify command. This authenticated data
includes the NvPCR name and priority.

The report digest that is supplied via varlink is supplied as the
qualifying data to the audit session attestation.

The returned report also includes the event log which is retrieved from
pcrlock's varlink interface, and the public area of the signing key in
both PEM encoded form and as a JSON encoded TPMT_PUBLIC structure.

The audit session attestation is always the last component. The
preceding components are all executed with the audit session, and the
signer requires the audit session to remain exclusive whilst generating
the report. What this does is provide evidence to a verifier that the
overall report is a single, consistent snapshot of the system (ie, no
other TPM commands executed whilst generating the report) because the
audit session attestation contains a field that indicates that the
session was exclusive at the time it was attested.

The set of attested PCRs and NvPCRs are currently hardcoded. It includes
all firmware PCRs with the exception of PCR6, and all other PCRs with
the exception of the IMA one (which isn't useful on its own without the
IMA log). All defined NvPCRs are included.

A verifier can verify the signed attestation components individually by
verifying their signatures against the supplied attestation data, using
the supplied public key.

For the PCR report component, a verifier can replay the measurements in
the log in order to reconstruct the PCR values, and then use this and
the PCR selection from the attestation in order to calculate the PCR
digest, and then check that this matches the one in the attestation.

For the NvPCR report components, a verifier checks that the NV index
name in the attestation matches one that is anchored in PCR9 to be sure
that the attestation is for a valid NvPCR. A verifier can then replay
the measurements in the log to ensure that the resulting NvPCR value
matches the one supplied in the attestation. The authenticated data is a
JSON blob containing the NvPCR name and priority. A verifier can digest
this and ensure it matches the qualifying data in the attestation.

The audit session attestation includes the audit session digest and
whether the audit session was still exclusive when the attestation was
generated. A verifier should confirm exclusivity. The digest includes a
record of all TPM commands executed with the audit session, as each
command results in it being extended with the command parameter hash and
the result parameter hash. A verifier should replay the preceding report
components to compute an expected audit digest and make sure that this
digest matches the one in the attestation in order to be sure that the
overall report represents a single, consistent snapshot. The other
report components provide sufficient information for a verifier to
reconstruct the cpHash and rpHash.

When reconstructing the cpHash for the PCR and NvPCR components, the
signing scheme is the NULL scheme (they use the attestation key's
signing scheme). The required signing key public area is supplied in
the report. The supplied qualifying data is in the extra data field of
the attestation.

The PCR selection is also required to reconstruct the cpHash for the
PCR component. The PCR selection is available in the attestation.

The name of the NV index is also required to reconstruct the cpHash for
the NvPCR components. The name is available in the attestation.

The rpHash for each component can be reconstructed from the supplied
attestation blob and signature.

In the future, we might want to add more report component types. Eg, we
could add report components for ordinary NV indexes. Using the audit
session means that we could also add additional components that the TPM
can't attest directly, such as the values of interesting TPM properties
(as the TPM2_GetCapability calls will appear in the audit session digest).

The attestation signing key is stored as a JSON file in the report
signer's state directory. Different types of keys are supported:
- Ordinary keys that are protected by an existing persistent TPM key
  such as the SRK or EK.
- Persistent TPM keys.
- Primary keys, which are recreated using a template on use.

When an ordinary or primary key is used, the report signer saves the
object context in its runtime directory. This context is used on future
invocations. This has the benefit that where a primary key is used, it
doesn't need to be recreated on each use. When an ordinary key is used,
we don't need to run TPM2_Load, which requires authorization with the
parent key, on each This authorization currently requires a policy
session for low-range EKs.

Regardless of the key type, it must be a restricted signing key. If no
key exists, an ordinary key is generated automatically.

The attestation key can also have an associated credential which is
attached to the signed report. The credential can be issued by a CA or
some other party during onboarding, and is presented to a verifier as
proof that the attestation key is authentic. It is considered an opaque
blob in order to not require it to be a specific format, such as a X.509
certificate. I've called this a "voucher" though because "cred" or
"credential" has other meanings in systemd.

As a follow-up to this, I plan to add a varlink interface for creating
attestation keys that can be used for report signing.
2026-08-20 15:30:20 +02:00
Andrew Dunn 8e3d6dc535 crypto-util: zero-pad ECC point coordinates to curve width
ecc_pkey_to_curve_x_y() marshalled the affine point coordinates with
BN_num_bytes() + BN_bn2bin(), which produce the minimal big-endian
encoding and strip any leading zero bytes. The affine coordinates are
elements of the curve's underlying field, though, and consumers expect
them at the fixed field width.

About 1 in 256 keys per coordinate has a most significant zero byte on
the curves whose field width is a whole number of bytes, and about 1 in 2
on P-521, where only the low bit of the leading byte is ever set. The
coordinate then comes out one or more bytes short of the field width. When
that happens tpm2_tpm2b_public_from_openssl_pkey() builds a TPM2B_PUBLIC
whose unique.ecc.x or .y is undersized. The TPM 2.0 reference
implementation requires each coordinate size to equal the key size in
bytes, so TPM2_LoadExternal() rejects the public area with TPM_RC_KEY.
The name a TPM derives for the object also stops matching the name
systemd computes in software, because the name hashes the marshalled
public area.

For a signed PCR policy the key is only round-tripped through the TPM at
unlock time, so an enrolled key with a leading-zero coordinate is
accepted by systemd-cryptenroll and systemd-measure and only fails when
the volume is first unlocked, with:

    Esys_LoadExternal() Esys Finish ErrorCode (0x000002dc)
    Failed to unseal secret using TPM2: State not recoverable

Derive the curve's field width from the EC group degree and zero-pad both
coordinates to it with BN_bn2binpad(). Padding a full-width coordinate is
a no-op, so only keys that a reference-conformant TPM rejects today change
their marshalling; no enrollment that currently unlocks changes behavior.
Every caller of this converter inherits the fix, including the
systemd-creds public-key binding.

Because the width now comes from the group rather than from the BIGNUM
being written, BN_bn2binpad() can fail where BN_bn2bin() could not, so
both calls return -EIO rather than asserting. It reports that through its
return value without touching the OpenSSL error queue, so these use
log_debug_errno() rather than log_openssl_errors(). EC_GROUP_get_degree
is file-local, matching the other libcrypto symbols used only here.

The tests pin the full expected coordinates for a P-384 key and a P-521
key that each have a leading zero byte. test-tpm2 gains a case pinning
the TPM object name for the P-384 key, because the name is derived from
the marshalled public area and this change alters it for exactly those
keys a reference-conformant TPM rejects today.
2026-08-20 04:06:04 +09:00
Yu Watanabe 06ef663ac0 main-func: add DEFINE_MAIN_FUNCTION_FIBER (#42238)
Provides a Tokio-like #[tokio::main] entry point for binaries that want
to run their main function inside a fiber on a default sd_event loop.

DEFINE_MAIN_FUNCTION_FIBER(impl) mirrors DEFINE_MAIN_FUNCTION but routes
impl through a new run_main_fiber() helper, which:

  - allocates the default sd_event,
  - enables exit-on-idle so the loop terminates once all event sources
    drain (structured concurrency: impl is expected to cancel and reap
    whatever it spawned before returning),
  - spawns impl as a fiber named after program_invocation_short_name,
  - installs SIGINT/SIGTERM handlers that cancel the main fiber (and
    disable themselves so the loop can go idle once the fiber unwinds),
  - runs sd_event_loop and propagates impl's return value via
    sd_future_result, normalising -ECANCELED to 0 since that just
    represents an orderly signal-driven shutdown.
2026-08-17 04:34:22 +09:00
Yu Watanabe 7ae2bc9199 pidfd-util: introduce pidfd_info_mask_is_supported() helper function
Since kernel v6.19, ioctl(PIDFD_GET_INFO) can return the set of
supported flags when PIDFD_INFO_SUPPORTED_MASK is specified.

This allows checking whether, e.g., PIDFD_INFO_COREDUMP_SIGNAL is
supported by the running kernel.
2026-08-15 01:35:23 +09:00
Yu Watanabe 73e67f5fb1 test: split out test-pidfd-util from test-process-util
The tested function pidfd_get_inode_id_self_cached() is provided by
pidfd-util.c. Let's introduce a new unit test for the file.
2026-08-15 01:35:23 +09:00
Yu Watanabe e3a224a46d sd-varlink: several fixlets (#43369) 2026-08-15 01:38:18 +09:00
dongshengyuan 507ba1e51c core: fix bind paths whitespace parsing
Stop warning on extra spaces in BindPaths= and BindReadOnlyPaths=.
Keep bind path parsing and serialization consistent.

Fixes: #43214
2026-08-15 00:26:40 +09:00
Yu Watanabe 0bd40fdc07 json-stream: do not take ownership of passed fds on failure
This makes json_stream_attach_fds() and json_stream_connect_fd_pair()
leave the passed file descriptors with the caller on failure. Callers
should call TAKE_FD() only after a successful call.

Also make qmp_client_connect_fd() always take ownership of the passed
file descriptor, including on failure. Previously, ownership of the
passed file descriptor on failure was unclear and depended on where
the function failed.
2026-08-14 23:55:51 +09:00
Daan De Meyer 581b9db76a main-func: add DEFINE_MAIN_FUNCTION_FIBER
Provide a Tokio-like #[tokio::main] entry point for binaries that want to run
their main function inside a fiber on a default sd_event loop.

DEFINE_MAIN_FUNCTION_FIBER(impl) mirrors DEFINE_MAIN_FUNCTION but routes impl
through a new run_main_fiber() helper. impl keeps the run() convention while
the loop's exit code keeps the exit status convention: impl's result never
becomes an exit code, an explicit exit status wins over a successful impl, and
impl's own failure wins over the exit status unless it is the -ECANCELED of a
fiber that the exit request cancelled. Signal policy stays with impl so
existing handlers such as sd_event_set_signal_exit() keep working.

The fiber is created fire-and-forget: as long as the loop reaches
SD_EVENT_FINISHED it unwinds the fiber through its exit event source, so we
never hold a reference we cannot drop because the fiber is still suspended. A
loop that fails internally never dispatches its exit sources, so the fiber may
stay suspended with no working loop left to unwind it; we leak both for now and
leave a TODO to keep the broken loop out of the default slot. Since the fiber
also refers to run_main_fiber()'s stack frame, refuse to run on a loop we don't
own, or from a fiber, as sd_fiber_new() leaves the exit event source disabled
there and nothing would unwind the fiber.

Cover argument forwarding, result propagation, explicit event exits, nested
invocations, signal-source registration, event-loop errors, and fiber stack
unwinding, and check after every run that the default event loop was released
again. Also build and run a separate test executable that expands
DEFINE_MAIN_FUNCTION_FIBER itself and verifies that a positive implementation
result maps to a successful process exit.
2026-08-14 15:42:22 +02:00
Skye Soss bfc302dad2 shared: add new securebits
The securebits parsing code now recognizes 'no-cap-ambient-raise'
(added in Linux 4.3), 'exec-restrict-file', and 'exec-deny-interactive'
(both added in Linux 6.14). The '-locked' variants were added too.
2026-08-13 13:21:28 -05: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
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
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
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
Daan De Meyer 06cb8fbe61 vmspawn: keep parsing options after the first argument
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.
2026-08-09 01:56:13 +09:00
dongshengyuan fd828b4bd6 test-nss-users: quote empty member lists
Use shell_maybe_quote() with SHELL_ESCAPE_EMPTY when
logging joined group and shadow group member vectors,
so empty vectors are rendered explicitly as "".

Follow-up for: f0d1266821

Follow-up for: 2eaca3ea5f
2026-08-04 22:45:15 +09:00
Yu Watanabe f4cb4596a3 core/bpf: avoid redundant BPF program loading (#43234)
Replaces #43211 and #43213.
2026-08-04 08:10:00 +09:00
dongshengyuan 78238fd7c9 socket: parse message queue size as IEC size
Allow MessageQueueMessageSize= to accept IEC size suffixes in socket unit files.
Support the same syntax for transient property assignments.
Keep MessageQueueMaxMessages= as a plain message count.
2026-08-02 19:17:51 +09:00
Zbigniew Jędrzejewski-Szmek 9eee40e687 selinux: alternate root support (#42768)
This PR changes some SELinux bits related to working with alternate
roots (specifically when using `--root` or `--image` on a bunch of
executables).

It addresses bug #42643 and it's hopefully the more whole approach than
the naive approach I PR'ed in #42644.

Before this PR the 'wrong' labels get applied because the path lookups
in the SELinux label database are prefixed with whatever the location of
the alternate root is (explained in more detail below).

Initially I had taken a very naive approach that did fix the issue by
stripping the alternate root from the path; however this still looks up
that path in the hosts' label database, which might differ from the one
contained in the alternate root.

So this expanded approach actually reads the label database from the
alternate root, strips the prefix *if* an alternate root is used
directly in `selinux-util.c` and then uses that to assign labels
instead.

See under the line for the behavior pre/post.

I've tried builds of this PR on both enforcing/non-enforcing/non-enabled
hosts *and* on enabled/non-enabled disk images and things seem to work
or at least fall back to ignoring MAC when required bits aren't present.

One thing is *if* an `/etc/selinux/config` is present that defines a
`SELINUXTYPE=` we *do* require the policy given to be present in the
image. This is the only new actual error in this code path that doesn't
get ignored.

We *could* verify that the path exists and also ignore it but I
personally don't think that's the right approach since the actual system
itself would likely also be broken anyhow. Let me know thoughts on that.

There's a tight coupling here still with the *hosts* SELinux policy in
that to set (potentially) unknown labels to the policy loaded in the
host kernel these executables would need to execute in a domain that
allows transitioning to `mac_admin`. I'd say that `install_t` is the
most likely candidate for that. See the first comment on this PR for
more explanation on it/request for input.

---

When mounting `a.raw` before running any tooling against it and showing
the `/etc/shadow` file labels we have:

```
€ sudo systemd-dissect --mount test/a.raw test/mnt/a
€ ls -Zlart test/mnt/a/etc/shadow
----------. 1 root root system_u:object_r:shadow_t:s0 520 Jun 27 07:55 test/mnt/a/etc/shadow
€ sudo systemd-dissect ---umount test/mnt/a
```

After running `systemd-firstboot` against the image, then remounting,
note the labels that have been changed to incorrect values:

```
€ sudo systemd-firstboot --image test/a.raw --root-password test    
/home/user/src/github.com/teamsbc/artifacts/test/a.raw: /etc/passwd written.
/home/user/src/github.com/teamsbc/artifacts/test/a.raw: /etc/shadow written.
€ sudo systemd-dissect --mount test/a.raw test/mnt/a           
€ ls -Zlart test/mnt/a/etc/shadow
----------. 1 root root system_u:object_r:init_var_run_t:s0 579 Jun 27 08:01 test/mnt/a/etc/shadow
```

The behavior before this PR looks up the labels in the label database of
the host, but the path that gets looked up is the path where the image
is temporarily mounted, or in the case of `--root` where the root is on
the host. Since that path doesn't define any labels we get the labels of
the location where the file was created on the host. In this case since
`--image` was used, which mounted things in a temporary location we end
up with `var_run_t`.

If this image is booted things that want to read `/etc/shadow` might not
be allowed to read files labeled this way; thus services fail to start,
and root can't login when SELinux is in enforcing mode.

After this PR is applied there are two main differences in how things
are handled. The first being that instead of reading the label database
from the host (which might have none, or have a different one from the
one contained inside an image or root) we read the label database from
inside the alternate root. This tries to make sure we get the correct
labels for given paths.

Second, and most importantly, if we did init SELinux with an alternate
root then any paths passed to the relevant label lookup functions strip
that alternate root from the path. While previously we'd look up a path
like `/run/dissect-XXXX/etc/shadow` we now look up a path like
`/etc/shadow` *and* this path gets looked up in the label database in
the alternate root.

Together these things give in my opinion better handling of SELinux in
alternate roots. To confirm things work here's the same operations on
the second copy of our image:

```
€ sudo systemd-dissect --mount test/b.raw test/mnt/b
artifacts € ls -Zlart test/mnt/b/etc/shadow
----------. 1 root root system_u:object_r:shadow_t:s0 520 Jun 27 07:55 test/mnt/b/etc/shadow
€ sudo ~/src/github.com/systemd/systemd/build/systemd-firstboot --image test/b.raw --root-password test
/home/user/src/github.com/teamsbc/artifacts/test/b.raw: /etc/passwd written.
/home/user/src/github.com/teamsbc/artifacts/test/b.raw: /etc/shadow written.
€ sudo systemd-dissect --mount test/b.raw test/mnt/b                                                  
€ ls -Zlart test/mnt/b/etc/shadow
----------. 1 root root system_u:object_r:shadow_t:s0 579 Jun 27 08:44 test/mnt/b/etc/shadow
```

Showing that we now have the correct labels applied.
2026-08-02 02:06:12 +02:00