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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user