From 0e57e534a45475d32d9a7510221fe52442d2f14c Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 23 Feb 2019 18:46:59 +0000 Subject: [PATCH] WIP: Add support for wrapping binaries (rpm, dracut, grubby) We need to be friendlier to people who are transitioning from "traditional" yum managed systems. This patchset starts to lay out the groundwork for supporting "intercepting" binaries that are in the tree. To start with for example, we wrap `/usr/bin/rpm` and cause it to drop privileges. This way it can't corrupt anything; we're not just relying on the read-only bind mount. For example nothing will accidentally get written to `/var/lib/rpm`. Now a tricky thing with this one is we *do* want it to write if we're in an unlocked state. There are various other examples of binaries we want to intercept, among them: - `grubby` -> `rpm-ostree kargs` - `dracut` -> `rpm-ostree initramfs` - `yum` -> well...we'll talk about that later --- Makefile-rpm-ostree.am | 3 +- docs/manual/treefile.md | 4 ++ rust/Cargo.lock | 20 ++++++ rust/Cargo.toml | 1 + rust/src/cliwrap.rs | 101 +++++++++++++++++++++++++++ rust/src/cliwrap/cliutil.rs | 104 ++++++++++++++++++++++++++++ rust/src/cliwrap/dracut.rs | 9 +++ rust/src/cliwrap/grubby.rs | 8 +++ rust/src/cliwrap/rpm.rs | 64 +++++++++++++++++ rust/src/lib.rs | 2 + rust/src/openat_utils.rs | 16 +++++ rust/src/treefile.rs | 10 +++ src/app/main.c | 2 + src/app/rpmostree-builtin-cliwrap.c | 50 +++++++++++++ src/app/rpmostree-builtins.h | 1 + src/libpriv/rpmostree-core.c | 6 ++ 16 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 rust/src/cliwrap.rs create mode 100644 rust/src/cliwrap/cliutil.rs create mode 100644 rust/src/cliwrap/dracut.rs create mode 100644 rust/src/cliwrap/grubby.rs create mode 100644 rust/src/cliwrap/rpm.rs create mode 100644 src/app/rpmostree-builtin-cliwrap.c diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index d64754006d..76ad7f63ba 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -29,6 +29,7 @@ rpm_ostree_SOURCES = src/app/main.c \ src/app/rpmostree-builtin-reload.c \ src/app/rpmostree-builtin-rebase.c \ src/app/rpmostree-builtin-cancel.c \ + src/app/rpmostree-builtin-cliwrap.c \ src/app/rpmostree-builtin-cleanup.c \ src/app/rpmostree-builtin-initramfs.c \ src/app/rpmostree-builtin-livefs.c \ @@ -99,7 +100,7 @@ librpmostree_rust_path = @abs_top_builddir@/target/@RUST_TARGET_SUBDIR@/librpmos # If the target directory exists, and isn't owned by our uid, then # we exit with a fatal error, since someone probably did `make && sudo make install`, # and in this case cargo will download into ~/.root which we don't want. -LIBRPMOSTREE_RUST_SRCS = $(wildcard rust/src/*.rs) rust/cbindgen.toml +LIBRPMOSTREE_RUST_SRCS = $(shell find rust/src/ -name '*.rs') rust/cbindgen.toml $(librpmostree_rust_path): Makefile $(LIBRPMOSTREE_RUST_SRCS) cd $(top_srcdir)/rust && \ export CARGO_TARGET_DIR=@abs_top_builddir@/target && \ diff --git a/docs/manual/treefile.md b/docs/manual/treefile.md index 18017e73d9..fe2e1ecc17 100644 --- a/docs/manual/treefile.md +++ b/docs/manual/treefile.md @@ -77,6 +77,10 @@ It supports the following parameters: specific filesystem drivers are included. If not specified, `--no-hostonly` will be used. + * `cliwrap`: boolean, optional. Defaults to `true`. If enabled, + rpm-ostree will replace binaries such as `/usr/bin/rpm` with + wrappers that intercept unsafe operations, or adjust functionality. + * `remove-files`: Array of files to delete from the generated tree. * `remove-from-packages`: Array, optional: Delete from specified packages diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 00912c1e08..f3cb8c1a2d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -388,6 +388,18 @@ name = "memoffset" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "nix" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -638,6 +650,7 @@ dependencies = [ "indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "openat 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", @@ -875,6 +888,11 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -958,6 +976,7 @@ dependencies = [ "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" @@ -1017,6 +1036,7 @@ dependencies = [ "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a45178306a..c55a738448 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Colin Walters ", "Jonathan Lebon ) -> Fallible<()> { + // We'll panic here if the vector is empty, but that is intentional; + // the outer code should always pass us at least one arg. + let name = args[0].as_str(); + let args : Vec<&str> = args.iter().map(|v| v.as_str()).collect(); + + // If we're not booted into ostree, just run the child directly. + if !cliutil::is_ostree_booted() { + cliutil::exec_real_wrapped(name, &args) + } else { + match name { + "rpm" => self::rpm::main(&args), + "dracut" => self::dracut::main(&args), + "grubby" => self::grubby::main(&args), + _ => bail!("Unknown wrapped binary: {}", name), + } + } +} + +/// Move the real binaries to a subdir, and replace them with +/// a shell script that calls our wrapping code. +fn write_wrappers(rootfs_dfd: &openat::Dir) -> Fallible<()> { + rootfs_dfd.create_dir(WRAP_DESTDIR, 0o755)?; + WRAPPED_BINARIES.par_iter().try_for_each(|&bin| { + let binpath = path::Path::new(bin); + + if !rootfs_dfd.exists(binpath)? { + return Ok(()); + } + + let name = binpath.file_name().unwrap().to_str().unwrap(); + let destpath = format!("{}/{}", WRAP_DESTDIR, name); + rootfs_dfd.local_rename(bin, destpath.as_str())?; + + let f = rootfs_dfd.write_file(binpath, 0o755)?; + let mut f = io::BufWriter::new(f); + f.write(b"#!/bin/sh\nexec /usr/bin/rpm-ostree cliwrap $0\n")?; + f.flush()?; + Ok(()) + }) +} + +mod ffi { + use super::*; + use crate::ffiutil::*; + use glib; + use libc; + use failure::ResultExt; + + #[no_mangle] + pub extern "C" fn ror_cliwrap_write_wrappers(rootfs_dfd: libc::c_int, gerror: *mut *mut glib_sys::GError) -> libc::c_int { + let rootfs_dfd = ffi_view_openat_dir(rootfs_dfd); + int_glib_error(write_wrappers(&rootfs_dfd).with_context(|e| format!("During cli wrapper replacement: {}", e)), gerror) + } + + #[no_mangle] + pub extern "C" fn ror_cliwrap_entrypoint(argv: *mut *mut libc::c_char, + gerror: *mut *mut glib_sys::GError) -> libc::c_int { + let v: Vec = unsafe { glib::translate::FromGlibPtrContainer::from_glib_none(argv) }; + int_glib_error(cliwrap_main(&v), gerror) + } +} +pub use self::ffi::*; diff --git a/rust/src/cliwrap/cliutil.rs b/rust/src/cliwrap/cliutil.rs new file mode 100644 index 0000000000..790e08ebdd --- /dev/null +++ b/rust/src/cliwrap/cliutil.rs @@ -0,0 +1,104 @@ +use failure::Fallible; +use libc; +use std::{thread, time, path}; +use std::ffi::CString; +use nix::sys::statvfs; +use nix::unistd; + +use crate::cliwrap; + +/// Returns true if the current process is booted via ostree. +pub fn is_ostree_booted() -> bool { + path::Path::new("/run/ostree-booted").exists() +} + +/// Returns true if the current process is running as root. +pub fn is_unlocked() -> Fallible { + Ok(!statvfs::statvfs("/usr")?.flags().contains(statvfs::FsFlags::ST_RDONLY)) +} + +/// Returns true if the current process is running as root. +pub fn am_privileged() -> bool { + unsafe { libc::getuid() == 0 } +} + +/// Wrapper for execv which accepts strings +fn execvp_strs(argv0: &str, argv: &[&str]) -> Fallible<()> { + let argv0 = CString::new(argv0).unwrap(); + let argv : Vec = argv.iter().map(|&v| CString::new(v).unwrap()).collect(); + unistd::execvp(&argv0, &argv)?; + Ok(()) +} + +/// Return the absolute path to the underlying wrapped binary +fn get_real_bin(bin_name: &str) -> String { + format!("/{}/{}", cliwrap::WRAP_DESTDIR, bin_name) +} + +/// Wrapper for execv which accepts strings +pub fn exec_real_wrapped + std::fmt::Display>(bin_name: T, argv: &[T]) -> Fallible<()> { + let bin_name = bin_name.as_ref(); + let real_bin = get_real_bin(bin_name); + let argv : Vec<&str> = std::iter::once(bin_name).chain(argv.iter().map(|v| v.as_ref())).collect(); + execvp_strs(real_bin.as_str(), &argv) +} + +/// Run a subprocess synchronously as user `bin` (dropping all capabilities). +pub fn run_unprivileged>( + with_warning: bool, + target_bin: &str, + argv: &[T], +) -> Fallible<()> { + // `setpriv` is in util-linux; we could do this internally, but this is easier. + let setpriv_argv = &[ + "setpriv", + "--no-new-privs", + "--reuid=bin", + "--regid=bin", + "--init-groups", + "--bounding-set", + "-all", + "--", + ]; + + let argv: Vec<&str> = argv.into_iter().map(AsRef::as_ref).collect(); + let drop_privileges = am_privileged (); + let app_name = "rpm-ostree"; + if with_warning { + let delay_s = 5; + eprintln!( + "{name}: NOTE: This system is ostree based.", + name = app_name + ); + if drop_privileges { + eprintln!(r#"{name}: Dropping privileges as `{bin}` was executed with not "known safe" arguments."#, + name=app_name, bin = target_bin); + } else { + eprintln!( + r#"{name}: Wrapped binary "{bin}" was executed with not "known safe" arguments."#, + name = app_name, + bin = target_bin + ); + } + eprintln!( + r##"{name}: You may invoke the real `{bin}` binary in `/{wrap_destdir}/{bin}`. +{name}: Continuing execution in {delay} seconds. +"##, + name = app_name, + wrap_destdir = cliwrap::WRAP_DESTDIR, + bin = target_bin, + delay = delay_s, + ); + thread::sleep(time::Duration::from_secs(delay_s)); + } + + if drop_privileges { + let real_bin = get_real_bin(target_bin); + let real_argv : Vec<&str> = setpriv_argv.iter().map(|&v| v) + .chain(std::iter::once(real_bin.as_str())) + .chain(argv).collect(); + execvp_strs("setpriv", &real_argv) + } else { + exec_real_wrapped(target_bin, &argv) + } +} diff --git a/rust/src/cliwrap/dracut.rs b/rust/src/cliwrap/dracut.rs new file mode 100644 index 0000000000..ffc319b44e --- /dev/null +++ b/rust/src/cliwrap/dracut.rs @@ -0,0 +1,9 @@ +use failure::Fallible; + +/// Primary entrypoint to running our wrapped `dracut` handling. +pub(crate) fn main(_argv: &[&str]) -> Fallible<()> { + eprintln!("This system is rpm-ostree based; initramfs handling is +integrated with the underlying ostree transaction mechanism. +Use `rpm-ostree initramfs` to control client-side initramfs generation."); + std::process::exit(1); +} diff --git a/rust/src/cliwrap/grubby.rs b/rust/src/cliwrap/grubby.rs new file mode 100644 index 0000000000..5384d8f9e1 --- /dev/null +++ b/rust/src/cliwrap/grubby.rs @@ -0,0 +1,8 @@ +use failure::Fallible; + +/// Primary entrypoint to running our wrapped `grubby` handling. +pub(crate) fn main(_argv: &[&str]) -> Fallible<()> { + eprintln!("This system is rpm-ostree based; grubby is not used. +Use `rpm-ostree kargs` instead."); + std::process::exit(1); +} diff --git a/rust/src/cliwrap/rpm.rs b/rust/src/cliwrap/rpm.rs new file mode 100644 index 0000000000..6a85007164 --- /dev/null +++ b/rust/src/cliwrap/rpm.rs @@ -0,0 +1,64 @@ +use clap::{App, Arg}; +use failure::Fallible; + +use crate::cliwrap::cliutil; + +/// Set of RPM arguments that we know are safe to pass through, +/// or that we handle internally. +fn new_rpm_app<'r>() -> App<'r, 'static> { + let name = "cli-ostree-wrapper-rpm"; + App::new(name) + .bin_name(name) + .version("0.1") + .about("Wrapper for rpm") + .arg(Arg::with_name("all").short("a")) + .arg(Arg::with_name("file").short("f")) + .arg(Arg::with_name("package").short("p")) + .arg(Arg::with_name("query").short("q")) + .arg(Arg::with_name("verify").short("V")) + .arg(Arg::with_name("version")) +} + +/// Primary entrypoint to running our wrapped `rpm` handling. +pub(crate) fn main(argv: &[&str]) -> Fallible<()> { + if cliutil::is_unlocked()? { + // For now if we're unlocked, just directly exec rpm. In the future we + // may choose to take over installing a package live. + cliutil::exec_real_wrapped("rpm", argv) + } else { + let mut app = new_rpm_app(); + let mut with_warning = true; + if let Ok(matches) = app.get_matches_from_safe_borrow(argv) { + // Implement custom option handling here + if matches.is_present("verify") { + println!("rpm --verify is not necessary for ostree-based systems. +All binaries in /usr are underneath a read-only bind mount. +If you wish to verify integrity, use `ostree fsck`."); + return Ok(()) + } + with_warning = false; + } + cliutil::run_unprivileged(with_warning, "rpm", argv) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_qa() { + let app = new_rpm_app(); + let argv = vec!["rpm", "-qa"]; + let matches = app.get_matches_from_safe(&argv); + assert!(matches.is_ok()); + } + + #[test] + fn test_unknown() { + let app = new_rpm_app(); + let argv = vec!["rpm", "--not-a-valid-arg"]; + let matches = app.get_matches_from_safe(&argv); + assert!(matches.is_err()); + } + +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 758bd29f0d..a1bec204a7 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -20,6 +20,8 @@ mod ffiutil; mod openat_utils; +mod cliwrap; +pub use cliwrap::*; mod composepost; pub use self::composepost::*; mod journal; diff --git a/rust/src/openat_utils.rs b/rust/src/openat_utils.rs index 4680b8eec6..d8f6fbdcf5 100644 --- a/rust/src/openat_utils.rs +++ b/rust/src/openat_utils.rs @@ -30,6 +30,9 @@ pub(crate) trait OpenatDirExt { // On modern filesystems the directory entry contains the type; if available, // return it. Otherwise invoke stat(). fn get_file_type(&self, e: &openat::Entry) -> io::Result; + + // Returns true iff file exists. + fn exists(&self, p: P) -> io::Result; } impl OpenatDirExt for openat::Dir { @@ -53,4 +56,17 @@ impl OpenatDirExt for openat::Dir { Ok(self.metadata(e.file_name())?.simple_type()) } } + + fn exists(&self, p: P) -> io::Result { + match self.metadata(p) { + Ok(_) => Ok(true), + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + Ok(false) + } else { + Err(e) + } + } + } + } } diff --git a/rust/src/treefile.rs b/rust/src/treefile.rs index 926d4c4670..39c06d4e0e 100644 --- a/rust/src/treefile.rs +++ b/rust/src/treefile.rs @@ -607,6 +607,9 @@ struct TreeComposeConfig { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "initramfs-args")] initramfs_args: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + // Defaults to `true` + cliwrap: Option, // Tree layout options #[serde(skip_serializing_if = "Option::is_none")] @@ -1097,6 +1100,13 @@ mod ffi { .unwrap_or(ptr::null_mut()) } + #[no_mangle] + pub extern "C" fn ror_treefile_get_cliwrap(tf: *mut Treefile) -> bool { + assert!(!tf.is_null()); + let tf = unsafe { &mut *tf }; + tf.parsed.cliwrap.unwrap_or(true) + } + #[no_mangle] pub extern "C" fn ror_treefile_free(tf: *mut Treefile) { if tf.is_null() { diff --git a/src/app/main.c b/src/app/main.c index 5cbcd6ddd2..75e8199bb2 100644 --- a/src/app/main.c +++ b/src/app/main.c @@ -118,6 +118,8 @@ static RpmOstreeCommand commands[] = { RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT | RPM_OSTREE_BUILTIN_FLAG_HIDDEN, NULL, rpmostree_builtin_start_daemon }, + { "cliwrap", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | RPM_OSTREE_BUILTIN_FLAG_HIDDEN, + NULL, rpmostree_builtin_cliwrap }, { NULL } }; diff --git a/src/app/rpmostree-builtin-cliwrap.c b/src/app/rpmostree-builtin-cliwrap.c new file mode 100644 index 0000000000..cbe835b57c --- /dev/null +++ b/src/app/rpmostree-builtin-cliwrap.c @@ -0,0 +1,50 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2019 Red Hat, Inc. + * + * This program 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 licence 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, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "rpmostree-builtins.h" +#include "rpmostree-libbuiltin.h" +#include "rpmostree-rust.h" + +#include + +gboolean +rpmostree_builtin_cliwrap (int argc, + char **argv, + RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GOptionContext) context = g_option_context_new (""); + + if (argc < 2) + return glnx_throw (error, "cliwrap: missing required subcommand"); + + g_autoptr(GPtrArray) args = g_ptr_array_new (); + for (int i = 1; i < argc; i++) + g_ptr_array_add (args, argv[i]); + g_ptr_array_add (args, NULL); + return ror_cliwrap_entrypoint ((char**)args->pdata, error); +} diff --git a/src/app/rpmostree-builtins.h b/src/app/rpmostree-builtins.h index 7122e0bf5c..dcbee961c9 100644 --- a/src/app/rpmostree-builtins.h +++ b/src/app/rpmostree-builtins.h @@ -31,6 +31,7 @@ G_BEGIN_DECLS GCancellable *cancellable, GError **error) BUILTINPROTO(compose); +BUILTINPROTO(cliwrap); BUILTINPROTO(upgrade); BUILTINPROTO(reload); BUILTINPROTO(usroverlay); diff --git a/src/libpriv/rpmostree-core.c b/src/libpriv/rpmostree-core.c index 508aa3d7b1..3dd23a048b 100644 --- a/src/libpriv/rpmostree-core.c +++ b/src/libpriv/rpmostree-core.c @@ -4200,6 +4200,12 @@ rpmostree_context_assemble (RpmOstreeContext *self, return FALSE; } + if (ror_treefile_get_cliwrap (self->treefile_rs)) + { + if (!ror_cliwrap_write_wrappers (tmprootfs_dfd, error)) + return FALSE; + } + /* Undo the /etc move above */ if (renamed_etc) {