diff --git a/Makefile-boot.am b/Makefile-boot.am index c07b6b8123..d4d5b0e16b 100644 --- a/Makefile-boot.am +++ b/Makefile-boot.am @@ -38,6 +38,7 @@ endif if BUILDOPT_SYSTEMD systemdsystemunit_DATA = src/boot/ostree-prepare-root.service \ src/boot/ostree-remount.service \ + src/boot/ostree-relabel-etc.service \ src/boot/ostree-boot-complete.service \ src/boot/ostree-finalize-staged.service \ src/boot/ostree-finalize-staged.path \ @@ -71,6 +72,7 @@ EXTRA_DIST += src/boot/dracut/module-setup.sh \ src/boot/ostree-prepare-root.service \ src/boot/ostree-finalize-staged.path \ src/boot/ostree-remount.service \ + src/boot/ostree-relabel-etc.service \ src/boot/ostree-finalize-staged.service \ src/boot/ostree-finalize-staged-hold.service \ src/boot/ostree-state-overlay@.service \ diff --git a/Makefile-switchroot.am b/Makefile-switchroot.am index 9a1d41371f..a66e576f89 100644 --- a/Makefile-switchroot.am +++ b/Makefile-switchroot.am @@ -22,11 +22,11 @@ ostree_prepare_root_CPPFLAGS = $(AM_CPPFLAGS) ostree_prepare_root_LDADD = if BUILDOPT_SYSTEMD -ostree_boot_PROGRAMS += ostree-remount +ostree_boot_PROGRAMS += ostree-remount ostree-relabel-etc else # It is built anyway as a side-effect of having the symlink in tests/, # and if we declare it here, it gets cleaned up properly -check_PROGRAMS += ostree-remount +check_PROGRAMS += ostree-remount ostree-relabel-etc endif if BUILDOPT_USE_STATIC_COMPILER @@ -63,9 +63,15 @@ ostree_remount_SOURCES = \ ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS) -Isrc/switchroot -I$(srcdir)/src/libotcore -I$(srcdir)/src/libotutil -I$(srcdir)/libglnx ostree_remount_LDADD = $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libotcore.la libotutil.la libglnx.la +ostree_relabel_etc_SOURCES = \ + src/switchroot/ostree-relabel-etc.c \ + $(NULL) +ostree_relabel_etc_CPPFLAGS = $(AM_CPPFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS) -Isrc/switchroot -I$(srcdir)/src/libotcore -I$(srcdir)/src/libotutil -I$(srcdir)/libglnx +ostree_relabel_etc_LDADD = $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libotcore.la libotutil.la libglnx.la + if USE_SELINUX -ostree_remount_CPPFLAGS += $(OT_DEP_SELINUX_CFLAGS) -ostree_remount_LDADD += $(OT_DEP_SELINUX_LIBS) +ostree_relabel_etc_CPPFLAGS += $(OT_DEP_SELINUX_CFLAGS) +ostree_relabel_etc_LDADD += $(OT_DEP_SELINUX_LIBS) endif if USE_COMPOSEFS diff --git a/src/boot/mkinitcpio/ostree b/src/boot/mkinitcpio/ostree index 3aa0659fb3..ebcba3ea8b 100644 --- a/src/boot/mkinitcpio/ostree +++ b/src/boot/mkinitcpio/ostree @@ -3,6 +3,7 @@ build() { add_binary /usr/lib/ostree/ostree-prepare-root add_binary /usr/lib/ostree/ostree-remount + add_binary /usr/lib/ostree/ostree-relabel-etc add_file /usr/lib/systemd/system/ostree-prepare-root.service add_symlink /usr/lib/systemd/system/initrd-root-fs.target.wants/ostree-prepare-root.service \ diff --git a/src/boot/ostree-relabel-etc.service b/src/boot/ostree-relabel-etc.service new file mode 100644 index 0000000000..8d190cd4e6 --- /dev/null +++ b/src/boot/ostree-relabel-etc.service @@ -0,0 +1,40 @@ +# Copyright (C) 2013 Colin Walters +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +[Unit] +Description=OSTree Relabel etc +Documentation=man:ostree(1) +DefaultDependencies=no +ConditionKernelCommandLine=ostree +OnFailure=emergency.target +Conflicts=umount.target +# Run after core mounts +After=-.mount var.mount +After=systemd-remount-fs.service +# But we run *before* most other core bootup services that need write access to /etc and /var +Before=local-fs.target umount.target +Before=systemd-random-seed.service plymouth-read-write.service systemd-journal-flush.service systemd-sysusers.service +Before=systemd-tmpfiles-setup.service systemd-rfkill.service systemd-rfkill.socket + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/lib/ostree/ostree-relabel-etc +StandardInput=null +StandardOutput=journal +StandardError=journal+console + +[Install] +WantedBy=local-fs.target diff --git a/src/libostree/ostree-impl-system-generator.c b/src/libostree/ostree-impl-system-generator.c index c5fe503287..7a543ae257 100644 --- a/src/libostree/ostree-impl-system-generator.c +++ b/src/libostree/ostree-impl-system-generator.c @@ -109,6 +109,11 @@ require_internal_units (const char *normal_dir, const char *early_dir, const cha < 0) return glnx_throw_errno_prefix (error, "symlinkat"); + if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-relabel-etc.service", normal_dir_dfd, + "local-fs.target.requires/ostree-relabel-etc.service") + < 0) + return glnx_throw_errno_prefix (error, "symlinkat"); + if (!glnx_shutil_mkdir_p_at (normal_dir_dfd, "multi-user.target.wants", 0755, cancellable, error)) return FALSE; if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-finalize-staged.path", normal_dir_dfd, diff --git a/src/switchroot/ostree-relabel-etc.c b/src/switchroot/ostree-relabel-etc.c new file mode 100644 index 0000000000..65f7e612b1 --- /dev/null +++ b/src/switchroot/ostree-relabel-etc.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2011 Colin Walters + * + * SPDX-License-Identifier: LGPL-2.0+ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Author: Colin Walters + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SELINUX +#include +#endif + +#include "ostree-mount-util.h" +#include "otcore.h" + +/* Relabel the directory $real_path, which is going to be an overlayfs mount, + * based on the content of an overlayfs upperdirectory that is in use by the mount. + * The goal is that we relabel in the overlay mount all the files that have been + * modified (directly or via parent copyup operations) since the overlayfs was + * mounted. This will be used for the /etc overlayfs mount where no selinux labels + * are set before selinux policy is loaded. + */ +static void +relabel_dir_for_upper (const char *upper_path, const char *real_path, gboolean is_dir) +{ +#ifdef HAVE_SELINUX + /* Ignore ENOENT, because if there is no file to relabel we can continue, + * systemd-sysusers runs in parallel and can create temporary files in /etc + * causing failures like: + * "Failed to relabel /etc/.#gshadowJzu4Rx: No such file or directory" + */ + if (selinux_restorecon (real_path, 0)) + { + if (errno == ENOENT) + return; + + err (EXIT_FAILURE, "Failed to relabel %s", real_path); + } + + if (!is_dir) + return; + + g_auto (GLnxDirFdIterator) dfd_iter = { + 0, + }; + + if (!glnx_dirfd_iterator_init_at (AT_FDCWD, upper_path, FALSE, &dfd_iter, NULL)) + err (EXIT_FAILURE, "Failed to open upper directory %s for relabeling", upper_path); + + while (TRUE) + { + struct dirent *dent; + + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, NULL)) + { + err (EXIT_FAILURE, "Failed to read upper directory %s for relabelin", upper_path); + break; + } + + if (dent == NULL) + break; + + g_autofree char *upper_child = g_build_filename (upper_path, dent->d_name, NULL); + g_autofree char *real_child = g_build_filename (real_path, dent->d_name, NULL); + relabel_dir_for_upper (upper_child, real_child, dent->d_type == DT_DIR); + } +#endif +} + +int +main (int argc, char *argv[]) +{ + g_autoptr (GError) error = NULL; + g_autoptr (GVariant) ostree_run_metadata_v = NULL; + { + glnx_autofd int fd = open (OTCORE_RUN_BOOTED, O_RDONLY | O_CLOEXEC); + if (fd < 0) + { + /* We really expect that nowadays that everything is done in the initramfs, + * but historically we created this file here, so we'll continue to do be + * sure here it exists. This code should be removed at some point though. + */ + if (errno == ENOENT) + { + int subfd = open (OTCORE_RUN_BOOTED, O_EXCL | O_CREAT | O_WRONLY | O_NOCTTY | O_CLOEXEC, + 0640); + if (subfd != -1) + (void)close (subfd); + } + else + { + err (EXIT_FAILURE, "failed to open %s", OTCORE_RUN_BOOTED); + } + } + else + { + if (!ot_variant_read_fd (fd, 0, G_VARIANT_TYPE_VARDICT, TRUE, &ostree_run_metadata_v, + &error)) + errx (EXIT_FAILURE, "failed to read %s: %s", OTCORE_RUN_BOOTED, error->message); + } + } + g_autoptr (GVariantDict) ostree_run_metadata = g_variant_dict_new (ostree_run_metadata_v); + + /* The /sysroot mount needs to be private to avoid having a mount for e.g. /var/cache + * also propagate to /sysroot/ostree/deploy/$stateroot/var/cache + * + * Today systemd remounts / (recursively) as shared, so we're undoing that as early + * as possible. See also a copy of this in ostree-prepare-root.c. + */ + if (mount ("none", "/sysroot", NULL, MS_REC | MS_PRIVATE, NULL) < 0) + perror ("warning: While remounting /sysroot MS_PRIVATE"); + + const char *transient_etc = NULL; + g_variant_dict_lookup (ostree_run_metadata, OTCORE_RUN_BOOTED_KEY_TRANSIENT_ETC, "&s", + &transient_etc); + + if (transient_etc) + { + /* If the initramfs created any files in /etc (directly or via overlay copy-up) they + * will be unlabeled, because the selinux policy is not loaded until after the + * pivot-root. So, for all files in the upper dir, relabel the corresponding overlay + * file. + * + * Also, note that during boot systemd will create a /run/machine-id -> + * /etc/machine-id bind mount (as /etc is read-only early on). It will then later + * replace this mount with a real one (in systemd-machine-id-commit.service). + * + * We need to label the actual overlayfs file, not the temporary bind-mount. To do + * this we unmount the covering mount before relabeling, but we do so in a temporary + * private namespace to avoid affecting other parts of the system. + */ + + glnx_autofd int initial_ns_fd = -1; + if (g_file_test ("/run/machine-id", G_FILE_TEST_EXISTS) + && g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) + { + initial_ns_fd = open ("/proc/self/ns/mnt", O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (initial_ns_fd < 0) + err (EXIT_FAILURE, "Failed to open initial namespace"); + + if (unshare (CLONE_NEWNS) < 0) + err (EXIT_FAILURE, "Failed to unshare initial namespace"); + + /* Ensure unmount is not propagated */ + if (mount ("none", "/etc", NULL, MS_REC | MS_PRIVATE, NULL) < 0) + err (EXIT_FAILURE, "warning: While remounting /etc MS_PRIVATE"); + + if (umount2 ("/etc/machine-id", MNT_DETACH) < 0) + err (EXIT_FAILURE, "Failed to unmount machine-id"); + } + + g_autofree char *upper = g_build_filename (transient_etc, "upper", NULL); + relabel_dir_for_upper (upper, "/etc", TRUE); + + if (initial_ns_fd != -1 && setns (initial_ns_fd, CLONE_NEWNS) < 0) + err (EXIT_FAILURE, "Failed to join initial namespace"); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/switchroot/ostree-remount.c b/src/switchroot/ostree-remount.c index 3babb75141..8ccd245f19 100644 --- a/src/switchroot/ostree-remount.c +++ b/src/switchroot/ostree-remount.c @@ -79,60 +79,6 @@ do_remount (const char *target, bool writable) printf ("Remounted %s: %s\n", writable ? "rw" : "ro", target); } -/* Relabel the directory $real_path, which is going to be an overlayfs mount, - * based on the content of an overlayfs upperdirectory that is in use by the mount. - * The goal is that we relabel in the overlay mount all the files that have been - * modified (directly or via parent copyup operations) since the overlayfs was - * mounted. This will be used for the /etc overlayfs mount where no selinux labels - * are set before selinux policy is loaded. - */ -static void -relabel_dir_for_upper (const char *upper_path, const char *real_path, gboolean is_dir) -{ -#ifdef HAVE_SELINUX - /* Ignore ENOENT, because if there is no file to relabel we can continue, - * systemd-sysusers runs in parallel and can create temporary files in /etc - * causing failures like: - * "Failed to relabel /etc/.#gshadowJzu4Rx: No such file or directory" - */ - if (selinux_restorecon (real_path, 0)) - { - if (errno == ENOENT) - return; - - err (EXIT_FAILURE, "Failed to relabel %s", real_path); - } - - if (!is_dir) - return; - - g_auto (GLnxDirFdIterator) dfd_iter = { - 0, - }; - - if (!glnx_dirfd_iterator_init_at (AT_FDCWD, upper_path, FALSE, &dfd_iter, NULL)) - err (EXIT_FAILURE, "Failed to open upper directory %s for relabeling", upper_path); - - while (TRUE) - { - struct dirent *dent; - - if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, NULL)) - { - err (EXIT_FAILURE, "Failed to read upper directory %s for relabelin", upper_path); - break; - } - - if (dent == NULL) - break; - - g_autofree char *upper_child = g_build_filename (upper_path, dent->d_name, NULL); - g_autofree char *real_child = g_build_filename (real_path, dent->d_name, NULL); - relabel_dir_for_upper (upper_child, real_child, dent->d_type == DT_DIR); - } -#endif -} - int main (int argc, char *argv[]) { @@ -180,48 +126,6 @@ main (int argc, char *argv[]) g_variant_dict_lookup (ostree_run_metadata, OTCORE_RUN_BOOTED_KEY_TRANSIENT_ETC, "&s", &transient_etc); - if (transient_etc) - { - /* If the initramfs created any files in /etc (directly or via overlay copy-up) they - * will be unlabeled, because the selinux policy is not loaded until after the - * pivot-root. So, for all files in the upper dir, relabel the corresponding overlay - * file. - * - * Also, note that during boot systemd will create a /run/machine-id -> - * /etc/machine-id bind mount (as /etc is read-only early on). It will then later - * replace this mount with a real one (in systemd-machine-id-commit.service). - * - * We need to label the actual overlayfs file, not the temporary bind-mount. To do - * this we unmount the covering mount before relabeling, but we do so in a temporary - * private namespace to avoid affecting other parts of the system. - */ - - glnx_autofd int initial_ns_fd = -1; - if (g_file_test ("/run/machine-id", G_FILE_TEST_EXISTS) - && g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) - { - initial_ns_fd = open ("/proc/self/ns/mnt", O_RDONLY | O_NOCTTY | O_CLOEXEC); - if (initial_ns_fd < 0) - err (EXIT_FAILURE, "Failed to open initial namespace"); - - if (unshare (CLONE_NEWNS) < 0) - err (EXIT_FAILURE, "Failed to unshare initial namespace"); - - /* Ensure unmount is not propagated */ - if (mount ("none", "/etc", NULL, MS_REC | MS_PRIVATE, NULL) < 0) - err (EXIT_FAILURE, "warning: While remounting /etc MS_PRIVATE"); - - if (umount2 ("/etc/machine-id", MNT_DETACH) < 0) - err (EXIT_FAILURE, "Failed to unmount machine-id"); - } - - g_autofree char *upper = g_build_filename (transient_etc, "upper", NULL); - relabel_dir_for_upper (upper, "/etc", TRUE); - - if (initial_ns_fd != -1 && setns (initial_ns_fd, CLONE_NEWNS) < 0) - err (EXIT_FAILURE, "Failed to join initial namespace"); - } - gboolean root_is_composefs = FALSE; g_variant_dict_lookup (ostree_run_metadata, OTCORE_RUN_BOOTED_KEY_COMPOSEFS, "b", &root_is_composefs); diff --git a/tests/kolainst/destructive/staged-deploy.sh b/tests/kolainst/destructive/staged-deploy.sh index 198814898f..56ca872adb 100755 --- a/tests/kolainst/destructive/staged-deploy.sh +++ b/tests/kolainst/destructive/staged-deploy.sh @@ -14,6 +14,7 @@ case "${AUTOPKGTEST_REBOOT_MARK:-}" in # Test our generator test -f /run/systemd/generator/multi-user.target.wants/ostree-finalize-staged.path test -f /run/systemd/generator/local-fs.target.requires/ostree-remount.service + test -f /run/systemd/generator/local-fs.target.requires/ostree-relabel-etc.service cat >/etc/systemd/system/sock-to-ignore.socket << 'EOF' [Socket]