diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index c3df4166d55..b06a4ed59ee 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -139,6 +139,14 @@ not be able to log via the syslog or journal protocols to the host logging infrastructure, unless the relevant sockets are mounted from the host, specifically: + The host's + os-release5 + file will be made available for the service (read-only) as + /run/host/os-release. + It will be updated automatically on soft reboot (see: + systemd-soft-reboot.service8), + in case the service is configured to survive it. + Mounting logging sockets into root environment @@ -172,6 +180,14 @@ Units making use of RootImage= automatically gain an After= dependency on systemd-udevd.service. + The host's + os-release5 + file will be made available for the service (read-only) as + /run/host/os-release. + It will be updated automatically on soft reboot (see: + systemd-soft-reboot.service8), + in case the service is configured to survive it. + diff --git a/src/core/execute.c b/src/core/execute.c index 067f7bdb8bd..9dafdffa08f 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3967,7 +3967,7 @@ static int apply_mount_namespace( _cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL, **read_write_paths_cleanup = NULL; _cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL, - *extension_dir = NULL; + *extension_dir = NULL, *host_os_release = NULL; const char *root_dir = NULL, *root_image = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL; char **read_write_paths; NamespaceInfo ns_info; @@ -4087,11 +4087,24 @@ static int apply_mount_namespace( extension_dir = strdup("/run/systemd/unit-extensions"); if (!extension_dir) return -ENOMEM; + + /* If running under a different root filesystem, propagate the host's os-release. We make a + * copy rather than just bind mounting it, so that it can be updated on soft-reboot. */ + if (root_dir || root_image) { + host_os_release = strdup("/run/systemd/propagate/os-release"); + if (!host_os_release) + return -ENOMEM; + } } else { assert(params->runtime_scope == RUNTIME_SCOPE_USER); if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0) return -ENOMEM; + + if (root_dir || root_image) { + if (asprintf(&host_os_release, "/run/user/" UID_FMT "/systemd/propagate/os-release", geteuid()) < 0) + return -ENOMEM; + } } if (root_image) { @@ -4139,6 +4152,7 @@ static int apply_mount_namespace( incoming_dir, extension_dir, root_dir || root_image ? params->notify_socket : NULL, + host_os_release, error_path); /* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports diff --git a/src/core/main.c b/src/core/main.c index bbbf77a7792..7094be8dba0 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -35,6 +35,7 @@ #include "clock-util.h" #include "conf-parser.h" #include "confidential-virt.h" +#include "copy.h" #include "cpu-set-util.h" #include "crash-handler.h" #include "dbus-manager.h" @@ -1382,6 +1383,38 @@ static int os_release_status(void) { return 0; } +static int setup_os_release(RuntimeScope scope) { + _cleanup_free_ char *os_release_dst = NULL; + const char *os_release_src = "/etc/os-release"; + int r; + + if (access("/etc/os-release", F_OK) < 0) { + if (errno != ENOENT) + log_debug_errno(errno, "Failed to check if /etc/os-release exists, ignoring: %m"); + + os_release_src = "/usr/lib/os-release"; + } + + if (scope == RUNTIME_SCOPE_SYSTEM) { + os_release_dst = strdup("/run/systemd/propagate/os-release"); + if (!os_release_dst) + return log_oom_debug(); + } else { + if (asprintf(&os_release_dst, "/run/user/" UID_FMT "/systemd/propagate/os-release", geteuid()) < 0) + return log_oom_debug(); + } + + r = mkdir_parents_label(os_release_dst, 0755); + if (r < 0 && r != -EEXIST) + return log_debug_errno(r, "Failed to create parent directory of %s, ignoring: %m", os_release_dst); + + r = copy_file(os_release_src, os_release_dst, /* open_flags= */ 0, 0644, COPY_MAC_CREATE); + if (r < 0) + return log_debug_errno(r, "Failed to create %s, ignoring: %m", os_release_dst); + + return 0; +} + static int write_container_id(void) { const char *c; int r = 0; /* avoid false maybe-uninitialized warning */ @@ -2253,6 +2286,12 @@ static int initialize_runtime( bump_file_max_and_nr_open(); test_usr(); write_container_id(); + + /* Copy os-release to the propagate directory, so that we update it for services running + * under RootDirectory=/RootImage= when we do a soft reboot. */ + r = setup_os_release(RUNTIME_SCOPE_SYSTEM); + if (r < 0) + log_warning_errno(r, "Failed to copy os-release for propagation, ignoring: %m"); } r = watchdog_set_device(arg_watchdog_device); @@ -2275,6 +2314,9 @@ static int initialize_runtime( (void) mkdir_p_label(p, 0755); (void) make_inaccessible_nodes(p, UID_INVALID, GID_INVALID); + r = setup_os_release(RUNTIME_SCOPE_USER); + if (r < 0) + log_warning_errno(r, "Failed to copy os-release for propagation, ignoring: %m"); break; } diff --git a/src/core/namespace.c b/src/core/namespace.c index f39ab2f4689..850454944e7 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1701,7 +1701,8 @@ static size_t namespace_calculate_mounts( const char *creds_path, const char* log_namespace, bool setup_propagate, - const char* notify_socket) { + const char* notify_socket, + const char* host_os_release) { size_t protect_home_cnt; size_t protect_system_cnt = @@ -1746,6 +1747,7 @@ static size_t namespace_calculate_mounts( !!log_namespace + setup_propagate + /* /run/systemd/incoming */ !!notify_socket + + !!host_os_release + ns_info->private_network + /* /sys */ ns_info->private_ipc; /* /dev/mqueue */ } @@ -2005,6 +2007,7 @@ int setup_namespace( const char *incoming_dir, const char *extension_dir, const char *notify_socket, + const char *host_os_release, char **error_path) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; @@ -2130,7 +2133,8 @@ int setup_namespace( creds_path, log_namespace, setup_propagate, - notify_socket); + notify_socket, + host_os_release); if (n_mounts > 0) { m = mounts = new0(MountEntry, n_mounts); @@ -2365,6 +2369,15 @@ int setup_namespace( .read_only = true, }; + if (host_os_release) + *(m++) = (MountEntry) { + .path_const = "/run/host/os-release", + .source_const = host_os_release, + .mode = BIND_MOUNT, + .read_only = true, + .ignore = true, /* Live copy, don't hard-fail if it goes missing */ + }; + assert(mounts + n_mounts == m); /* Prepend the root directory where that's necessary */ diff --git a/src/core/namespace.h b/src/core/namespace.h index 4ddd6a7d583..44e8f097dac 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -133,6 +133,7 @@ int setup_namespace( const char *incoming_dir, const char *extension_dir, const char *notify_socket, + const char *host_os_release, char **error_path); #define RUN_SYSTEMD_EMPTY "/run/systemd/empty" diff --git a/src/portable/portable.c b/src/portable/portable.c index 5891547b90e..c2d01cf4dbc 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -1052,20 +1052,12 @@ static int install_chroot_dropin( return log_debug_errno(r, "Failed to generate marker string for portable drop-in: %m"); if (endswith(m->name, ".service")) { - const char *os_release_source, *root_type; + const char *root_type; _cleanup_free_ char *base_name = NULL; Image *ext; root_type = root_setting_from_image(type); - if (access("/etc/os-release", F_OK) < 0) { - if (errno != ENOENT) - return log_debug_errno(errno, "Failed to check if /etc/os-release exists: %m"); - - os_release_source = "/usr/lib/os-release"; - } else - os_release_source = "/etc/os-release"; - r = path_extract_filename(m->image_path ?: image_path, &base_name); if (r < 0) return log_debug_errno(r, "Failed to extract basename from '%s': %m", m->image_path ?: image_path); @@ -1075,7 +1067,6 @@ static int install_chroot_dropin( "[Service]\n", root_type, image_path, "\n" "Environment=PORTABLE=", base_name, "\n" - "BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n" "LogExtraFields=PORTABLE=", base_name, "\n")) return -ENOMEM; diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index b6ee628533e..25aafc35ca8 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -205,6 +205,7 @@ TEST(protect_kernel_logs) { NULL, NULL, NULL, + NULL, NULL); assert_se(r == 0); diff --git a/src/test/test-ns.c b/src/test/test-ns.c index 3a3af3584d4..77afd2f6b9e 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -107,6 +107,7 @@ int main(int argc, char *argv[]) { NULL, NULL, NULL, + NULL, NULL); if (r < 0) { log_error_errno(r, "Failed to set up namespace: %m"); diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index cf31ec72630..b97766a1e24 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -583,4 +583,6 @@ grep -q -F "MARKER_CONFEXT_123" /etc/testfile systemd-confext unmerge rm -rf /run/confexts/ testjob/ +systemd-run -P -p RootImage="${image}.raw" cat /run/host/os-release | cmp "${os_release}" + touch /testok diff --git a/test/units/testsuite-82.sh b/test/units/testsuite-82.sh index a078d97e75c..a1c82a9fc9d 100755 --- a/test/units/testsuite-82.sh +++ b/test/units/testsuite-82.sh @@ -54,6 +54,8 @@ elif [ -f /run/testsuite82.touch2 ]; then # Test that we really are in the new overlayfs root fs read -r x /tmp/nextroot-lower/lower - mount -t overlay nextroot /run/nextroot -o lowerdir=/:/tmp/nextroot-lower,ro + + # Copy os-release away, so that we can manipulate it and check that it is updated in the propagate + # directory across soft reboots. + mkdir -p /tmp/nextroot-lower/usr/lib + cp /etc/os-release /tmp/nextroot-lower/usr/lib/os-release + echo MARKER=1 >>/tmp/nextroot-lower/usr/lib/os-release + cmp /etc/os-release /run/systemd/propagate/os-release + (! grep -q MARKER=1 /etc/os-release) + + mount -t overlay nextroot /run/nextroot -o lowerdir=/tmp/nextroot-lower:/,ro # Bind our current root into the target so that we later can return to it mount --bind / /run/nextroot/original-root