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.
verb_chid(), verb_blame() and verb_identify_tpm2() all render their output
via table_print_with_pager() with arg_json_format_flags, so JSON output has
been implemented for them since they were introduced. However, none of them
was ever added to the verb allowlist that gates --json=, so e.g.
"systemd-analyze chid --json=pretty" failed with:
Option --json= is only supported for security, inspect-elf, ...
Add them to the allowlist (and the error message) so the option is accepted.
These are the only remaining verbs that render through arg_json_format_flags
but were not allowlisted.
Note that has-tpm2 is deliberately not included: verb_has_tpm2() defers to
verb_has_tpm2_generic(), which writes plain text via printf() and has no JSON
support, so accepting --json= there would silently ignore it.
This went unnoticed for chid because the TEST-65-ANALYZE coverage for it is
guarded behind a check for SMBIOS board_vendor and a non-container
environment, so it only runs on bare-metal/VM runners.
SetLinkDomains() tracks whether the configured search domain list changed
so it can skip redundant persistence. The route_only flag is part of the
D-Bus API and is persisted as a ~ prefix, but flipping an existing domain
between search and route-only only updated in-memory state and left
changed false. The method then skipped link_save_user() and
manager_write_resolv_conf(), leaving stale state until restart.
Reproduced on Ubuntu with systemd 259:
sudo resolvectl domain enp1s0 route-only-repro.test
sudo resolvectl domain enp1s0 '~route-only-repro.test'
resolvectl domain enp1s0
grep '^DOMAINS=' /run/systemd/resolve/netif/$(cat /sys/class/net/enp1s0/ifindex)
grep -H route-only-repro.test /run/systemd/resolve/*.conf
Before, resolvectl showed ~route-only-repro.test, but the persisted state
file still showed DOMAINS=route-only-repro.test, and the generated
resolv.conf files still contained search route-only-repro.test.
After this change, the state file is updated to
DOMAINS=~route-only-repro.test and the generated resolv.conf files are
refreshed so the route-only domain is not emitted as a search domain.
Follow-up for: cbf23f3853
Alibaba Cloud ECS instances expose "Alibaba Cloud ECS" in DMI
product_name/bios_vendor and "Alibaba Cloud" in sys_vendor. None of
these match dmi_vendor_table[], so systemd-detect-virt --vm reports
"none" on these machines, even though they run on a KVM-based
hypervisor. This is especially visible on ARM64 instances, where the
CPUID fallback does not exist (x86 only) and /proc/device-tree is
not populated.
Add "Alibaba Cloud ECS" to dmi_vendor_table[], mapping to
VIRTUALIZATION_KVM. The narrower prefix avoids matching the bare
"Alibaba Cloud" sys_vendor value, reducing the false-positive
surface for Alibaba's bare-metal (X-Dragon) instances compared to
the original broader prefix. bios_vendor also carries the same
string on ECS VMs though, so the protection is not complete —
fully resolving the VM-vs-bare-metal ambiguity would need the
SMBIOS BIOS-Characteristics bit check that the Amazon EC2 entry
uses.
The existing "back to DMI" fallback path in detect_vm() reports
"kvm" on both x86 and ARM64.
When stdio is passed as file descriptors, the provider of those
descriptors remains responsible for their ownership and access mode.
This keeps run0 in a pipeline from changing the caller's TTY ownership
to the target user, which can otherwise break later terminal access from
the original user.
Fixes#43336
--cvm is a mode switch, so it must not stop option parsing.
Keep parsing after it to honor later options and validate arguments.
Before: --cvm --quiet printed none; --cvm unexpected-argument printed none.
After: --cvm --quiet prints nothing; extra arguments are rejected.
Follow-up for: 5e0c61f64d
tries should be line-oriented like the other --print= modes.
Terminate its counter output with a newline to avoid glued CLI output.
Before: output bytes were 2b 32 2d 33.
After: output bytes are 2b 32 2d 33 0a.
Follow-up for: 9e61ed1115
- pcrextend: Fix use-after-free/double-free error in pcrextend_pcr_now
With SignInitrdPCRs=disabled in mkosi the TPM/PCR services are broken
which triggers the error path in the varlink call which means that we
get parameters set in the response. The wrong ownership assumption when
looking at the reply with _cleanup_ set caused a memory corruption that
surfaced in systemd-sysext with an ABRT.
This is a common mistake done with the API, fix it by not setting
_cleanup_(sd_json_variant_unrefp).
- pkcs7: Correct error handling for pkcs7_extract_signers
The lack of error handling for sym_i2d_X509_NAME/sym_i2d_ASN1_INTEGER
means that we get a NULL iovec that will cause an assertion failure
later. The use of regular free for OpenSSL is probably okay but since
the allocator can differ, one should use OPENSSL_free.
Add error handling and do an explicit copy instead of ownership transfer
between possibly different allocators.
RestrictFileSystemAccess= currently denies execution from any overlayfs
mount, even when every layer sits on a signed dm-verity device. The
enforcement hooks compare file->f_inode->i_sb->s_dev against the map of
trusted devices and for a file on an overlay that's the overlay's own
anonymous device, never the backing block device. So the fairly common
image based setup where a signed DDI is the lower layer and a writable
upper is stacked on top of it is out.
Kernel v7.2 grew the bpf_real_data_inode() kfunc which resolves a file
to
the inode hosting its data. Use it from the two enforcement hooks that
get
to see the union-level file. Execution through such an overlay then
works
while anything whose data lives in an untrusted upper layer is still
denied.
All of it is gated on kernel support. The kfunc is a __weak __ksym and
every call sits behind a const volatile .rodata flag that PID1 only sets
when the kernel BTF advertises the function, so the object still loads
on
older kernels and keeps denying union filesystems exactly like before.
If
loading with resolution enabled fails anyway the loader retries with the
flag off. A setup failure here is fatal to PID1 startup and I'd rather
degrade than not boot.
Two things worth pointing out. The mmap_file hook matters more than it
looks like. binfmt_elf maps the PT_INTERP interpreter via vm_mmap() so
ld.so is only ever checked there and never by bprm_check_security. And
file_mprotect deliberately doesn't use the kfunc. It runs under
mmap_write_lock where sleeping in d_real() would invert the documented
i_rwsem -> mmap_lock order, and it doesn't need it anyway since
ovl_mmap()
has already installed the backing file into vma->vm_file by then.
Known limitations:
* With metacopy=on an untrusted upper layer can still own a trusted
binary's mode, ownership and setuid bits since the resolution only
follows the data. Documented in the man page, just don't build that.
* composefs with a separate data directory isn't covered. That needs
fs-verity digests and bpf_get_fsverity_digest() takes a struct file
and gets the overlay inode, so it doesn't compose with this today.
* erofs page cache sharing is denied. The inode comes from a pseudo
filesystem whose s_dev is never in the verity map.
* Multi-device filesystems like btrfs stay unsupported, as before.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
With SignInitrdPCRs=disabled in mkosi the TPM/PCR services are broken
which triggers the error path in the varlink call which means that we
get parameters set in the response. The wrong ownership assumption when
looking at the reply with _cleanup_ set caused a memory corruption that
surfaced in systemd-sysext with an ABRT.
This is a common mistake done with the API, fix it by not setting
_cleanup_(sd_json_variant_unrefp).
The lack of error handling for sym_i2d_X509_NAME/sym_i2d_ASN1_INTEGER
means that we get a NULL iovec that will cause an assertion failure
later. The use of regular free for OpenSSL is probably okay but since
the allocator can differ, one should use OPENSSL_free.
Add error handling and do an explicit copy instead of ownership transfer
between possibly different allocators.
On many-CPU hosts, the CPU% column in systemd-cgtop can exceed the
hardcoded %6.1f format width (e.g. 12769.4% on a 128-CPU host),
causing column overflow and table misalignment.
Follow the existing maxtcpu pattern for CPU Time: dynamically compute
the maximum width of CPU percentage values and use it for both the
header and data rows.
Fixes#39819
Have the attach helper print whether the loader enabled union filesystem
resolution and assert that this agrees with the kernel BTF.
Then mount the signed minimal image as an overlayfs lower layer with a
tmpfs upper. On kernels with bpf_real_data_inode() executing through the
overlay works while a file created in the upper layer or copied up into
it is denied. Without the kfunc execution through the overlay is denied
outright.
The overlay uses metacopy=off so that the copy-up test really does move
the data over to the untrusted upper layer.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Probe the kernel BTF for bpf_real_data_inode() and set the BPF program's
.rodata flag accordingly. The flag has to be written for every open of
the skeleton because .rodata is frozen once the object is loaded, so fold
the two open and load attempts into a common helper.
A positive BTF probe isn't a guarantee. It tells us that the function
exists, not that it is registered as an LSM kfunc with the signature we
expect. So if loading with resolution enabled fails anyway, retry with
the flag off. Files on union filesystems are then denied like they were
before which is a lot better than turning kernel drift into a boot
failure.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Add bpf_kernel_has_kfunc() which checks whether the kernel's vmlinux BTF
advertises a function of a given name.
The three btf__* symbols it needs are optional in the dlopen table.
btf__load_vmlinux_btf() only exists since libbpf 0.5 and a libbpf that
lacks any of them should degrade this probe, not fail dlopen_bpf() and
with it all of BPF. Note, libbpf < 1.0 returns error pointers instead of
NULL, so the result is checked with bpf_get_error_translated().
Finding the name doesn't tell us all that much though. It tells us that
the function exists, not that it is registered as a kfunc for the program
type we care about. So callers still have to be prepared for the program
load to fail.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Execution from an overlayfs mount is currently denied even when every
layer sits on a signed dm-verity device. The hooks compare
file->f_inode->i_sb->s_dev against the map of trusted devices and for a
file on an overlay that's the overlay's own anonymous device, never the
backing block device.
Kernel v7.2 grew the bpf_real_data_inode() kfunc which resolves a file to
the inode hosting its data. Use it from bprm_check_security and mmap_file
which both get to see the union-level file. They become lsm.s/ programs
because the kfunc is sleepable. That doesn't raise our kernel
requirements, both have been in sleepable_lsm_hooks since v5.11.
The file_mprotect hook stays non-sleepable. It runs under mmap_write_lock
where sleeping in d_real() would invert the documented i_rwsem ->
mmap_lock order, and it doesn't need the kfunc anyway. By the time a vma
exists ovl_mmap() has already installed the backing file into
vma->vm_file.
The initramfs trust window keeps comparing the mount device. If the
initrd is itself a union mount then userspace only ever recorded the
union's anonymous s_dev. The verity map is keyed by block device so that
one gets compared against the resolved device.
The kfunc is declared __weak __ksym and every call is gated on a const
volatile .rodata flag that PID1 only sets when the kernel provides it. So
the object still loads on pre-7.2 kernels where files on union
filesystems are denied as before. The flag stays off until the loader
learns to set it in a followup.
The file argument of bpf_lsm_mmap_file() is annotated __nullable since
v7.0 so the verifier won't let us hand it to the kfunc unless we branch
on it first.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The bprm_check_security and file_mprotect hooks currently fetch the file
pointer with BPF_CORE_READ_INTO(), i.e. bpf_probe_read_kernel(). That
hands us back a plain scalar and since 1a5c01d2508a ("bpf: Make
KF_TRUSTED_ARGS the default for all kfuncs") in v7.0 such a pointer can't
be passed to a kfunc at all.
Dereference the fields directly instead. Both bprm->file and vma->vm_file
are in the verifier's BTF_TYPE_SAFE_TRUSTED(_OR_NULL) lists so the loads
give us PTR_TRUSTED registers, and vmlinux.h pushes preserve_access_index
for all records so we don't lose CO-RE.
No functional changes. This is just so we can hand the pointers to the
bpf_real_data_inode() kfunc in the next patch.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
vmspawn's positional arguments are extra kernel command line arguments
rather than a command to execute, so unlike nspawn or run it has nothing
to shield from the option parser: a kernel command line argument never
begins with a dash. Stopping at the first one only means an option that
follows one is silently taken for another kernel command line argument,
with no diagnostic:
systemd-vmspawn -i image.raw console=ttyS0 --set-credential=foo:bar
boots with "--set-credential=foo:bar" appended to the kernel command
line instead of setting a credential.
The stop was right when it was written. In 9de3cc1484, vmspawn's first
commit, the trailing arguments were appended to the QEMU command line
directly, so they really were options that had to reach QEMU unparsed.
4291f4461e turned them into extra kernel command line arguments passed
through SMBIOS three weeks later, and updated the man page to say so,
but left the "+" in the optstring behind; 4c778c51c0 and 5ff0ccaf8b then
carried it into the new option parser as
OPTION_PARSER_STOP_AT_FIRST_NONOPTION.
Use the default parser mode, which permutes options ahead of the
arguments; "--" still ends option parsing for an argument that does look
like an option. test-options gains the case this turns on, an option
after a positional argument in the default mode, beside the existing one
for the mode vmspawn no longer uses.
When --json is requested, resolution errors returned by resolved still went
through text logging. That made scripts parse stderr even though the
varlink error already carried structured fields such as rcode and EDE.
Add a small JSON error dumper for resolvectl query errors. It outputs the
queried name and varlink error id alongside the original varlink parameters
so DNSError and DNSSECValidationFailed expose rcode, extendedDNSErrorCode,
extendedDNSErrorMessage, queryString, and result without duplicating the
schema in resolvectl.
Keep non-JSON text output and silent warn_missing NXDOMAIN handling
unchanged. Document the JSON resolution error object in resolvectl(1),
including the distinction between resolved errors and local invocation or
connection errors.
Closes#42904
For some of us building image based operating systems with
`systemd-sysupdate` it's useful to have UKI filenames that encode
`IMAGE_VERSION` (and architecture). For example
`SimonOS_45.20260805.0_x86-64.efi`.
Currently to do this I have to manually post-process around the `.efi`
produced by `60-ukify` because it is named
`$ENTRY_TOKEN-$KERNEL_VERSION.efi`. It'd be very useful if I could skip
that step and instead have the image built directly without this
post-processing step; then extract the UKI and /usr partitions and plop
them onto my update server. It makes the build much more "install RPMs
into a tree and you're done" (barring other things `60-ukify` currently
can't do but I have follow-ups for those in mind).
This PR adds a knob to `install.conf` to let users specify a custom
format for the output filename for both UKI's and BLS entries produced
by `kernel-install`.
It accepts the following specifiers:
- `%e`: entry token
- `%v`: kernel version being installed
- Several common specifiers
The default is %e-%v, which is the current behavior. With
`entry_name_format=%M_%A_%a`, an OS with `IMAGE_ID=SimonOS` and
`IMAGE_VERSION=1.0` would get moved to `SimonOS_1.0_x86-64.efi`.
The resolved name is exported as `$KERNEL_INSTALL_ENTRY_NAME` for shell
plugins. Both `90-uki-copy.install` and `90-loaderentry.install` are
updated to use it, with a fallback to `$ENTRY_TOKEN-$KERNEL_VERSION` for
compatibility with older kernel-install versions.
compare_boot_ids() (from 262299dccb) skips
the tail entry realtime timestamp ordering when the compared locations
are from two different machines (and hence have two different machine
IDs) - in that case we fall through to a realtime comparison between the
two locations. This works fine except for journals that were imported
via systemd-journal-remote - the machine ID in the journal header is set
to the receiving machine's ID instead of the original one; but the
original machine ID is kept in the _MACHINE_ID= field.
This, however, defeats the check in compare_boot_ids() so we treat the
remote journals as local ones and try to compare the boots based on
their tail entry realtime timestamps, which results in grouping the
entries by their boot ID instead of interleaving them by their realtime
timestamp.
Given that we (transitively) call journal_file_read_tail_timestamp()
from compare_boot_ids(), where we both (re)set the newest_machine_id
field to the machine ID from the journal header and have the tail entry
at hand, let's try to get the _MACHINE_ID= field from the tail entry and
set newest_machine_id to that if it's present and valid. This way
compare_boot_ids() will correctly determine that the entries are from
two different machines and compare_locations() eventually falls through
to the entry realtime check, so we end up with properly interleaved
entries instead of entries grouped by boot ID.
The performance impact should be pretty negligible in vast majority of
cases, as we'd do this only when:
- opening a new file
- comparing entries across different boots
- advancing to a next location
The first case is a one-time thing only per journal. The second and
third case might call this a bit more frequently, but we fall through to
the _MACHINE_ID= parsing only if the journal file got a new entry since
the last check. The most significant impact would be on journals that
are being actively written into, but even then iterating through the
handful of typical fields is negligible compared to what the rest of the
function does. Also, since 8f8ab4bf98 the
journal_file_read_tail_timestamp() calls are ratelimited when walking
through the journal, lessening the impact even further.
Resolves: #34169
A UKI might have a version from .osrelease which is not tied to the
kernel version. Pretty print the .uname section too, if present.
type: Boot Loader Specification Type #2 (UKI, .efi)
title: Debian GNU/Linux 13 (trixie) (34.186) (selected)
id: debian-trixie_34.186_x86-64.efi
source: /boot/efi//EFI/Linux/debian-trixie_34.186_x86-64.efi (on the EFI System Partition)
sort-key: debian-trixie
version: 34.186
uname: 6.12.100+deb13-amd64
linux: /boot/efi//EFI/Linux/debian-trixie_34.186_x86-64.efi
profile: 0
We currently blindly send the configured reload signal (e.g. SIGHUP or
SIGUSR1) to the main service PID for Type=notify-reload units, even if
the service hasn't installed a userspace handler. This can lead to
invoking unintended default behavior (typically process termination) in
daemons which later deprecate and remove their reload handler.
This is a real problem we have seen in production on multiple occasions.
In one particularly egregious instance, a production distributed storage
service had a large percentage of its nodes all terminate at once when
sent a reload signal. In this case a signal handler had been removed,
but another place still sending the signal was missed.
To mitigate this, introduce two new checks for Type=notify-reload
services:
1. On READY=1: When the service first sends READY=1 during initial
startup, we check for the handler. If it's missing, the service startup
is failed with SERVICE_FAILURE_PROTOCOL. This enforces the contract that
services *must* install their handler before signalling readiness.
2. On reload: Immediately before sending the reload signal, we check
again. If the handler is now missing, we log a warning but still send
the signal. This preserves the requested operator action while making
the missing handler visible.
The startup check catches definite static misconfiguration before the
service is accepted as ready. The reload check is advisory because the
service has already started successfully, but warns if its handler later
disappears. Together these provide a best-effort safety net while
preserving the agreed reload semantics.
pretty_boot_time() and acquire_host_info() only feed the SVG header.
Do not query them when plot output is rendered as table or JSON.
Follow-up for ff46b2f97c.
Test that custom entry_name_format=%e_%v produces correctly-named BLS
loader entries (the-token_1.1.4.conf) and UKI files (the-token_1.1.5.efi),
verifying that the full add/remove cycle works with the new entry name.
Replace the hardcoded $ENTRY_TOKEN-$KERNEL_VERSION pattern with
$KERNEL_INSTALL_ENTRY_NAME for the BLS Type#1 loader entry filename.
Falls back to $ENTRY_TOKEN-$KERNEL_VERSION when the variable is not set.
Replace the hardcoded $ENTRY_TOKEN-$KERNEL_VERSION pattern with
$KERNEL_INSTALL_ENTRY_NAME, which is resolved by kernel-install from the
entry_name_format= setting. Falls back to $ENTRY_TOKEN-$KERNEL_VERSION
when the variable is not set, for compatibility with older kernel-install.
Read entry_name_format= from install.conf (or KERNEL_INSTALL_ENTRY_NAME_FORMAT
env var) and resolve it using specifier_printf() to produce a configurable entry
name. The resolved name is exported as KERNEL_INSTALL_ENTRY_NAME for plugins.
Override specifiers:
%e - entry token
%m - machine ID (kernel-install's resolved machine ID)
%v - kernel version being installed (overrides standard %v)
All COMMON_SYSTEM_SPECIFIERS are also available (%a for architecture, %A for
IMAGE_VERSION, %M for IMAGE_ID, etc.).
Default format is "%e-%v", preserving the existing $ENTRY_TOKEN-$KERNEL_VERSION
behavior.
Extend load_kernel_install_conf_at() to parse the new entry_name_format=
setting from install.conf. This plumbing only, no behavioral change yet.
Existing callers pass NULL for the new parameter.
The entry_name_format= setting will allow configuring the boot entry
filename format using specifiers, replacing the current hardcoded
$ENTRY_TOKEN-$KERNEL_VERSION pattern. This is useful for image-based OS
builds (e.g. via mkosi, or image-builder) that require versioned UKI
filenames in the ESP for sysupdate A/B updates.
Having this configuration option allows to do that directly at build
time without having to side-step kernel-install and moving files around
manually.
CurrentSymlink= accepts a symlink path, but the parser used NAME_MAX
for specifier expansion. Absolute and target-relative paths can be
longer than one filename component while still staying below PATH_MAX.
Reproducer: configure CurrentSymlink= to a path longer than NAME_MAX
and run systemd-sysupdate list with debug logging.
Before: sysupdate logged that CurrentSymlink= specifier expansion
failed and ignored the setting.
After: the transfer parses cleanly and list emits no CurrentSymlink=
specifier expansion warning.
Follow-up for: 43cc7a3ef4
138829b784 moved Documentation=, AppStream=, and related URL parsing
into sysupdate-config.c, but kept NAME_MAX as the specifier expansion
limit. These settings are URLs, not filenames.
Reproducer: configure a feature Documentation= URL longer than
NAME_MAX and run systemd-sysupdate features FEATURE.
Before: the URL was ignored after ENAMETOOLONG during specifier
expansion.
After: features FEATURE prints the long Documentation URL.
Follow-up for: 138829b784
systemd drop-ins use an empty assignment to reset list and scalar
settings. MinVersion= and ProtectVersion= treated an empty assignment
as an invalid version and kept the earlier value active.
Reproducer: define MinVersion=v2 and ProtectVersion=v1, then add a
drop-in with empty MinVersion= and ProtectVersion= assignments.
Before: sysupdate logged invalid-version warnings for the empty values.
After: list logs no invalid-version warnings and shows both v1 and v2.
Follow-up for: 43cc7a3ef4
sysupdate.d(5) documents PartitionFlags= as a hexadecimal integer, and
sysupdate patterns format partition flags with PRIx64. The parser used
safe_atou64(), which rejected values such as abcdef.
Reproducer: set PartitionFlags=abcdef in a partition target transfer
and run systemd-sysupdate list with debug logging.
Before: sysupdate logged:
Failed to parse partition flags, ignoring: abcdef
After: the transfer parses cleanly and list emits no partition-flags
parse warning.
Follow-up for: 43cc7a3ef4
48fff6a27a switched filesystem transfers to predictable
.sysupdate.partial.* names, but transfer_remove_temporary()
still only scanned the legacy .# prefix.
Remove the current partial prefix there too. Pending instances
are deliberately left for transfer_vacuum(), so ProtectVersion=
can still spare protected pending versions.
Follow-up for: 48fff6a27a
sysupdate.d(5) documents linux-generic as the default
MatchPartitionType= for partition targets. After 10eaca4159,
omitting the option left partition_type_set false and update failed
before acquiring the image.
Reproducer: create a partition target transfer without
MatchPartitionType= and run systemd-sysupdate update.
Before: update failed with:
Partition type must be set for partition targets.
After: update succeeds on a linux-generic slot and installs the
version, leaving the GPT label as part-v1.
Follow-up for: 10eaca4159
Since 262299dccb compare_locations() falls
through to a realtime-based comparison in compare_boot_ids() if the
compared entries have different boot IDs (and seqnum IDs). This can,
however, cause us to pick up the wrong entry/file on systems with
unreliable/missing RTC, where early-boot entries of a boot might have
earlier realtime timestamp compared to the previous boot.
For example, here's a minimal reproducer consisting of two journal files
containing three entries from two separate boot IDs:
$ journalctl --file one.journal -o export
__CURSOR=s=ba37c62fcacf406aad088166c4963cd1;i=1;b=c220efc91dd440deb89d82c98ddb11a5;m=186a0;t=1e8480;x=39d4323a8e5a248d
__REALTIME_TIMESTAMP=2000000
__MONOTONIC_TIMESTAMP=100000
__SEQNUM=1
__SEQNUM_ID=ba37c62fcacf406aad088166c4963cd1
_BOOT_ID=c220efc91dd440deb89d82c98ddb11a5
NUMBER=1
$ journalctl --file two.journal -o export
__CURSOR=s=9d15197efcc54d8ca5d6e7cce28373e2;i=1;b=686dc35e78f64b6ba126018ab6e8bae9;m=186a0;t=f4240;x=57db1c7bd38b37ed
__REALTIME_TIMESTAMP=1000000
__MONOTONIC_TIMESTAMP=100000
__SEQNUM=1
__SEQNUM_ID=9d15197efcc54d8ca5d6e7cce28373e2
_BOOT_ID=686dc35e78f64b6ba126018ab6e8bae9
NUMBER=2
__CURSOR=s=9d15197efcc54d8ca5d6e7cce28373e2;i=2;b=686dc35e78f64b6ba126018ab6e8bae9;m=30d40;t=2dc6c0;x=3513732159948113
__REALTIME_TIMESTAMP=3000000
__MONOTONIC_TIMESTAMP=200000
__SEQNUM=2
__SEQNUM_ID=9d15197efcc54d8ca5d6e7cce28373e2
_BOOT_ID=686dc35e78f64b6ba126018ab6e8bae9
NUMBER=3
The first file (one.journal) contains a single entry from the first boot
(c220efc91dd440deb89d82c98ddb11a5) and a realtime timestamp of 2s. The
second file (two.journal) contains two entries from the second boot
(686dc35e78f64b6ba126018ab6e8bae9) - the first entry has a realtime
timestamp of 1s (i.e. _earlier_ than the tail timestamp of the first
boot), and the second entry has a realtime timestamp of 3s (i.e. after
a time correction).
This works fine for forward iteration, but seeking directly to the
second entry is when things go south:
First entry:
$ python3 <<EOF
c = "s=ba37c62fcacf406aad088166c4963cd1;i=1;b=c220efc91dd440deb89d82c98ddb11a5;m=186a0;t=1e8480;x=39d4323a8e5a248d"
from systemd import journal; j = journal.Reader(path="."); j.seek_cursor(c); j.get_next(); print(j.test_cursor(c))
EOF
True
Second entry:
$ python3 <<EOF
c = "s=9d15197efcc54d8ca5d6e7cce28373e2;i=1;b=686dc35e78f64b6ba126018ab6e8bae9;m=186a0;t=f4240;x=57db1c7bd38b37ed"
from systemd import journal; j = journal.Reader(path="."); j.seek_cursor(c); j.get_next(); print(j.test_cursor(c))
EOF
False
That's because:
- we call sd_journal_seek_cursor(...) and parse it (seqnum, seqnum_id,
boot_id, realtime, ...)
- we call sd_journal_next() -> real_journal_next() which iterates over
all open journal files to find all candidate entries and pick the
best one
- for each journal file we call next_beyond_location()
- for next_beyond_location(one.journal) we get:
- last_direction is _DIRECTION_INVALID, so we call find_location_with_matches()
- we fall all the way through to journal_file_move_to_entry_by_realtime()
- here we try to find the first entry with realtime >= 1s; this file
has only one entry with realtime = 2s which matches
- we got a match and new_file is unset, so we set it to one.journal
- for next_beyond_location(two.journal) we get:
- we fall through to find_location_with_matches()
- here we have a matching seqnum, so we call journal_file_move_to_entry_by_seqnum()
- we got another match but new_file is set, so we call compare_locations(two.journal, one.journal):
- we fall through to compare_boot_ids()
- we get the tail timestamps of both journal files and return
CMP(second boot, first boot) -> CMP(3s, 2s) = 1
- this bubbles up back to real_journal_next() where found = false, so
new_file remains one.journal (the wrong journal file)
- this is followed by set_location(one.journal), which makes the
following sd_journal_test_cursor() call fail, as the
boot_id/seqnum_id/... of the current position don't match with the
cursor
To fix this, let's track the journal file that holds the exact cursor
match, and override the chosen candidate if needed once we process all
open journals.
Resolves: #31516
shallow_join_strv() shallow-copies the first strv into a temporary
array, then uses strv_contains() while appending entries from the
second strv. That helper requires a NUL-terminated strv.
Keep the temporary array terminated before each lookup. After an
append, the previous terminator is overwritten, so postponing the
NULL write until after the loop would let the next strv_contains()
scan past the initialized entries.
This is a correctness fix, not an optimization: it avoids a
potential out-of-bounds read when deduplicating the second strv.
Commit 52ceba53d3 made multipart replies append in receive
order, but each append walked the chain from the head.
Cache the tail on the chain head, so new fragments can be
linked directly. The visible next-pointer chain and order stay
unchanged.
Follow-up for: 52ceba53d3
This change adds explicit D-Bus policy rules to allow calling the
Freeze and Thaw methods on the systemd interfaces.
- Add D-Bus policy rules for Manager interface FreezeUnit/ThawUnit methods
- Add D-Bus policy rules for Unit interface Freeze/Thaw methods
- --strict=/--full moved to the OPTION_LONG() parser framework that
replaced getopt in the meantime
- the is-supported commit is dropped, main grew that verb independently.
That also gets rid of the early tpm2_support() gate that broke CI back
then, and TEST-70-TPM2 runs with a software TPM these days anyway
- special_glyph() → glyph() renames
- the log table PCR filter needs an EVENT_LOG_RECORD_IS_PCR() guard now
that the event log has NV-index records
We currently blindly send the configured reload signal (e.g. SIGHUP or
SIGUSR1) to the main service PID for Type=notify-reload units, even if
the service hasn't installed a userspace handler. This can lead to
invoking unintended default behavior (typically process termination) in
daemons which later deprecate and remove their reload handler.
This is a real problem we have seen in production on multiple occasions.
In one particularly egregious instance, a production distributed storage
service had a large percentage of its nodes all terminate at once when
sent a reload signal. In this case a signal handler had been removed, but
another place still sending the signal was missed.
To mitigate this, introduce signal handler validation for
Type=notify-reload services. We check both SigCgt (traditional handlers
via sigaction) and SigBlk (blocked signals for signalfd) to detect valid
handler configurations:
1. On first READY=1: When the service first sends READY=1 during initial
startup, we check for the handler. If it's missing, the service
startup is aborted with Result=protocol.
2. On reload: Immediately before sending the reload signal, we check
again. If the handler is missing, we log a warning but still send
the signal.
The rationale is that a missing handler at first startup represents a
definite misconfiguration that should be caught early. A missing handler
at reload time is warned about, but we defer to the operator's judgement
since the service already started successfully.
These checks are a best-effort, pragmatic safety net. They are naturally
racy, but in practice they prevent the common class of bugs from static
misconfiguration or software updates.
Add new pidref_has_sigcgt() and pidref_has_sigblk() helpers. These read
the SigCgt and SigBlk bitmasks from /proc/<pid>/status to detect either
traditional signal handlers installed via sigaction(2), or blocked signals
that are typically handled via signalfd(2), such as through
sd_event_add_signal().
Both mechanisms represent valid ways to handle a signal without invoking
the default action. Share the parsing and pidref verification between the
two public helpers.