mirror of
https://github.com/ostreedev/ostree.git
synced 2024-10-26 08:55:19 +03:00
deploy: Handle a read-only /boot
I'd like to encourage people to make OSTree-managed systems more strictly read-only in multiple places. Ideally everywhere is read-only normally besides `/var/`, `/tmp/`, and `/run`. `/boot` is a good example of something to make readonly. Particularly now that there's work on the `admin unlock` verb, we need to protect the system better against things like `rpm -Uvh kernel.rpm` because the RPM-packaged kernel won't understand how to do OSTree right. In order to make this work of course, we *do* need to remount `/boot` as writable when we're doing an upgrade that changes the kernel configuration. So the strategy is to detect whether it's read-only, and if so, temporarily mount read-write, then remount read-only when the upgrade is done. We can generalize this in the future to also do `/etc` (and possibly `/sysroot/ostree/` although that gets tricky). One detail: In order to detect "is this path a mountpoint" is nontrivial - I looked at copying the systemd code, but the right place is to use `libmount` anyways.
This commit is contained in:
parent
b842429bf2
commit
8894bb3949
@ -159,6 +159,11 @@ libostree_1_la_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS)
|
||||
libostree_1_la_LIBADD += $(OT_INTERNAL_SOUP_LIBS)
|
||||
endif
|
||||
|
||||
if USE_LIBMOUNT
|
||||
libostree_1_la_CFLAGS += $(OT_DEP_LIBMOUNT_CFLAGS)
|
||||
libostree_1_la_LIBADD += $(OT_DEP_LIBMOUNT_LIBS)
|
||||
endif
|
||||
|
||||
if USE_SELINUX
|
||||
libostree_1_la_CFLAGS += $(OT_DEP_SELINUX_CFLAGS)
|
||||
libostree_1_la_LIBADD += $(OT_DEP_SELINUX_LIBS)
|
||||
|
26
configure.ac
26
configure.ac
@ -192,6 +192,31 @@ AS_IF([ test x$with_selinux != xno ], [
|
||||
if test x$with_selinux != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +selinux"; fi
|
||||
AM_CONDITIONAL(USE_SELINUX, test $with_selinux != no)
|
||||
|
||||
dnl This is what is in RHEL7.2 right now, picking it arbitrarily
|
||||
LIBMOUNT_DEPENDENCY="mount >= 2.23.0"
|
||||
|
||||
AC_ARG_WITH(libmount,
|
||||
AS_HELP_STRING([--without-libmount], [Do not use libmount]),
|
||||
:, with_libmount=maybe)
|
||||
|
||||
AS_IF([ test x$with_libmount != xno ], [
|
||||
AC_MSG_CHECKING([for $LIBMOUNT_DEPENDENCY])
|
||||
PKG_CHECK_EXISTS($LIBMOUNT_DEPENDENCY, have_libmount=yes, have_libmount=no)
|
||||
AC_MSG_RESULT([$have_libmount])
|
||||
AS_IF([ test x$have_libmount = xno && test x$with_libmount != xmaybe ], [
|
||||
AC_MSG_ERROR([libmount is enabled but could not be found])
|
||||
])
|
||||
AS_IF([ test x$have_libmount = xyes], [
|
||||
AC_DEFINE([HAVE_LIBMOUNT], 1, [Define if we have libmount.pc])
|
||||
PKG_CHECK_MODULES(OT_DEP_LIBMOUNT, $LIBMOUNT_DEPENDENCY)
|
||||
with_libmount=yes
|
||||
], [
|
||||
with_libmount=no
|
||||
])
|
||||
], [ with_libmount=no ])
|
||||
if test x$with_libmount != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +libmount"; fi
|
||||
AM_CONDITIONAL(USE_LIBMOUNT, test $with_libmount != no)
|
||||
|
||||
# Enabled by default because I think people should use it.
|
||||
AC_ARG_ENABLE(rofiles-fuse,
|
||||
[AS_HELP_STRING([--enable-rofiles-fuse],
|
||||
@ -260,6 +285,7 @@ echo "
|
||||
libsoup (retrieve remote HTTP repositories): $with_soup
|
||||
libsoup TLS client certs: $have_libsoup_client_certs
|
||||
SELinux: $with_selinux
|
||||
libmount: $with_libmount
|
||||
libarchive (parse tar files directly): $with_libarchive
|
||||
static deltas: yes (always enabled now)
|
||||
man pages (xsltproc): $enable_man
|
||||
|
@ -100,7 +100,10 @@ deployment lists. This happens when doing an upgrade that does not
|
||||
include the kernel; think of a simple translation update. OSTree
|
||||
optimizes for this case because on some systems `/boot` may be on a
|
||||
separate medium such as flash storage not optimized for significant
|
||||
amounts of write traffic.
|
||||
amounts of write traffic. Related to this, modern OSTree has support
|
||||
for having `/boot` be a read-only mount by default - it will
|
||||
automatically remount read-write just for the portion of time
|
||||
necessary to update the bootloader configuration.
|
||||
|
||||
To implement this, OSTree also maintains the directory
|
||||
`/ostree/boot.<replaceable>bootversion</replaceable>`, which is a set
|
||||
|
@ -22,6 +22,12 @@
|
||||
|
||||
#include <gio/gunixinputstream.h>
|
||||
#include <gio/gunixoutputstream.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#ifdef HAVE_LIBMOUNT
|
||||
#include <libmount.h>
|
||||
#endif
|
||||
|
||||
#include "ostree-sysroot-private.h"
|
||||
#include "ostree-deployment-private.h"
|
||||
@ -1646,6 +1652,47 @@ cleanup_legacy_current_symlinks (OstreeSysroot *self,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
is_ro_mount (const char *path)
|
||||
{
|
||||
#ifdef HAVE_LIBMOUNT
|
||||
/* Dragging in all of this crud is apparently necessary just to determine
|
||||
* whether something is a mount point.
|
||||
*
|
||||
* Systemd has a totally different implementation in
|
||||
* src/basic/mount-util.c.
|
||||
*/
|
||||
struct libmnt_table *tb = mnt_new_table_from_file ("/proc/self/mountinfo");
|
||||
struct libmnt_fs *fs;
|
||||
struct libmnt_cache *cache;
|
||||
gboolean is_mount = FALSE;
|
||||
struct statvfs stvfsbuf;
|
||||
|
||||
if (!tb)
|
||||
return FALSE;
|
||||
|
||||
/* to canonicalize all necessary paths */
|
||||
cache = mnt_new_cache ();
|
||||
mnt_table_set_cache (tb, cache);
|
||||
|
||||
fs = mnt_table_find_target(tb, path, MNT_ITER_BACKWARD);
|
||||
is_mount = fs && mnt_fs_get_target (fs);
|
||||
mnt_free_cache (cache);
|
||||
mnt_free_table (tb);
|
||||
|
||||
if (!is_mount)
|
||||
return FALSE;
|
||||
|
||||
/* We *could* parse the options, but it seems more reliable to
|
||||
* introspect the actual mount at runtime.
|
||||
*/
|
||||
if (statvfs (path, &stvfsbuf) == 0)
|
||||
return (stvfsbuf.f_flag & ST_RDONLY) != 0;
|
||||
|
||||
#endif
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_write_deployments:
|
||||
* @self: Sysroot
|
||||
@ -1667,6 +1714,7 @@ ostree_sysroot_write_deployments (OstreeSysroot *self,
|
||||
gboolean requires_new_bootversion = FALSE;
|
||||
gboolean found_booted_deployment = FALSE;
|
||||
gboolean bootloader_is_atomic = FALSE;
|
||||
gboolean boot_was_ro_mount = FALSE;
|
||||
|
||||
g_assert (self->loaded);
|
||||
|
||||
@ -1754,6 +1802,20 @@ ostree_sysroot_write_deployments (OstreeSysroot *self,
|
||||
glnx_unref_object OstreeRepo *repo = NULL;
|
||||
gboolean show_osname = FALSE;
|
||||
|
||||
if (self->booted_deployment)
|
||||
boot_was_ro_mount = is_ro_mount ("/boot");
|
||||
|
||||
g_debug ("boot is ro: %s", boot_was_ro_mount ? "yes" : "no");
|
||||
|
||||
if (boot_was_ro_mount)
|
||||
{
|
||||
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
|
||||
{
|
||||
glnx_set_prefix_error_from_errno (error, "%s", "Remounting /boot read-write");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_ostree_sysroot_query_bootloader (self, &bootloader, cancellable, error))
|
||||
goto out;
|
||||
|
||||
@ -1879,6 +1941,18 @@ ostree_sysroot_write_deployments (OstreeSysroot *self,
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
if (boot_was_ro_mount)
|
||||
{
|
||||
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_RDONLY | MS_SILENT, NULL) < 0)
|
||||
{
|
||||
/* Only make this a warning because we don't want to
|
||||
* completely bomb out if some other process happened to
|
||||
* jump in and open a file there.
|
||||
*/
|
||||
int errsv = errno;
|
||||
g_printerr ("warning: Failed to remount /boot read-only: %s\n", strerror (errsv));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user