daemon: Add a sanitycheck(/bin/true) before we deploy a tree

This is a followup to https://github.com/projectatomic/rpm-ostree/pull/888
but more comprehensive; in the layering case, the sanitycheck runs
after all the `%posttrans` scripts, so we'll get a consistent error message
for the `rm -rf /` test.

We also do the sanitycheck for the "pure ostree" case, as well as cases
where we didn't actually layer packages (including `ex override remove` as
well as simply regenerating an initrd).

There's obviously a lot more we could do in a sanitycheck; as I say in the
comment it's tempting to consider trying to boot systemd (in a fully volatile
config), but for now let's do this. In the end of course the admin has rollback
too.

Closes: #892
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-07-21 14:26:31 -04:00 committed by Atomic Bot
parent ddc0f40355
commit 34b5a004a8
7 changed files with 77 additions and 1 deletions

View File

@ -32,6 +32,7 @@
#include "rpmostree-rpm-util.h"
#include "rpmostree-postprocess.h"
#include "rpmostree-output.h"
#include "rpmostree-scripts.h"
#include "rpmostree-unpacker.h"
#include "ostree-repo.h"
@ -1016,6 +1017,22 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self,
cancellable, error))
return FALSE;
/* Also do a sanitycheck even if there's no local mutation; it's basically free
* and might save someone in the future. The RPMOSTREE_SKIP_SANITYCHECK
* environment variable is just used by test-basic.sh currently.
*/
if (!self->final_revision)
{
g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self->sysroot, new_deployment);
glnx_fd_close int deployment_dfd = -1;
if (!glnx_opendirat (ostree_sysroot_get_fd (self->sysroot), deployment_path, TRUE,
&deployment_dfd, error))
return FALSE;
if (!rpmostree_deployment_sanitycheck (deployment_dfd, cancellable, error))
return FALSE;
}
if (self->final_revision)
{
/* Generate a temporary ref for the new deployment in case we are

View File

@ -177,6 +177,9 @@ rpmostree_treespec_new_from_keyfile (GKeyFile *keyfile,
}
add_canonicalized_string_array (&builder, "instlangs", "instlangs-all", keyfile);
if (g_key_file_get_boolean (keyfile, "tree", "skip-sanity-check", NULL))
g_variant_builder_add (&builder, "{sv}", "skip-sanity-check", g_variant_new_boolean (TRUE));
{ gboolean documentation = TRUE;
g_autofree char *value = g_key_file_get_value (keyfile, "tree", "documentation", NULL);
@ -2984,6 +2987,16 @@ rpmostree_context_assemble_tmprootfs (RpmOstreeContext *self,
return FALSE;
}
/* We want this to be the first error message if something went wrong
* with a script; see https://github.com/projectatomic/rpm-ostree/pull/888
*/
gboolean skip_sanity_check = FALSE;
g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check);
if (!skip_sanity_check &&
!rpmostree_deployment_sanitycheck (tmprootfs_dfd, cancellable, error))
return FALSE;
if (have_systemctl)
{
if (renameat (tmprootfs_dfd, "usr/bin/systemctl.rpmostreesave",
@ -2997,6 +3010,12 @@ rpmostree_context_assemble_tmprootfs (RpmOstreeContext *self,
return FALSE;
}
}
else
{
/* Also do a sanity check even if we have no layered packages */
if (!rpmostree_deployment_sanitycheck (tmprootfs_dfd, cancellable, error))
return FALSE;
}
g_clear_pointer (&ordering_ts, rpmtsFree);

View File

@ -21,6 +21,7 @@
#include "config.h"
#include <gio/gio.h>
#include <systemd/sd-journal.h>
#include "rpmostree-output.h"
#include "rpmostree-bwrap.h"
#include <err.h>
@ -350,3 +351,35 @@ rpmostree_pre_run_sync (DnfPackage *pkg,
return TRUE;
}
/* Ensure that we can at least execute /usr/bin/true inside the new root.
* See https://github.com/projectatomic/rpm-ostree/pull/888
*
* Currently at least on Fedora this will run through e.g. the dynamic linker
* and hence some bits of glibc.
*
* We could consider doing more here, perhaps even starting systemd in a
* volatile mode, but that could just as easily be a separate tool.
*/
gboolean
rpmostree_deployment_sanitycheck (int rootfs_fd,
GCancellable *cancellable,
GError **error)
{
/* Used by the test suite */
if (getenv ("RPMOSTREE_SKIP_SANITYCHECK"))
return TRUE;
GLNX_AUTO_PREFIX_ERROR ("sanitycheck", error);
g_autoptr(RpmOstreeBwrap) bwrap =
rpmostree_bwrap_new (rootfs_fd, RPMOSTREE_BWRAP_IMMUTABLE, error,
"--ro-bind", "./usr/etc", "/etc",
NULL);
rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/true", NULL);
if (!bwrap)
return FALSE;
if (!rpmostree_bwrap_run (bwrap, error))
return FALSE;
sd_journal_print (LOG_INFO, "sanitycheck(/usr/bin/true) successful");
return TRUE;
}

View File

@ -64,3 +64,8 @@ rpmostree_pre_run_sync (DnfPackage *pkg,
int rootfs_fd,
GCancellable *cancellable,
GError **error);
gboolean
rpmostree_deployment_sanitycheck (int rootfs_fd,
GCancellable *cancellable,
GError **error);

View File

@ -42,6 +42,7 @@ cat > foo.conf <<EOF
ref=foo
packages=foo
repos=test-repo
skip-sanity-check=true
EOF
rpm-ostree ex container assemble foo.conf

View File

@ -61,5 +61,6 @@ EOF
# Tell rpm-ostree to connect to the session bus instead of system
export RPMOSTREE_USE_SESSION_BUS=1
export RPMOSTREE_SKIP_SANITYCHECK=1
exec dbus-run-session --config-file=${test_tmpdir}/session.conf $@

View File

@ -94,4 +94,4 @@ if vm_rpmostree install rmrf 2>err.txt; then
fi
vm_cmd test -f /home/testuser/somedata -a -f /etc/fstab -a -f /tmp/sometmpfile -a -f /var/tmp/sometmpfile
# This is the error today, we may improve it later
assert_file_has_content err.txt 'renameat(usr/bin/systemctl): No such file or directory'
assert_file_has_content err.txt 'error: sanitycheck: Executing bwrap(/usr/bin/true)'