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
+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));