When passing --size=auto to repart, it will pre-calculate the image size and
resize the image to that size before partitioning. Currently, that fails when
passing a large grain size, complaining that the auto-sized image is too small
to fit the data.
The reason for this is that the current code simply assumes the GPT metadata
size taken away from the usable size by fdisk is static (1044KiB), when it
actually is more complicated than that:
There's two ranges of GPT metadata: One at the beginning of the image, and one
at the end of the image. And there's the first usable block that is defined by
fdisk when creating the partition table.
The static value of 1044KiB usually works, because fdisk sets the first usable
block to 1MiB (so 1024KiB), leaving 20KiB of leeway for the secondary GPT at
the end of the image.
Now as soon as the first partition starts at an offset higher than 1024KiB, we
lose the 20KiB leeway for the secondary GPT, and the partitions will no longer
fit.
What we should do, is first of all round up to the grain size instead of 4096
(as that's the minimum offset our first partition will start at), and second of
all properly subtract the secondary GPT at the end.
Also confirm we don't regress on this anymore by adding a test that uses a 2MiB
grain size, breaking the old code.
The "Donate to preceding partition" logic is dead code since commit
https://github.com/jonas2515/systemd/commit/19903a433507897449c086b72abb5e133e431336
("repart: split out context_grow_partition_one()").
context_grow_partition_one() gets passed a free area and a partition,
and it has
an early-return check to ensure the partition it got passed belongs to
the free
area it got passed. That means we compare the FreeArea a to the FreeArea
a->after->allocated_to_area, which always yields FALSE.
Fix the behavior of donating any left over space to the preceding
partition
by adding that partition to the loop below (and relying on the
partitions list
being ordered according to physical partition offsets).
Since this behavior is not that easy to trigger, mention how to trigger
it in a
comment, and add a test for it as well.
When no prefix is specified, portablectl inspect first tries the
prefix derived from the image name. This keeps inspect aligned with
attach behavior.
If that lookup finds no matching units, retry with validated
PORTABLE_PREFIXES read from the image os-release. This makes inspect
work for images whose filename does not match their portable service
prefix.
Keep metadata error handling explicit so request-construction failures
are not logged twice, while sd_bus_call() failures still include the
inspect context.
Add a TEST-29-PORTABLE regression case for a directory image whose
name does not match the portable service prefix.
Fixes#37296.
Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
systemd-sysext handles the sysupdate notification hook,
but the hook refreshes both sysexts and confexts.
Build the refresh context for each image class so confext settings,
such as Mutable=yes, are applied when confexts are refreshed.
Add regression coverage for the mutable confext overlay.
Fixes#42873
Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
Reproducer:
unit=run-wait-rae-$(date +%s)
sudo timeout 3s systemd-run --wait --remain-after-exit \
--unit="$unit" /bin/true
echo $?
systemctl is-active "$unit.service"
Before, the command timed out with exit status 124 while the service
stayed active. --wait waits for deactivation, but RemainAfterExit=yes
keeps the service active after the command exits.
Follow-up for 2a453c2ee3
Reproducer:
sudo systemd-run --wait --verbose --json=short /bin/echo hi
Before, systemd-run printed JSON metadata to stdout while --verbose
also spawned journalctl output on stdout. The resulting stream mixed JSON
with journal lines, so reject the conflicting options.
Follow-up for 744ca8f616
Reproducer:
sudo systemd-run --scope --json=short /bin/echo hi
Before, systemd-run printed JSON metadata to stdout and then executed
the scope command on the same stdout. The combined stream was not valid
JSON, so reject --json= in scope mode.
Follow-up for fe5a6c47af
Reproducer:
unit=run-json-trigger-$(date +%s)
sudo systemd-run --json=short --unit="$unit" \
--on-active=30s \
/bin/true
Before, trigger mode accepted --json=short but printed only human-readable
"Running timer as unit" and "Will run service" lines. Reject the option
until trigger mode has structured output.
Follow-up for fe5a6c47af
Reproducer:
sudo systemd-run --wait --pipe --json=short /bin/echo hi
Before, systemd-run wrote its JSON metadata to stdout and then passed
the command stdout through the same stream. The combined output was not
valid machine-readable JSON, so reject the conflicting modes.
Follow-up for fe5a6c47af
Reproducer:
sudo systemd-run --scope --uid=nobody /usr/bin/id
Before, the command ran as nobody but kept the caller supplementary
root group, for example groups=65534(nogroup),0(root). Scope mode
performs the uid/gid switch locally, so initialize the target user groups
before dropping privileges.
Follow-up for 4de33e7f32
Reproducer:
unit=run-explicit-path-$(date +%s)
systemd-run --unit="$unit.path" \
--path-property=PathExists=/tmp \
/bin/true
echo $?
Before, an explicit .path unit name was not recognized as the
trigger unit. It was mangled again as a service name, so PID 1 rejected
the transient request with an already-loaded unit conflict.
Follow-up for d59ef3e243
Reproducer:
sudo systemd-run --scope --ignore-failure /bin/false
echo $?
Before, the option was accepted but had no effect because scope mode
executes the command locally after creating the scope. The flag is only
encoded into service ExecStart properties, so accept it only where it can
be applied.
Follow-up for 1072d94731
Reproducer:
unit=run-nb-trigger-$(date +%s)
systemd-run --no-block --collect --unit="$unit" \
--socket-property=ListenStream=/proc/systemd-run-repro/socket \
/usr/bin/true
echo $?
Before, systemd-run still waited for the trigger unit job and
propagated the socket start failure. With --no-block it should only
verify and enqueue the request, as the service path already does.
Follow-up for 3d161f991e
Treat an empty volume name alongside other fields as unset instead of
rejecting it as invalid.
Example use case:
```
EncryptedVolume=:none:discard
```
In this case, the volume name is not specified so it can be generated as
luks-UUID.
From the docs:
> EncryptedVolume=
> Specifies how the encrypted partition should be set up. Takes at least
one and at most four fields separated with a colon (":"). The first
field specifies the encrypted volume name under /dev/mapper/. If not
specified, "luks-UUID" will be used where "UUID" is the LUKS UUID.
The "Donate to preceding partition" logic is dead code since commit
19903a4 ("repart: split out context_grow_partition_one()").
context_grow_partition_one() gets passed a free area and a partition, and it has
an early-return check to ensure the partition it got passed belongs to the free
area it got passed. That means we compare the FreeArea a to the FreeArea
a->after->allocated_to_area, which always yields FALSE.
Fix the behavior of donating any left over space to the preceding partition
by adding that partition to the loop below (and relying on the partitions list
being ordered according to physical partition offsets).
Since this behavior is not that easy to trigger, mention how to trigger it in a
comment, and add a test for it as well.
The current TPM2 PIN mode is flawed as a compromised TPM directly
exposes
the sealed secret which is the LUKS volume key itself
(https://github.com/systemd/systemd/pull/27502 and
https://github.com/systemd/systemd/issues/37386).
Goal: add Argon2id-based PIN hardening to TPM2 enrollment, making
the TPM a second factor rather than a single point of failure:
1. Password + salt → Argon2id → 512-bit key split into Key1 + Key2
2. Key2 (base64-encoded) is used as the PIN to seal a random secret
in the TPM
3. Key1 + unsealed secret → HKDF-SHA256 → final LUKS volume key
This implementation ensures that if the TPM is compromised, an attacker
still needs the password to derive Key1 and combine it with the unsealed
secret.
The --tpm2-with-pin= option now accepts three values:
- false (no PIN used)
- true (PIN hardened with Argon2id - default)
- "direct" (legacy PIN without Argon2id for backward compatibility)
Argon2id parameters are customizable via:
--tpm2-argon2id-memory=
--tpm2-argon2id-iterations=
--tpm2-argon2id-parallelism=
--tpm2-argon2id-iter-time=
These default to a function of available CPUs and physical memory, with
a benchmark that scales iterations to the target time (default: 2s) and
falls back to ARGON2ID_PARAMETERS_DEFAULT (64 MiB, 8 iter, 4 lanes) when
auto detection fails.
Also if the runtime OpenSSL lacks Argon2id support (< 3.2), the feature
silently falls back to direct PIN mode with a warning.
Added includes:
- src/cryptenroll/cryptenroll.c: cpu-set-util.h, limits-util.h,
time-util.h
for Argon2id benchmark auto-tuning (cpus_online, physical_memory_scale,
now/usec_t)
- src/cryptenroll/cryptenroll-tpm2.c: crypto-util.h for
Argon2IdParameters
struct in load_volume_key_tpm2()
- src/shared/tpm2-util.h: crypto-util.h for Argon2IdParameters in
tpm2_make_luks2_json() API
- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c: crypto-util.h for
kdf_argon2id_derive()/kdf_hkdf_sha256() on the token unlock path
Currently, --copy-from= copies the paddings in between the source partitions,
as well as the trailing padding that is at the end of the source partition table.
It doesn't copy the leading padding at the start of the source partition table
though.
This seems inconsistent, and likely it was an oversight that the trailing padding
is copied.
Fix that, and add a test to ensure we don't regress.
The --copy-from= argument currently is documented as "copied partitions will have
the same size". This doesn't hold true in the case where a different grain-size is
passed to repart. Because `partition_min/max_size()` currently do rounding, the
size is implicitly rounded to grain size, and therefore partitions are enlarged
to align to grain size whenever possible.
Clarify this behavior and change the manpage, and also add a test for it.
This is a little bit confusing, but grain size is not actually stored in the gpt
metadata. Rather, fdisk's `get_grain_size()` returns an autodiscovered "optimal io
size" value as grain size. This might not actually be the grain size that the
disk we're copying is using.
Since we're setting the padding of the copied partitions using that value from
fdisk, we're rounding the new paddings by fdisk's optimal grain size, which is
usually 1MiB (a lot more then the default 4KiB that we're using otherwise).
Set the grain size here to 1 byte instead, ensuring that the min/max padding set
is exactly the padding that was present before.
Also add a test to confirm the behavior is fixed: The test calls --copy-from= on
an existing disk with 4MiB grain size, and because we pass --grain-size=512, now
no rounding should happen and the paddings should be transferred to exactly the
same size.
Return from argument parsing when --wait-daemon= cannot be parsed.
This matches the other timeout options.
Reproducer:
udevadm trigger --dry-run --wait-daemon=bad
Before:
Failed to parse timeout value 'bad', ignoring: Invalid argument
Follow-up:
2001622c58
The settle command does not define positional arguments.
Reject them during argument parsing instead of silently ignoring
them.
Reproducer:
udevadm settle /no/such/argument
Before:
The command exits successfully and ignores the argument.
Follow-up:
c71509028f
The documentation says --initialized= is ignored when --removed is
specified.
Track --removed separately so it wins regardless of option order.
Reproducer:
udevadm wait --timeout=0 --removed --initialized=no /dev/no-such-test-device
Before:
Timed out for waiting devices being added.
Follow-up:
aa2b0d8d29
The --attr-match= and --attr-nomatch= filters are documented as
FILE[=VALUE], like the trigger filters.
Accept a bare attribute name for sysfs attribute presence matches.
Reproducer:
udevadm info --export-db --attr-match=ifindex
Before:
Expected <ATTR>=<value> instead of 'ifindex'
Follow-up:
a6b4b2fa01
The current TPM2 PIN mode is flawed as a compromised TPM directly exposes
the sealed secret which is the LUKS volume key itself (#27502 and #37386).
Goal: add Argon2id-based PIN hardening to TPM2 enrollment, making
the TPM a second factor rather than a single point of failure:
1. Password + salt -> Argon2id -> 512-bit key split into Key1 + Key2
2. Key2 (base64-encoded) is used as the PIN to seal a random secret
in the TPM
3. Key1 + unsealed secret -> HKDF-SHA256 -> final LUKS volume key
This implementation ensures that if the TPM is compromised, an attacker
still needs the password to derive Key1 and combine it with the unsealed
secret.
The --tpm2-with-pin= option now accepts three values:
- false (no PIN used)
- true (PIN hardened with Argon2id - default)
- "direct" (legacy PIN without Argon2id for backward compatibility)
Argon2id parameters are customizable via:
--tpm2-argon2id-memory=
--tpm2-argon2id-iterations=
--tpm2-argon2id-parallelism=
--tpm2-argon2id-iter-time=
These default to a function of available CPUs and physical memory, with
a benchmark that scales iterations to the target time (default: 2s) and
falls back to ARGON2ID_PARAMETERS_DEFAULT (64 MiB, 8 iter, 4 lanes) when
auto detection fails.
Also if the runtime OpenSSL lacks Argon2id support (< 3.2), the feature
silently falls back to direct PIN mode with a warning.
Added includes:
- src/cryptenroll/cryptenroll.c: cpu-set-util.h, limits-util.h, time-util.h
for Argon2id benchmark auto-tuning (cpus_online, physical_memory_scale,
now/usec_t)
- src/cryptenroll/cryptenroll-tpm2.c: crypto-util.h for Argon2IdParameters
struct in load_volume_key_tpm2()
- src/shared/tpm2-util.h: crypto-util.h for Argon2IdParameters in
tpm2_make_luks2_json() API
- src/shared/tpm2-util.c: limits-util.h, tpm2-util.h for physical_memory() validation
of Argon2id memory cost and function prototypes
- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c: crypto-util.h for
kdf_argon2id_derive()/kdf_hkdf_sha256() on the token unlock path
This PR only sets the default to "relaxed" - I can change the default
to "tofu" if desired. But for that we will also need to update the NEWS
file to ensure everyone is aware of this new default.
---
This PR adds a new `systemd.credentials-boot=` kernel
commandline that allows to control if credentials with
a `null` key are accepted.
The possible options are:
* strict: always insist on tpm encryption
* tofu: allow null encryption in firstboot mode and when no tpm is
available
* relaxed: allow null encryption when sb is off, or no tpm is available
* off: allow null encryption always
The default is `relaxed` which is exactly the behavior we had before.
This replaces the initial idea of using plaintext credentials
at firstboot (thanks to Lennart for this nicer and simpler design).
---
With that we can drop `- firstboot: optionally accept credentials at
firstboot without authentication` from TODO.md
When we have two match patterns, e.g., because the repository layout
changed and we look for old and new style files, we can have the case
that one pattern suggests a descend into a subdir but the other pattern
would be a direct match. This was missed and only the descent was done.
Yet the other way round also has to be covered: We can't just use the
direct match because it might be that it's rejected if the type doesn't
fit (directory vs. regular file). In this case we should still descent.
Remember that we want to descend but continue checking the other
pattern first to maybe get a direct match. Introduce a new combined
return code YES_AND_RETRY to give the full information to the caller
which now can decide to fallback to a descent. While at it, use the
stat info for the directory check. Also add tests to ensure that the
order of patterns doesn't matter and we handle the above corner cases.
While for sysupdate it's fine to consume a large set of all possible
update payloads in a single directory this is not so handy for managing
and serving the update payloads. Since this large update folder is not
where the build output directly gets written to one has to create
copies and later possibly delete this added set of files.
Support matching for filenames in subdirectories by having a new **/
match pattern prefix which matches any number of nested subdirectories
or no subdirectory at all. For simplicity it's only allowed at the start
of a pattern and not a regular wildcard as the rest because the main
use case is to descend into subdirectories and only do the pattern
matching for the basenames. This way one can create a SHA256SUMS file
in the top folder and have it include all update payloads from the
release-specific (or arch-specific) subdirectories. Something similar
was already supported for directory sources where the match pattern can
start with a subdirectory path. Do also support this for SHA256SUMS for
parity while we are at it. Having the new wildcard makes mirroring also
easier because one does not have to follow the exact subdirectory layout
and one can filter by folder instead of by filename. It also makes it
possible to point the same transfer files with the new wildcard to
either a SHA256SUMS file that uses release-specific (or arch-specific)
subdirectories and includes all versions or to a SHA256SUMS file as
generated from mkosi that does not use subdirectories because it only
has files for a single version.
With the upcoming UAPI.16 JSON format we will also be able to encode
subdirectories and it makes sense to add this to SHA256SUMS for being
able to convert them. It also supports custom URLs for each entry which
is more powerful than the (arbitrary) subdirectory feature used here but
subdirectories have the advantage that they don't break mirroring.
This change also fixes the existing subdirectory handling bugs where
everything greater than two subdirectory levels failed to work because
rel_joined instead of de->d_name got used, symlinks were followed, and
we would continue silently on non-ENOENT errors.
Adds dm-clone device setup at boot via a new /etc/clonetab config file,
following the crypttab/veritytab pattern.
- Add systemd-clonesetup-generator to parse /etc/clonetab and generate units.
- Add systemd-clonesetup binary to create/remove dm-clone devices via ioctl.
- Add clonesetup.target for ordering dm-clone activation at boot.
- Add region_size= option in clonetab to configure dm-clone hydration granularity.
- Add clonetab(5) and systemd-clonesetup-generator(8) man pages.
Fixes: https://github.com/systemd/systemd/issues/39500
Commit 9b917abe02 (udev-builtin:
simplify code a bit) changed builtin lookup from copying the first
command word and comparing it with streq() to comparing only the first
command word length with strneq().
That made prefixes such as "k" or "key" resolve to the "keyboard"
builtin, depending on the builtins table order. This was not the
original behavior.
Require the matched builtin name to end at the first command word
boundary so only complete builtin names are accepted. Arguments after
the builtin name continue to work as before.
udevadm info --cleanup-db cleaned links and tags by comparing them
against /run/udev/data, but asserted when that data directory was
missing.
Treat a missing data directory as an empty database, so stale link
and tag entries can be removed without aborting.
Add a database test that runs cleanup-db with links and tags present
but no data directory.
Reproducer:
$ sudo unshare --mount --propagation private sh -c '
mount -t tmpfs tmpfs /run/udev
mkdir -p /run/udev/links/foo
udevadm info --cleanup-db
'
Assertion 'datadir' failed at src/udev/udevadm-info.c:676, function cleanup_dirs_after_db_cleanup(). Aborting.
Aborted (core dumped)
The command should handle a missing /run/udev/data directory instead
of aborting.
The --children-max= option parsed into an unsigned value and then
stored it in an int sentinel field.
Values above INT_MAX could wrap negative and make the command look as
if no control option had been specified.
Reject oversized values before assigning the parsed value.
Reproducer:
$ udevadm control --children-max=2147483648
No control command option is specified.
$ echo $?
1
The value should be rejected as out of range instead of being wrapped
into the unset sentinel value.
When IMPORT{program} output was truncated, the code intended to drop
the last incomplete result line before importing properties.
It edited the command buffer instead of the output buffer, so the
incomplete line could still be parsed and imported.
Trim the result buffer and cover the behavior with the existing IMPORT
test.
Follow-up for 6b6e471a32.
Reproducer:
Configure an IMPORT{program} rule whose output exceeds UDEV_LINE_SIZE
and ends with an incomplete property line, for example:
TRUNCATED_OK=yes
TRUNCATED_BAD=<very long value without terminating newline>
Only TRUNCATED_OK should be imported. Before this fix, the truncation
handler edited the command buffer instead of the output buffer, so
TRUNCATED_BAD could still be parsed from the truncated result.
This introduces a test utility to validate the presence and correctness
of dlopen ELF notes.
The test extracts and compares dlopen ELF notes between the dynamic and
static versions of target binaries to catch missing entries. Furthermore,
it scans the code for dlopen_foo() invocations to ensure that no stale or
unused notes remain in the final binary, thereby verifying the linker's
garbage-collection integration.
When we enroll two UKIs with different PCR pub keys into one LUKS slot/
token each, we can encounter the wrong one and should not give up
but continue iterating.
When nested mounts appear under a sysext hierarchy like this:
mkdir -p /opt/trigger/
mount -t tmpfs tmpfs /opt/trigger
mkdir -p /opt/trigger/inner
mount -t tmpfs tmpfs /opt/trigger/inner
Then systemd-sysext merge will lose the inner mount because it uses a
regular bind mount with propagation and then unmounts the source,
unmounting all children with it which propagates (as found out in
https://github.com/flatcar/Flatcar/issues/2111).
To solve this, clone the sub mount with MS_PRIVATE to decouple sub
mounts from the original mount. Then attach the cloned mount instead of
doing regular bind mounts. For old kernels we still attach the cloned
mount but we fallback to cloning without MS_PRIVATE. This change also
affects mount_private_apivfs which is used for private /proc, /sys, and
cgroupfs but I think it makes sense there, too, instead of only doing
mount_setattr for sysext alone because, e.g., a container and the host
should not be leaking mount actions into each other for these mounts.
This commit adds a new `systemd.credentials_boot_policy=` kernel
commandline that allows to control if credentials with
a `null` key are accepted.
The possible options are:
* strict: always insist on tpm encryption
* tofu: allow null encryption in firstboot mode and when no tpm is available
* relaxed: allow null encryption when sb is off, or no tpm is available
* off: allow null encryption always
The default is currently `relaxed` which is the same behavior
as before.
This replaces the initial idea of using plaintext credentials
at firstboot (thanks to Lennart for this nicer and simpler design).
Note that this also moves `in_first_boot()` to `basic/initrd-util`
which is a better fit now.
When lock-pe or lock-uki read from stdin,
copy non-regular input to a seekable temporary fd before hashing it.
Fixes#42893.
Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
dnstls_stream_connect_tls() reads the manager-wide dns_over_tls_mode
field directly, so when the global mode was 'no' or 'opportunistic' but
a link was configured with strict DNSOverTLS=yes, the DoT connection
was established without SSL_VERIFY_PEER or hostname/IP checks.
Use dns_server_get_dns_over_tls_mode() which honours the per-link
override.
Add StandardInputFileDescriptor, StandardOutputFileDescriptor and
StandardErrorFileDescriptor to the Service context of the Varlink
StartTransient() method. Each carries the push-order index of a file descriptor
passed along with the method call (via SCM_RIGHTS), and connects it to the
transient service's standard input/output/error. This is the Varlink equivalent
of the StandardInputFileDescriptor= / StandardOutputFileDescriptor= /
StandardErrorFileDescriptor= D-Bus transient properties behind
"systemd-run --pipe", which had no Varlink counterpart.
The manager Varlink server now enables SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT
so clients may attach descriptors to their method calls, matching other
fd-accepting Varlink services (mountfsd, networkd, vmspawn, ...). The indices are
resolved with sd_varlink_peek_dup_fd() after the polkit authorization check and
stored on the Service exactly like the D-Bus path
(bus_set_transient_exec_context_fd()): the fds land in stdin_fd/stdout_fd/
stderr_fd and exec_context.stdio_as_fds is set, so the existing exec-invoke
plumbing wires them to the spawned process unchanged. Unsuitable fds (bad
index, wrong access mode) are rejected as InvalidParameter, other resolution
failures propagate as raw errnos.
Add coverage to TEST-74-AUX-UTILS.varlinkctl-unit.sh, passing regular files as
the stdout/stderr fds and asserting the unit's output lands on them.
Commit edea370222 (portable: remove drop-in configs even if the main unit file does not exist)
taught detach to handle leftover .service.d directories
after the main unit symlink was removed.
portable_get_state_internal() still had the same blind
spot: it only considered regular unit entries, so portablectl
is-attached could report detached while a portable drop-in directory was still present.
Handle those drop-in-only entries like detach does, preserve
the existing unit-file based enabled checks when the
main unit file is still present, and add TEST-29 coverage.
Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
This commit implements `journalctl -f` like behavior for the
varlink API of journalctl. It is used via:
```
$ varlinkctl call -E \
/run/systemd/io.systemd.JournalAccess \
io.systemd.JournalAccess.GetEntries '{"follow": true, "limit": 10}'
```
This gives the last 10 message and then it keeps the connection
open and output each new log line that matches the set filters.
The code is modeled after `journalctl -f`. It seems there is little
to extract into shared code here so I left it for now.
Allow graphical installers to use system sysinstall. This will be used
by gnome-setup to install GNOME OS and ideally installers of other
distributions talk to the same varlink interface.