1
0
mirror of https://github.com/systemd/systemd.git synced 2025-05-30 05:05:57 +03:00

core: copy the host's os-release for /run/host/os-release

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
This commit is contained in:
Luca Boccassi 2023-07-18 15:44:27 +01:00
parent 1dfa58edd3
commit 3f37a82545
10 changed files with 106 additions and 14 deletions

View File

@ -139,6 +139,14 @@
not be able to log via the syslog or journal protocols to the host logging infrastructure, unless the 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> 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> <example>
<title>Mounting logging sockets into root environment</title> <title>Mounting logging sockets into root environment</title>
@ -172,6 +180,14 @@
<para>Units making use of <varname>RootImage=</varname> automatically gain an <para>Units making use of <varname>RootImage=</varname> automatically gain an
<varname>After=</varname> dependency on <filename>systemd-udevd.service</filename>.</para> <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> <xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry> </varlistentry>

View File

@ -3967,7 +3967,7 @@ static int apply_mount_namespace(
_cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL, _cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL,
**read_write_paths_cleanup = NULL; **read_write_paths_cleanup = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = 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; const char *root_dir = NULL, *root_image = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL;
char **read_write_paths; char **read_write_paths;
NamespaceInfo ns_info; NamespaceInfo ns_info;
@ -4087,11 +4087,24 @@ static int apply_mount_namespace(
extension_dir = strdup("/run/systemd/unit-extensions"); extension_dir = strdup("/run/systemd/unit-extensions");
if (!extension_dir) if (!extension_dir)
return -ENOMEM; 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 { } else {
assert(params->runtime_scope == RUNTIME_SCOPE_USER); assert(params->runtime_scope == RUNTIME_SCOPE_USER);
if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0) if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0)
return -ENOMEM; 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) { if (root_image) {
@ -4139,6 +4152,7 @@ static int apply_mount_namespace(
incoming_dir, incoming_dir,
extension_dir, extension_dir,
root_dir || root_image ? params->notify_socket : NULL, root_dir || root_image ? params->notify_socket : NULL,
host_os_release,
error_path); error_path);
/* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports /* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports

View File

@ -35,6 +35,7 @@
#include "clock-util.h" #include "clock-util.h"
#include "conf-parser.h" #include "conf-parser.h"
#include "confidential-virt.h" #include "confidential-virt.h"
#include "copy.h"
#include "cpu-set-util.h" #include "cpu-set-util.h"
#include "crash-handler.h" #include "crash-handler.h"
#include "dbus-manager.h" #include "dbus-manager.h"
@ -1382,6 +1383,38 @@ static int os_release_status(void) {
return 0; 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) { static int write_container_id(void) {
const char *c; const char *c;
int r = 0; /* avoid false maybe-uninitialized warning */ int r = 0; /* avoid false maybe-uninitialized warning */
@ -2253,6 +2286,12 @@ static int initialize_runtime(
bump_file_max_and_nr_open(); bump_file_max_and_nr_open();
test_usr(); test_usr();
write_container_id(); 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); r = watchdog_set_device(arg_watchdog_device);
@ -2275,6 +2314,9 @@ static int initialize_runtime(
(void) mkdir_p_label(p, 0755); (void) mkdir_p_label(p, 0755);
(void) make_inaccessible_nodes(p, UID_INVALID, GID_INVALID); (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; break;
} }

View File

@ -1701,7 +1701,8 @@ static size_t namespace_calculate_mounts(
const char *creds_path, const char *creds_path,
const char* log_namespace, const char* log_namespace,
bool setup_propagate, bool setup_propagate,
const char* notify_socket) { const char* notify_socket,
const char* host_os_release) {
size_t protect_home_cnt; size_t protect_home_cnt;
size_t protect_system_cnt = size_t protect_system_cnt =
@ -1746,6 +1747,7 @@ static size_t namespace_calculate_mounts(
!!log_namespace + !!log_namespace +
setup_propagate + /* /run/systemd/incoming */ setup_propagate + /* /run/systemd/incoming */
!!notify_socket + !!notify_socket +
!!host_os_release +
ns_info->private_network + /* /sys */ ns_info->private_network + /* /sys */
ns_info->private_ipc; /* /dev/mqueue */ ns_info->private_ipc; /* /dev/mqueue */
} }
@ -2005,6 +2007,7 @@ int setup_namespace(
const char *incoming_dir, const char *incoming_dir,
const char *extension_dir, const char *extension_dir,
const char *notify_socket, const char *notify_socket,
const char *host_os_release,
char **error_path) { char **error_path) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
@ -2130,7 +2133,8 @@ int setup_namespace(
creds_path, creds_path,
log_namespace, log_namespace,
setup_propagate, setup_propagate,
notify_socket); notify_socket,
host_os_release);
if (n_mounts > 0) { if (n_mounts > 0) {
m = mounts = new0(MountEntry, n_mounts); m = mounts = new0(MountEntry, n_mounts);
@ -2365,6 +2369,15 @@ int setup_namespace(
.read_only = true, .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); assert(mounts + n_mounts == m);
/* Prepend the root directory where that's necessary */ /* Prepend the root directory where that's necessary */

View File

@ -133,6 +133,7 @@ int setup_namespace(
const char *incoming_dir, const char *incoming_dir,
const char *extension_dir, const char *extension_dir,
const char *notify_socket, const char *notify_socket,
const char *host_os_release,
char **error_path); char **error_path);
#define RUN_SYSTEMD_EMPTY "/run/systemd/empty" #define RUN_SYSTEMD_EMPTY "/run/systemd/empty"

View 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"); return log_debug_errno(r, "Failed to generate marker string for portable drop-in: %m");
if (endswith(m->name, ".service")) { if (endswith(m->name, ".service")) {
const char *os_release_source, *root_type; const char *root_type;
_cleanup_free_ char *base_name = NULL; _cleanup_free_ char *base_name = NULL;
Image *ext; Image *ext;
root_type = root_setting_from_image(type); 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); r = path_extract_filename(m->image_path ?: image_path, &base_name);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to extract basename from '%s': %m", m->image_path ?: image_path); 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", "[Service]\n",
root_type, image_path, "\n" root_type, image_path, "\n"
"Environment=PORTABLE=", base_name, "\n" "Environment=PORTABLE=", base_name, "\n"
"BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n"
"LogExtraFields=PORTABLE=", base_name, "\n")) "LogExtraFields=PORTABLE=", base_name, "\n"))
return -ENOMEM; return -ENOMEM;

View File

@ -205,6 +205,7 @@ TEST(protect_kernel_logs) {
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
NULL); NULL);
assert_se(r == 0); assert_se(r == 0);

View File

@ -107,6 +107,7 @@ int main(int argc, char *argv[]) {
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
NULL); NULL);
if (r < 0) { if (r < 0) {
log_error_errno(r, "Failed to set up namespace: %m"); log_error_errno(r, "Failed to set up namespace: %m");

View File

@ -583,4 +583,6 @@ grep -q -F "MARKER_CONFEXT_123" /etc/testfile
systemd-confext unmerge systemd-confext unmerge
rm -rf /run/confexts/ testjob/ rm -rf /run/confexts/ testjob/
systemd-run -P -p RootImage="${image}.raw" cat /run/host/os-release | cmp "${os_release}"
touch /testok touch /testok

View File

@ -54,6 +54,8 @@ elif [ -f /run/testsuite82.touch2 ]; then
# Test that we really are in the new overlayfs root fs # Test that we really are in the new overlayfs root fs
read -r x </lower read -r x </lower
test "$x" = "miep" 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 # Switch back to the original root, away from the overlayfs
mount --bind /original-root /run/nextroot 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 mkdir -p /run/nextroot /tmp/nextroot-lower /original-root
mount -t tmpfs tmpfs /tmp/nextroot-lower mount -t tmpfs tmpfs /tmp/nextroot-lower
echo miep >/tmp/nextroot-lower/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 # Bind our current root into the target so that we later can return to it
mount --bind / /run/nextroot/original-root mount --bind / /run/nextroot/original-root