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:
Colin Walters committed 2016-03-21 12:49:05 -04:00
1 parent b842429bf2
commit 8894bb3949
4 files changed
+109 -1

No files matched your search

+74
View File
@@ -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;
}