]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: copy the host's os-release for /run/host/os-release
authorLuca Boccassi <bluca@debian.org>
Tue, 18 Jul 2023 14:44:27 +0000 (15:44 +0100)
committerLuca Boccassi <bluca@debian.org>
Tue, 18 Jul 2023 16:26:02 +0000 (17:26 +0100)
Currently for portable services we automatically add a bind mount
os-release -> /run/host/os-release. This becomes problematic for the
soft-reboot case, as it's likely that portable services will be configured
to survive it, and thus would forever keep a reference to the old host's
os-release, which would be a problem because it becomes outdated, and also
it stops the old rootfs from being garbage collected.

Create a copy when the manager starts under /run/systemd/propagate instead,
and bind mount that for all services using RootDirectory=/RootImage=, so
that on soft-reboot the content gets updated (without creating a new file,
so the existing bind mounts will see the new content too).

This expands the /run/host/os-release protocol to more services, but I
think that's a nice thing to have too.

Closes https://github.com/systemd/systemd/issues/28023

man/systemd.exec.xml
src/core/execute.c
src/core/main.c
src/core/namespace.c
src/core/namespace.h
src/portable/portable.c
src/test/test-namespace.c
src/test/test-ns.c
test/units/testsuite-50.sh
test/units/testsuite-82.sh

index c3df4166d5511d6bc160169d13940a3730b4de7e..b06a4ed59eea2ef1069f3363ba04216fa0d935f0 100644 (file)
         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:</para>
 
+        <para>The host's
+        <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        file will be made available for the service (read-only) as
+        <filename>/run/host/os-release</filename>.
+        It will be updated automatically on soft reboot (see:
+        <citerefentry><refentrytitle>systemd-soft-reboot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>),
+        in case the service is configured to survive it.</para>
+
         <example>
           <title>Mounting logging sockets into root environment</title>
 
         <para>Units making use of <varname>RootImage=</varname> automatically gain an
         <varname>After=</varname> dependency on <filename>systemd-udevd.service</filename>.</para>
 
+        <para>The host's
+        <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        file will be made available for the service (read-only) as
+        <filename>/run/host/os-release</filename>.
+        It will be updated automatically on soft reboot (see:
+        <citerefentry><refentrytitle>systemd-soft-reboot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>),
+        in case the service is configured to survive it.</para>
+
         <xi:include href="system-only.xml" xpointer="singular"/></listitem>
       </varlistentry>
 
index 067f7bdb8bd02dc301631ac7cbbea74c79914769..9dafdffa08f407f41a1262713cb9a7ca49957e5c 100644 (file)
@@ -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
index bbbf77a7792dbfa16e03943679716770b8ee2ad3..7094be8dba0648ecb3685ac3ebc6c5e8213011f7 100644 (file)
@@ -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;
         }
 
index f39ab2f4689b037777158fbdd2327cb812b7b878..850454944e7220ac165ba35b4f882535933ef234 100644 (file)
@@ -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 */
index 4ddd6a7d583df36312d820e79c426645b39a99fe..44e8f097dac11fe47bba06c404b4744651b10b8e 100644 (file)
@@ -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"
index 5891547b90ec967ac08ee4dabef18771f49ebb4f..c2d01cf4dbce7579092a960296ec257e9d8d2ee6 100644 (file)
@@ -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;
 
index b6ee628533e76a72b71eae8f838928f7080bad65..25aafc35ca837dda241061be465256c6d6325439 100644 (file)
@@ -205,6 +205,7 @@ TEST(protect_kernel_logs) {
                                     NULL,
                                     NULL,
                                     NULL,
+                                    NULL,
                                     NULL);
                 assert_se(r == 0);
 
index 3a3af3584d437ed91611312b39721b2249a45b4b..77afd2f6b9eb81f1c23701fc153674c04a26647b 100644 (file)
@@ -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");
index cf31ec72630a310d987759f758d6016c0d810f70..b97766a1e2419f651df9b16cb325563ae57a7eb6 100755 (executable)
@@ -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
index a078d97e75c4ea45f701077d3409627561f12d56..a1c82a9fc9d0f87f101886e4eec5220ac0d1c9e2 100755 (executable)
@@ -54,6 +54,8 @@ elif [ -f /run/testsuite82.touch2 ]; then
     # Test that we really are in the new overlayfs root fs
     read -r x </lower
     test "$x" = "miep"
+    cmp /etc/os-release /run/systemd/propagate/os-release
+    grep -q MARKER=1 /etc/os-release
 
     # Switch back to the original root, away from the overlayfs
     mount --bind /original-root /run/nextroot
@@ -92,7 +94,16 @@ elif [ -f /run/testsuite82.touch ]; then
     mkdir -p /run/nextroot /tmp/nextroot-lower /original-root
     mount -t tmpfs tmpfs /tmp/nextroot-lower
     echo miep >/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