1
0
mirror of https://github.com/systemd/systemd synced 2026-04-01 04:34:51 +02:00

Compare commits

...

20 Commits

Author SHA1 Message Date
Yu Watanabe
a2366debce
core/exec-credentials: port to new mount API, ensure atomicity for creds installation (#39637) 2025-11-12 09:00:23 +09:00
Yu Watanabe
19db9a99e6
systemctl: fix edit and cat verbs with --global flag (#39606)
The --global flag has been broken since commit 
d77d42ed3ae95ee035dce4707777b077d1a9bf8b, which added a
blanket restriction on acquiring D-Bus connections when
arg_runtime_scope is RUNTIME_SCOPE_GLOBAL. This was done to prevent
crashes, but inadvertently broke legitimate use cases like 'systemctl
edit --global' and 'systemctl cat --global'.

The issue is that verb_edit() and verb_cat() were unconditionally
calling acquire_bus(), which triggers the restriction and fails with
"--global is not supported for this operation."

This commit fixes the issue by making bus acquisition conditional,
following the same pattern used in verb_enable():

- Only acquire the bus when install_client_side() returns NO (i.e., for
system and user scopes)
- For client-side operations (--global, --root, etc.), skip bus
acquisition and use mangle_names() instead of expand_unit_names()
- Update find_paths_to_edit() and verb_cat() to handle NULL bus by
forcing client-side path lookups
- Skip bus-dependent checks (unit_is_masked, need_daemon_reload) when
bus is NULL

This allows both 'systemctl edit --global' and 'systemctl cat --global'
to work correctly by performing all operations client-side without
requiring a connection to the system or user manager.

Fixes #31272
2025-11-12 08:59:06 +09:00
Yu Watanabe
a1cb4fae86
sd-path: add new type SD_PATH_SEARCH_SYSCTL (#38680)
Aim of this patches set, is to add a new type SD_PATH_SEARCH_SYSCTL for
sd_path_lookup() and sd_path_lookup_strv(). This new type is used to get the
directories list used by systemd-sysctl:

-  /etc/sysctl.d/
-  /run/sysctl.d/
-  /usr/local/lib/sysctl.d/
-  /usr/lib/sysctl.d/

This implements the change in libsystemd, systemd-path, and systemd-sysctl.
2025-11-12 08:54:09 +09:00
Goffredo Baroncelli
f379784550 systemd-sysctl: add SD_PATH_SEARCH_SYSCTL
Update systemd-sysctl to use libsystemd with the new type
SD_PATH_SEARCH_SYSCTL to get the directories list from which
load the .conf files.
2025-11-12 08:49:55 +09:00
Dimitri John Ledkov
31b4dea5f0
bootctl: calculate secureboot state taking MokSBStateRT into account (#39298)
shim is often used as part of the EFI boot chain with Linux kernels.

shim has an option to disable all verification of binaries it loads.
This can be performed by end-user using mokutil / mokmanager EFI app,
which set BootServices only variable MokSBState. shim honors that, and
mirrors it as readonly MokSBStateRT for the post-ExitBootService access.

Thus presense of MokSBStateRT is an indicator that shim was used during
boot chain.

Some OEM vendors are known to set MokSBState variable, without user
having done so.

When verification is disabled, one should assume secureboot is insecure,
because any EFI binary was allowed to run, including but not limited to
unsigned or revoked:
- grub
- systemd-boot
- UKI
- linux kernel

Linux kernel also has code to check this variable, and correctly report
that Secure Boot is disabled, see:
-
3a86608788/drivers/firmware/efi/libstub/secureboot.c (L57)

With this change bootctl output changes like this:
```diff
 System:
       Firmware: n/a (n/a)
  Firmware Arch: x64
-   Secure Boot: enabled (user)
+   Secure Boot: disabled (insecure)
   TPM2 Support: yes
   Measured UKI: no
   Boot into FW: supported
```

This implementation is trying to mimic mokutil behaviour like this one:
```
$ mokutil --sb-state
SecureBoot enabled
SecureBoot validation is disabled in shim
```

As well as the linux kernel behavior of:
```
$ journalctl -b | grep 'Secure boot disabled'
kernel: Secure boot disabled
```

Note that MokSBState is extended into PCR7 as well as also into PCR14.
For more details see https://github.com/rhboot/shim/blob/main/README.tpm
2025-11-12 08:47:44 +09:00
Masanari Iida
036100d745 systemd-logind: Add signal section in man systemd-logind
This patch adds signal setion in man systemd-logind
2025-11-12 08:45:55 +09:00
Mike Yuan
1d73c596fa core/exec-invoke: do not check array being non-NULL
Addresses
https://download.copr.fedorainfracloud.org/results/packit/systemd-systemd-39680/fedora-rawhide-i386/09787959-systemd/builder-live.log.gz
2025-11-11 22:41:24 +00:00
Goffredo Baroncelli
52efca4723 systemd-path: add new type SD_PATH_SEARCH_SYSCTL
Add new type SD_PATH_SEARCH_SYSCTL to the ones that systemd-path already
know.

Before the change:
$ systemd-path | egrep sysctl
sysctl: /usr/lib/sysctl.d

After the change:
$ ./systemd-path | egrep sysctl
search-sysctl: /etc/sysctl.d:/run/sysctl.d:/usr/local/lib/sysctl.d:/usr/lib/sysctl.d
sysctl: /usr/lib/sysctl.d
2025-11-11 19:31:16 +01:00
gvenugo3
ebd222b1c8 systemctl: support --global and --root in edit and cat
Make bus acquisition conditional in verb_edit() and verb_cat(), following
the same pattern used in verb_enable(). When install_client_side() returns
non-zero (indicating --global, --root, offline, or similar scenarios), skip
acquiring a D-Bus connection and perform all operations client-side.

Changes:
- Only acquire bus when install_client_side() returns NO
- Use mangle_names() instead of expand_unit_names() in client-side mode
- Pass force_client_side flag based on bus availability
- Skip bus-dependent operations (need_daemon_reload, etc.) when bus is NULL

This allows 'systemctl edit --global' and 'systemctl cat --global' to work
correctly, fixing the regression introduced by commit d77d42ed3a.

Test cases added to verify:
- Creating and editing global user units with --runtime
- Reading global units with cat --global
- Proper detection and rejection of masked units in client-side mode
- Tests use /run/ instead of /etc/ for safer temporary testing

Fixes https://github.com/systemd/systemd/issues/31272
2025-11-11 10:00:29 -07:00
gvenugo3
423a8ffccc systemctl: check if unit is masked in unit_find_paths()
When operating in client-side mode (force_client_side=true), unit_find_paths()
now checks if the unit file is masked (symlinked to /dev/null or empty) and
returns -ERFKILL, matching the behavior of the server-side path.

This centralizes masked unit detection in one place, making it consistent
across both client-side and server-side operations.
2025-11-11 09:55:59 -07:00
gvenugo3
cedc98c581 systemctl: drop unnecessary unit_is_masked() check in edit
The unit_is_masked() check will be performed later by unit_find_paths(),
making this early check redundant.
2025-11-11 09:54:49 -07:00
Mike Yuan
2a64b56da4
acl-util: fall back to fchmod() if libacl is not around, too 2025-11-10 23:06:14 +01:00
Mike Yuan
4175cd4f40
acl-util: drop now unused fd_acl_make_writable() 2025-11-10 23:06:14 +01:00
Mike Yuan
bc48ac39c4
core/exec-credential: remove no longer needed per-cred atomic update logic
Now that we guarantee at higher level that we work on unique
credential dir, this becomes unnecessary.
2025-11-10 23:06:14 +01:00
Mike Yuan
44f23805b0
core/exec-credential: work around tmpfs reconfigure bug 2025-11-10 23:06:14 +01:00
Mike Yuan
d796c6b7c6
core/exec-credential: port to new mount API, ensure atomicity for creds installation
This allows us to kill a great deal of complexity imposed
by the mountns and workspace reuse.
2025-11-10 23:06:14 +01:00
Goffredo Baroncelli
457a39a866 libsystemd: add new type SD_PATH_SEARCH_SYSCTL for sd_path_lookup*
Add the new type SD_PATH_SEARCH_SYSCTL to libsystemd.
With this new type sd_path_lookup() and sd_path_lookup_strv() will
return the paths used by systemd-sysctl(1) to search the .conf files:

           /etc/sysctl.d/
           /run/sysctl.d/
           /usr/local/lib/sysctl.d/
           /usr/lib/sysctl.d/

Refer to sysctl.d(5) man page.

Note: the old type SD_PATH_SYSCTL is still available, and returns the
last path (/usr/lib/sysctl.d/).
2025-11-10 22:58:25 +01:00
Mike Yuan
e4c7e4c27a
mount-util: introduce fsmount_credentials_fs()
While at it, remove effectively unused size and ro params.
2025-11-10 22:38:21 +01:00
Mike Yuan
c7b5f7f30f
creds-util: use U64_* macros where appropriate 2025-11-10 22:38:21 +01:00
Mike Yuan
4338440c97
core/exec-credential: do not pass CGroupContext deep down the stack
The repetitive re-evaluation of device_nodes_restricted() is wasteful.
Just pre-determine whether we need to always go by IPC and store it
in a bool.
2025-11-10 22:38:21 +01:00
26 changed files with 367 additions and 465 deletions

View File

@ -76,6 +76,7 @@
<constant>SD_PATH_SEARCH_CONFIGURATION_FACTORY</constant>,
<constant>SD_PATH_SEARCH_STATE_FACTORY</constant>,
<constant>SD_PATH_SEARCH_CONFIGURATION</constant>,
<constant>SD_PATH_SEARCH_SYSCTL</constant>,
<constant>SD_PATH_SYSTEMD_UTIL</constant>,
<constant>SD_PATH_SYSTEMD_SYSTEM_UNIT</constant>,

View File

@ -95,6 +95,16 @@
Desktop Environments</ulink>.</para>
</refsect1>
<refsect1>
<title>Signal</title>
<variablelist>
<varlistentry>
<term><constant>SIGHUP</constant></term>
<listitem><para>Reloads the service configuration file.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">

View File

@ -377,10 +377,11 @@ SecureBootMode efi_get_secure_boot_mode(void) {
int audit = read_flag(EFI_GLOBAL_VARIABLE_STR("AuditMode"));
int deployed = read_flag(EFI_GLOBAL_VARIABLE_STR("DeployedMode"));
int setup = read_flag(EFI_GLOBAL_VARIABLE_STR("SetupMode"));
log_debug("Secure boot variables: SecureBoot=%d AuditMode=%d DeployedMode=%d SetupMode=%d",
secure, audit, deployed, setup);
int moksb = read_flag(EFI_SHIMLOCK_VARIABLE_STR("MokSBStateRT"));
log_debug("Secure boot variables: SecureBoot=%d AuditMode=%d DeployedMode=%d SetupMode=%d MokSBStateRT=%d",
secure, audit, deployed, setup, moksb);
return (cache = decode_secure_boot_mode(secure, audit > 0, deployed > 0, setup > 0));
return (cache = decode_secure_boot_mode(secure, audit > 0, deployed > 0, setup > 0, moksb > 0));
}
#endif

View File

@ -15,6 +15,8 @@
#define EFI_VENDOR_DATABASE_STR SD_ID128_MAKE_UUID_STR(d7,19,b2,cb,3d,3a,45,96,a3,bc,da,d0,0e,67,65,6f)
#define EFI_VENDOR_SYSTEMD SD_ID128_MAKE(8c,f2,64,4b,4b,0b,42,8f,93,87,6d,87,60,50,dc,67)
#define EFI_VENDOR_SYSTEMD_STR SD_ID128_MAKE_UUID_STR(8c,f2,64,4b,4b,0b,42,8f,93,87,6d,87,60,50,dc,67)
#define EFI_VENDOR_SHIMLOCK SD_ID128_MAKE(60,5d,ab,50,e0,46,43,00,ab,b6,3d,d8,10,dd,8b,23)
#define EFI_VENDOR_SHIMLOCK_STR SD_ID128_MAKE_UUID_STR(60,5d,ab,50,e0,46,43,00,ab,b6,3d,d8,10,dd,8b,23)
#define EFI_VARIABLE_NON_VOLATILE UINT32_C(0x00000001)
#define EFI_VARIABLE_BOOTSERVICE_ACCESS UINT32_C(0x00000002)
@ -30,6 +32,7 @@
#define EFI_GLOBAL_VARIABLE_STR(name) EFI_VENDOR_VARIABLE_STR(EFI_VENDOR_GLOBAL_STR, name)
#define EFI_LOADER_VARIABLE_STR(name) EFI_VENDOR_VARIABLE_STR(EFI_VENDOR_LOADER_STR, name)
#define EFI_SYSTEMD_VARIABLE_STR(name) EFI_VENDOR_VARIABLE_STR(EFI_VENDOR_SYSTEMD_STR, name)
#define EFI_SHIMLOCK_VARIABLE_STR(name) EFI_VENDOR_VARIABLE_STR(EFI_VENDOR_SHIMLOCK_STR, name)
#define EFIVAR_PATH(variable) "/sys/firmware/efi/efivars/" variable
#define EFIVAR_CACHE_PATH(variable) "/run/systemd/efivars/" variable

View File

@ -20,7 +20,7 @@ bool secure_boot_enabled(void) {
}
SecureBootMode secure_boot_mode(void) {
bool secure, audit = false, deployed = false, setup = false;
bool secure, audit = false, deployed = false, setup = false, moksb = false;
EFI_STATUS err;
err = efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"SecureBoot", &secure);
@ -32,8 +32,9 @@ SecureBootMode secure_boot_mode(void) {
(void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"AuditMode", &audit);
(void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"DeployedMode", &deployed);
(void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"SetupMode", &setup);
(void) efivar_get_boolean_u8(MAKE_GUID_PTR(SHIM_LOCK), u"MokSBStateRT", &moksb);
return decode_secure_boot_mode(secure, audit, deployed, setup);
return decode_secure_boot_mode(secure, audit, deployed, setup, moksb);
}
/*

View File

@ -30,9 +30,6 @@ struct ShimLock {
EFI_STATUS __sysv_abi__ (*read_header) (void *data, uint32_t datasize, void *context);
};
#define SHIM_LOCK_GUID \
{ 0x605dab50, 0xe046, 0x4300, { 0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23 } }
#define SHIM_IMAGE_LOADER_GUID \
{ 0x1f492041, 0xfadb, 0x4e59, { 0x9e, 0x57, 0x7c, 0xaf, 0xe7, 0x3a, 0x55, 0xab } }

View File

@ -29,7 +29,6 @@
#include "siphash24.h"
#include "stat-util.h"
#include "strv.h"
#include "tmpfile-util.h"
#include "user-util.h"
ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc) {
@ -320,7 +319,6 @@ static int write_credential(
gid_t gid,
bool ownership_ok) {
_cleanup_free_ char *tmp = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
@ -328,16 +326,10 @@ static int write_credential(
assert(id);
assert(data || size == 0);
r = tempfn_random_child("", "cred", &tmp);
if (r < 0)
return r;
fd = openat(dfd, tmp, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0600);
fd = openat(dfd, id, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0600);
if (fd < 0)
return -errno;
CLEANUP_TMPFILE_AT(dfd, tmp);
r = loop_write(fd, data, size);
if (r < 0)
return r;
@ -359,11 +351,6 @@ static int write_credential(
return r;
}
r = RET_NERRNO(renameat(dfd, tmp, dfd, id));
if (r < 0)
return r;
tmp = mfree(tmp); /* disarm CLEANUP_TMPFILE_AT() */
return 0;
}
@ -426,36 +413,20 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
return 0;
}
static bool device_nodes_restricted(
const ExecContext *c,
const CGroupContext *cgroup_context) {
assert(c);
assert(cgroup_context);
/* Returns true if we have any reason to believe we might not be able to access the TPM device
* directly, even if we run as root/PID 1. This could be because /dev/ is replaced by a private
* version, or because a device node access list is configured. */
if (c->private_devices)
return true;
if (cgroup_context_has_device_policy(cgroup_context))
return true;
return false;
}
struct load_cred_args {
const ExecContext *context;
const CGroupContext *cgroup_context;
const ExecParameters *params;
const char *unit;
bool always_ipc;
bool encrypted;
int write_dfd;
uid_t uid;
gid_t gid;
bool ownership_ok;
uint64_t left;
};
@ -486,7 +457,7 @@ static int maybe_decrypt_and_write_credential(
flags |= CREDENTIAL_ANY_SCOPE;
if (!device_nodes_restricted(args->context, args->cgroup_context)) {
if (!args->always_ipc) {
r = decrypt_credential_and_warn(
id,
now(CLOCK_REALTIME),
@ -787,38 +758,49 @@ static int load_cred_recurse_dir_cb(
return RECURSE_DIR_CONTINUE;
}
static bool device_nodes_restricted(
const ExecContext *c,
const CGroupContext *cgroup_context) {
assert(c);
assert(cgroup_context);
/* Returns true if we have any reason to believe we might not be able to access the TPM device
* directly, even if we run as root/PID 1. This could be because /dev/ is replaced by a private
* version, or because a device node access list is configured. */
if (c->private_devices)
return true;
if (cgroup_context_has_device_policy(cgroup_context))
return true;
return false;
}
static int acquire_credentials(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
const char *p,
int dfd,
uid_t uid,
gid_t gid,
bool ownership_ok) {
_cleanup_close_ int dfd = -EBADF;
int r;
assert(context);
assert(cgroup_context);
assert(params);
assert(unit);
assert(p);
dfd = open(p, O_DIRECTORY|O_CLOEXEC);
if (dfd < 0)
return -errno;
r = fd_acl_make_writable(dfd); /* Add the "w" bit, if we are reusing an already set up credentials dir where it was unset */
if (r < 0)
return r;
assert(dfd >= 0);
struct load_cred_args args = {
.context = context,
.cgroup_context = cgroup_context,
.params = params,
.unit = unit,
.always_ipc = device_nodes_restricted(context, cgroup_context),
.write_dfd = dfd,
.uid = uid,
.gid = gid,
@ -919,7 +901,15 @@ static int acquire_credentials(
return r;
}
r = fd_acl_make_read_only(dfd); /* Now take away the "w" bit */
return 0;
}
static int credentials_dir_finalize_permissions(int dfd, uid_t uid, gid_t gid, bool ownership_ok) {
int r;
assert(dfd >= 0);
r = fd_acl_make_read_only(dfd); /* Take away the "w" bit */
if (r < 0)
return r;
@ -943,157 +933,159 @@ static int acquire_credentials(
return 0;
}
static int setup_credentials_plain_dir(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
const char *cred_dir,
uid_t uid,
gid_t gid) {
_cleanup_free_ char *t = NULL, *workspace = NULL;
_cleanup_(rm_rf_safep) const char *workspace_rm = NULL;
_cleanup_close_ int dfd = -EBADF;
int r;
assert(context);
assert(params);
assert(unit);
assert(cred_dir);
/* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
* it into place, so that users can't access half-initialized credential stores. */
t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials");
if (!t)
return -ENOMEM;
r = mkdir_label(t, 0700);
if (r < 0 && r != -EEXIST)
return r;
workspace = path_join(t, unit);
if (!workspace)
return -ENOMEM;
dfd = open_mkdir(workspace, O_CLOEXEC|O_EXCL, 0700);
if (dfd < 0)
return log_debug_errno(dfd, "Failed to create workspace for credentials: %m");
workspace_rm = workspace;
(void) label_fix_full(dfd, /* inode_path = */ NULL, cred_dir, /* flags = */ 0);
r = acquire_credentials(context, cgroup_context, params, unit, dfd, uid, gid, /* ownership_ok = */ false);
if (r < 0)
return r;
r = RET_NERRNO(rename(workspace, cred_dir));
if (r >= 0)
workspace_rm = NULL;
if (r == -EEXIST) {
log_debug_errno(r, "Credential dir '%s' already populated, exchanging with workspace.", cred_dir);
r = RET_NERRNO(renameat2(AT_FDCWD, workspace, AT_FDCWD, cred_dir, RENAME_EXCHANGE));
}
if (r < 0)
return log_debug_errno(r, "Failed to move credentials workspace into place: %m");
/* rename() requires both the source and target to be writable, hence lock down write permission
* as last step. */
r = credentials_dir_finalize_permissions(dfd, uid, gid, /* ownership_ok = */ false);
if (r < 0)
return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m");
return 0;
}
static int setup_credentials_internal(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
const char *final, /* This is where the credential store shall eventually end up at */
const char *workspace, /* This is where we can prepare it before moving it to the final place */
bool reuse_workspace, /* Whether to reuse any existing workspace mount if it already is a mount */
bool must_mount, /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
const char *cred_dir,
uid_t uid,
gid_t gid) {
bool final_mounted;
int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true
* if we mounted something; false if we definitely can't mount anything */
_cleanup_close_ int fs_fd = -EBADF, mfd = -EBADF, dfd = -EBADF;
bool dir_mounted;
int r;
assert(context);
assert(params);
assert(unit);
assert(final);
assert(workspace);
assert(cred_dir);
r = path_is_mount_point(final);
if (r < 0)
return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", final);
final_mounted = r > 0;
if (final_mounted) {
if (FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) {
r = umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
if (r < 0)
return r;
final_mounted = false;
} else {
/* We can reuse the previous credential dir */
r = dir_is_empty(final, /* ignore_hidden_or_backup = */ false);
if (r < 0)
return r;
if (r == 0) {
log_debug("Credential dir for unit '%s' already set up, skipping.", unit);
return 0;
}
if (!FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) {
/* We may reuse the previous credential dir */
r = dir_is_empty(cred_dir, /* ignore_hidden_or_backup = */ false);
if (r < 0)
return r;
if (r == 0) {
log_debug("Credential dir for unit '%s' already set up, skipping.", unit);
return 0;
}
}
if (reuse_workspace) {
r = path_is_mount_point(workspace);
if (r < 0)
return r;
if (r > 0)
workspace_mounted = true; /* If this is already a mount, and we are supposed to reuse
* it, let's keep this in mind */
else
workspace_mounted = -1; /* We need to figure out if we can mount something to the workspace */
} else
workspace_mounted = -1; /* ditto */
r = path_is_mount_point(cred_dir);
if (r < 0)
return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", cred_dir);
dir_mounted = r > 0;
/* If both the final place and the workspace are mounted, we have no mounts to set up, based on
* the assumption that they're actually the same tmpfs (but the latter with MS_RDONLY different).
* If the workspace is not mounted, we just bind the final place over and make it writable. */
must_mount = must_mount || final_mounted;
if (workspace_mounted < 0) {
if (!final_mounted)
/* Nothing is mounted on the workspace yet, let's try to mount a new tmpfs if
* not using the final place. */
r = mount_credentials_fs(workspace, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
if (final_mounted || r < 0) {
/* If using final place or failed to mount new tmpfs, make a bind mount from
* the final to the workspace, so that we can make it writable there. */
r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL);
if (r < 0) {
if (!ERRNO_IS_PRIVILEGE(r))
/* Propagate anything that isn't a permission problem. */
return r;
if (must_mount)
/* If it's not OK to use the plain directory fallback, propagate all
* errors too. */
return r;
/* If we lack privileges to bind mount stuff, then let's gracefully proceed
* for compat with container envs, and just use the final dir as is.
* Final place must not be mounted in this case (refused by must_mount
* above) */
workspace_mounted = false;
} else {
/* Make the new bind mount writable (i.e. drop MS_RDONLY) */
r = mount_nofollow_verbose(LOG_DEBUG,
NULL,
workspace,
NULL,
MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false),
NULL);
if (r < 0)
return r;
workspace_mounted = true;
}
} else
workspace_mounted = true;
mfd = fsmount_credentials_fs(&fs_fd);
if (ERRNO_IS_NEG_PRIVILEGE(mfd) && !dir_mounted) {
log_debug_errno(mfd, "Lacking privilege to mount credentials fs, falling back to plain directory.");
return setup_credentials_plain_dir(context, cgroup_context, params, unit, cred_dir, uid, gid);
}
if (mfd < 0)
return log_debug_errno(mfd, "Failed to mount credentials fs: %m");
assert(workspace_mounted >= 0);
assert(!must_mount || workspace_mounted);
dfd = fd_reopen(mfd, O_DIRECTORY|O_CLOEXEC);
if (dfd < 0)
return dfd;
const char *where = workspace_mounted ? workspace : final;
(void) label_fix_full(AT_FDCWD, where, final, 0);
r = acquire_credentials(context, cgroup_context, params, unit, where, uid, gid, workspace_mounted);
if (r < 0) {
/* If we're using final place as workspace, and failed to acquire credentials, we might
* have left half-written creds there. Let's get rid of the whole mount, so future
* calls won't reuse it. */
if (final_mounted)
(void) umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
(void) label_fix_full(dfd, /* inode_path = */ NULL, cred_dir, /* flags = */ 0);
r = acquire_credentials(context, cgroup_context, params, unit, dfd, uid, gid, /* ownership_ok = */ true);
if (r < 0)
return r;
}
if (workspace_mounted) {
if (!final_mounted) {
/* Make workspace read-only now, so that any bind mount we make from it defaults to
* read-only too */
r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL);
if (r < 0)
return r;
r = credentials_dir_finalize_permissions(dfd, uid, gid, /* ownership_ok = */ true);
if (r < 0)
return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m");
/* And mount it to the final place, read-only */
r = mount_nofollow_verbose(LOG_DEBUG, workspace, final, NULL, MS_MOVE, NULL);
} else
/* Otherwise we just get rid of the bind mount of final place */
r = umount_verbose(LOG_DEBUG, workspace, MNT_DETACH|UMOUNT_NOFOLLOW);
// Work around a kernel bug that results in tmpfs reconfiguration failure.
// FIXME: drop this once https://lore.kernel.org/linux-fsdevel/20251108190930.440685-1-me@yhndnzj.com/
// is merged and hits the distro kernels.
(void) fsconfig(fs_fd, FSCONFIG_SET_FLAG, "noswap", NULL, 0);
if (fsconfig(fs_fd, FSCONFIG_SET_FLAG, "ro", NULL, 0) < 0)
return -errno;
if (fsconfig(fs_fd, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0) < 0)
return -errno;
log_debug("Successfully reconfigured credentials fs to be read only.");
if (dir_mounted) {
/* Firstly, try to move beneath the existing mount, which guarantees strictly atomic replacement
* (needs kernel >= 6.5) */
r = move_mount(mfd, "", AT_FDCWD, cred_dir, MOVE_MOUNT_F_EMPTY_PATH|MOVE_MOUNT_BENEATH);
if (r >= 0)
return umount_verbose(LOG_DEBUG, cred_dir, MNT_DETACH|UMOUNT_NOFOLLOW);
if (errno != EINVAL)
return log_debug_errno(errno, "Failed to move credentials fs into place: %m");
log_debug_errno(errno, "Unable to move credentials fs beneath existing mount '%s', unmounting instead: %m",
cred_dir);
r = umount_verbose(LOG_DEBUG, cred_dir, MNT_DETACH|UMOUNT_NOFOLLOW);
if (r < 0)
return r;
} else {
_cleanup_free_ char *parent = NULL;
/* If we do not have our own mount put used the plain directory fallback, then we need to
* open access to the top-level credential directory and the per-service directory now */
r = path_extract_directory(final, &parent);
if (r < 0)
return r;
if (chmod(parent, 0755) < 0)
return -errno;
}
r = move_mount(mfd, "", AT_FDCWD, cred_dir, MOVE_MOUNT_F_EMPTY_PATH);
if (r < 0)
return log_debug_errno(errno, "Failed to move credentials fs into place: %m");
return 0;
}
@ -1136,96 +1128,12 @@ int exec_setup_credentials(
if (r < 0 && r != -EEXIST)
return r;
r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_NEW_MOUNTNS, NULL);
if (r < 0) {
_cleanup_(rmdir_and_freep) char *u = NULL; /* remove the temporary workspace if we can */
_cleanup_free_ char *t = NULL;
/* If this is not a privilege or support issue then propagate the error */
if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
return r;
/* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
* it into place, so that users can't access half-initialized credential stores. */
t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials");
if (!t)
return -ENOMEM;
/* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
* directory outside of /run/credentials/ first, and then move it over to /run/credentials/
* after it is fully set up */
u = path_join(t, unit);
if (!u)
return -ENOMEM;
FOREACH_STRING(i, t, u) {
r = mkdir_label(i, 0700);
if (r < 0 && r != -EEXIST)
return log_debug_errno(r, "Failed to make directory '%s': %m", i);
}
r = setup_credentials_internal(
context,
cgroup_context,
params,
unit,
p, /* final mount point */
u, /* temporary workspace to overmount */
true, /* reuse the workspace if it is already a mount */
false, /* it's OK to fall back to a plain directory if we can't mount anything */
uid,
gid);
if (r < 0)
return r;
} else if (r == 0) {
/* We managed to set up a mount namespace, and are now in a child. That's great. In this case
* we can use the same directory for all cases, after turning off propagation. Question
* though is: where do we turn off propagation exactly, and where do we place the workspace
* directory? We need some place that is guaranteed to be a mount point in the host, and
* which is guaranteed to have a subdir we can mount over. /run/ is not suitable for this,
* since we ultimately want to move the resulting file system there, i.e. we need propagation
* for /run/ eventually. We could use our own /run/systemd/bind mount on itself, but that
* would be visible in the host mount table all the time, which we want to avoid. Hence, what
* we do here instead we use /dev/ and /dev/shm/ for our purposes. We know for sure that
* /dev/ is a mount point and we now for sure that /dev/shm/ exists. Hence we can turn off
* propagation on the former, and then overmount the latter.
*
* Yes it's nasty playing games with /dev/ and /dev/shm/ like this, since it does not exist
* for this purpose, but there are few other candidates that work equally well for us, and
* given that we do this in a privately namespaced short-lived single-threaded process that
* no one else sees this should be OK to do. */
/* Turn off propagation from our namespace to host */
r = mount_nofollow_verbose(LOG_DEBUG, NULL, "/dev", NULL, MS_SLAVE|MS_REC, NULL);
if (r < 0)
goto child_fail;
r = setup_credentials_internal(
context,
cgroup_context,
params,
unit,
p, /* final mount point */
"/dev/shm", /* temporary workspace to overmount */
false, /* do not reuse /dev/shm if it is already a mount, under no circumstances */
true, /* insist that something is mounted, do not allow fallback to plain directory */
uid,
gid);
if (r < 0)
goto child_fail;
_exit(EXIT_SUCCESS);
child_fail:
_exit(EXIT_FAILURE);
}
r = setup_credentials_internal(context, cgroup_context, params, unit, p, uid, gid);
/* If the credentials dir is empty and not a mount point, then there's no point in having it. Let's
* try to remove it. This matters in particular if we created the dir as mount point but then didn't
* actually end up mounting anything on it. In that case we'd rather have ENOENT than EACCESS being
* seen by users when trying access this inode. */
(void) rmdir(p);
return 0;
return r;
}

View File

@ -2134,7 +2134,7 @@ static int build_environment(
}
if (!sd_id128_is_null(p->invocation_id)) {
assert(p->invocation_id_string);
assert(!isempty(p->invocation_id_string));
r = strv_extend_joined_with_size(&e, &n, "INVOCATION_ID=", p->invocation_id_string);
if (r < 0)

View File

@ -103,7 +103,7 @@ static int acquire_credential_directory(ImportCredentialsContext *c, const char
(void) mount_nofollow_verbose(LOG_WARNING, NULL, path, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), NULL);
else if (with_mount)
/* If not a mount point yet, and the credentials are not encrypted, then let's try to mount a no-swap fs there */
(void) mount_credentials_fs(path, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
(void) mount_credentials_fs(path);
c->target_dir_fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
if (c->target_dir_fd < 0)

View File

@ -80,3 +80,6 @@ typedef struct {
GUID_DEF(0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72)
#define EFI_CERT_TYPE_PKCS7_GUID \
GUID_DEF(0x4aafd29d, 0x68df, 0x49ee, 0x8a, 0xa9, 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7)
#define SHIM_LOCK_GUID \
GUID_DEF(0x605dab50, 0xe046, 0x4300 ,0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23)

View File

@ -10,13 +10,17 @@ static const sd_char * const table[_SECURE_BOOT_MAX] = {
[SECURE_BOOT_DEPLOYED] = STR_C("deployed"),
[SECURE_BOOT_SETUP] = STR_C("setup"),
[SECURE_BOOT_USER] = STR_C("user"),
[SECURE_BOOT_TAINTED] = STR_C("tainted"),
};
const sd_char *secure_boot_mode_to_string(SecureBootMode m) {
return (m >= 0 && m < _SECURE_BOOT_MAX) ? table[m] : NULL;
}
SecureBootMode decode_secure_boot_mode(bool secure, bool audit, bool deployed, bool setup) {
SecureBootMode decode_secure_boot_mode(bool secure, bool audit, bool deployed, bool setup, bool moksb) {
/* shim verification can be disabled by moksb, some OEMs set it */
if (secure && moksb)
return SECURE_BOOT_TAINTED;
/* See figure 32-4 Secure Boot Modes from UEFI Specification 2.9 */
if (secure && deployed && !audit && !setup)
return SECURE_BOOT_DEPLOYED;

View File

@ -51,9 +51,10 @@ typedef enum SecureBootMode {
SECURE_BOOT_DEPLOYED,
SECURE_BOOT_SETUP,
SECURE_BOOT_USER,
SECURE_BOOT_TAINTED,
_SECURE_BOOT_MAX,
_SECURE_BOOT_INVALID = -EINVAL,
} SecureBootMode;
const sd_char *secure_boot_mode_to_string(SecureBootMode m);
SecureBootMode decode_secure_boot_mode(bool secure, bool audit, bool deployed, bool setup);
SecureBootMode decode_secure_boot_mode(bool secure, bool audit, bool deployed, bool setup, bool moksb);

View File

@ -683,7 +683,11 @@ static int get_search(uint64_t type, char ***ret) {
*ret = TAKE_PTR(l);
return 0;
}}
}
case SD_PATH_SEARCH_SYSCTL:
return strv_from_nulstr(ret, CONF_PATHS_NULSTR("sysctl.d"));
}
return -EOPNOTSUPP;
}

View File

@ -66,6 +66,7 @@ static const char* const path_table[_SD_PATH_MAX] = {
[SD_PATH_SEARCH_CONFIGURATION_FACTORY] = "search-configuration-factory",
[SD_PATH_SEARCH_STATE_FACTORY] = "search-state-factory",
[SD_PATH_SEARCH_CONFIGURATION] = "search-configuration",
[SD_PATH_SEARCH_SYSCTL] = "search-sysctl",
[SD_PATH_SYSTEMD_UTIL] = "systemd-util",

View File

@ -682,16 +682,12 @@ int fd_acl_make_read_only(int fd) {
r = dlopen_libacl();
if (r < 0)
return r;
goto maybe_fallback;
acl = sym_acl_get_fd(fd);
if (!acl) {
if (!ERRNO_IS_NOT_SUPPORTED(errno))
return -errno;
/* No ACLs? Then just update the regular mode_t */
return fd_acl_make_read_only_fallback(fd);
r = -errno;
goto maybe_fallback;
}
for (r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
@ -729,75 +725,18 @@ int fd_acl_make_read_only(int fd) {
return 0;
if (sym_acl_set_fd(fd, acl) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno))
return -errno;
return fd_acl_make_read_only_fallback(fd);
r = -errno;
goto maybe_fallback;
}
return 1;
}
int fd_acl_make_writable(int fd) {
_cleanup_(acl_freep) acl_t acl = NULL;
acl_entry_t i;
int r;
/* Safely adds the writable bit to the owner's ACL entry of this inode. (And only the owner's! This
* not the obvious inverse of fd_acl_make_read_only() hence!) */
r = dlopen_libacl();
if (r < 0)
maybe_fallback:
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
return r;
acl = sym_acl_get_fd(fd);
if (!acl) {
if (!ERRNO_IS_NOT_SUPPORTED(errno))
return -errno;
/* No ACLs? Then just update the regular mode_t */
return fd_acl_make_writable_fallback(fd);
}
for (r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
r > 0;
r = sym_acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
acl_permset_t permset;
acl_tag_t tag;
int b;
if (sym_acl_get_tag_type(i, &tag) < 0)
return -errno;
if (tag != ACL_USER_OBJ)
continue;
if (sym_acl_get_permset(i, &permset) < 0)
return -errno;
b = sym_acl_get_perm(permset, ACL_WRITE);
if (b < 0)
return -errno;
if (b)
return 0; /* Already set? Then there's nothing to do. */
if (sym_acl_add_perm(permset, ACL_WRITE) < 0)
return -errno;
break;
}
if (r < 0)
return -errno;
if (sym_acl_set_fd(fd, acl) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno))
return -errno;
return fd_acl_make_writable_fallback(fd);
}
return 1;
/* No ACLs? Then just update the regular mode_t */
return fd_acl_make_read_only_fallback(fd);
}
#endif
@ -818,23 +757,6 @@ int fd_acl_make_read_only_fallback(int fd) {
return 1;
}
int fd_acl_make_writable_fallback(int fd) {
struct stat st;
assert(fd >= 0);
if (fstat(fd, &st) < 0)
return -errno;
if ((st.st_mode & 0200) != 0) /* already set */
return 0;
if (fchmod(fd, (st.st_mode & 07777) | 0200) < 0)
return -errno;
return 1;
}
int inode_type_can_acl(mode_t mode) {
return IN_SET(mode & S_IFMT, S_IFSOCK, S_IFREG, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO);
}

View File

@ -4,7 +4,6 @@
#include "shared-forward.h"
int fd_acl_make_read_only_fallback(int fd);
int fd_acl_make_writable_fallback(int fd);
#if HAVE_ACL
#include <acl/libacl.h> /* IWYU pragma: export */
@ -56,7 +55,6 @@ int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *ret);
int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask);
int fd_acl_make_read_only(int fd);
int fd_acl_make_writable(int fd);
/* acl_free() takes multiple argument types. Multiple cleanup functions are necessary. */
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(acl_t, sym_acl_free, acl_freep, NULL);
@ -89,10 +87,6 @@ static inline int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask) {
static inline int fd_acl_make_read_only(int fd) {
return fd_acl_make_read_only_fallback(fd);
}
static inline int fd_acl_make_writable(int fd) {
return fd_acl_make_writable_fallback(fd);
}
#endif
int inode_type_can_acl(mode_t mode);

View File

@ -9,7 +9,7 @@
#define CREDENTIAL_NAME_MAX FDNAME_MAX
/* Put a size limit on the individual credential */
#define CREDENTIAL_SIZE_MAX (1024U*1024U)
#define CREDENTIAL_SIZE_MAX (1U * U64_MB)
/* Refuse to store more than 1M per service, after all this is unswappable memory. Note that for now we put
* this to the same limit as the per-credential limit, i.e. if the user has n > 1 credentials instead of 1 it
@ -18,7 +18,7 @@
/* Put a size limit on encrypted credentials (which is the same as the unencrypted size plus a spacious 128K of extra
* space for headers, IVs, exported TPM2 key material and so on. */
#define CREDENTIAL_ENCRYPTED_SIZE_MAX (CREDENTIAL_SIZE_MAX + 128U*1024U)
#define CREDENTIAL_ENCRYPTED_SIZE_MAX (CREDENTIAL_SIZE_MAX + 128U * U64_KB)
bool credential_name_valid(const char *s);
bool credential_glob_valid(const char *s);

View File

@ -7,6 +7,7 @@
#include "alloc-util.h"
#include "chase.h"
#include "creds-util.h"
#include "dissect-image.h"
#include "errno-util.h"
#include "extract-word.h"
@ -1831,58 +1832,65 @@ unsigned long credentials_fs_mount_flags(bool ro) {
return MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported()|(ro ? MS_RDONLY : 0);
}
int mount_credentials_fs(const char *path, size_t size, bool ro) {
_cleanup_free_ char *opts = NULL;
int r, noswap_supported;
int fsmount_credentials_fs(int *ret_fsfd) {
_cleanup_close_ int fs_fd = -EBADF;
char size_str[DECIMAL_STR_MAX(uint64_t)];
/* Mounts a file system we can place credentials in, i.e. with tight access modes right from the
* beginning, and ideally swapping turned off. In order of preference:
*
* 1. tmpfs if it supports "noswap"
* 1. tmpfs if it supports "noswap" (needs kernel >= 6.3)
* 2. ramfs
* 3. tmpfs if it doesn't support "noswap"
* 3. tmpfs without "noswap"
*/
noswap_supported = mount_option_supported("tmpfs", "noswap", NULL); /* Check explicitly to avoid kmsg noise */
if (noswap_supported > 0) {
_cleanup_free_ char *noswap_opts = NULL;
fs_fd = fsopen("tmpfs", FSOPEN_CLOEXEC);
if (fs_fd < 0)
return -errno;
if (asprintf(&noswap_opts, "mode=0700,nr_inodes=1024,size=%zu,noswap", size) < 0)
return -ENOMEM;
if (fsconfig(fs_fd, FSCONFIG_SET_STRING, "nr_inodes", "1024", 0) < 0)
return -errno;
/* Best case: tmpfs with noswap (needs kernel >= 6.3) */
xsprintf(size_str, "%" PRIu64, CREDENTIALS_TOTAL_SIZE_MAX);
if (fsconfig(fs_fd, FSCONFIG_SET_STRING, "size", size_str, 0) < 0)
return -errno;
r = mount_nofollow_verbose(
LOG_DEBUG,
"tmpfs",
path,
"tmpfs",
credentials_fs_mount_flags(ro),
noswap_opts);
if (r >= 0)
return r;
if (fsconfig(fs_fd, FSCONFIG_SET_FLAG, "noswap", NULL, 0) < 0) {
if (errno != EINVAL)
return -errno;
int ramfs_fd = fsopen("ramfs", FSOPEN_CLOEXEC);
if (ramfs_fd >= 0)
close_and_replace(fs_fd, ramfs_fd);
}
r = mount_nofollow_verbose(
LOG_DEBUG,
"ramfs",
path,
"ramfs",
credentials_fs_mount_flags(ro),
"mode=0700");
if (r >= 0)
return r;
if (fsconfig(fs_fd, FSCONFIG_SET_STRING, "mode", "0700", 0) < 0)
return -errno;
if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%zu", size) < 0)
return -ENOMEM;
if (fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0)
return -errno;
return mount_nofollow_verbose(
LOG_DEBUG,
"tmpfs",
path,
"tmpfs",
credentials_fs_mount_flags(ro),
opts);
int mfd = fsmount(fs_fd, FSMOUNT_CLOEXEC,
ms_flags_to_mount_attr(credentials_fs_mount_flags(/* ro = */ false)));
if (mfd < 0)
return -errno;
if (ret_fsfd)
*ret_fsfd = TAKE_FD(fs_fd);
return mfd;
}
int mount_credentials_fs(const char *path) {
_cleanup_close_ int mfd = -EBADF;
assert(path);
mfd = fsmount_credentials_fs(/* ret_fsfd = */ NULL);
if (mfd < 0)
return mfd;
return RET_NERRNO(move_mount(mfd, "", AT_FDCWD, path, MOVE_MOUNT_F_EMPTY_PATH));
}
int make_fsmount(

View File

@ -159,7 +159,8 @@ int make_mount_point_inode_from_path(const char *source, const char *dest, mode_
int trigger_automount_at(int dir_fd, const char *path);
unsigned long credentials_fs_mount_flags(bool ro);
int mount_credentials_fs(const char *path, size_t size, bool ro);
int fsmount_credentials_fs(int *ret_fsfd);
int mount_credentials_fs(const char *path);
int make_fsmount(int error_log_level, const char *what, const char *type, unsigned long flags, const char *options, int userns_fd);

View File

@ -4,6 +4,8 @@
#include <stdio.h>
#include <sys/stat.h>
#include "sd-path.h"
#include "alloc-util.h"
#include "build.h"
#include "conf-files.h"
@ -279,10 +281,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
return 0;
}
static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent) {
static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent, const char **search_paths) {
return conf_file_read(
/* root = */ NULL,
(const char**) CONF_PATHS_STRV("sysctl.d"),
search_paths,
path,
parse_line,
sysctl_options,
@ -305,7 +307,7 @@ static int read_credential_lines(OrderedHashmap **sysctl_options) {
if (!j)
return log_oom();
return parse_file(sysctl_options, j, /* ignore_enoent= */ true);
return parse_file(sysctl_options, j, /* ignore_enoent= */ true, /* search_paths= */ NULL);
}
static int cat_config(char **files) {
@ -440,8 +442,13 @@ static int parse_argv(int argc, char *argv[]) {
static int run(int argc, char *argv[]) {
_cleanup_ordered_hashmap_free_ OrderedHashmap *sysctl_options = NULL;
_cleanup_strv_free_ char **search_paths = NULL;
int r;
r = sd_path_lookup_strv(SD_PATH_SEARCH_SYSCTL, /* suffix= */ NULL, &search_paths);
if (r < 0)
return log_error_errno(r, "Failed to get sysctl.d/ search paths: %m");
r = parse_argv(argc, argv);
if (r <= 0)
return r;
@ -458,12 +465,12 @@ static int run(int argc, char *argv[]) {
/* Use (argument):n, where n==1 for the first positional arg */
RET_GATHER(r, parse_line("(argument)", ++pos, *arg, /* invalid_config = */ NULL, &sysctl_options));
else
RET_GATHER(r, parse_file(&sysctl_options, *arg, false));
RET_GATHER(r, parse_file(&sysctl_options, *arg, /* ignore_enoent= */ false, (const char**) search_paths));
}
} else {
_cleanup_strv_free_ char **files = NULL;
r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("sysctl.d"));
r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) search_paths);
if (r < 0)
return log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
@ -471,7 +478,7 @@ static int run(int argc, char *argv[]) {
return cat_config(files);
STRV_FOREACH(f, files)
RET_GATHER(r, parse_file(&sysctl_options, *f, true));
RET_GATHER(r, parse_file(&sysctl_options, *f, /* ignore_enoent= */ true, /* search_paths= */ NULL));
RET_GATHER(r, read_credential_lines(&sysctl_options));
}

View File

@ -24,7 +24,7 @@ int verb_cat(int argc, char *argv[], void *userdata) {
_cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
_cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL;
sd_bus *bus;
sd_bus *bus = NULL;
bool first = true;
int r, rc = 0;
@ -39,17 +39,24 @@ int verb_cat(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
if (install_client_side() == INSTALL_CLIENT_SIDE_NO) {
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
r = maybe_extend_with_unit_dependencies(bus, &names);
if (r < 0)
return r;
r = maybe_extend_with_unit_dependencies(bus, &names);
if (r < 0)
return r;
} else {
/* In client-side mode (--global, --root, etc.), just mangle names without bus interaction */
r = mangle_names("to cat", strv_skip(argv, 1), &names);
if (r < 0)
return r;
}
pager_open(arg_pager_flags);
@ -57,7 +64,7 @@ int verb_cat(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *fragment_path = NULL;
_cleanup_strv_free_ char **dropin_paths = NULL;
r = unit_find_paths(bus, *name, &lp, false, &cached_id_map, &cached_name_map, &fragment_path, &dropin_paths);
r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ !bus, &cached_id_map, &cached_name_map, &fragment_path, &dropin_paths);
if (r == -ERFKILL) {
printf("%s# Unit %s is masked%s.\n",
ansi_highlight_magenta(),
@ -87,7 +94,7 @@ int verb_cat(int argc, char *argv[], void *userdata) {
else
puts("");
if (need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */
if (bus && need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */
fprintf(stderr,
"%s# Warning: %s changed on disk, the version systemd has loaded is outdated.\n"
"%s# This output shows the current version of the unit's original fragment and drop-in files.\n"
@ -211,7 +218,6 @@ static int find_paths_to_edit(
const char *drop_in;
int r;
assert(bus);
assert(context);
assert(names);
@ -241,8 +247,8 @@ static int find_paths_to_edit(
_cleanup_free_ char *path = NULL;
_cleanup_strv_free_ char **unit_paths = NULL;
r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ false, &cached_id_map, &cached_name_map, &path, &unit_paths);
if (r == -EKEYREJECTED) {
r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ !bus, &cached_id_map, &cached_name_map, &path, &unit_paths);
if (r == -EKEYREJECTED && bus) {
/* If loading of the unit failed server side complete, then the server won't tell us
* the unit file path. In that case, find the file client side. */
@ -327,7 +333,7 @@ int verb_edit(int argc, char *argv[], void *userdata) {
.read_from_stdin = arg_stdin,
};
_cleanup_strv_free_ char **names = NULL;
sd_bus *bus;
sd_bus *bus = NULL;
int r;
if (!on_tty() && !arg_stdin)
@ -340,13 +346,20 @@ int verb_edit(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
if (install_client_side() == INSTALL_CLIENT_SIDE_NO) {
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
} else {
/* In client-side mode (--global, --root, etc.), just mangle names without bus interaction */
r = mangle_names("to edit", strv_skip(argv, 1), &names);
if (r < 0)
return r;
}
if (strv_isempty(names))
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns.");
@ -354,14 +367,6 @@ int verb_edit(int argc, char *argv[], void *userdata) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"With 'edit --stdin --full', exactly one unit for editing must be specified.");
STRV_FOREACH(tmp, names) {
r = unit_is_masked(bus, *tmp);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to check if unit %s is masked: %m", *tmp);
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit %s: unit is masked.", *tmp);
}
r = find_paths_to_edit(bus, &context, names);
if (r < 0)
return r;

View File

@ -29,6 +29,7 @@
#include "reboot-util.h"
#include "runtime-scope.h"
#include "set.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "systemctl.h"
@ -589,6 +590,13 @@ int unit_find_paths(
return log_error_errno(r, "Failed to find fragment for '%s': %m", unit_name);
if (_path) {
/* Check if unit is masked (symlinked to /dev/null or empty) */
r = null_or_empty_path(_path);
if (r < 0)
return log_error_errno(r, "Failed to check if '%s' is masked: %m", unit_name);
if (r > 0)
return -ERFKILL; /* special case: no logging */
path = strdup(_path);
if (!path)
return log_oom();

View File

@ -129,6 +129,8 @@ __extension__ enum {
SD_PATH_USER_CREDENTIAL_STORE_ENCRYPTED,
SD_PATH_USER_SEARCH_CREDENTIAL_STORE_ENCRYPTED,
SD_PATH_SEARCH_SYSCTL,
_SD_PATH_MAX,
_SD_PATH_INVALID = UINT64_MAX
};

View File

@ -112,30 +112,6 @@ TEST_RET(fd_acl_make_read_only) {
cmd = strjoina("stat ", fn);
ASSERT_OK_ZERO_ERRNO(system(cmd));
log_info("writable");
ASSERT_OK_POSITIVE(fd_acl_make_writable(fd));
ASSERT_OK_ERRNO(fstat(fd, &st));
ASSERT_EQ(st.st_mode & 0222, 0200u);
cmd = strjoina("getfacl -p ", fn);
ASSERT_OK_ZERO_ERRNO(system(cmd));
cmd = strjoina("stat ", fn);
ASSERT_OK_ZERO_ERRNO(system(cmd));
log_info("read-only");
ASSERT_OK_POSITIVE(fd_acl_make_read_only(fd));
ASSERT_OK_ERRNO(fstat(fd, &st));
ASSERT_EQ(st.st_mode & 0222, 0000u);
cmd = strjoina("getfacl -p ", fn);
ASSERT_OK_ZERO_ERRNO(system(cmd));
cmd = strjoina("stat ", fn);
ASSERT_OK_ZERO_ERRNO(system(cmd));
return 0;
}

View File

@ -573,4 +573,48 @@ systemctl daemon-reload
systemctl enable --now test-WantedBy.service || :
systemctl daemon-reload
# Test systemctl edit --global and systemctl cat --global (issue #31272)
GLOBAL_UNIT_NAME="systemctl-test-$RANDOM.service"
GLOBAL_MASKED_UNIT="systemctl-test-masked-$RANDOM.service"
# Test 1: Create a new global user unit with --force and --runtime
systemctl edit --global --runtime --stdin --full --force "$GLOBAL_UNIT_NAME" <<EOF
[Unit]
Description=Test global unit
[Service]
ExecStart=/bin/true
EOF
# Verify the unit file was created in /run/systemd/user/
test -f "/run/systemd/user/$GLOBAL_UNIT_NAME"
# Test 2: Read the global unit with systemctl cat --global
systemctl cat --global "$GLOBAL_UNIT_NAME" | grep -q "ExecStart=/bin/true"
# Test 3: Edit existing global unit (add a drop-in)
systemctl edit --global --runtime --stdin "$GLOBAL_UNIT_NAME" <<EOF
[Service]
Environment=TEST=value
EOF
# Verify drop-in was created
test -f "/run/systemd/user/$GLOBAL_UNIT_NAME.d/override.conf"
systemctl cat --global "$GLOBAL_UNIT_NAME" | grep -q "Environment=TEST=value"
# Test 4: Create a masked global unit in /run/
mkdir -p /run/systemd/user
ln -sf /dev/null "/run/systemd/user/$GLOBAL_MASKED_UNIT"
# Test 5: Verify cat shows it's masked
systemctl cat --global "$GLOBAL_MASKED_UNIT" 2>&1 | grep -q "masked"
# Test 6: Verify edit refuses to edit masked unit
(! systemctl edit --global --runtime --stdin --full "$GLOBAL_MASKED_UNIT" </dev/null 2>&1) | grep -q "masked"
# Cleanup global test units
rm -f "/run/systemd/user/$GLOBAL_UNIT_NAME"
rm -rf "/run/systemd/user/$GLOBAL_UNIT_NAME.d"
rm -f "/run/systemd/user/$GLOBAL_MASKED_UNIT"
touch /testok

View File

@ -163,6 +163,7 @@ systemd-inhibit.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="
systemd-inhibit.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--why="]
systemd-inhibit.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--mode="]
systemd-inhibit.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--list"]
systemd-logind.service.xml ./refsect1[title="Signal"]/
systemd-notify.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--ready"]
systemd-notify.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--pid="]
systemd-notify.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--status="]