From 89fb28f1d3ce35e837dfd64a7df14c88832d33cf Mon Sep 17 00:00:00 2001 From: Skye Soss Date: Thu, 13 Aug 2026 13:20:03 -0500 Subject: [PATCH 1/2] include/uapi: add linux/securebits.h --- src/include/uapi/linux/securebits.h | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/include/uapi/linux/securebits.h diff --git a/src/include/uapi/linux/securebits.h b/src/include/uapi/linux/securebits.h new file mode 100644 index 0000000000..8bad2e1613 --- /dev/null +++ b/src/include/uapi/linux/securebits.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_SECUREBITS_H +#define _LINUX_SECUREBITS_H + +/* Each securesetting is implemented using two bits. One bit specifies + whether the setting is on or off. The other bit specify whether the + setting is locked or not. A setting which is locked cannot be + changed from user-level. */ +#define issecure_mask(X) (1 << (X)) + +#define SECUREBITS_DEFAULT 0x00000000 + +/* When set UID 0 has no special privileges. When unset, we support + inheritance of root-permissions and suid-root executable under + compatibility mode. We raise the effective and inheritable bitmasks + *of the executable file* if the effective uid of the new process is + 0. If the real uid is 0, we raise the effective (legacy) bit of the + executable file. */ +#define SECURE_NOROOT 0 +#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ + +#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT)) +#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED)) + +/* When set, setuid to/from uid 0 does not trigger capability-"fixup". + When unset, to provide compatiblility with old programs relying on + set*uid to gain/lose privilege, transitions to/from uid 0 cause + capabilities to be gained/lost. */ +#define SECURE_NO_SETUID_FIXUP 2 +#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ + +#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP)) +#define SECBIT_NO_SETUID_FIXUP_LOCKED \ + (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED)) + +/* When set, a process can retain its capabilities even after + transitioning to a non-root user (the set-uid fixup suppressed by + bit 2). Bit-4 is cleared when a process calls exec(); setting both + bit 4 and 5 will create a barrier through exec that no exec()'d + child can use this feature again. */ +#define SECURE_KEEP_CAPS 4 +#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ + +#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS)) +#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED)) + +/* When set, a process cannot add new capabilities to its ambient set. */ +#define SECURE_NO_CAP_AMBIENT_RAISE 6 +#define SECURE_NO_CAP_AMBIENT_RAISE_LOCKED 7 /* make bit-6 immutable */ + +#define SECBIT_NO_CAP_AMBIENT_RAISE (issecure_mask(SECURE_NO_CAP_AMBIENT_RAISE)) +#define SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED \ + (issecure_mask(SECURE_NO_CAP_AMBIENT_RAISE_LOCKED)) + +/* See Documentation/userspace-api/check_exec.rst */ +#define SECURE_EXEC_RESTRICT_FILE 8 +#define SECURE_EXEC_RESTRICT_FILE_LOCKED 9 /* make bit-8 immutable */ + +#define SECBIT_EXEC_RESTRICT_FILE (issecure_mask(SECURE_EXEC_RESTRICT_FILE)) +#define SECBIT_EXEC_RESTRICT_FILE_LOCKED \ + (issecure_mask(SECURE_EXEC_RESTRICT_FILE_LOCKED)) + +/* See Documentation/userspace-api/check_exec.rst */ +#define SECURE_EXEC_DENY_INTERACTIVE 10 +#define SECURE_EXEC_DENY_INTERACTIVE_LOCKED 11 /* make bit-10 immutable */ + +#define SECBIT_EXEC_DENY_INTERACTIVE \ + (issecure_mask(SECURE_EXEC_DENY_INTERACTIVE)) +#define SECBIT_EXEC_DENY_INTERACTIVE_LOCKED \ + (issecure_mask(SECURE_EXEC_DENY_INTERACTIVE_LOCKED)) + +#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ + issecure_mask(SECURE_NO_SETUID_FIXUP) | \ + issecure_mask(SECURE_KEEP_CAPS) | \ + issecure_mask(SECURE_NO_CAP_AMBIENT_RAISE) | \ + issecure_mask(SECURE_EXEC_RESTRICT_FILE) | \ + issecure_mask(SECURE_EXEC_DENY_INTERACTIVE)) +#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) + +#define SECURE_ALL_UNPRIVILEGED (issecure_mask(SECURE_EXEC_RESTRICT_FILE) | \ + issecure_mask(SECURE_EXEC_DENY_INTERACTIVE)) + +#endif /* _LINUX_SECUREBITS_H */ From bfc302dad21a7488e52f14e981ce7ed2240f0483 Mon Sep 17 00:00:00 2001 From: Skye Soss Date: Thu, 13 Aug 2026 10:03:44 -0500 Subject: [PATCH 2/2] shared: add new securebits The securebits parsing code now recognizes 'no-cap-ambient-raise' (added in Linux 4.3), 'exec-restrict-file', and 'exec-deny-interactive' (both added in Linux 6.14). The '-locked' variants were added too. --- man/systemd.exec.xml | 10 +++++++--- src/shared/securebits-util.c | 30 ++++++++++++++++++++++++++++++ src/test/test-secure-bits.c | 6 ++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 7561425dc9..5775e6c641 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -953,11 +953,15 @@ CapabilityBoundingSet=~CAP_B CAP_C Controls the secure bits set for the executed process. Takes a space-separated combination of options from the following list: , , - , , , and - . This option may appear more than once, in which case the secure bits are + , , , + , , , + , , + , and . + This option may appear more than once, in which case the secure bits are ORed. If the empty string is assigned to this option, the bits are reset to 0. This does not affect commands prefixed with +. See capabilities7 for + project='man-pages'>capabilities7 + and Executability check for details. diff --git a/src/shared/securebits-util.c b/src/shared/securebits-util.c index bd682e6cbe..ec613f89bc 100644 --- a/src/shared/securebits-util.c +++ b/src/shared/securebits-util.c @@ -22,6 +22,18 @@ static inline const char* secure_bit_to_string(int i) { return "noroot"; case SECURE_NOROOT_LOCKED: return "noroot-locked"; + case SECURE_NO_CAP_AMBIENT_RAISE: + return "no-cap-ambient-raise"; + case SECURE_NO_CAP_AMBIENT_RAISE_LOCKED: + return "no-cap-ambient-raise-locked"; + case SECURE_EXEC_RESTRICT_FILE: + return "exec-restrict-file"; + case SECURE_EXEC_RESTRICT_FILE_LOCKED: + return "exec-restrict-file-locked"; + case SECURE_EXEC_DENY_INTERACTIVE: + return "exec-deny-interactive"; + case SECURE_EXEC_DENY_INTERACTIVE_LOCKED: + return "exec-deny-interactive-locked"; default: assert_not_reached(); } @@ -55,6 +67,12 @@ int secure_bits_to_strv(int i, char ***ret) { SECURE_NO_SETUID_FIXUP_LOCKED, SECURE_NOROOT, SECURE_NOROOT_LOCKED, + SECURE_NO_CAP_AMBIENT_RAISE, + SECURE_NO_CAP_AMBIENT_RAISE_LOCKED, + SECURE_EXEC_RESTRICT_FILE, + SECURE_EXEC_RESTRICT_FILE_LOCKED, + SECURE_EXEC_DENY_INTERACTIVE, + SECURE_EXEC_DENY_INTERACTIVE_LOCKED, }; int r; @@ -98,6 +116,18 @@ int secure_bits_from_string(const char *s) { secure_bits |= 1 << SECURE_NOROOT; else if (streq(word, "noroot-locked")) secure_bits |= 1 << SECURE_NOROOT_LOCKED; + else if (streq(word, "no-cap-ambient-raise")) + secure_bits |= 1 << SECURE_NO_CAP_AMBIENT_RAISE; + else if (streq(word, "no-cap-ambient-raise-locked")) + secure_bits |= 1 << SECURE_NO_CAP_AMBIENT_RAISE_LOCKED; + else if (streq(word, "exec-restrict-file")) + secure_bits |= 1 << SECURE_EXEC_RESTRICT_FILE; + else if (streq(word, "exec-restrict-file-locked")) + secure_bits |= 1 << SECURE_EXEC_RESTRICT_FILE_LOCKED; + else if (streq(word, "exec-deny-interactive")) + secure_bits |= 1 << SECURE_EXEC_DENY_INTERACTIVE; + else if (streq(word, "exec-deny-interactive-locked")) + secure_bits |= 1 << SECURE_EXEC_DENY_INTERACTIVE_LOCKED; } return secure_bits; diff --git a/src/test/test-secure-bits.c b/src/test/test-secure-bits.c index d217876e2c..5bf6431048 100644 --- a/src/test/test-secure-bits.c +++ b/src/test/test-secure-bits.c @@ -11,6 +11,12 @@ static const char * const string_bits[] = { "no-setuid-fixup-locked", "noroot", "noroot-locked", + "no-cap-ambient-raise", + "no-cap-ambient-raise-locked", + "exec-restrict-file", + "exec-restrict-file-locked", + "exec-deny-interactive", + "exec-deny-interactive-locked", NULL };