Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

relabel-etc: Split remount into, remount and relabel-etc #3267

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile-boot.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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/[email protected] \
Expand Down
14 changes: 10 additions & 4 deletions Makefile-switchroot.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/boot/mkinitcpio/ostree
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
40 changes: 40 additions & 0 deletions src/boot/ostree-relabel-etc.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright (C) 2013 Colin Walters <[email protected]>
#
# 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 <https://www.gnu.org/licenses/>.

[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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need var.mount here.
Actually let's just use:
RequiresMountsFor=/etc.

After=systemd-remount-fs.service
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one can (and should) be Before=systemd-remount-fs.service instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I was thinking about this more and I think we're in a situation where actually we tell people using composefs in particular to just mount the rootfs writable in the initramfs, so the whole role of systemd-remount-fs.service is a no-op from our PoV basically.

At least...I thought we enforced that, but now I can't find it.

# 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
Comment on lines +28 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can drop all this stuff, as all of that is After=systemd-remount-fs.service.

But I'm fine going belt-and-suspenders here with e.g. Before=ostree-remount.service - which already has all those Before= itself.

To combine all this I'd suggest:

Before=local-fs-pre.target systemd-remount-fs.service
# This one is already itself After=systemd-remount-fs.service, but for redundancy
Before=ostree-remount.service


[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/ostree/ostree-relabel-etc
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so far we haven't introduced many new little binaries, but instead we've been tending to dispatch via hidden CLI verbs, see e.g. ostree admin finalize-staged. I'd slightly prefer that, i.e. we introduce a new ostree admin relabel-etc (which should barf if it's not run under systemd, i.e. we check INVOCATION_ID etc).

StandardInput=null
StandardOutput=journal
StandardError=journal+console

[Install]
WantedBy=local-fs.target
5 changes: 5 additions & 0 deletions src/libostree/ostree-impl-system-generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with this as is, but just a note: I think we can change things so that the initramfs code itself acts as a generator and writes the enablement symlink into /run directly then.

As is it's a bit silly because the initramfs writes the file in /run which says that transient etc is enabled, then we boot in the real root, run a binary which reads that file and exits if the flag isn't found...but we can bypass all that and be slightly more efficient by just telling systemd whether or not to run the binary.

"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,
Expand Down
179 changes: 179 additions & 0 deletions src/switchroot/ostree-relabel-etc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2011 Colin Walters <[email protected]>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tangentially related, I think you can update this to be you and or/alex, or just say (C) Red Hat, Inc.

*
* 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 <https://www.gnu.org/licenses/>.
*
* Author: Colin Walters <[email protected]>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been dropping this in new code BTW, "git log" is source of truth and I don't think I wrote any of this code.

*/

#include "config.h"

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <unistd.h>
#ifdef HAVE_SELINUX
#include <selinux/restorecon.h>
#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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: I think this entire binary could become conditional on SELinux actually, i.e. we don't even compile or ship it if built without selinux support.

/* 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this copy here.

* 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);
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);
}
97 changes: 0 additions & 97 deletions src/switchroot/ostree-remount.c
Original file line number Diff line number Diff line change
Expand Up @@ -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[])
{
Expand Down Expand Up @@ -179,49 +125,6 @@ main (int argc, char *argv[])
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");
}

gboolean root_is_composefs = FALSE;
g_variant_dict_lookup (ostree_run_metadata, OTCORE_RUN_BOOTED_KEY_COMPOSEFS, "b",
&root_is_composefs);
Expand Down
Loading
Loading