core: add LUOSession= unit setting (#42530)

Acquiring a LUO session from /dev/liveupdate requires privileges, and
also the device is a single-owner driver so only a single process can
open it at any given time.

Add a LUOSession= service settings that allows units running without
privileges to get a session assigned to them.

The kernel imposes a 64 chars limit on session names, which is too short
to avoid clashes, so derive a hash from joining the unit name with the
parameter name, that way two units using the same setting don't clash.
This commit is contained in:
Luca Boccassi
2026-06-19 19:35:16 +01:00
committed by GitHub
16 changed files with 370 additions and 10 deletions
+7
View File
@@ -2881,6 +2881,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
readonly u NFileDescriptorStore = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s FileDescriptorStorePreserve = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as LUOSession = ['...', ...];
readonly s StatusText = '...';
readonly i StatusErrno = ...;
readonly s StatusBusError = '...';
@@ -3599,6 +3601,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property FileDescriptorStorePreserve is not documented!-->
<!--property LUOSession is not documented!-->
<!--property ReloadResult is not documented!-->
<!--property CleanResult is not documented!-->
@@ -4247,6 +4251,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="FileDescriptorStorePreserve"/>
<variablelist class="dbus-property" generated="True" extra-ref="LUOSession"/>
<variablelist class="dbus-property" generated="True" extra-ref="StatusText"/>
<variablelist class="dbus-property" generated="True" extra-ref="StatusErrno"/>
@@ -12840,6 +12846,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>IOPressureWatch</varname>,
<varname>CPUSetPartition</varname>, and
<varname>OOMRules</varname> were added in version 261.</para>
<para><varname>LUOSession</varname> was added in version 262.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>
+36
View File
@@ -1297,6 +1297,42 @@ RestartMaxDelaySec=160s</programlisting>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>LUOSession=</varname></term>
<listitem><para>Takes a whitespace-separated list of names. For each name, when the service is
started the service manager creates a LUO session via the <ulink
url="https://docs.kernel.org/userspace-api/liveupdate.html">Live Update Orchestrator</ulink> (LUO,
i.e. <filename>/dev/liveupdate</filename>) and hands the resulting session file descriptor to the
service through the file descriptor store, using the configured name as its
<literal>FDNAME=</literal>. The session is hence passed to the service's processes via
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
like any other file descriptor store entry. The service may then preserve additional file
descriptors (such as <citerefentry
project='man-pages'><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
memory file descriptors) in the session, which survive a <literal>kexec</literal>-based reboot and
can be retrieved again on the other side.</para>
<para>A session is only handed out if one with the same name is not already present in the service's
file descriptor store. This way a session handed out on first start, or restored across a
<literal>kexec</literal>, is reused on the next start rather than replaced. The kernel-level session
name is derived deterministically from the unit name and the configured name, so that it remains
stable and unique, and it fits within the length limit imposed by the kernel.</para>
<para>This setting implies <varname>FileDescriptorStoreMax=</varname> is set to at least the number
of configured sessions. To ensure the sessions are kept pinned and preserved across a
<literal>kexec</literal>, combine this with <varname>FileDescriptorStorePreserve=yes</varname> (see
above). If <filename>/dev/liveupdate</filename> is not available no session is handed out, and the
service is started normally. This setting may be specified more than once, in which case the lists
are combined. If the empty string is assigned the list is reset and all prior assignments have no
effect.</para>
<para>For further information on the file descriptor store see the <ulink
url="https://systemd.io/FILE_DESCRIPTOR_STORE">File Descriptor Store</ulink> overview.</para>
<xi:include href="system-only.xml" xpointer="singular"/>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>USBFunctionDescriptors=</varname></term>
<listitem><para>Configure the location of a file containing
+3
View File
@@ -1129,6 +1129,9 @@ bool string_is_safe(const char *p, StringSafeFlags flags) {
if (!FLAGS_SET(flags, STRING_ALLOW_GLOBS) && strchr(GLOB_CHARS, *t))
return false;
if (FLAGS_SET(flags, STRING_DISALLOW_WHITESPACE) && strchr(WHITESPACE, *t))
return false;
if (FLAGS_SET(flags, STRING_ASCII) && (uint8_t) *t >= 0x80)
return false;
}
+8 -7
View File
@@ -222,13 +222,14 @@ static inline int strdup_to(char **ret, const char *src) {
}
typedef enum StringSafeFlags {
STRING_ASCII = 1 << 0, /* Verify string is 7-Bit ASCII (rather than just UTF-8) */
STRING_ALLOW_EMPTY = 1 << 1, /* Allow empty strings */
STRING_ALLOW_NEWLINES = 1 << 2, /* Allow newlines (\n) */
STRING_ALLOW_BACKSLASHES = 1 << 3, /* Allow backslashes (\) */
STRING_ALLOW_QUOTES = 1 << 4, /* Allow quotes (" or ') */
STRING_ALLOW_GLOBS = 1 << 5, /* Allow globs (?, * or [) */
STRING_FILENAME = 1 << 6, /* Verify the string is valid as regular filename */
STRING_ASCII = 1 << 0, /* Verify string is 7-Bit ASCII (rather than just UTF-8) */
STRING_ALLOW_EMPTY = 1 << 1, /* Allow empty strings */
STRING_ALLOW_NEWLINES = 1 << 2, /* Allow newlines (\n) */
STRING_ALLOW_BACKSLASHES = 1 << 3, /* Allow backslashes (\) */
STRING_ALLOW_QUOTES = 1 << 4, /* Allow quotes (" or ') */
STRING_ALLOW_GLOBS = 1 << 5, /* Allow globs (?, * or [) */
STRING_FILENAME = 1 << 6, /* Verify the string is valid as regular filename */
STRING_DISALLOW_WHITESPACE = 1 << 7, /* Refuse whitespace (space, tab, newline, …) */
} StringSafeFlags;
bool string_is_safe(const char *p, StringSafeFlags flags) _pure_;
+33
View File
@@ -21,6 +21,7 @@
#include "fd-util.h"
#include "glyph-util.h"
#include "locale-util.h"
#include "luo-util.h"
#include "manager.h"
#include "mount-util.h"
#include "open-file.h"
@@ -380,6 +381,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NFileDescriptorStore", "u", property_get_size_as_uint32, offsetof(Service, n_fd_store), 0),
SD_BUS_PROPERTY("FileDescriptorStorePreserve", "s", bus_property_get_exec_preserve_mode, offsetof(Service, fd_store_preserve_mode), 0),
SD_BUS_PROPERTY("LUOSession", "as", NULL, offsetof(Service, luo_sessions), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("StatusBusError", "s", NULL, offsetof(Service, status_bus_error), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
@@ -648,6 +650,37 @@ static int bus_service_set_transient_property(
if (streq(name, "FileDescriptorStorePreserve"))
return bus_set_transient_exec_preserve_mode(u, name, &s->fd_store_preserve_mode, message, flags, reterr_error);
if (streq(name, "LUOSession")) {
_cleanup_strv_free_ char **l = NULL;
r = sd_bus_message_read_strv(message, &l);
if (r < 0)
return r;
STRV_FOREACH(p, l)
if (!luo_session_name_is_valid(*p))
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid LUO session name: %s", *p);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
if (strv_isempty(l)) {
s->luo_sessions = strv_free(s->luo_sessions);
unit_write_settingf(u, flags, name, "%s=", name);
} else {
r = strv_extend_strv(&s->luo_sessions, l, /* filter_duplicates= */ true);
if (r < 0)
return r;
strv_sort_uniq(s->luo_sessions);
STRV_FOREACH(p, l)
unit_write_settingf(u, flags, name, "%s=%s", name, *p);
}
}
return 1;
}
if (streq(name, "NotifyAccess"))
return bus_set_transient_notify_access(u, name, &s->notify_access, message, flags, reterr_error);
+1
View File
@@ -484,6 +484,7 @@ Service.NonBlocking, config_parse_bool,
Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name)
Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max)
Service.FileDescriptorStorePreserve, config_parse_exec_preserve_mode, 0, offsetof(Service, fd_store_preserve_mode)
Service.LUOSession, config_parse_luo_sessions, 0, offsetof(Service, luo_sessions)
Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access)
Service.Sockets, config_parse_service_sockets, 0, 0
Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0
+59
View File
@@ -41,6 +41,7 @@
#include "limits-util.h"
#include "load-fragment.h"
#include "log.h"
#include "luo-util.h"
#include "manager.h"
#include "mountpoint-util.h"
#include "nsflags.h"
@@ -6726,3 +6727,61 @@ int config_parse_protect_hostname(
free_and_replace(c->private_hostname, h);
return 1;
}
int config_parse_luo_sessions(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
char ***sessions = ASSERT_PTR(data);
assert(filename);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
*sessions = strv_free(*sessions);
return 0;
}
for (const char *p = rvalue;;) {
_cleanup_free_ char *word = NULL;
int r;
r = extract_first_word(&p, &word, /* separators= */ NULL, /* flags= */ 0);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse LUOSession= value, ignoring: %s", rvalue);
return 0;
}
if (r == 0)
return 0;
if (!luo_session_name_is_valid(word)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"LUO session name contains invalid characters, ignoring: %s", word);
continue;
}
if (strv_contains(*sessions, word)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Duplicate LUO session name, ignoring: %s", word);
continue;
}
r = strv_extend(sessions, word);
if (r < 0)
return log_oom();
}
strv_sort(*sessions);
}
+1
View File
@@ -172,6 +172,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_memory_thp);
CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_partition);
CONFIG_PARSER_PROTOTYPE(config_parse_luo_sessions);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+111 -1
View File
@@ -1,11 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <linux/audit.h> /* IWYU pragma: keep */
#include <linux/liveupdate.h>
#include <math.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sd-bus.h"
#include "sd-id128.h"
#include "sd-json.h"
#include "sd-messages.h"
@@ -32,8 +34,10 @@
#include "fileio.h"
#include "format-util.h"
#include "glyph-util.h"
#include "id128-util.h"
#include "image-policy.h"
#include "log.h"
#include "luo-util.h"
#include "manager.h"
#include "memfd-util.h"
#include "mount-util.h"
@@ -524,7 +528,7 @@ static void service_truncate_fd_store(Service *s) {
* parsed and FileDescriptorStoreMax= shrunk the configured limit. Newest entries are at the head
* of the list, so drop from the head (newest first). */
while (s->n_fd_store > s->n_fd_store_max) {
while (s->n_fd_store > s->n_fd_store_max + strv_length(s->luo_sessions)) {
ServiceFDStore *fs = ASSERT_PTR(s->fd_store);
log_unit_debug(UNIT(s), "Dropping stored fd '%s' to honor FileDescriptorStoreMax=%u.",
strna(fs->fdname), s->n_fd_store_max);
@@ -617,6 +621,8 @@ static void service_done(Unit *u) {
service_release_extra_fds(s);
s->root_directory_fd = asynchronous_close(s->root_directory_fd);
s->luo_sessions = strv_free(s->luo_sessions);
s->mount_request = sd_bus_message_unref(s->mount_request);
}
@@ -753,6 +759,102 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo
return 0;
}
/* Build a deterministic LUO session name from the unit's name and the session name, with stable length. */
static int service_build_luo_session_name(Service *s, const char *name, char **ret) {
_cleanup_free_ char *full = NULL, *result = NULL;
assert(s);
assert(name);
assert(ret);
full = strjoin(UNIT(s)->id, "/", name);
if (!full)
return -ENOMEM;
/* The kernel embeds the session name in the anon_inode path shown in /proc/self/fd/, i.e.
* "anon_inode:[luo_session] <name>". On kernels lacking commit 97b67e64affb ("dcache: permit
* dynamic_dname()s up to NAME_MAX") that path must fit dynamic_dname()'s historical 64 byte limit,
* otherwise reading it back will fail. Can be simplified once Ubuntu 26.04 support is dropped. */
// FIXME: allow longer prefix once Ubuntu 26.04 support is dropped
size_t name_max = 64U - STRLEN("anon_inode:[luo_session] ") - 1U; /* = 38 */
size_t digest_chars = 24U; /* 96 bit, leaves room for a useful unit id prefix within name_max */
assert_cc(64U - STRLEN("anon_inode:[luo_session] ") - 1U < LIVEUPDATE_SESSION_NAME_LENGTH);
assert_cc(24U < SD_ID128_STRING_MAX);
assert_cc(24U + 1U < 64U - STRLEN("anon_inode:[luo_session] ") - 1U);
char digest[SD_ID128_STRING_MAX];
sd_id128_to_string(id128_digest(full, SIZE_MAX), digest);
if (asprintf(&result, "%.*s-%.*s",
(int) (name_max - digest_chars - 1U), UNIT(s)->id,
(int) digest_chars, digest) < 0)
return -ENOMEM;
*ret = TAKE_PTR(result);
return 0;
}
static int service_setup_luo_sessions(Service *s) {
int r;
assert(s);
/* For each configured LUOSession=, create a fresh LUO session and hand it to the service via the fd
* store (and thus LISTEN_FDS, with the configured name as FDNAME). The kernel-level session name is
* derived deterministically from the unit and the configured name so it stays stable and fits the
* kernel's length limit. */
if (strv_isempty(s->luo_sessions))
return 0;
if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) {
log_unit_debug(UNIT(s), "LUOSession= is only supported in the system manager, ignoring.");
return 0;
}
_cleanup_close_ int device_fd = luo_open_device();
if (device_fd < 0) {
if (ERRNO_IS_NEG_DEVICE_ABSENT(device_fd)) {
log_unit_debug_errno(UNIT(s), device_fd, "No /dev/liveupdate device found, not handing out LUO sessions.");
return 0;
}
return log_unit_warning_errno(UNIT(s), device_fd, "Failed to open /dev/liveupdate: %m");
}
STRV_FOREACH(name, s->luo_sessions) {
_cleanup_free_ char *session_name = NULL;
_cleanup_close_ int session_fd = -EBADF;
bool already_given_out = false;
LIST_FOREACH(fd_store, fs, s->fd_store)
if (streq_ptr(fs->fdname, *name) && fd_is_luo_session(fs->fd) > 0) {
already_given_out = true;
break;
}
if (already_given_out)
continue;
r = service_build_luo_session_name(s, *name, &session_name);
if (r < 0)
return log_unit_warning_errno(UNIT(s), r, "Failed to build LUO session name for '%s': %m", *name);
session_fd = luo_create_session(device_fd, session_name);
if (session_fd < 0) {
log_unit_warning_errno(UNIT(s), session_fd, "Failed to create LUO session '%s', ignoring: %m", session_name);
continue;
}
r = service_add_fd_store(s, TAKE_FD(session_fd), *name, /* do_poll= */ false, /* propagate_upstream= */ false);
if (r < 0)
return log_unit_warning_errno(UNIT(s), r, "Failed to hand out LUO session '%s': %m", *name);
log_unit_debug(UNIT(s), "Handed out LUO session '%s' (kernel name '%s').", *name, session_name);
}
return 0;
}
int service_propagate_fd_store_mapping_upstream(Manager *m) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL;
_cleanup_close_ int fd = -EBADF;
@@ -1119,6 +1221,10 @@ static int service_add_extras(Service *s) {
if (r < 0)
return r;
/* Each configured LUOSession= is handed to the service through the fd store, hence make sure the
* store is large enough to hold them all. */
s->n_fd_store_max += strv_length(s->luo_sessions);
/* If the service needs the notify socket, let's enable it automatically. */
if (s->notify_access == NOTIFY_NONE &&
(IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0))
@@ -2749,6 +2855,10 @@ static void service_enter_start(Service *s) {
service_unwatch_control_pid(s);
service_unwatch_main_pid(s);
r = service_setup_luo_sessions(s);
if (r < 0)
goto fail;
r = service_adverse_to_leftover_processes(s);
if (r < 0)
goto fail;
+2
View File
@@ -246,6 +246,8 @@ typedef struct Service {
unsigned n_fd_store_max;
ExecPreserveMode fd_store_preserve_mode;
char **luo_sessions; /* LUOSession= setting — list of session names to create/manage */
int stdin_fd;
int stdout_fd;
int stderr_fd;
+1
View File
@@ -2688,6 +2688,7 @@ static const BusProperty service_properties[] = {
{ "TimeoutStartFailureMode", bus_append_string },
{ "TimeoutStopFailureMode", bus_append_string },
{ "FileDescriptorStorePreserve", bus_append_string },
{ "LUOSession", bus_append_strv },
{ "PermissionsStartOnly", bus_append_parse_boolean },
{ "RootDirectoryStartOnly", bus_append_parse_boolean },
{ "RemainAfterExit", bus_append_parse_boolean },
+5
View File
@@ -115,6 +115,11 @@ int luo_session_finish(int session_fd) {
return RET_NERRNO(ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &args));
}
bool luo_session_name_is_valid(const char *name) {
/* Used for FDNAME: no whitespace, no ":", no control characters, etc. */
return fdname_is_valid(name) && string_is_safe(name, STRING_DISALLOW_WHITESPACE);
}
int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL;
_cleanup_free_ int *fd_list = NULL;
+2
View File
@@ -36,6 +36,8 @@ int luo_session_preserve_fd(int session_fd, int fd, uint64_t token);
int luo_session_retrieve_fd(int session_fd, uint64_t token);
int luo_session_finish(int session_fd);
bool luo_session_name_is_valid(const char *name);
int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds);
int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd);
+59 -2
View File
@@ -326,9 +326,64 @@ static int do_check_hijack(void) {
return 0;
}
static int do_check_sessions(int argc, char *argv[]) {
const char *e;
_cleanup_strv_free_ char **names = NULL;
int n_fds, n_verified = 0, r;
/* Verify that named LUO sessions are present in the fd store */
e = getenv("LISTEN_FDS");
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No LISTEN_FDS environment variable set!");
r = safe_atoi(e, &n_fds);
if (r < 0)
return log_error_errno(r, "Failed to parse LISTEN_FDS='%s': %m", e);
e = getenv("LISTEN_FDNAMES");
if (e) {
names = strv_split(e, ":");
if (!names)
return log_oom();
}
for (int i = 2; i < argc; i++) {
const char *session_name = argv[i];
int fd = -EBADF;
/* Find the fd by name */
STRV_FOREACH(name, names) {
int idx = (int) (name - names);
if (idx >= n_fds)
break;
if (streq(*name, session_name)) {
fd = SD_LISTEN_FDS_START + idx;
break;
}
}
if (fd < 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"LUO session '%s' not found in fd store!", session_name);
r = fd_is_luo_session(fd);
if (r < 0)
return log_error_errno(r, "Failed to check if fd '%s' is a LUO session: %m", session_name);
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
"fd '%s' is not a LUO session!", session_name);
log_info("Verified LUO session '%s' is present and valid.", session_name);
n_verified++;
}
log_info("All %d LUO session(s) verified.", n_verified);
return 0;
}
static int run(int argc, char *argv[]) {
if (argc < 2 || argc > 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s store|check|store-hijack|check-hijack [PREFIX]", argv[0]);
if (argc < 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s store|check|store-hijack|check-hijack [PREFIX] | check-sessions NAME...", argv[0]);
const char *prefix = argc > 2 ? argv[2] : "luosession";
@@ -340,6 +395,8 @@ static int run(int argc, char *argv[]) {
return do_store_hijack();
if (streq(argv[1], "check-hijack"))
return do_check_hijack();
if (streq(argv[1], "check-sessions"))
return do_check_sessions(argc, argv);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command: %s", argv[1]);
}
+15
View File
@@ -1607,6 +1607,21 @@ TEST(string_is_safe) {
ASSERT_FALSE(string_is_safe("/foo", STRING_FILENAME));
ASSERT_FALSE(string_is_safe("foo/bar", STRING_FILENAME));
/* STRING_DISALLOW_WHITESPACE: rejects whitespace (space, tab, newline, carriage return). */
ASSERT_TRUE(string_is_safe("hello", STRING_DISALLOW_WHITESPACE));
ASSERT_TRUE(string_is_safe("foo-bar_baz", STRING_DISALLOW_WHITESPACE));
ASSERT_TRUE(string_is_safe("über", STRING_DISALLOW_WHITESPACE)); /* valid UTF-8 still allowed */
ASSERT_TRUE(string_is_safe("hello world", 0)); /* space accepted by default */
ASSERT_FALSE(string_is_safe("hello world", STRING_DISALLOW_WHITESPACE)); /* but not with the flag */
ASSERT_FALSE(string_is_safe(" ", STRING_DISALLOW_WHITESPACE));
ASSERT_FALSE(string_is_safe("foo ", STRING_DISALLOW_WHITESPACE));
ASSERT_FALSE(string_is_safe(" foo", STRING_DISALLOW_WHITESPACE));
ASSERT_FALSE(string_is_safe("a\tb", STRING_DISALLOW_WHITESPACE));
ASSERT_FALSE(string_is_safe("a\rb", STRING_DISALLOW_WHITESPACE));
/* The flag overrides STRING_ALLOW_NEWLINES for the newline character, which is whitespace too. */
ASSERT_TRUE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES));
ASSERT_FALSE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES | STRING_DISALLOW_WHITESPACE));
/* Pairwise combinations. */
ASSERT_TRUE(string_is_safe("", STRING_ALLOW_EMPTY | STRING_ASCII));
ASSERT_FALSE(string_is_safe("über", STRING_ALLOW_EMPTY | STRING_ASCII));
+27
View File
@@ -60,6 +60,17 @@ RemainAfterExit=yes
FileDescriptorStoreMax=${maxfd}
FileDescriptorStorePreserve=yes
ExecStart=${cmd}
EOF
# Create a unit with LUOSession= to test server-managed session lifecycle
cat >"/run/systemd/system/TEST-91-LIVEUPDATE-session.service" <<EOF
[Service]
Type=oneshot
RemainAfterExit=yes
FileDescriptorStoreMax=10
FileDescriptorStorePreserve=yes
LUOSession=test-session-1 test-session-2
ExecStart=/usr/lib/systemd/tests/unit-tests/manual/test-luo check-sessions test-session-1 test-session-2
EOF
}
@@ -226,6 +237,16 @@ EOF
/run/TEST-91-LIVEUPDATE-failure.attempt \
/run/TEST-91-LIVEUPDATE-failure.preserve-broken
systemctl daemon-reload
# Verify the LUOSession= sessions was restored. Starting the unit again must
# reuse the restored sessions rather than handing out fresh ones (the names are
# already present in the fd store), and the payload verifies they are valid.
n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-session.service)
echo "Session unit fd store count after kexec: $n_fds"
test "$n_fds" -eq 2
systemctl start TEST-91-LIVEUPDATE-session.service
n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-session.service)
test "$n_fds" -eq 2
else
# Create memfds with known content and push them to our fd store.
# Also request a LUO session, store a memfd in it, and push the session fd to the fd store.
@@ -285,6 +306,12 @@ EOF
test "$n_fds" -eq 3
done
# Test LUOSession= setting: start the unit and verify sessions are in its fd store
systemctl start TEST-91-LIVEUPDATE-session.service
n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-session.service)
echo "Session unit fd store count: $n_fds"
test "$n_fds" -eq 2 # test-session-1 and test-session-2
# 'systemctl kexec' auto-loads the default boot entry (i.e. the booted UKI,
# via EFI LoaderEntrySelected/LoaderEntryDefault). Append a marker to the
# kernel command line so we can tell the two boots apart, and also the current