When multiple swap devices exist, prefer one with enough free space
to hold the hibernation image over one that cannot, regardless of
priority. If no swap can fit, fall back to priority-first selection.
This avoids deterministically failing hibernation when the
highest-priority swap is too small but a lower-priority one fits.
Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
Co-developed-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This adds the following:
1. systemd-report gains a new --sign= option, taking a boolean. If true,
this makes systemd-report generate + systemd-report upload generate a
signed report, instead of a regular one. The signatures are collected
from Varlink-based backends.
2. One such backend is added which does a simple Ed21159 based signing
scheme.
3. this adds a new metrics source which just reports text files
symlinked into a special dir as metrics. This is used to report the
Ed21159 public key as metric, by default, if it exists.
4. finally, systemd-report itself is turned into a varlink service. this
is useful for example for extracting a report from a system coming in
via the varlink/http bridge.
I thought a long time about the format of signing of reports. Initially
i intended to do this like homed's user record signing, i.e. require
normalization of the record, then normalize the record, and write it out
in dense form, since the result. Finally insert the resulting hash into
the user record itself. People have pointed me to the inherent messiness
of signing JSON this way though, as it requires any participant that
wishes to sign/authenticate records this way to implement the exact same
normalization/formatting rules, and in particular in the area of
floating point numbers (of which metrics presumably will have many) this
is quite problematic.
This signing hence goes a different way. instead of expecting
signer+verifier to independently come to the same normalized text form
of the json data, let's instead output a JSON-SEQ sequence, where the
first object is the report, and any subsequent objects are one signature
each. the signatures are supposed to cover the precise binary
representation of the first element in the JSON-SEQ stream. (i.e. from
the RS to the NL).
or in other words: a verifier would receive the JSON-SEQ stream, split
it up before each RS. Then it would leave object 1 unparsed for the
moment, and parse objects 2…n. It would then authenticate object 1's
precise binary representation with objects 2…n. Once that checks out, it
would parse object 1, and use it as report.
Since a1f4fd3876 udev processes
run in an 'udev' subcgroup, to avoid killing control processes
when clearing workers. But the main process is still in the same
cgroup, so the atomic cgroup.kill cannot be used.
If the main subcgroup exists, try to create a sibling 'workers'
cgroup, and use it for workers processes, and use cgroup.kill if
available.
This is especially useful as rules can spawn arbitrary programs/scripts
and TasksMax= is set to unlimited.
Acquiring a LUO session from /dev/liveupdate requires privileges, and
also the device is a single-owner driver so only a single process can
open it at any given time.
Add a LUOSession= service settings that allows units running without
privileges to get a session assigned to them.
The kernel imposes a 64 chars limit on session names, which is too short
to avoid clashes, so derive a hash from joining the unit name with the
parameter name, that way two units using the same setting don't clash.
IMDS access requires networking, hence we need to run after
network-online.target. Everything else would be racy and result in
likely timeouts, because we might try to contact the network too early.
This reverts commit 3f33f4d057.
This fixes 2 issues with `ukify`:
`config_list_prepend` needs a list. The 2 more used entries are
specially treated (parsed into a list) and therefore happen to work, but
trying to set `UKI/Firmware` throws a type error, as it is passed as
just a string.
The `UKI/SBAT` config entry is a list but used the default `config_push`
value which sets it if unset (which also failed since its default of
`[]` is not `None`) - change this to `config_list_prepend`.
I did consider adding a `config_list_append`, but as far as I'm aware,
the order of sbat entries should not change the outcome anyway.
OpenSSL 4 broke ABI, so we need to look for both SONAMEs.
Try libcrypto.so.3 first, and fallback to libcrypto.so.4,
so that the older and more stable version is used if both
are installed, giving distros time to fix regressions.
Cover the full preference order (SHA256 > SHA384 > SHA512 > SHA1), the
single-bank cases and the -EOPNOTSUPP path. Also test the _legacy()
variant: a set with SHA384/SHA512 active but no SHA256 must resolve to
SHA1, not SHA384, pinning down the backwards-compatibility behavior that
keeps legacy sealed secrets unsealable.
Co-developed-by: Claude Opus 4.8 <noreply@anthropic.com>
tpm2_get_best_pcr_bank() only ever considered SHA256 and SHA1, both in
the firmware-reported LoaderTpm2ActivePcrBanks path and in the
capability-based guesswork. On a TPM whose only active bank is SHA384 it
returned -EOPNOTSUPP, which for units ordered before sysinit.target
(systemd-pcrextend/systemd-pcrproduct.service) propagates and hangs the
boot, even with --graceful (that only covers "no TPM present").
Introduce a preference table (SHA256 > SHA384 > SHA512 > SHA1) and select
from it. SHA256 stays top for backwards compatibility; SHA384 is
preferred over SHA512 as it produces a shorter digest and so uses less of
the bounded TPM event log; SHA1 is the last resort. SM3/SHA3 are excluded
as we cannot compute those digests in software.
The legacy unseal path in tpm2_unseal() re-derives the bank for old
enrollments that did not record one (pcr_bank == UINT16_MAX). Those were
sealed by code that only picked SHA256 or SHA1, so the wider preference
could now re-derive a stronger bank the secret was never bound to and
silently fail to unseal. To stay compatible, the selection takes the
preference list as a parameter, and a _legacy() variant restricted to
{SHA256, SHA1} is used for that path; fresh-sealing callers keep the full
preference.
Co-developed-by: Claude Opus 4.8 <noreply@anthropic.com>
The binary catalog reader trusted two values straight from a (possibly
hostile) database: open_mmap() summed header_size + n_items *
catalog_item_size in uint64 with no overflow check, and find_id() added
the matched item's offset to the map base with no upper bound. Reachable
through sd_journal_get_catalog() with $SYSTEMD_CATALOG set, this let
catalog_get()/catalog_list() strdup() a string starting outside the
mapping. Guard the size math with MUL_SAFE/INC_SAFE and reject item
offsets that fall outside the file.
A single incorrect password attempt from the user would be tried
twice, because the first failed try would think it was the
try_discover_key method that failed, and would invalidate that instead
of the actual failed password. This resulted in the user being
prompted one fewer time than they should be.
Fixes#38495
journalctl needs gcrypt to set up the journal sealing
keys and other such operations, but gets no dlopen note
with the dependency. Add the dlopen macro with a recommends
level so it can be skipped, but gets pulled in by default
on user/desktop systems.
Follow-up for 590e226437
These signatures can be provided by arbitrary Varlink backends symlinked
into /run/systemd/report/sign.
Also add a proper Varlink IDL for the upload interface, since we are
extended it now.
In Meta production we have been considering using journald more widely
for some time. One of the blockers to doing that which I have noticed is
that often journald seems to have vastly less data after lockups/power
failures compared to plain files, which is not great when debugging
outages.
On small write rates this tends to be hard to reproduce, but when
writing thousands of messages a second, an unclean shutdown can result
in the end result being an active journal file with a header that
records an arena larger than the data that actually reached disk. What
happens is then that journalctl then discards the entire file(!),
completely ignoring that there is a huge amount of data which is
actually perfectly readable.
The reason for that is that the journal header is updated on every
append, while the file size and newly written arena contents are only
made durable on the filesystem's own schedule. After a crash, the header
can therefore describe writes which were logically completed by journald
but whose backing data or file metadata never reached disk.
Take the following example of how this can happen at high log rates:
1. journald appends objects into an mmap()ed arena, periodically growing
the file with fallocate() in FILE_SIZE_INCREASE (8M) steps and
advancing the header's arena_size tail pointers as it goes along.
2. The header is dirtied on every append, and its arena_size is advanced
at each fallocate(). It is, from the kernel's perspective, an
ordinary data page and is only made durable by the kernel's periodic
page cache writeback on its own schedule. The file's length, by
contrast, is metadata, made durable only when the filesystem commits
a transaction (or on an fsync(), which journald does not issue
between sync intervals).
3. journald marks journals NOCOW, so the header's data block is
overwritten in place and is decoupled from the size metadata. Nothing
orders the two with respect to each other. Writeback therefore can
routinely persist a header whose arena_size has run ahead of the file
length recorded on disk.
4. Power is lost. On the next boot the persisted header reflects an
arena_size and tail pointers which have been advanced for appends.
However their payload and the file metadata were never committed, so
header_size + arena_size now points well past the end of the file as
it exists on disk.
5. journal_file_verify_header() then rejects this with -ENODATA:
if (... || header_size + arena_size > (uint64_t) f->last_stat.st_size)
return -ENODATA;
That is correct when opening for writing, because we must not append to
a file whose recorded state we cannot trust, and the caller must rotate
it away. But the same check also runs on read only opens, where it is
actively harmful. In the case of journalctl, the entire file is skipped,
even though the data hash table, the field hash table, and the head of
the array all are present and fully intact, and the great majority of
entries are physically present. In fact, only a very small part of the
most recently written tail is missing, but everything before is
readable. This results in mistakenly rejecting the entire file as
corrupt.
This happens extremely frequently on machines with high write rates
during power cuts or lockups. In testing writing ~7500 msg/s through
journald and then cutting power, I reproduced it in ten out of ten
attempts across different machines.
In each case, the header was left claiming ~296M of arena while only
~192-208M had reached disk. In this case, journalctl reports that it has
recovered 0 of ~335000 messages. Whether a given crash trips the
condition depends on where it falls relative to the header's writeback,
but when it does, the loss today is total. After this patch the vast
majority of messages can be retrieved.
Let's fix this by keeping the rejection for writing, but for read-only
opens, let's just clamp the arena to the real file size and skip the
consistency checks on the now unreliable tail pointers. The reader will
walk the entry array chain from its intact head and stop at the
truncation point by the bounds check that already exists, so there's no
need to do any more than that there.
You might also wonder, why not address this on the write side? That
would be astronomically expensive and require an fsync() after every
fallocate().
In terms of improvements, when reading from a file affected in the way
described above, previously journalctl recovers 0 entries, and now it
can recover all actually intact entries.
On i386 with musl and libucontext, many tests crash with SIGSEGV
when using fibers implemented on top of ucontext APIs.
For example:
```
929/1498 libsystemd - systemd:test-event-future FAIL 0.97s killed by signal 11 SIGSEGV
/* test_sd_event_run_timer */
run-suspend-timer: Scheduling fiber
/home/pmos/build/src/systemd-261-rc3/tools/test-crash-trace.sh: line 18: 19994 Segmentation fault (core dumped) "$@"
===== exit 139 — replaying under gdb =====
/* test_sd_event_run_timer */
run-suspend-timer: Scheduling fiber
Program received signal SIGSEGV, Segmentation fault.
0xf7d68be3 in sd_event_add_time_relative (e=0xf7ffd3f0, ret=0xf7b01fa0, clock=1, usec=10000, accuracy=0, callback=0x565564ad <inner_timer_handler>, userdata=0xf7b01fa4) at ../src/libsystemd/sd-event/sd-event.c:1455
1455 void *userdata) {
Thread 1 (process 20485 "test-event-futu"):
#0 0xf7d68be3 in sd_event_add_time_relative (e=0xf7ffd3f0, ret=0xf7b01fa0, clock=1, usec=10000, accuracy=0, callback=0x565564ad <inner_timer_handler>, userdata=0xf7b01fa4) at ../src/libsystemd/sd-event/sd-event.c:1455
t = 17868423377094431728
r = <optimized out>
#1 0x565574c7 in sd_event_run_timer_fiber (userdata=0x0) at ../src/libsystemd/sd-event/test-event-future.c:329
inner = 0xf7ffd3f0
source = 0x0
counter = 0
r = <optimized out>
#2 0xf7dab308 in fiber_entry_point () at ../src/libsystemd/sd-future/fiber.c:208
_cleanup_log_unset_prefix_5 = 0x0
__unique_prefix_c6 = 0x5655bb30
f = 0xf7b06840
__func__ = "fiber_entry_point"
fake_stack_save = <optimized out>
#3 0xf7b032ae in setcontext () from /lib/libucontext.so.1
No symbol table info available.
#4 0x00000000 in ?? ()
No symbol table info available.
```
Building systemd with -mstackrealign makes all observed failures
disappear, suggesting a stack alignment issue in the interaction
between compiler-generated code and the ucontext implementation.
This resembles historical stack alignment issues in glibc's i386
makecontext() implementation, which required fixes to preserve the
expected stack alignment.
As a workaround, add -mstackrealign when building on i386 with musl
and libucontext.
Suggested-by: ChatGPT (OpenAI)
Acquiring a LUO session from /dev/liveupdate requires privileges,
and also the device is a single-owner driver so only a single
process can open it at any given time.
Add a LUOSession= service settings that allows units running
without privileges to get a session assigned to them.
The kernel imposes a 64 chars limit on session names, which is
too short to avoid clashes, so derive a hash from joining the
unit name with the parameter name, that way two units using
the same setting don't clash.
No longer exists since latest spec changes, as the binaries have moved
to another package so it doesn't get autogenerated anymore
Follow-up for db9d7265f5
The previous policy was primarily written from a standpoint
that AI models are not very good and we didn't wanna waste any
time reviewing PRs generated by AI. Now that AI models have become
actually good and their output is just as good as regular contributions,
let's stop requiring the disclosure as its pointless to still have it,
it doesn't really matter anymore whether a patch was written with or
without
AI. It's up to the author to make sure they're not wasting our time by
submitting unreviewed, untested code upstream, regardless of whether
that
code is written by an AI or not.
The new policy is inspired by https://github.com/lxc/incus/pull/3506,
with
various removals to be less adverse to the usage of AI.
2026-06-17T10:11:08.3789573Z Untracked files:
2026-06-17T10:11:08.3790064Z (use "git add <file>..." to include in what will be committed)
2026-06-17T10:11:08.3790566Z systemd-network.pre
2026-06-17T10:11:08.3790908Z systemd-resolve.pre
Follow-up for db9d7265f5
The previous policy was primarily written from a standpoint
that AI models are not very good and we didn't wanna waste any
time reviewing PRs generated by AI. Now that AI models have become
actually good and their output is just as good as regular contributions,
let's stop requiring the disclosure as its pointless to still have it,
it doesn't really matter anymore whether a patch was written with or without
AI. It's up to the author to make sure they're not wasting our time by
submitting unreviewed, untested code upstream, regardless of whether that
code is written by an AI or not.
The new policy is inspired by https://github.com/lxc/incus/pull/3506, with
various removals to be less adverse to the usage of AI.
No longer exists since latest spec changes, as the binaries have
moved to another package so it doesn't get autogenerated anymore
Follow-up for db9d7265f5