1
0
mirror of https://github.com/systemd/systemd synced 2026-03-18 19:14:46 +01:00

Compare commits

..

20 Commits

Author SHA1 Message Date
Kai Lueke
fd8a1deb0b sysext: Get verity user certs from given --root=
The verity user certs weren't looked up in the given --root= for
systemd-sysext which made it fail to set up extensions with a strict
image policy.
Look up verity user certs from inside the --root= when we operate on
images in it. The main use case where this matters is when the initrd
sets up the extensions for the final system and thus systemd-sysext
should do the same thing as it would do in the final system.
2026-01-05 06:53:50 +09:00
Yu Watanabe
2c751f3420
Tweak setting of boot timeout variables (#40125) 2026-01-05 06:50:49 +09:00
Yu Watanabe
3a1231f4c7
core: several follow-ups (#40140)
Replaces #39925
Closes #39925
2026-01-05 06:31:48 +09:00
Mike Yuan
75f04abf16 core: move several checks from _start() to _test_startable() where appropriate
If these basic sanity checks fail, there's no point in
bumping ratelimit.
2026-01-05 06:11:26 +09:00
Lucas Werkmeister
f8e1a7a66e man/systemd.socket: Document JoinsNamespaceOf= support
This has been supported since systemd v242 (specifically commit
7619cb32f0 if I’m not mistaken; added to NEWS in commit 4107452e51), but
the man page still claimed otherwise.
2026-01-05 03:20:26 +09:00
Yu Watanabe
4b23f4f4c2
nss-systemd: always fill sg_adm and sg_mem in shadow groups (#40218)
The `sg_adm` and `sg_mem` fields are not always set in shadow groups,
which can lead to issues with foreign tools like shadow's `sg` command.
Since other NSS implementations properly set these fields and it would
otherwise be impossible to access `administrators` and `members`
information from JSON files, it's bets to always fill these fields.

Even though `sg` is a nice example which should be already installed,
the issue itself can be reproduced with this simple program as well. It
relies on filled `sg_adm` and `sg_mem` fields just like `sg` does:

```
#include <err.h>
#include <gshadow.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
        struct sgrp *s;
        char **p;

        if (argc != 2)
                errx(1, "usage: poc group");

        s = getsgnam(argv[1]);
        printf("name: %s\n", s->sg_namp);
        printf("admins:\n");
        p = s->sg_adm;
        while (*p != NULL) {
                printf("- %s\n", *p);
                p++;
        }
        printf("members:\n");
        p = s->sg_mem;
        while (*p != NULL) {
                printf("- %s\n", *p);
                p++;
        }
}
```

Run it like this: `./poc root`

Proof of Concept (Arch Linux, which uses systemd with systemd-userdbd
and shadow's sg):

```
$ grep systemd /etc/nsswitch.conf 
passwd: files systemd
group: files [SUCCESS=merge] systemd
shadow: files systemd
gshadow: files systemd
```

Issue with intrinsic groups:

Run as unprivileged user, who has no access to `/etc/gshadow` to trigger
nss-systemd (strace disables setuid of sg)
```
$ strace sg root
write(2, "sg: list.c:169: is_on_list: Asse"..., 61sg: list.c:169: is_on_list: Assertion `NULL != list' failed.
) = 61
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa7e9c0c000
gettid()                                = 1882
getpid()                                = 1882
tgkill(1882, 1882, SIGABRT)             = 0
--- SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=1882, si_uid=1000} ---
+++ killed by SIGABRT (core dumped) +++
Aborted                    (core dumped) strace sg root
```

Issue with groups through systemd-userdbd:

1. Create a custom group (as root)
```
cat > /etc/userdb/sg-poc.group << EOF
{
  "groupName": "sg-poc",
  "gid": 6123,
  "administrators": [
    "root"
  ],
  "members": [
    "bin"
  ]
}
EOF
ln -s sg-poc.group /etc/userdb/6123.group
```

2. Verify that group actually exists
```
$ userdbctl group sg-poc
  Group name: sg-poc
 Disposition: regular
         GID: 6123
      Admins: root
     Service: io.systemd.NameServiceSwitch
```

3. Run `sg` to switch into group `sg-poc` as regular user, this time
with setuid, i.e. no strace as before
```
$ sg sg-poc
sg: list.c:169: is_on_list: Assertion `NULL != list' failed.
Aborted                    (core dumped) sg sg-poc
```
2026-01-05 03:12:26 +09:00
Zbigniew Jędrzejewski-Szmek
8a27100d06 shared/install: ignore aliasing failure when doing presets
In recent Fedora, preset-all fails:
[  155s] Failed to preset unit: File '/buildroot/etc/systemd/user/dbus.service'
         already exists and is a symlink to /usr/lib/systemd/user/dbus-broker.service
[  155s] ‣ "systemctl --root=/buildroot --global preset-all" returned non-zero exit code 1.

Strictly speaking, this is an error in configuration. The presets specify that
both dbus-broker.service and dbus-daemon.service shall be enabled and they both
claim the 'dbus.service' alias. But this kind of error is very easy to make.
Failing the preset operation is too harsh, since in most cases the system will
work fine without an alias and changes in unrelated components can cause the
conflict.

Let's reuse the same logic that was added in
ad5fdd391248432e0c105003a8a13f821bde0b8e: when enabling the unit through
'preset' or 'preset-all', print the message, but suppress the error. When
enabling through 'enable', fail the operation.
2026-01-05 03:06:37 +09:00
Yu Watanabe
c1f873b4f4
Logging cleanups (#40271) 2026-01-05 03:06:09 +09:00
Tobias Stoeckmann
2eaca3ea5f nss-systemd: add unit test for sg_adm/sg_mem
Add a test for getsgnam_r to verify that sg_adm and sg_mem always point
to a NULL-terminated string vector.

Extend the gr_mem check of struct group for non-NULL values as well.
2026-01-04 18:01:36 +01:00
Tobias Stoeckmann
57682793da nss-systemd: set sg_adm/sg_mem for all groups
Fill sg_adm and sg_mem in nss_pack_group_record_shadow to stay
compatible with other NSS getsgnam implementations which set these
members to NULL terminated string arrays.

Tools like shadow's sg would trigger a NULL pointer dereference with
groups only found through nss-systemd otherwise.
2026-01-04 18:01:36 +01:00
Tobias Stoeckmann
239903d44c nss-systemd: set sg_adm/sg_mem in intrinsic groups
The sg_adm and sg_mem fields are supposed to point to a NULL terminated
string array. If these are NULL, some foreign tools like shadow's sg
trigger NULL pointer dereferences (or fortunately their asset() calls).
2026-01-04 18:01:13 +01:00
Zbigniew Jędrzejewski-Szmek
4a4be1015b inhibit: fix borked double logging on error
Previously, if execution failed, we'd log at error level both from the
child and the parent, and we were using a bogus variable for the argument
name:
$ build/systemd-inhibit list
Failed to execute : No such file or directory
list failed with exit status 1.

In general, we can and should assume that the program the user is calling
is well behaved, so it'll log the error on its own if appropriate. So we
shouldn't log on "normal errors", but only if the child is terminated by
a signal.

And since the program name is controlled by the user, use quotes everywhere
to avoid ambiguity.

Now:
$ build/systemd-inhibit false
(nothing)
$ build/systemd-inhibit bash -c 'kill -SEGV $$'
src/basic/process-util.c:895: 'bash' terminated by signal SEGV.
2026-01-04 14:07:57 +01:00
Zbigniew Jędrzejewski-Szmek
22aa8c4879 basic/process-util: reduce scope of variables 2026-01-04 14:07:57 +01:00
Zbigniew Jędrzejewski-Szmek
1ee73c884e basic/process-util: use synthetic errno in two more places 2026-01-04 14:07:57 +01:00
Zbigniew Jędrzejewski-Szmek
b30f77cd12 ssh-generator: reword error message
We have two error messages with exactly the same message.
Let's change one so that it is possible to distinguish them
in logs.
2026-01-04 14:07:57 +01:00
Zbigniew Jędrzejewski-Szmek
1d26e7c1d5 bootctl: round the timeout up
I think this is better: if I specify 0.5s, I'd be suprised if the
menu didn't show up at all.
2026-01-04 13:58:37 +01:00
Zbigniew Jędrzejewski-Szmek
abd17aa3f4 bootctl: rework setting of menu timeout variables
menu-force and menu-hidden were added in 97f077df052c75224dcc73375bfaaa69af6a1c26,
menu-disable was added in 6efdd7fec5106205240332bd3b7fd2f93d4d9d4c, a year later.
So we can assume that if the feature flag is set, the other string values are
supported too. The comment that there's no way check that was added later in
5b45fad4fcfa2dd81f25b13fe8d7717f62fa5843, but it was incorrect even at that
time.

Fixes https://github.com/systemd/systemd/issues/39167. As described in the
issue, we documented various string values in the BLI, but bootctl didn't use
the string values. At the time menu-force and menu-hidden were added, using
numerical values for compatibility made sense. But that stopped being needed
when a string value that didn't have a strictly equivalent numerical value and
a feature flag were added.

When converting a large number to menu-force, message is downgraded to debug,
since the severity of the issue is very minor. Debug messages are added in
other places when the requested setting is modified too.
2026-01-04 13:55:44 +01:00
Franck Bui
8e92910b98
core/transaction: when isolating, keep triggered units only if their triggers are actually active
Follow-up for 32d6707dd1692d41e12f5469dfdcbc10f14d6619.

Co-authored-by: Mike Yuan <me@yhndnzj.com>
2025-12-31 19:33:19 +01:00
Mike Yuan
2716906246
core/unit: do not check for unreachable job type
Refer to 7cb0030f6cec6c0a83c7c11ecc4adfb55aaf0e0b for the rationale.
2025-12-31 19:33:19 +01:00
Mike Yuan
d7bc1e3be9
core/exec-invoke: use RET_NERRNO to avoid clobbering errno
Follow-up for 72ce1046e8aa872af8edcfba407e6f0489662fda

string_table_lookup_to_string_fallback() might interfere
with errno, hence store it in r first.
2025-12-31 19:32:58 +01:00
28 changed files with 341 additions and 125 deletions

View File

@ -90,7 +90,7 @@
socket passing (i.e. sockets passed in via standard input and output, using socket passing (i.e. sockets passed in via standard input and output, using
<varname>StandardInput=socket</varname> in the service file).</para> <varname>StandardInput=socket</varname> in the service file).</para>
<para>All network sockets allocated through <filename>.socket</filename> units are allocated in the host's network <para>By default, network sockets allocated through <filename>.socket</filename> units are allocated in the host's network
namespace (see <citerefentry namespace (see <citerefentry
project='man-pages'><refentrytitle>network_namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>). This project='man-pages'><refentrytitle>network_namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>). This
does not mean however that the service activated by a configured socket unit has to be part of the host's network does not mean however that the service activated by a configured socket unit has to be part of the host's network
@ -101,6 +101,11 @@
the host's network namespace is only permitted through the activation sockets passed in while all sockets allocated the host's network namespace is only permitted through the activation sockets passed in while all sockets allocated
from the service code itself will be associated with the service's own namespace, and thus possibly subject to a from the service code itself will be associated with the service's own namespace, and thus possibly subject to a
restrictive configuration.</para> restrictive configuration.</para>
<para>Alternatively, it is possible to run a <filename>.socket</filename> unit in another network namespace
by setting <option>PrivateNetwork=yes</option> in combination with <varname>JoinsNamespaceOf=</varname>, see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> and
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details.</para>
</refsect1> </refsect1>
<refsect1> <refsect1>

View File

@ -389,11 +389,6 @@ int container_get_leader(const char *machine, pid_t *pid) {
} }
int pid_is_kernel_thread(pid_t pid) { int pid_is_kernel_thread(pid_t pid) {
_cleanup_free_ char *line = NULL;
unsigned long long flags;
size_t l, i;
const char *p;
char *q;
int r; int r;
if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */ if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */
@ -401,7 +396,8 @@ int pid_is_kernel_thread(pid_t pid) {
if (!pid_is_valid(pid)) if (!pid_is_valid(pid))
return -EINVAL; return -EINVAL;
p = procfs_file_alloca(pid, "stat"); const char *p = procfs_file_alloca(pid, "stat");
_cleanup_free_ char *line = NULL;
r = read_one_line_file(p, &line); r = read_one_line_file(p, &line);
if (r == -ENOENT) if (r == -ENOENT)
return -ESRCH; return -ESRCH;
@ -409,14 +405,14 @@ int pid_is_kernel_thread(pid_t pid) {
return r; return r;
/* Skip past the comm field */ /* Skip past the comm field */
q = strrchr(line, ')'); char *q = strrchr(line, ')');
if (!q) if (!q)
return -EINVAL; return -EINVAL;
q++; q++;
/* Skip 6 fields to reach the flags field */ /* Skip 6 fields to reach the flags field */
for (i = 0; i < 6; i++) { for (size_t i = 0; i < 6; i++) {
l = strspn(q, WHITESPACE); size_t l = strspn(q, WHITESPACE);
if (l < 1) if (l < 1)
return -EINVAL; return -EINVAL;
q += l; q += l;
@ -428,7 +424,7 @@ int pid_is_kernel_thread(pid_t pid) {
} }
/* Skip preceding whitespace */ /* Skip preceding whitespace */
l = strspn(q, WHITESPACE); size_t l = strspn(q, WHITESPACE);
if (l < 1) if (l < 1)
return -EINVAL; return -EINVAL;
q += l; q += l;
@ -439,6 +435,7 @@ int pid_is_kernel_thread(pid_t pid) {
return -EINVAL; return -EINVAL;
q[l] = 0; q[l] = 0;
unsigned long long flags;
r = safe_atollu(q, &flags); r = safe_atollu(q, &flags);
if (r < 0) if (r < 0)
return r; return r;
@ -883,25 +880,23 @@ int pidref_wait_for_terminate_and_check(const char *name, PidRef *pidref, WaitFl
siginfo_t status; siginfo_t status;
r = pidref_wait_for_terminate(pidref, &status); r = pidref_wait_for_terminate(pidref, &status);
if (r < 0) if (r < 0)
return log_full_errno(prio, r, "Failed to wait for %s: %m", strna(name)); return log_full_errno(prio, r, "Failed to wait for '%s': %m", strna(name));
if (status.si_code == CLD_EXITED) { if (status.si_code == CLD_EXITED) {
if (status.si_status != EXIT_SUCCESS) if (status.si_status != EXIT_SUCCESS)
log_full(flags & WAIT_LOG_NON_ZERO_EXIT_STATUS ? LOG_ERR : LOG_DEBUG, log_full(flags & WAIT_LOG_NON_ZERO_EXIT_STATUS ? LOG_ERR : LOG_DEBUG,
"%s failed with exit status %i.", strna(name), status.si_status); "'%s' failed with exit status %i.", strna(name), status.si_status);
else else
log_debug("%s succeeded.", name); log_debug("'%s' succeeded.", name);
return status.si_status; return status.si_status;
} else if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED)) { } else if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED))
return log_full_errno(prio, SYNTHETIC_ERRNO(EPROTO),
"'%s' terminated by signal %s.", strna(name), signal_to_string(status.si_status));
log_full(prio, "%s terminated by signal %s.", strna(name), signal_to_string(status.si_status)); return log_full_errno(prio, SYNTHETIC_ERRNO(EPROTO),
return -EPROTO; "'%s' failed due to unknown reason.", strna(name));
}
log_full(prio, "%s failed due to unknown reason.", strna(name));
return -EPROTO;
} }
int kill_and_sigcont(pid_t pid, int sig) { int kill_and_sigcont(pid_t pid, int sig) {

View File

@ -15,51 +15,71 @@
#include "virt.h" #include "virt.h"
static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_timeout_size) { static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_timeout_size) {
char utf8[DECIMAL_STR_MAX(usec_t)]; char buf[DECIMAL_STR_MAX(usec_t)];
char16_t *encoded;
usec_t timeout; usec_t timeout;
bool menu_disabled = false; uint64_t loader_features = 0;
int r; int r;
assert(arg1); assert(arg1);
assert(ret_timeout); assert(ret_timeout);
assert(ret_timeout_size); assert(ret_timeout_size);
assert_cc(STRLEN("menu-disabled") < ELEMENTSOF(utf8)); assert_cc(STRLEN("menu-force") < ELEMENTSOF(buf));
assert_cc(STRLEN("menu-hidden") < ELEMENTSOF(buf));
assert_cc(STRLEN("menu-disabled") < ELEMENTSOF(buf));
/* Note: Since there is no way to query if the bootloader supports the string tokens, we explicitly /* Use feature EFI_LOADER_FEATURE_MENU_DISABLE as a mark that the boot loader supports the other
* set their numerical value(s) instead. This means that some of the sd-boot internal ABI has leaked * string values too. When unsupported, convert to the timeout with the closest meaning.
* although the ship has sailed and the side-effects are self-contained.
*/ */
if (streq(arg1, "menu-force"))
timeout = USEC_INFINITY;
else if (streq(arg1, "menu-hidden"))
timeout = 0;
else if (streq(arg1, "menu-disabled")) {
uint64_t loader_features = 0;
if (streq(arg1, "menu-force")) {
(void) efi_loader_get_features(&loader_features); (void) efi_loader_get_features(&loader_features);
if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) {
log_debug("Using maximum timeout instead of '%s'.", arg1);
timeout = USEC_INFINITY;
arg1 = NULL;
}
} else if (streq(arg1, "menu-hidden")) {
(void) efi_loader_get_features(&loader_features);
if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) {
log_debug("Using zero timeout instead of '%s'.", arg1);
timeout = 0;
arg1 = NULL; /* replace the arg by printed timeout value later */
}
} else if (streq(arg1, "menu-disabled")) {
(void) efi_loader_get_features(&loader_features);
if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) { if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) {
if (arg_graceful() == ARG_GRACEFUL_NO) if (arg_graceful() == ARG_GRACEFUL_NO)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled'."); return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Loader does not support '%s'.", arg1);
log_warning("Loader does not support 'menu-disabled', setting anyway."); log_warning("Using zero timeout instead of '%s'.", arg1);
timeout = 0;
arg1 = NULL;
} }
menu_disabled = true;
} else { } else {
r = parse_time(arg1, &timeout, USEC_PER_SEC); r = parse_time(arg1, &timeout, USEC_PER_SEC);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to parse timeout '%s': %m", arg1); return log_error_errno(r, "Failed to parse timeout '%s': %m", arg1);
if (timeout != USEC_INFINITY && timeout > UINT32_MAX * USEC_PER_SEC)
log_warning("Timeout is too long and will be treated as 'menu-force' instead."); assert_cc(USEC_INFINITY > UINT32_MAX * USEC_PER_SEC);
if (timeout > UINT32_MAX * USEC_PER_SEC && timeout != USEC_INFINITY)
log_debug("Timeout is too long and will be clamped to maximum timeout.");
arg1 = NULL;
} }
if (menu_disabled) if (!arg1) {
xsprintf(utf8, "menu-disabled"); timeout = DIV_ROUND_UP(timeout, USEC_PER_SEC);
else xsprintf(buf, USEC_FMT, MIN(timeout, UINT32_MAX));
xsprintf(utf8, USEC_FMT, MIN(timeout / USEC_PER_SEC, UINT32_MAX)); }
encoded = utf8_to_utf16(utf8, SIZE_MAX); char16_t *encoded = utf8_to_utf16(arg1 ?: buf, SIZE_MAX);
if (!encoded) if (!encoded)
return log_oom(); return log_oom();

View File

@ -804,10 +804,6 @@ static int automount_start(Unit *u) {
if (path_is_mount_point(a->where) > 0) if (path_is_mount_point(a->where) > 0)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EEXIST), "Path %s is already a mount point, refusing start.", a->where); return log_unit_error_errno(u, SYNTHETIC_ERRNO(EEXIST), "Path %s is already a mount point, refusing start.", a->where);
r = unit_test_trigger_loaded(u);
if (r < 0)
return r;
r = unit_acquire_invocation_id(u); r = unit_acquire_invocation_id(u);
if (r < 0) if (r < 0)
return r; return r;
@ -1047,6 +1043,10 @@ static int automount_test_startable(Unit *u) {
Automount *a = ASSERT_PTR(AUTOMOUNT(u)); Automount *a = ASSERT_PTR(AUTOMOUNT(u));
int r; int r;
r = unit_test_trigger_loaded(u);
if (r < 0)
return r;
r = unit_test_start_limit(u); r = unit_test_start_limit(u);
if (r < 0) { if (r < 0) {
automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT); automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT);

View File

@ -5463,17 +5463,14 @@ int exec_invoke(
.sched_flags = context->cpu_sched_reset_on_fork ? SCHED_FLAG_RESET_ON_FORK : 0, .sched_flags = context->cpu_sched_reset_on_fork ? SCHED_FLAG_RESET_ON_FORK : 0,
}; };
r = sched_setattr(/* pid= */ 0, &attr, /* flags= */ 0); r = RET_NERRNO(sched_setattr(/* pid= */ 0, &attr, /* flags= */ 0));
if (r < 0) { if (r == -EINVAL && !sched_policy_supported(context->cpu_sched_policy)) {
if (errno != EINVAL || sched_policy_supported(attr.sched_policy)) {
*exit_status = EXIT_SETSCHEDULER;
return log_error_errno(errno, "Failed to set up CPU scheduling: %m");
}
_cleanup_free_ char *s = NULL; _cleanup_free_ char *s = NULL;
(void) sched_policy_to_string_alloc(context->cpu_sched_policy, &s); (void) sched_policy_to_string_alloc(context->cpu_sched_policy, &s);
log_warning_errno(r, "CPU scheduling policy %s is not supported, proceeding without.", strna(s));
log_warning_errno(errno, "CPU scheduling policy %s is not supported, proceeding without.", strna(s)); } else if (r < 0) {
*exit_status = EXIT_SETSCHEDULER;
return log_error_errno(r, "Failed to set up CPU scheduling: %m");
} }
} }

View File

@ -2605,6 +2605,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
r = dissected_image_decrypt( r = dissected_image_decrypt(
dissected_image, dissected_image,
/* root= */ NULL,
/* passphrase= */ NULL, /* passphrase= */ NULL,
p->verity, p->verity,
p->root_image_policy, p->root_image_policy,

View File

@ -632,10 +632,6 @@ static int path_start(Unit *u) {
assert(IN_SET(p->state, PATH_DEAD, PATH_FAILED)); assert(IN_SET(p->state, PATH_DEAD, PATH_FAILED));
r = unit_test_trigger_loaded(u);
if (r < 0)
return r;
r = unit_acquire_invocation_id(u); r = unit_acquire_invocation_id(u);
if (r < 0) if (r < 0)
return r; return r;
@ -902,6 +898,10 @@ static int path_test_startable(Unit *u) {
Path *p = ASSERT_PTR(PATH(u)); Path *p = ASSERT_PTR(PATH(u));
int r; int r;
r = unit_test_trigger_loaded(u);
if (r < 0)
return r;
r = unit_test_start_limit(u); r = unit_test_start_limit(u);
if (r < 0) { if (r < 0) {
path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT); path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT);

View File

@ -2623,20 +2623,6 @@ static int socket_start(Unit *u) {
Socket *s = ASSERT_PTR(SOCKET(u)); Socket *s = ASSERT_PTR(SOCKET(u));
int r; int r;
/* Cannot run this without the service being around */
if (UNIT_ISSET(s->service)) {
Service *service = ASSERT_PTR(SERVICE(UNIT_DEREF(s->service)));
if (UNIT(service)->load_state != UNIT_LOADED)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOENT),
"Socket service %s not loaded, refusing.", UNIT(service)->id);
/* If the service is already active we cannot start the socket */
if (SOCKET_SERVICE_IS_ACTIVE(service, /* allow_finalize= */ false))
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EBUSY),
"Socket service %s already active, refusing.", UNIT(service)->id);
}
assert(IN_SET(s->state, SOCKET_DEAD, SOCKET_FAILED)); assert(IN_SET(s->state, SOCKET_DEAD, SOCKET_FAILED));
r = unit_acquire_invocation_id(u); r = unit_acquire_invocation_id(u);
@ -3642,6 +3628,20 @@ static int socket_test_startable(Unit *u) {
SOCKET_START_POST)) SOCKET_START_POST))
return false; return false;
/* Cannot run this without the service being around */
if (UNIT_ISSET(s->service)) {
Service *service = ASSERT_PTR(SERVICE(UNIT_DEREF(s->service)));
if (UNIT(service)->load_state != UNIT_LOADED)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOENT),
"Socket service %s not loaded, refusing.", UNIT(service)->id);
/* If the service is already active we cannot start the socket */
if (SOCKET_SERVICE_IS_ACTIVE(service, /* allow_finalize= */ false))
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EBUSY),
"Socket service %s already active, refusing.", UNIT(service)->id);
}
r = unit_test_start_limit(u); r = unit_test_start_limit(u);
if (r < 0) { if (r < 0) {
socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT); socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT);

View File

@ -668,10 +668,6 @@ static int timer_start(Unit *u) {
assert(IN_SET(t->state, TIMER_DEAD, TIMER_FAILED)); assert(IN_SET(t->state, TIMER_DEAD, TIMER_FAILED));
r = unit_test_trigger_loaded(u);
if (r < 0)
return r;
r = unit_acquire_invocation_id(u); r = unit_acquire_invocation_id(u);
if (r < 0) if (r < 0)
return r; return r;
@ -917,6 +913,10 @@ static int timer_test_startable(Unit *u) {
Timer *t = ASSERT_PTR(TIMER(u)); Timer *t = ASSERT_PTR(TIMER(u));
int r; int r;
r = unit_test_trigger_loaded(u);
if (r < 0)
return r;
r = unit_test_start_limit(u); r = unit_test_start_limit(u);
if (r < 0) { if (r < 0) {
timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT); timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT);

View File

@ -1178,6 +1178,28 @@ static bool shall_stop_on_isolate(Transaction *tr, Unit *u) {
if (hashmap_contains(tr->jobs, u)) if (hashmap_contains(tr->jobs, u))
return false; return false;
/* Keep units that are triggered by units we want to keep around. */
Unit *other;
UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_TRIGGERED_BY) {
if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
continue;
/* Is the trigger about to go down? */
Job *other_job = hashmap_get(tr->jobs, other);
bool has_stop = false;
LIST_FOREACH(transaction, j, other_job)
if (j->type == JOB_STOP) {
has_stop = true;
break;
}
if (has_stop)
continue;
if (other->ignore_on_isolate || other_job)
return false;
}
return true; return true;
} }
@ -1191,7 +1213,6 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
HASHMAP_FOREACH_KEY(u, k, m->units) { HASHMAP_FOREACH_KEY(u, k, m->units) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
Unit *o;
/* Ignore aliases. */ /* Ignore aliases. */
if (u->id != k) if (u->id != k)
@ -1204,16 +1225,6 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
if (!shall_stop_on_isolate(tr, u)) if (!shall_stop_on_isolate(tr, u))
continue; continue;
/* Keep units that are triggered by units we want to keep around. */
bool keep = false;
UNIT_FOREACH_DEPENDENCY(o, u, UNIT_ATOM_TRIGGERED_BY)
if (!shall_stop_on_isolate(tr, o)) {
keep = true;
break;
}
if (keep)
continue;
r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, TRANSACTION_MATTERS, &e); r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, TRANSACTION_MATTERS, &e);
if (r < 0) if (r < 0)
log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %s", bus_error_message(&e, r)); log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %s", bus_error_message(&e, r));

View File

@ -3927,8 +3927,7 @@ bool unit_active_or_pending(Unit *u) {
if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)))
return true; return true;
if (u->job && if (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART))
IN_SET(u->job->type, JOB_START, JOB_RELOAD_OR_START, JOB_RESTART))
return true; return true;
return false; return false;

View File

@ -368,11 +368,11 @@ static int run(int argc, char *argv[]) {
/* Child */ /* Child */
execvp(arguments[0], arguments); execvp(arguments[0], arguments);
log_open(); log_open();
log_error_errno(errno, "Failed to execute %s: %m", argv[optind]); log_error_errno(errno, "Failed to execute '%s': %m", arguments[0]);
_exit(EXIT_FAILURE); _exit(EXIT_FAILURE);
} }
return pidref_wait_for_terminate_and_check(argv[optind], &pidref, WAIT_LOG); return pidref_wait_for_terminate_and_check(argv[optind], &pidref, WAIT_LOG_ABNORMAL);
} }
} }

View File

@ -295,7 +295,7 @@ int bus_image_method_get_hostname(
int r; int r;
if (!image->metadata_valid) { if (!image->metadata_valid) {
r = image_read_metadata(image, &image_policy_container, m->runtime_scope); r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
if (r < 0) if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
} }
@ -314,7 +314,7 @@ int bus_image_method_get_machine_id(
int r; int r;
if (!image->metadata_valid) { if (!image->metadata_valid) {
r = image_read_metadata(image, &image_policy_container, m->runtime_scope); r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
if (r < 0) if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
} }
@ -343,7 +343,7 @@ int bus_image_method_get_machine_info(
int r; int r;
if (!image->metadata_valid) { if (!image->metadata_valid) {
r = image_read_metadata(image, &image_policy_container, m->runtime_scope); r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
if (r < 0) if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
} }
@ -361,7 +361,7 @@ int bus_image_method_get_os_release(
int r; int r;
if (!image->metadata_valid) { if (!image->metadata_valid) {
r = image_read_metadata(image, &image_policy_container, m->runtime_scope); r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
if (r < 0) if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
} }

View File

@ -627,7 +627,7 @@ static int list_image_one_and_maybe_read_metadata(Manager *m, sd_varlink *link,
assert(image); assert(image);
if (should_acquire_metadata(am) && !image->metadata_valid) { if (should_acquire_metadata(am) && !image->metadata_valid) {
r = image_read_metadata(image, &image_policy_container, m->runtime_scope); r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
if (r < 0 && am != ACQUIRE_METADATA_GRACEFUL) if (r < 0 && am != ACQUIRE_METADATA_GRACEFUL)
return log_debug_errno(r, "Failed to read image metadata: %m"); return log_debug_errno(r, "Failed to read image metadata: %m");
if (r < 0) if (r < 0)

View File

@ -540,6 +540,7 @@ static int vl_method_mount_image(
r = dissected_image_decrypt( r = dissected_image_decrypt(
di, di,
/* root= */ NULL,
p.password, p.password,
&verity, &verity,
use_policy, use_policy,

View File

@ -75,6 +75,8 @@ static const struct group root_group = {
static const struct sgrp root_sgrp = { static const struct sgrp root_sgrp = {
.sg_namp = (char*) "root", .sg_namp = (char*) "root",
.sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID, .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
.sg_adm = (char*[]) { NULL },
.sg_mem = (char*[]) { NULL },
}; };
static const struct group nobody_group = { static const struct group nobody_group = {
@ -87,6 +89,8 @@ static const struct group nobody_group = {
static const struct sgrp nobody_sgrp = { static const struct sgrp nobody_sgrp = {
.sg_namp = (char*) NOBODY_GROUP_NAME, .sg_namp = (char*) NOBODY_GROUP_NAME,
.sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID, .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
.sg_adm = (char*[]) { NULL },
.sg_mem = (char*[]) { NULL },
}; };
typedef struct GetentData { typedef struct GetentData {
@ -257,12 +261,18 @@ static enum nss_status copy_synthesized_sgrp(
assert(src); assert(src);
assert(src->sg_namp); assert(src->sg_namp);
assert(src->sg_passwd); assert(src->sg_passwd);
assert(src->sg_adm);
assert(!*src->sg_adm); /* Our synthesized records' sg_adm is always just NULL... */
assert(src->sg_mem);
assert(!*src->sg_mem); /* Our synthesized records' sg_mem is always just NULL... */
size_t required = size_t required =
strlen(src->sg_namp) + 1 + strlen(src->sg_namp) + 1 +
strlen(src->sg_passwd) + 1; strlen(src->sg_passwd) + 1 +
sizeof(char*) + /* NULL terminator storage for src->sg_adm */
sizeof(char*); /* NULL terminator storage for src->sg_mem */
if (buflen < required) { if (buflen < ALIGN(required)) {
*errnop = ERANGE; *errnop = ERANGE;
return NSS_STATUS_TRYAGAIN; return NSS_STATUS_TRYAGAIN;
} }
@ -274,7 +284,10 @@ static enum nss_status copy_synthesized_sgrp(
/* String fields point into the user-provided buffer */ /* String fields point into the user-provided buffer */
dest->sg_namp = buffer; dest->sg_namp = buffer;
dest->sg_passwd = stpcpy(dest->sg_namp, src->sg_namp) + 1; dest->sg_passwd = stpcpy(dest->sg_namp, src->sg_namp) + 1;
strcpy(dest->sg_passwd, src->sg_passwd); dest->sg_adm = ALIGN_PTR(stpcpy(dest->sg_passwd, src->sg_passwd) + 1);
*dest->sg_adm = NULL;
dest->sg_mem = dest->sg_adm + 1;
*dest->sg_mem = NULL;
return NSS_STATUS_SUCCESS; return NSS_STATUS_SUCCESS;
} }

View File

@ -411,6 +411,40 @@ enum nss_status userdb_getgrgid(
return NSS_STATUS_SUCCESS; return NSS_STATUS_SUCCESS;
} }
/* Counts string pointers (including terminating NULL element) of given
* string vector strv and stores amount of pointers in n and total
* length of all contained strings including NUL bytes in len. */
static void nss_count_strv(char * const *strv, size_t *n, size_t *len) {
STRV_FOREACH(str, strv) {
(*len) += sizeof(char*); /* space for array entry */
(*len) += strlen(*str) + 1;
(*n)++;
}
(*len) += sizeof(char*); /* trailing NULL in array entry */
(*n)++;
}
/* Performs deep copy of given string vector src and stores content
* of contained strings into buf with references to these strings
* in dst. At dst location, a new NULL-terminated string vector is
* created. The dst and buf locations are updated to point just behind
* the last pointer or char respectively. Returns total amount of
* pointers in newly created string vector in dst, including the
* terminating NULL element. */
static size_t nss_deep_copy_strv(char * const *src, char ***dst, char **buf) {
char *p = *buf;
size_t i = 0;
STRV_FOREACH(str, src) {
(*dst)[i++] = p;
p = stpcpy(p, *str) + 1;
}
(*dst)[i++] = NULL;
*dst += i;
*buf = p;
return i;
}
int nss_pack_group_record_shadow( int nss_pack_group_record_shadow(
GroupRecord *hr, GroupRecord *hr,
struct sgrp *sgrp, struct sgrp *sgrp,
@ -418,7 +452,8 @@ int nss_pack_group_record_shadow(
size_t buflen) { size_t buflen) {
const char *hashed; const char *hashed;
size_t required; char **array = NULL, *p;
size_t i = 0, n = 0, required;
assert(hr); assert(hr);
assert(sgrp); assert(sgrp);
@ -429,15 +464,26 @@ int nss_pack_group_record_shadow(
assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]); assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
required += strlen(hashed) + 1; required += strlen(hashed) + 1;
nss_count_strv(hr->administrators, &n, &required);
nss_count_strv(hr->members, &n, &required);
if (buflen < required) if (buflen < required)
return -ERANGE; return -ERANGE;
*sgrp = (struct sgrp) {
.sg_namp = buffer,
};
assert(buffer); assert(buffer);
p = buffer + sizeof(void*) * (n + 1); /* place member strings right after the ptr array */
array = (char**) buffer; /* place ptr array at beginning of buffer, under assumption buffer is aligned */
sgrp->sg_mem = array;
i += nss_deep_copy_strv(hr->members, &array, &p);
sgrp->sg_adm = array;
i += nss_deep_copy_strv(hr->administrators, &array, &p);
assert_se(i == n);
sgrp->sg_namp = p;
sgrp->sg_passwd = stpcpy(sgrp->sg_namp, hr->group_name) + 1; sgrp->sg_passwd = stpcpy(sgrp->sg_namp, hr->group_name) + 1;
strcpy(sgrp->sg_passwd, hashed); strcpy(sgrp->sg_passwd, hashed);

View File

@ -61,7 +61,7 @@ int bus_image_common_get_os_release(
return 1; return 1;
if (!image->metadata_valid) { if (!image->metadata_valid) {
r = image_read_metadata(image, &image_policy_service, m->runtime_scope); r = image_read_metadata(image, /* root= */ NULL, &image_policy_service, m->runtime_scope);
if (r < 0) if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
} }

View File

@ -2027,7 +2027,7 @@ int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol
return 0; return 0;
} }
int image_read_metadata(Image *i, const ImagePolicy *image_policy, RuntimeScope scope) { int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope) {
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
int r; int r;
@ -2153,6 +2153,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy, RuntimeScope
r = dissected_image_decrypt( r = dissected_image_decrypt(
m, m,
root,
/* passphrase= */ NULL, /* passphrase= */ NULL,
&verity, &verity,
image_policy, image_policy,

View File

@ -71,7 +71,7 @@ int image_get_pool_usage(RuntimeScope scope, ImageClass class, uint64_t *ret);
int image_get_pool_limit(RuntimeScope scope, ImageClass class, uint64_t *ret); int image_get_pool_limit(RuntimeScope scope, ImageClass class, uint64_t *ret);
int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol, bool use_btrfs_quota); int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol, bool use_btrfs_quota);
int image_read_metadata(Image *i, const ImagePolicy *image_policy, RuntimeScope scope); int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope);
bool image_in_search_path(RuntimeScope scope, ImageClass class, const char *root, const char *image); bool image_in_search_path(RuntimeScope scope, ImageClass class, const char *root, const char *image);

View File

@ -3093,7 +3093,7 @@ static char* dm_deferred_remove_clean(char *name) {
} }
DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean); DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) { static int validate_signature_userspace(const VeritySettings *verity, const char *root, DissectImageFlags flags) {
int r; int r;
/* Returns > 0 if signature checks out, == 0 if not, < 0 on unexpected errors */ /* Returns > 0 if signature checks out, == 0 if not, < 0 on unexpected errors */
@ -3138,7 +3138,7 @@ static int validate_signature_userspace(const VeritySettings *verity, DissectIma
/* Because installing a signature certificate into the kernel chain is so messy, let's optionally do /* Because installing a signature certificate into the kernel chain is so messy, let's optionally do
* userspace validation. */ * userspace validation. */
r = conf_files_list_nulstr(&certs, ".crt", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d")); r = conf_files_list_nulstr(&certs, ".crt", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to enumerate certificates: %m"); return log_debug_errno(r, "Failed to enumerate certificates: %m");
if (strv_isempty(certs)) { if (strv_isempty(certs)) {
@ -3200,6 +3200,7 @@ static int validate_signature_userspace(const VeritySettings *verity, DissectIma
static int do_crypt_activate_verity( static int do_crypt_activate_verity(
struct crypt_device *cd, struct crypt_device *cd,
const char *root,
const char *name, const char *name,
const VeritySettings *verity, const VeritySettings *verity,
DissectImageFlags flags, DissectImageFlags flags,
@ -3249,7 +3250,7 @@ static int do_crypt_activate_verity(
/* Preferably propagate the original kernel error, so that the fallback logic can work, /* Preferably propagate the original kernel error, so that the fallback logic can work,
* as the device-mapper is finicky around concurrent activations of the same volume */ * as the device-mapper is finicky around concurrent activations of the same volume */
k = validate_signature_userspace(verity, flags); k = validate_signature_userspace(verity, root, flags);
if (k < 0) if (k < 0)
return k; return k;
if (k == 0) { if (k == 0) {
@ -3309,6 +3310,7 @@ static int verity_partition(
PartitionDesignator designator, PartitionDesignator designator,
DissectedPartition *m, /* data partition */ DissectedPartition *m, /* data partition */
DissectedPartition *v, /* verity partition */ DissectedPartition *v, /* verity partition */
const char *root, /* The root to get user verity certs from (for a sysext) */
const VeritySettings *verity, const VeritySettings *verity,
DissectImageFlags flags, DissectImageFlags flags,
PartitionPolicyFlags policy_flags, PartitionPolicyFlags policy_flags,
@ -3394,7 +3396,7 @@ static int verity_partition(
goto check; /* The device already exists. Let's check it. */ goto check; /* The device already exists. Let's check it. */
/* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */ /* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */
r = do_crypt_activate_verity(cd, name, verity, flags, policy_flags); r = do_crypt_activate_verity(cd, root, name, verity, flags, policy_flags);
if (r >= 0) if (r >= 0)
goto try_open; /* The device is activated. Let's open it. */ goto try_open; /* The device is activated. Let's open it. */
/* libdevmapper can return EINVAL when the device is already in the activation stage. /* libdevmapper can return EINVAL when the device is already in the activation stage.
@ -3488,7 +3490,7 @@ static int verity_partition(
*/ */
sym_crypt_free(cd); sym_crypt_free(cd);
cd = NULL; cd = NULL;
return verity_partition(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, policy_flags, d); return verity_partition(designator, m, v, root, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, policy_flags, d);
} }
return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name); return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name);
@ -3508,6 +3510,7 @@ success:
int dissected_image_decrypt( int dissected_image_decrypt(
DissectedImage *m, DissectedImage *m,
const char *root, /* The root to get user verity certs from (for a sysext) */
const char *passphrase, const char *passphrase,
const VeritySettings *verity, const VeritySettings *verity,
const ImagePolicy *policy, const ImagePolicy *policy,
@ -3564,7 +3567,7 @@ int dissected_image_decrypt(
k = partition_verity_hash_of(i); k = partition_verity_hash_of(i);
if (k >= 0) { if (k >= 0) {
r = verity_partition(i, p, m->partitions + k, verity, flags, fl, d); r = verity_partition(i, p, m->partitions + k, root, verity, flags, fl, d);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -3597,7 +3600,7 @@ int dissected_image_decrypt_interactively(
n--; n--;
for (;;) { for (;;) {
r = dissected_image_decrypt(m, passphrase, verity, image_policy, flags); r = dissected_image_decrypt(m, /* root= */ NULL, passphrase, verity, image_policy, flags);
if (r >= 0) if (r >= 0)
return r; return r;
if (r == -EKEYREJECTED) if (r == -EKEYREJECTED)
@ -4862,7 +4865,8 @@ int verity_dissect_and_mount(
r = dissected_image_decrypt( r = dissected_image_decrypt(
dissected_image, dissected_image,
NULL, /* root= */ NULL,
/* passphrase= */ NULL,
verity, verity,
image_policy, image_policy,
dissect_image_flags); dissect_image_flags);

View File

@ -171,7 +171,7 @@ void dissected_image_close(DissectedImage *m);
DissectedImage* dissected_image_unref(DissectedImage *m); DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags); int dissected_image_decrypt(DissectedImage *m, const char *root, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags);
int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags); int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags);
int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);

View File

@ -1942,6 +1942,7 @@ int unit_file_verify_alias(
static int install_info_symlink_alias( static int install_info_symlink_alias(
RuntimeScope scope, RuntimeScope scope,
UnitFileFlags file_flags,
InstallInfo *info, InstallInfo *info,
const LookupPaths *lp, const LookupPaths *lp,
const char *config_path, const char *config_path,
@ -1996,6 +1997,10 @@ static int install_info_symlink_alias(
broken = r == 0; /* symlink target does not exist? */ broken = r == 0; /* symlink target does not exist? */
r = create_symlink(lp, alias_target ?: info->path, alias_path, force || broken, changes, n_changes); r = create_symlink(lp, alias_target ?: info->path, alias_path, force || broken, changes, n_changes);
if (r == -EEXIST && FLAGS_SET(file_flags, UNIT_FILE_IGNORE_AUXILIARY_FAILURE))
/* We cannot realize the alias because a conflicting alias exists.
* Do not propagate this as error. */
continue;
if (r != 0 && ret >= 0) if (r != 0 && ret >= 0)
ret = r; ret = r;
} }
@ -2160,7 +2165,7 @@ static int install_info_apply(
* because they might would pointing to a non-existent or wrong unit. */ * because they might would pointing to a non-existent or wrong unit. */
return r; return r;
r = install_info_symlink_alias(scope, info, lp, config_path, force, changes, n_changes); r = install_info_symlink_alias(scope, file_flags, info, lp, config_path, force, changes, n_changes);
q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->wanted_by, ".wants/", changes, n_changes); q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->wanted_by, ".wants/", changes, n_changes);
if (q != 0 && r >= 0) if (q != 0 && r >= 0)

View File

@ -2,6 +2,7 @@
#pragma once #pragma once
#include <grp.h> #include <grp.h>
#include <gshadow.h>
#include <netdb.h> #include <netdb.h>
#include <nss.h> #include <nss.h>
#include <pwd.h> #include <pwd.h>
@ -291,3 +292,9 @@ typedef enum nss_status (*_nss_getgrgid_r_t)(
struct group *gr, struct group *gr,
char *buffer, size_t buflen, char *buffer, size_t buflen,
int *errnop); int *errnop);
typedef enum nss_status (*_nss_getsgnam_r_t)(
const char *name,
struct sgrp *sg,
char *buffer, size_t buflen,
int *errnop);

View File

@ -35,6 +35,6 @@ int vsock_get_local_cid_or_warn(unsigned *ret) {
return 0; return 0;
} }
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m"); return log_error_errno(r, "Failed to query host's AF_VSOCK CID: %m");
return 1; return 1;
} }

View File

@ -1923,7 +1923,7 @@ static int merge_subprocess(
if (r < 0) if (r < 0)
return r; return r;
r = dissected_image_decrypt(m, /* passphrase= */ NULL, &verity_settings, pick_image_policy(img), flags); r = dissected_image_decrypt(m, arg_root, /* passphrase= */ NULL, &verity_settings, pick_image_policy(img), flags);
if (r < 0) if (r < 0)
return r; return r;
@ -2199,7 +2199,7 @@ static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **re
return log_error_errno(r, "Failed to discover images: %m"); return log_error_errno(r, "Failed to discover images: %m");
HASHMAP_FOREACH(img, images) { HASHMAP_FOREACH(img, images) {
r = image_read_metadata(img, image_class_info[image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM); r = image_read_metadata(img, arg_root, image_class_info[image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name); return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
} }

View File

@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <gshadow.h>
#include <pwd.h> #include <pwd.h>
#include <stdlib.h> #include <stdlib.h>
@ -35,7 +36,22 @@ static void print_struct_group(const struct group *gr) {
gr->gr_name, gr->gr_gid); gr->gr_name, gr->gr_gid);
log_info(" passwd=\"%s\"", gr->gr_passwd); log_info(" passwd=\"%s\"", gr->gr_passwd);
assert_se(members = strv_join(gr->gr_mem, ", ")); assert_se(members = strv_join(ASSERT_PTR(gr->gr_mem), ", "));
// FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available
log_info(" members=%s", members);
}
static void print_struct_sgrp(const struct sgrp *sg) {
_cleanup_free_ char *administrators = NULL, *members = NULL;
log_info(" \"%s\"", sg->sg_namp);
log_info(" passwd=\"%s\"", sg->sg_passwd);
assert_se(administrators = strv_join(ASSERT_PTR(sg->sg_adm), ", "));
// FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available
log_info(" administrators=%s", administrators);
assert_se(members = strv_join(ASSERT_PTR(sg->sg_mem), ", "));
// FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available // FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available
log_info(" members=%s", members); log_info(" members=%s", members);
} }
@ -92,6 +108,32 @@ static void test_getgrnam_r(void *handle, const char *module, const char *name)
print_struct_group(&gr); print_struct_group(&gr);
} }
static void test_getsgnam_r(void *handle, const char *module, const char *name) {
const char *fname;
_nss_getsgnam_r_t f;
char buffer[arg_bufsize];
int errno1 = 999; /* nss-dns doesn't set those */
enum nss_status status;
char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
struct sgrp sg;
fname = strjoina("_nss_", module, "_getsgnam_r");
f = dlsym(handle, fname);
log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
if (!f) {
log_info("%s not defined", fname);
return;
}
status = f(name, &sg, buffer, sizeof buffer, &errno1);
log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s",
fname, name,
nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
errno1, errno1 > 0 ? ERRNO_NAME(errno1) : "---");
if (status == NSS_STATUS_SUCCESS)
print_struct_sgrp(&sg);
}
static void test_getpwuid_r(void *handle, const char *module, uid_t uid) { static void test_getpwuid_r(void *handle, const char *module, uid_t uid) {
const char *fname; const char *fname;
_nss_getpwuid_r_t f; _nss_getpwuid_r_t f;
@ -147,6 +189,7 @@ static void test_getgrgid_r(void *handle, const char *module, gid_t gid) {
static void test_byname(void *handle, const char *module, const char *name) { static void test_byname(void *handle, const char *module, const char *name) {
test_getpwnam_r(handle, module, name); test_getpwnam_r(handle, module, name);
test_getgrnam_r(handle, module, name); test_getgrnam_r(handle, module, name);
test_getsgnam_r(handle, module, name);
puts(""); puts("");
} }

View File

@ -181,6 +181,52 @@ prepare_extension_image_raw() {
prepend_trap "rm -rf ${ext_dir@Q}.raw" prepend_trap "rm -rf ${ext_dir@Q}.raw"
} }
prepare_extension_image_raw_verity() {
local root=${1:-}
local hierarchy=${2:?}
local ext_dir ext_release name tmpcrt
name="test-extension"
ext_dir="$root/var/lib/extensions/$name"
ext_release="$ext_dir/usr/lib/extension-release.d/extension-release.$name"
tmpcrt=$(mktemp --directory "/tmp/test-sysext.crt.XXXXXXXXXX")
prepend_trap "rm -rf ${ext_dir@Q} ${ext_dir@Q}.raw '$root/etc/verity.d/test-ext.crt' '$tmpcrt'"
mkdir -p "${ext_release%/*}"
echo "ID=_any" >"$ext_release"
mkdir -p "$ext_dir/$hierarchy"
touch "$ext_dir$hierarchy/preexisting-file-in-extension-image"
tee >"$tmpcrt/verity.openssl.cnf" <<EOF
[ req ]
prompt = no
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
C = DE
ST = Test State
L = Test Locality
O = Org Name
OU = Org Unit Name
CN = Common Name
emailAddress = test@email.com
EOF
openssl req \
-config "$tmpcrt/verity.openssl.cnf" \
-new -x509 \
-newkey rsa:1024 \
-keyout "$tmpcrt/test-ext.key" \
-out "$tmpcrt/test-ext.crt" \
-days 365 \
-nodes
systemd-repart --make-ddi=sysext \
--private-key="$tmpcrt/test-ext.key" --certificate="$tmpcrt/test-ext.crt" \
--copy-source="$ext_dir" "$ext_dir.raw"
rm -rf "$ext_dir"
mkdir -p "$root/etc/verity.d"
mv "$tmpcrt/test-ext.crt" "$root/etc/verity.d/"
rm -rf "$tmpcrt"
}
prepare_extension_mutable_dir() { prepare_extension_mutable_dir() {
local dir=${1:?} local dir=${1:?}
@ -1239,6 +1285,28 @@ extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
run_systemd_sysext "$fake_root" unmerge run_systemd_sysext "$fake_root" unmerge
) )
( init_trap
: "Check if verity user certs get loaded from --root="
fake_root=${roots_dir:+"$roots_dir/verity-user-cert-from-root"}
hierarchy=/opt
# On OpenSUSE Tumbleweed EROFS is not supported
if [ -e /usr/lib/modprobe.d/60-blacklist_fs-erofs.conf ]; then
echo >&2 "Skipping test due to missing erofs support"
exit 0
fi
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image_raw_verity "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
run_systemd_sysext "$fake_root" merge --image-policy=root=signed+absent:usr=signed+absent
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
)
} # End of run_sysext_tests } # End of run_sysext_tests