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.
The `exec-restrict-file` and `exec-deny-interactive` securebits are
particularly useful in combination with `RestrictFileSystemAccess=` to
allow developers to extend the "only execute immutable files" concept to
script interpreters. Those securebits are not documented in the
[`capabilities(7)`](https://man7.org/linux/man-pages/man7/capabilities.7.html)
manpage, only in the [Executability
check](https://docs.kernel.org/userspace-api/check_exec.html) kernel
docs.
Replace opaque built-in review behavior with explicit parallel review lenses for
correctness, concurrency, security, architecture, coding style, and regression
coverage, plus bounded domain-specific reviewers and independent validation.
Prefetch pull request discussion in the setup job while letting the read-only
review job fetch and inspect the full commit history without per-commit
worktrees.
Keep GitHub writes isolated to the setup and posting jobs, pin the Claude models
and CLI, and bound execution below the AWS credential lifetime. Materialize
review instructions from the trusted base revision and scope agent writes to the
structured result directory so untrusted pull request content cannot widen the
review authority.
Treat the posting job as the authoritative trust boundary: validate all
model-selected commits, paths, lines, and thread IDs, normalize degenerate line
ranges, bound sanitized output, and surface GitHub API failures in the tracking
comment before performing writes. Retain daandemeyer/systemd in the repository
allowlist so the workflow can be exercised end to end on the personal fork.
Manual runs against fork PR #15 covered OIDC authentication, sandboxing, result
artifact handoff, tracking-comment updates, and validation of posted output.
Signed-off-by: Daan De Meyer <daan@amutable.com>
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.
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
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.
So far there's only one multi-line abstract. But as pointed out in the
review, it's silly to handle footer but not the abstract.
help_abstract() is not reused because it only works with one-line
abstracts. (It always prints an empty line to separate paragraphs.)
So the formatting is done anew in print_wrapped(). I think this is OK,
since it's all very simple. After everything has been converted to
COMMAND() and its helpers, help_abstract() and the sister functions will
become unused and can be dropped then.
The footer is now wrapped to min($COLUMNS, 80) when it is printed, so
CommandDescription footers do not need to be hard-wrapped in the source,
and make use of the available terminal width. Newlines embedded in the
text are preserved and each line is wrapped independently, so explicit
line breaks can still be used where needed, e.g. before the example
command line in varlinkctl.
The text is wrapped at max of 80 columns to avoid very wrong lines on
wide terminals. Such lines are hard to read.
A new helper is added for this. We already have format_strv_width in
format-table, but it requires a strv, so we'd need to conver the data,
and returns a formatted string, which we'd need to print ourselves. So
overall, it seems better to add a dedicated helper.
Before this series, many help() functions opened the pager. This got lost
in the translation. Starting the pager requires access to arg_pager_flags.
While we could pipe this through in case when command_print_help() is
called directly, but this hard in the case when verb_help_auto() or
VERB_COMMON_HELP_AUTO/VERB_COMMON_HELP_AUTO_HIDDEN are used. Let's instead
extend the (const static) CommandDescription structure with a pointer to
the arg_pager_flags variable. This restores the pager functionality in a
uniform fashion with a one-line change in the individual binaries.
Previously, the systemd-resolve compat alias was described in the same
CommandDescription structure and resolvectl. But they have different
option sets, so this doesn't really work. The alias is deprecated, so
describe it as such in the abstract, but give it a separate entry.
$ build/resolvectl --introspect-cli
{
"mediaType": "application/vnd.io.systemd.cli-introspection-0",
"commands": [
{
"names": [ "resolvconf" ],
"abstract": [
"Register DNS server and domain configuration with systemd-resolved."
],
...
},
{
"names": [ "resolvectl" ],
"abstract": [
"Send control commands to the network name resolution manager, or\nresolve domain names, IPv4 and IPv6 addresses, DNS records, and services."
],
...
},
{
"names": [ "systemd-resolve" ],
"abstract": [
"Send control commands to the network name resolution manager, or\nresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n\nThis command is deprecated. Use resolvectl.1 instead."
],
...
}
]
}
This adds the helpers to add a hidden --introspect-cli option which
prints a machine-readable description of the program's interface and
exits. Applicable to any programs that have been converted to
VERB_COMMAND().
The output format follows the draft CLI-Introspection Specification
(https://github.com/behrmann/uapi-specifications/blob/cli-introspection/specs/cli_introspection.md):
a single JSON document with a fixed "mediaType" string and an array of
command objects, one per VERB_COMMAND() defined in the binary, so multicall
binaries are described in full. Options are taken from the command's option
namespace, so e.g. systemd-dissect does not list the mount.ddi mount-helper
options.
$ build/systemd-id128 --json=pretty --introspect-cli
{
"mediaType" : "application/vnd.io.systemd.cli-introspection-0",
"commands" : [
{
"names" : [
"systemd-id128"
],
"version" : "262~devel",
"features" : [
"-PAM",
...
],
"abstract" : [
"Generate and print 128-bit identifiers."
],
"options" : [
{
"names" : [
"-h",
"--help"
],
"argument" : "no_argument",
"help" : "Show this help"
},
...
],
"verbs" : [
{
"names" : [
"new"
],
"abstract" : [
"Generate a new ID"
],
"maxArguments" : 1
},
...
]
}
]
}
$ build/systemd-id128 --introspect-cli | jq '.commands[0].options[].names[-1]'
"--help"
"--version"
"--no-pager"
"--no-legend"
"--json"
"-j"
"--pretty"
"--value"
"--app-specific"
"--uuid"
"--introspect-cli"
We add a CommandDescription that contains basic info about the program:
- names (the main name and deprecated aliases)
- abstract, man page list, footer
This gets stored as a fake verb entry through VERB_COMMAND macro.
We can then generate the full help for the program from the verbs
and option arrays.
The formatting of man page links is changed from the form used
previously: the section is printed as ".N" instead of "(N)". This
for has the following advantages:
- the form with parenthesis looks like a function call, so e.g. close(1)
is both a valid call and a man page link.
- the new form can be automatically selected with a double click in the
typical terminal configurations, while '(' would usually terminate the
selection area.
- parentheses are special in shell syntax, so even the user manages to
successfully select and paste them, they still have to add quoting or
escaping on the command line.
- the new form is shorter
- it looks better
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
--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
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
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>
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.
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.
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.
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
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
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
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.
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.
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.