diff --git a/Makefile-libpriv.am b/Makefile-libpriv.am index 6d2b5526..3ed9bace 100644 --- a/Makefile-libpriv.am +++ b/Makefile-libpriv.am @@ -28,8 +28,6 @@ librpmostreepriv_sources = \ src/libpriv/rpmostree-core.cxx \ src/libpriv/rpmostree-core.h \ src/libpriv/rpmostree-core-private.h \ - src/libpriv/rpmostree-bwrap.cxx \ - src/libpriv/rpmostree-bwrap.h \ src/libpriv/rpmostree-kernel.cxx \ src/libpriv/rpmostree-kernel.h \ src/libpriv/rpmostree-origin.cxx \ diff --git a/rust/src/bwrap.rs b/rust/src/bwrap.rs index 828987cd..f8261daf 100644 --- a/rust/src/bwrap.rs +++ b/rust/src/bwrap.rs @@ -2,22 +2,269 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::cxxrsutil::CxxResult; -use anyhow::{anyhow, Context, Result}; +use crate::cxxrsutil::*; +use crate::ffi::BubblewrapMutability; +use anyhow::{Context, Result}; use fn_error_context::context; +use openat_ext::OpenatDirExt; +use std::convert::TryInto; +use std::num::NonZeroUsize; use std::os::unix::io::AsRawFd; -use std::rc::Rc; +use std::path::Path; +use std::pin::Pin; +use std::process::Command; + +// Links in the rootfs to /usr +static USR_LINKS: &[&str] = &["lib", "lib32", "lib64", "bin", "sbin"]; +// This is similar to what systemd does, except we drop /usr/local, since scripts shouldn't see it. +static PATH_VAR: &str = "PATH=/usr/sbin:/usr/bin"; pub(crate) struct Bubblewrap { - pub(crate) rootfs_fd: Rc, + pub(crate) rootfs_fd: openat::Dir, + + executed: bool, + argv: Vec, + child_argv0: Option, + launcher: gio::SubprocessLauncher, // 🚀 + + tempdirs: Vec, +} + +impl Drop for Bubblewrap { + fn drop(&mut self) { + for d in self.tempdirs.drain(..) { + // We need to unmount the tempdirs, before letting + // the drop handler recursively remove them. + let success = Command::new("fusermount") + .arg("-u") + .arg(d.path()) + .status() + .map_err(anyhow::Error::new) + .and_then(|status| -> Result<()> { + if !status.success() { + Err(anyhow::anyhow!("{}", status)) + } else { + Ok(()) + } + }) + .err() + .map(|e| { + systemd::journal::print(4, &format!("{}", e)); + }) + .is_none(); + if !success { + // If fusermount fails, then we cannot remove it; just leak it. + let _ = d.into_path(); + } + } + } +} + +// nspawn by default doesn't give us CAP_NET_ADMIN; see +// https://pagure.io/releng/issue/6602#comment-71214 +// https://pagure.io/koji/pull-request/344#comment-21060 +// Theoretically we should do capable(CAP_NET_ADMIN) +// but that's a lot of ugly code, and the only known +// place we hit this right now is nspawn. Plus +// we want to use userns down the line anyways where +// we'll regain CAP_NET_ADMIN. +fn running_in_nspawn() -> bool { + std::env::var_os("container").as_deref() == Some(std::ffi::OsStr::new("systemd-nspawn")) +} + +fn setup_rofiles_in(rootfs: &openat::Dir, path: &str) -> Result { + let path = path.trim_start_matches('/'); + let tempdir = tempfile::Builder::new() + .prefix("rpmostree-rofiles-fuse") + .tempdir()?; + let status = std::process::Command::new("rofiles-fuse") + .arg("--copyup") + .arg(path) + .arg(tempdir.path()) + .current_dir(format!("/proc/self/fd/{}", rootfs.as_raw_fd())) + .status()?; + if !status.success() { + return Err(anyhow::anyhow!("{}", status)); + } + Ok(tempdir) +} + +fn child_wait_check( + child: gio::Subprocess, + cancellable: Option<&gio::Cancellable>, +) -> std::result::Result<(), glib::Error> { + match child.wait(cancellable) { + Ok(_) => { + let estatus = child.get_exit_status(); + glib::spawn_check_exit_status(estatus)?; + Ok(()) + } + Err(e) => { + child.force_exit(); + Err(e) + } + } } impl Bubblewrap { /// Create a new Bubblewrap instance - pub(crate) fn new(rootfs_fd: &Rc) -> Self { - Self { - rootfs_fd: Rc::clone(rootfs_fd), + pub(crate) fn new(rootfs_fd: &openat::Dir) -> Result { + let rootfs_fd = rootfs_fd.sub_dir(".")?; + + let lang = std::env::var_os("LANG"); + let lang = lang.as_ref().map(|s| s.to_str()).flatten().unwrap_or("C"); + let lang_var = format!("LANG={}", lang); + let lang_var = Path::new(&lang_var); + + let launcher = gio::SubprocessLauncher::new(gio::SubprocessFlags::NONE); + let child_rootfs_fd = rootfs_fd.as_raw_fd(); + launcher.set_child_setup(move || { + nix::unistd::fchdir(child_rootfs_fd).expect("fchdir"); + }); + + let path_var = Path::new(PATH_VAR); + launcher.set_environ(&[lang_var, path_var]); + + // ⚠⚠⚠ If you change this, also update scripts/bwrap-script-shell.sh ⚠⚠⚠ + let mut argv = vec![ + "bwrap", + "--dev", + "/dev", + "--proc", + "/proc", + "--dir", + "/run", + "--dir", + "/tmp", + "--chdir", + "/", + "--ro-bind", + "/sys/block", + "/sys/block", + "--ro-bind", + "/sys/bus", + "/sys/bus", + "--ro-bind", + "/sys/class", + "/sys/class", + "--ro-bind", + "/sys/dev", + "/sys/dev", + "--ro-bind", + "/sys/devices", + "/sys/devices", + "--die-with-parent", /* Since 0.1.8 */ + /* Here we do all namespaces except the user one. + * Down the line we want to do a userns too I think, + * but it may need some mapping work. + */ + "--unshare-pid", + "--unshare-uts", + "--unshare-ipc", + "--unshare-cgroup-try", + ]; + + if !running_in_nspawn() { + argv.push("--unshare-net"); } + + /* Capabilities; this is a subset of the Docker (1.13 at least) default. + * Specifically we strip out in addition to that: + * + * "cap_net_raw" (no use for this in %post, and major source of security vulnerabilities) + * "cap_mknod" (%post should not be making devices, it wouldn't be persistent anyways) + * "cap_audit_write" (we shouldn't be auditing anything from here) + * "cap_net_bind_service" (nothing should be doing IP networking at all) + * + * But crucially we're dropping a lot of other capabilities like + * "cap_sys_admin", "cap_sys_module", etc that Docker also drops by default. + * We don't want RPM scripts to be doing any of that. Instead, do it from + * systemd unit files. + * + * Also this way we drop out any new capabilities that appear. + */ + if nix::unistd::getuid() == nix::unistd::Uid::from_raw(0) { + argv.extend(&[ + "--cap-drop", + "ALL", + "--cap-add", + "cap_chown", + "--cap-add", + "cap_dac_override", + "--cap-add", + "cap_fowner", + "--cap-add", + "cap_fsetid", + "--cap-add", + "cap_kill", + "--cap-add", + "cap_setgid", + "--cap-add", + "cap_setuid", + "--cap-add", + "cap_setpcap", + "--cap-add", + "cap_sys_chroot", + "--cap-add", + "cap_setfcap", + ]); + } + + let mut argv: Vec<_> = argv.into_iter().map(|s| s.to_string()).collect(); + + for &name in USR_LINKS.iter() { + if let Some(stbuf) = rootfs_fd.metadata_optional(name)? { + if !matches!(stbuf.simple_type(), openat::SimpleType::Symlink) { + continue; + } + + argv.push("--symlink".to_string()); + argv.push(format!("usr/{}", name)); + argv.push(format!("/{}", name)); + } + } + + Ok(Self { + rootfs_fd, + executed: false, + argv, + launcher, + child_argv0: None, + tempdirs: Vec::new(), + }) + } + + fn setup_rofiles(&mut self, path: &str) -> Result<()> { + let tmpdir = setup_rofiles_in(&self.rootfs_fd, path)?; + let tmpdir_path = tmpdir.path().to_str().expect("tempdir str"); + self.bind_readwrite(tmpdir_path, path); + self.tempdirs.push(tmpdir); + Ok(()) + } + + pub(crate) fn new_with_mutability( + rootfs_fd: &openat::Dir, + mutability: BubblewrapMutability, + ) -> Result { + let mut ret = Self::new(rootfs_fd)?; + match mutability { + BubblewrapMutability::Immutable => { + ret.bind_read("usr", "/usr"); + ret.bind_read("etc", "/etc"); + } + BubblewrapMutability::RoFiles => { + ret.setup_rofiles("/usr")?; + ret.setup_rofiles("/etc")?; + } + BubblewrapMutability::MutateFreely => { + ret.bind_readwrite("usr", "/usr"); + ret.bind_readwrite("etc", "/etc"); + } + o => { + panic!("Invalid BubblewrapMutability: {:?}", o); + } + } + Ok(ret) } fn setup_rofiles(&mut self, path: &str) -> Result<()> { @@ -32,11 +279,181 @@ impl Bubblewrap { pub(crate) fn get_rootfs_fd(&self) -> i32 { self.rootfs_fd.as_raw_fd() } + + pub(crate) fn append_bwrap_arg(&mut self, arg: &str) { + self.append_bwrap_argv(&[arg]) + } + + pub(crate) fn append_bwrap_argv(&mut self, args: &[&str]) { + self.argv.extend(args.iter().map(|s| s.to_string())); + } + + pub(crate) fn append_child_arg(&mut self, arg: &str) { + self.append_child_argv(&[arg]) + } + + pub(crate) fn append_child_argv(&mut self, args: &[&str]) { + // Record the binary name for later error messages + if self.child_argv0.is_none() { + self.child_argv0 = Some(self.argv.len().try_into().expect("args")); + } + self.argv.extend(args.iter().map(|s| s.to_string())); + } + + /// Set an environment variable + pub(crate) fn setenv(&mut self, k: &str, v: &str) { + self.launcher.setenv(k, v, true); + } + + /// Take a file descriptor + pub(crate) fn take_fd(&mut self, source_fd: i32, target_fd: i32) { + self.launcher.take_fd(source_fd, target_fd); + } + + /// Inherit stdin + pub(crate) fn set_inherit_stdin(&mut self) { + self.launcher.set_flags(gio::SubprocessFlags::STDIN_INHERIT); + } + + /// Take a file descriptor for stdin + pub(crate) fn take_stdin_fd(&mut self, source_fd: i32) { + self.launcher.take_stdin_fd(source_fd); + } + + /// Take a file descriptor for stdout + pub(crate) fn take_stdout_fd(&mut self, source_fd: i32) { + self.launcher.take_stdout_fd(source_fd); + } + + /// Take a file descriptor for stderr + pub(crate) fn take_stderr_fd(&mut self, source_fd: i32) { + self.launcher.take_stderr_fd(source_fd); + } + + /// Take a file descriptor for stderr + pub(crate) fn take_stdout_and_stderr_fd(&mut self, source_fd: i32) { + self.launcher.take_stdout_fd(source_fd); + self.launcher.set_flags(gio::SubprocessFlags::STDERR_MERGE); + } + + /// Bind source to destination in the container (readonly) + pub(crate) fn bind_read(&mut self, src: &str, dest: &str) { + self.append_bwrap_argv(&["--ro-bind", src, dest]); + } + + /// Bind source to destination in the container (read-write) + pub(crate) fn bind_readwrite(&mut self, src: &str, dest: &str) { + self.append_bwrap_argv(&["--bind", src, dest]); + } + + // Set /var to be read-only, but with a transient writable /var/tmp + pub(crate) fn var_tmp_tmpfs(&mut self) { + self.bind_read("./var", "/var"); + self.append_bwrap_argv(&["--tmpfs", "/var/tmp"]); + } + + fn spawn(&mut self) -> Result<(gio::Subprocess, String)> { + assert!(!self.executed); + self.executed = true; + + let child_argv0_i: usize = self.child_argv0.expect("child argument").into(); + let child_argv0 = format!("bwrap({})", self.argv[child_argv0_i].as_str()); + let argv: Vec<_> = self.argv.iter().map(|s| s.as_ref()).collect(); + let child = self.launcher.spawnv(&argv)?; + Ok((child, child_argv0)) + } + + /// Execute the container, capturing stdout. + fn run_captured(&mut self, cancellable: Option<&gio::Cancellable>) -> Result { + self.launcher.set_flags(gio::SubprocessFlags::STDOUT_PIPE); + let (child, argv0) = self.spawn()?; + let (stdout, stderr) = child.communicate(None, cancellable)?; + // we never pipe just stderr, so we don't expect it to be captured + assert!(stderr.is_none()); + let stdout = stdout.expect("stdout"); + + child_wait_check(child, cancellable).context(argv0)?; + + Ok(stdout) + } + + /// Execute the container. This method uses the normal gtk-rs `Option` for the cancellable. + fn run_inner(&mut self, cancellable: Option<&gio::Cancellable>) -> Result<()> { + let (child, argv0) = self.spawn()?; + child_wait_check(child, cancellable).context(argv0)?; + Ok(()) + } + + /// Execute the instance; requires a cancellable for C++. + pub(crate) fn run( + &mut self, + mut cancellable: Pin<&mut crate::FFIGCancellable>, + ) -> CxxResult<()> { + let cancellable = &cancellable.gobj_wrap(); + self.run_inner(Some(cancellable))?; + Ok(()) + } } #[context("Creating bwrap instance")] /// Create a new Bubblewrap instance pub(crate) fn bubblewrap_new(rootfs_fd: i32) -> CxxResult> { - let fd = Rc::new(openat::Dir::open(format!("/proc/self/fd/{}", rootfs_fd))?); - Ok(Box::new(Bubblewrap::new(&fd))) + let rootfs_fd = crate::ffiutil::ffi_view_openat_dir(rootfs_fd); + Ok(Box::new(Bubblewrap::new(&rootfs_fd)?)) +} + +pub(crate) fn bubblewrap_new_with_mutability( + rootfs_fd: i32, + mutability: crate::ffi::BubblewrapMutability, +) -> Result> { + let rootfs_fd = crate::ffiutil::ffi_view_openat_dir(rootfs_fd); + Ok(Box::new(Bubblewrap::new_with_mutability( + &rootfs_fd, mutability, + )?)) +} + +/// Synchronously run bubblewrap, allowing mutability, and optionally capturing stdout. +pub(crate) fn bubblewrap_run_sync( + rootfs_dfd: i32, + args: &Vec, + capture_stdout: bool, + unified_core: bool, +) -> CxxResult> { + let rootfs_dfd = &crate::ffiutil::ffi_view_openat_dir(rootfs_dfd); + let tempetc = crate::core::prepare_tempetc_guard(rootfs_dfd.as_raw_fd())?; + let mutability = if unified_core { + BubblewrapMutability::RoFiles + } else { + BubblewrapMutability::MutateFreely + }; + let mut bwrap = Bubblewrap::new_with_mutability(rootfs_dfd, mutability)?; + + if unified_core { + bwrap.bind_read("var", "/var"); + } else { + bwrap.bind_readwrite("var", "/var") + } + + let args: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + bwrap.append_child_argv(&args); + + let cancellable = &gio::Cancellable::new(); + let cancellable = Some(cancellable); + if capture_stdout { + let buf = bwrap.run_captured(cancellable)?; + tempetc.undo()?; + Ok(buf.as_ref().iter().map(|&x| x).collect()) + } else { + bwrap.run_inner(cancellable)?; + tempetc.undo()?; + Ok(Vec::new()) + } +} + +#[context("bwrap test failed, see ")] +/// Create a new Bubblewrap instance +pub(crate) fn bubblewrap_selftest() -> CxxResult<()> { + let fd = openat::Dir::open("/")?; + let _ = bubblewrap_run_sync(fd.as_raw_fd(), &vec!["true".to_string()], false, true)?; + Ok(()) } diff --git a/rust/src/composepost.rs b/rust/src/composepost.rs index f36a454c..68f1f032 100644 --- a/rust/src/composepost.rs +++ b/rust/src/composepost.rs @@ -9,6 +9,7 @@ //! a filesystem tree (usually containing mostly RPMs) in //! order to prepare it as an OSTree commit. +use crate::bwrap; use crate::cxxrsutil::CxxResult; use anyhow::{anyhow, Context, Result}; use fn_error_context::context; @@ -194,9 +195,9 @@ fn compose_postprocess_scripts( rootfs_dfd.write_file_contents(target_binpath, 0o755, script)?; println!("Executing `postprocess` inline script '{}'", i); - let child_argv = vec![binpath.clone()]; - crate::ffi::bwrap_run_mutable(rootfs_dfd.as_raw_fd(), &binpath, &child_argv, unified_core)?; - + let child_argv = vec![binpath.to_string()]; + let _ = + bwrap::bubblewrap_run_sync(rootfs_dfd.as_raw_fd(), &child_argv, false, unified_core)?; rootfs_dfd.remove_file(target_binpath)?; } @@ -209,9 +210,14 @@ fn compose_postprocess_scripts( rootfs_dfd.write_file_with(target_binpath, 0o755, |w| std::io::copy(&mut reader, w))?; println!("Executing postprocessing script"); - let child_argv = vec![binpath.to_string()]; - crate::ffi::bwrap_run_mutable(rootfs_dfd.as_raw_fd(), binpath, &child_argv, unified_core) - .context("Executing postprocessing script")?; + let child_argv = &vec![binpath.to_string()]; + let _ = crate::bwrap::bubblewrap_run_sync( + rootfs_dfd.as_raw_fd(), + child_argv, + false, + unified_core, + ) + .context("Executing postprocessing script")?; rootfs_dfd.remove_file(target_binpath)?; println!("Finished postprocessing script"); @@ -332,11 +338,8 @@ pub(crate) fn compose_postprocess_mutate_os_release( // find the real path to os-release using bwrap; this is an overkill but safer way // of resolving a symlink relative to a rootfs (see discussions in // https://github.com/projectatomic/rpm-ostree/pull/410/) - let argv: Vec = ["realpath", "/etc/os-release"] - .iter() - .map(|s| s.to_string()) - .collect(); - let path = crate::ffi::bwrap_run_captured(rootfs_dfd.as_raw_fd(), &argv) + let argv = &vec!["realpath".to_string(), "/etc/os-release".to_string()]; + let path = crate::bwrap::bubblewrap_run_sync(rootfs_dfd.as_raw_fd(), argv, true, true) .context("Running realpath")?; let path = String::from_utf8(path).context("Parsing realpath")?; let path = path.trim_start_matches("/").trim_end(); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 88012fb7..c336fa07 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -63,14 +63,48 @@ pub mod ffi { fn client_handle_fd_argument(arg: &str, arch: &str) -> Result>; } + #[derive(Debug)] + enum BubblewrapMutability { + Immutable, + RoFiles, + MutateFreely, + } + // bubblewrap.rs extern "Rust" { type Bubblewrap; - fn bubblewrap_new(rootfs_fd: i32) -> Result>; - fn get_rootfs_fd(&self) -> i32; - } + fn bubblewrap_selftest() -> Result<()>; + fn bubblewrap_run_sync( + rootfs_dfd: i32, + args: &Vec, + capture_stdout: bool, + unified_core: bool, + ) -> Result>; + fn bubblewrap_new(rootfs_fd: i32) -> Result>; + fn bubblewrap_new_with_mutability( + rootfs_fd: i32, + mutability: BubblewrapMutability, + ) -> Result>; + fn get_rootfs_fd(&self) -> i32; + + fn append_bwrap_arg(&mut self, arg: &str); + fn append_child_arg(&mut self, arg: &str); + fn setenv(&mut self, k: &str, v: &str); + fn take_fd(&mut self, source_fd: i32, target_fd: i32); + fn set_inherit_stdin(&mut self); + fn take_stdin_fd(&mut self, source_fd: i32); + fn take_stdout_fd(&mut self, source_fd: i32); + fn take_stderr_fd(&mut self, source_fd: i32); + fn take_stdout_and_stderr_fd(&mut self, source_fd: i32); + + fn bind_read(&mut self, src: &str, dest: &str); + fn bind_readwrite(&mut self, src: &str, dest: &str); + fn var_tmp_tmpfs(&mut self); + + fn run(&mut self, cancellable: Pin<&mut GCancellable>) -> Result<()>; + } // builtins/apply_live.rs extern "Rust" { @@ -372,17 +406,6 @@ pub mod ffi { fn main_print_error(msg: &str); } - unsafe extern "C++" { - include!("rpmostree-bwrap.h"); - fn bwrap_run_mutable( - rootfs_dfd: i32, - binpath: &str, - child_argv: &Vec, - unified_core_mode: bool, - ) -> Result<()>; - fn bwrap_run_captured(rootfs_dfd: i32, child_argv: &Vec) -> Result>; - } - unsafe extern "C++" { include!("rpmostree-clientlib.h"); fn client_require_root() -> Result<()>; diff --git a/src/app/rpmostree-compose-builtin-rojig.cxx b/src/app/rpmostree-compose-builtin-rojig.cxx index 0cce05c7..9e7fbccc 100644 --- a/src/app/rpmostree-compose-builtin-rojig.cxx +++ b/src/app/rpmostree-compose-builtin-rojig.cxx @@ -38,7 +38,6 @@ #include "rpmostree-compose-builtins.h" #include "rpmostree-util.h" -#include "rpmostree-bwrap.h" #include "rpmostree-core.h" #include "rpmostree-json-parsing.h" #include "rpmostree-rojig-build.h" @@ -236,8 +235,7 @@ rpm_ostree_rojig_compose_new (const char *treefile_path, /* Test whether or not bwrap is going to work - we will fail inside e.g. a Docker * container without --privileged or userns exposed. */ - if (!rpmostree_bwrap_selftest (error)) - return FALSE; + rpmostreecxx::bubblewrap_selftest(); if (opt_cachedir) { diff --git a/src/app/rpmostree-compose-builtin-tree.cxx b/src/app/rpmostree-compose-builtin-tree.cxx index a3f53a9f..ae5f6ba6 100644 --- a/src/app/rpmostree-compose-builtin-tree.cxx +++ b/src/app/rpmostree-compose-builtin-tree.cxx @@ -40,7 +40,6 @@ #include "rpmostree-compose-builtins.h" #include "rpmostree-util.h" #include "rpmostree-composeutil.h" -#include "rpmostree-bwrap.h" #include "rpmostree-core.h" #include "rpmostree-json-parsing.h" #include "rpmostree-rojig-build.h" @@ -583,8 +582,8 @@ rpm_ostree_compose_context_new (const char *treefile_pathstr, /* Test whether or not bwrap is going to work - we will fail inside e.g. a Docker * container without --privileged or userns exposed. */ - if (!(opt_download_only || opt_download_only_rpms) && !rpmostree_bwrap_selftest (error)) - return FALSE; + if (!(opt_download_only || opt_download_only_rpms)) + rpmostreecxx::bubblewrap_selftest(); self->repo = ostree_repo_open_at (AT_FDCWD, opt_repo, cancellable, error); if (!self->repo) diff --git a/src/app/rpmostree-composeutil.cxx b/src/app/rpmostree-composeutil.cxx index 670c923b..b80a0709 100644 --- a/src/app/rpmostree-composeutil.cxx +++ b/src/app/rpmostree-composeutil.cxx @@ -37,7 +37,6 @@ #include "rpmostree-composeutil.h" #include "rpmostree-util.h" -#include "rpmostree-bwrap.h" #include "rpmostree-core.h" #include "rpmostree-json-parsing.h" #include "rpmostree-rojig-build.h" diff --git a/src/libpriv/rpmostree-bwrap.cxx b/src/libpriv/rpmostree-bwrap.cxx deleted file mode 100644 index 2a0e55b2..00000000 --- a/src/libpriv/rpmostree-bwrap.cxx +++ /dev/null @@ -1,660 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2016 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 "rpmostree-bwrap.h" - -#include -#include -#include -#include "rpmostree-util.h" -#include "rpmostree-cxxrs.h" -#include - -static void -teardown_rofiles (GLnxTmpDir *mnt_tmp); - -void -rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...) -{ - va_list args; - char *arg; - - va_start (args, argv_array); - while ((arg = va_arg (args, char *))) - g_ptr_array_add (argv_array, g_strdup (arg)); - va_end (args); -} - -struct RpmOstreeBwrap { - guint refcount; - - gboolean executed; - - rust::Box bwrap_rs; - GSubprocessLauncher *launcher; /* 🚀 */ - GPtrArray *argv; - const char *child_argv0; - GLnxTmpDir rofiles_mnt_usr; - GLnxTmpDir rofiles_mnt_etc; - - GSpawnChildSetupFunc child_setup_func; - gpointer child_setup_data; -}; - -RpmOstreeBwrap * -rpmostree_bwrap_ref (RpmOstreeBwrap *bwrap) -{ - bwrap->refcount++; - return bwrap; -} - -void -rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap) -{ - bwrap->refcount--; - if (bwrap->refcount > 0) - return; - - teardown_rofiles (&bwrap->rofiles_mnt_usr); - teardown_rofiles (&bwrap->rofiles_mnt_etc); - - bwrap->bwrap_rs.~Box(); - g_clear_object (&bwrap->launcher); - g_ptr_array_unref (bwrap->argv); - g_free (bwrap); -} - -/* Configure the process to inherit stdin */ -void -rpmostree_bwrap_set_inherit_stdin (RpmOstreeBwrap *bwrap) -{ - g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDIN_INHERIT); -} - -void -rpmostree_bwrap_append_bwrap_argv (RpmOstreeBwrap *bwrap, ...) -{ - va_list args; - char *arg; - - g_assert (!bwrap->executed); - - va_start (args, bwrap); - while ((arg = va_arg (args, char *))) - g_ptr_array_add (bwrap->argv, g_strdup (arg)); - va_end (args); -} - -/* Set /var to be read-only, but with a transient writable /var/tmp */ -void -rpmostree_bwrap_var_tmp_tmpfs (RpmOstreeBwrap *bwrap) -{ - rpmostree_bwrap_bind_read (bwrap, "./var", "/var"); - rpmostree_bwrap_append_bwrap_argv (bwrap, "--tmpfs", "/var/tmp", NULL); -} - -void -rpmostree_bwrap_bind_read (RpmOstreeBwrap *bwrap, const char *src, const char *dest) -{ - rpmostree_bwrap_append_bwrap_argv (bwrap, "--ro-bind", src, dest, NULL); -} - -void -rpmostree_bwrap_bind_readwrite (RpmOstreeBwrap *bwrap, const char *src, const char *dest) -{ - rpmostree_bwrap_append_bwrap_argv (bwrap, "--bind", src, dest, NULL); -} - -void -rpmostree_bwrap_append_child_argv (RpmOstreeBwrap *bwrap, ...) -{ - va_list args; - char *arg; - - g_assert (!bwrap->executed); - - va_start (args, bwrap); - while ((arg = va_arg (args, char *))) - { - char *v = g_strdup (arg); - g_ptr_array_add (bwrap->argv, v); - /* Stash argv0 for error messages */ - if (!bwrap->child_argv0) - bwrap->child_argv0 = v; - } - va_end (args); -} - -void -rpmostree_bwrap_append_child_argva (RpmOstreeBwrap *bwrap, int argc, char **argv) -{ - g_assert (!bwrap->executed); - g_assert_cmpint (argc, >=, 0); - while (argc > 0) - { - const char *arg = *argv; - g_assert (arg); - g_ptr_array_add (bwrap->argv, g_strdup (arg)); - argc--; - argv++; - } -} - -static void -child_setup_fchdir (gpointer user_data) -{ - int fd = GPOINTER_TO_INT (user_data); - if (fchdir (fd) < 0) - err (1, "fchdir"); -} - -static gboolean -setup_rofiles (RpmOstreeBwrap *bwrap, - const char *path, - GLnxTmpDir *mnt_tmp, - GError **error) -{ - GLNX_AUTO_PREFIX_ERROR ("rofiles setup", error); - const char *relpath = path + strspn (path, "/"); - const char *rofiles_argv[] = { "rofiles-fuse", "--copyup", relpath, NULL, NULL}; - - g_auto(GLnxTmpDir) local_mnt_tmp = { 0, }; - if (!glnx_mkdtemp ("rpmostree-rofiles-fuse.XXXXXX", 0700, &local_mnt_tmp, error)) - return FALSE; - - const char *rofiles_mntpath = local_mnt_tmp.path; - rofiles_argv[3] = rofiles_mntpath; - - int estatus; - if (!g_spawn_sync (NULL, (char**)rofiles_argv, NULL, G_SPAWN_SEARCH_PATH, - child_setup_fchdir, GINT_TO_POINTER (bwrap->bwrap_rs->get_rootfs_fd()), - NULL, NULL, &estatus, error)) - return FALSE; - if (!g_spawn_check_exit_status (estatus, error)) - return FALSE; - - rpmostree_bwrap_bind_readwrite (bwrap, rofiles_mntpath, path); - - /* And transfer ownership of the tmpdir */ - *mnt_tmp = local_mnt_tmp; - local_mnt_tmp.initialized = FALSE; - - return TRUE; -} - - -/* We don't want a failure to unmount to be fatal, so all we do here - * is log. Though in practice what we *really* want is for the - * fusermount to be in the bwrap namespace, and hence tied by the - * kernel to the lifecycle of the container. This would require - * special casing for somehow doing FUSE mounts in bwrap. Which - * would be hard because NO_NEW_PRIVS turns off the setuid bits for - * fuse. - * - * Or: just hard switch to overlayfs now that it will soon be - * available even unprivileged https://lwn.net/Articles/803203/ - */ -static void -teardown_rofiles (GLnxTmpDir *mnt_tmp) -{ - g_assert (mnt_tmp); - if (!mnt_tmp->initialized) - return; - - g_autoptr(GError) tmp_error = NULL; - const char *fusermount_argv[] = { "fusermount", "-u", mnt_tmp->path, NULL}; - int estatus; - - GLNX_AUTO_PREFIX_ERROR ("rofiles teardown", &tmp_error); - - if (!g_spawn_sync (NULL, (char**)fusermount_argv, NULL, G_SPAWN_SEARCH_PATH, - NULL, NULL, NULL, NULL, &estatus, &tmp_error)) - { - g_prefix_error (&tmp_error, "Executing fusermount: "); - rpmostree_journal_error (tmp_error); - return; - } - if (!g_spawn_check_exit_status (estatus, &tmp_error)) - { - g_prefix_error (&tmp_error, "Executing fusermount: "); - rpmostree_journal_error (tmp_error); - return; - } - - (void)glnx_tmpdir_delete (mnt_tmp, NULL, NULL); -} - -/* nspawn by default doesn't give us CAP_NET_ADMIN; see - * https://pagure.io/releng/issue/6602#comment-71214 - * https://pagure.io/koji/pull-request/344#comment-21060 - * - * Theoretically we should do capable(CAP_NET_ADMIN) - * but that's a lot of ugly code, and the only known - * place we hit this right now is nspawn. Plus - * we want to use userns down the line anyways where - * we'll regain CAP_NET_ADMIN. - */ -static gboolean -running_in_nspawn (void) -{ - return g_strcmp0 (getenv ("container"), "systemd-nspawn") == 0; -} - -RpmOstreeBwrap * -rpmostree_bwrap_new_base (int rootfs_fd, GError **error) -{ - g_autoptr(RpmOstreeBwrap) ret = g_new0 (RpmOstreeBwrap, 1); - static const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"}; - - ret->refcount = 1; - ret->bwrap_rs = rpmostreecxx::bubblewrap_new(rootfs_fd); - ret->argv = g_ptr_array_new_with_free_func (g_free); - ret->launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - - /* Initialize launcher now; it may also be modified by our API methods */ - const char *current_lang = getenv ("LANG"); - if (!current_lang) - current_lang = "C"; - - const char *lang_var = (const char*) glnx_strjoina ("LANG=", current_lang); - /* This is similar to what systemd does, except: - * - We drop /usr/local, since scripts shouldn't see it. - * - We pull in the current process' LANG, since that's what people - * have historically expected from RPM scripts. - */ - const char *bwrap_env[] = {"PATH=/usr/sbin:/usr/bin", lang_var, NULL}; - g_subprocess_launcher_set_environ (ret->launcher, (char**)bwrap_env); - - /* ⚠⚠⚠ If you change this, also update scripts/bwrap-script-shell.sh ⚠⚠⚠ */ - rpmostree_bwrap_append_bwrap_argv (ret, - "bwrap", - "--dev", "/dev", - "--proc", "/proc", - "--dir", "/run", - "--dir", "/tmp", - "--chdir", "/", - "--ro-bind", "/sys/block", "/sys/block", - "--ro-bind", "/sys/bus", "/sys/bus", - "--ro-bind", "/sys/class", "/sys/class", - "--ro-bind", "/sys/dev", "/sys/dev", - "--ro-bind", "/sys/devices", "/sys/devices", - "--die-with-parent", /* Since 0.1.8 */ - /* Here we do all namespaces except the user one. - * Down the line we want to do a userns too I think, - * but it may need some mapping work. - */ - "--unshare-pid", - "--unshare-uts", - "--unshare-ipc", - "--unshare-cgroup-try", - NULL); - - if (!running_in_nspawn ()) - rpmostree_bwrap_append_bwrap_argv (ret, "--unshare-net", NULL); - - /* Capabilities; this is a subset of the Docker (1.13 at least) default. - * Specifically we strip out in addition to that: - * - * "cap_net_raw" (no use for this in %post, and major source of security vulnerabilities) - * "cap_mknod" (%post should not be making devices, it wouldn't be persistent anyways) - * "cap_audit_write" (we shouldn't be auditing anything from here) - * "cap_net_bind_service" (nothing should be doing IP networking at all) - * - * But crucially we're dropping a lot of other capabilities like - * "cap_sys_admin", "cap_sys_module", etc that Docker also drops by default. - * We don't want RPM scripts to be doing any of that. Instead, do it from - * systemd unit files. - * - * Also this way we drop out any new capabilities that appear. - */ - if (getuid () == 0) - rpmostree_bwrap_append_bwrap_argv (ret, "--cap-drop", "ALL", - "--cap-add", "cap_chown", - "--cap-add", "cap_dac_override", - "--cap-add", "cap_fowner", - "--cap-add", "cap_fsetid", - "--cap-add", "cap_kill", - "--cap-add", "cap_setgid", - "--cap-add", "cap_setuid", - "--cap-add", "cap_setpcap", - "--cap-add", "cap_sys_chroot", - "--cap-add", "cap_setfcap", - NULL); - - for (guint i = 0; i < G_N_ELEMENTS (usr_links); i++) - { - const char *subdir = usr_links[i]; - struct stat stbuf; - g_autofree char *srcpath = NULL; - g_autofree char *destpath = NULL; - - if (!glnx_fstatat_allow_noent (rootfs_fd, subdir, &stbuf, AT_SYMLINK_NOFOLLOW, error)) - return FALSE; - if (errno == ENOENT || !S_ISLNK (stbuf.st_mode)) - continue; - - srcpath = g_strconcat ("usr/", subdir, NULL); - destpath = g_strconcat ("/", subdir, NULL); - rpmostree_bwrap_append_bwrap_argv (ret, "--symlink", srcpath, destpath, NULL); - } - - return util::move_nullify (ret); -} - -RpmOstreeBwrap * -rpmostree_bwrap_new (int rootfs_fd, - RpmOstreeBwrapMutability is_mutable, - GError **error) -{ - g_autoptr(RpmOstreeBwrap) ret = rpmostree_bwrap_new_base (rootfs_fd, error); - if (!ret) - return FALSE; - - switch (is_mutable) - { - case RPMOSTREE_BWRAP_IMMUTABLE: - rpmostree_bwrap_bind_read (ret, "usr", "/usr"); - rpmostree_bwrap_bind_read (ret, "etc", "/etc"); - break; - case RPMOSTREE_BWRAP_MUTATE_ROFILES: - if (!setup_rofiles (ret, "/usr", - &ret->rofiles_mnt_usr, error)) - return NULL; - if (!setup_rofiles (ret, "/etc", - &ret->rofiles_mnt_etc, error)) - return NULL; - break; - case RPMOSTREE_BWRAP_MUTATE_FREELY: - rpmostree_bwrap_bind_readwrite (ret, "usr", "/usr"); - rpmostree_bwrap_bind_readwrite (ret, "etc", "/etc"); - break; - } - - return util::move_nullify (ret); -} - -static void -bwrap_child_setup (gpointer data) -{ - RpmOstreeBwrap *bwrap = (RpmOstreeBwrap*)data; - - if (fchdir (bwrap->bwrap_rs->get_rootfs_fd()) < 0) - err (1, "fchdir"); - - if (bwrap->child_setup_func) - bwrap->child_setup_func (bwrap->child_setup_data); -} - -void -rpmostree_bwrap_set_child_setup (RpmOstreeBwrap *bwrap, - GSpawnChildSetupFunc func, - gpointer data) -{ - g_assert (!bwrap->executed); - bwrap->child_setup_func = func; - bwrap->child_setup_data = data; -} - - -/* Set an environment variable in the child process */ -void -rpmostree_bwrap_setenv (RpmOstreeBwrap *bwrap, const char *name, const char *value) -{ - g_subprocess_launcher_setenv (bwrap->launcher, name, value, TRUE); -} - -/* Transfer ownership of @source_fd to child at @target_fd */ -void -rpmostree_bwrap_take_fd (RpmOstreeBwrap *bwrap, - int source_fd, - int target_fd) -{ - g_subprocess_launcher_take_fd (bwrap->launcher, source_fd, target_fd); -} - -void -rpmostree_bwrap_take_stdin_fd (RpmOstreeBwrap *bwrap, - int source_fd) -{ - g_subprocess_launcher_take_stdin_fd (bwrap->launcher, source_fd); -} - -void -rpmostree_bwrap_take_stdout_fd (RpmOstreeBwrap *bwrap, - int source_fd) -{ - g_subprocess_launcher_take_stdout_fd (bwrap->launcher, source_fd); -} - -void -rpmostree_bwrap_take_stderr_fd (RpmOstreeBwrap *bwrap, - int source_fd) -{ - g_subprocess_launcher_take_stderr_fd (bwrap->launcher, source_fd); -} - -void -rpmostree_bwrap_take_stdout_and_stderr_fd (RpmOstreeBwrap *bwrap, - int source_fd) -{ - g_subprocess_launcher_take_stdout_fd (bwrap->launcher, source_fd); - g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDERR_MERGE); -} - -/* Execute @bwrap, optionally capturing stdout or stderr. Must have been configured. After - * executing this method, the @bwrap instance cannot be run again. - */ -gboolean -rpmostree_bwrap_run_captured (RpmOstreeBwrap *bwrap, - GBytes **stdout_buf, - GBytes **stderr_buf, - GCancellable *cancellable, - GError **error) -{ - if (stdout_buf) - g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); - if (stderr_buf) - g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDERR_PIPE); - - const char *errmsg = (const char*) glnx_strjoina ("Executing bwrap(", (char*)bwrap->child_argv0, ")"); - GLNX_AUTO_PREFIX_ERROR (errmsg, error); - - g_autoptr(GSubprocess) subproc = rpmostree_bwrap_execute (bwrap, error); - if (!subproc) - return FALSE; - - if (stdout_buf || stderr_buf) - { - if (!g_subprocess_communicate (subproc, NULL, cancellable, - stdout_buf, stderr_buf, error)) - return FALSE; - } - - if (!g_subprocess_wait (subproc, cancellable, error)) - { - /* Now, it's possible @cancellable has been set, which means the process - * hasn't terminated yet. AFAIK that should be the only cause for the - * process not having exited now, but we just kill the process regardless - * on error here. The GSubprocess code ignores the request if we've - * already reaped it. - * - * Right now we run bwrap --die-with-parent, but until we do the whole txn - * as a subprocess, the script would leak until rpm-ostreed exited. - */ - g_subprocess_force_exit (subproc); - return FALSE; - } - int estatus = g_subprocess_get_exit_status (subproc); - if (!g_spawn_check_exit_status (estatus, error)) - return FALSE; - - return TRUE; -} - -/* Execute @bwrap. Must have been configured. After executing this method, the @bwrap - * instance cannot be run again. - */ -gboolean -rpmostree_bwrap_run (RpmOstreeBwrap *bwrap, - GCancellable *cancellable, - GError **error) -{ - return rpmostree_bwrap_run_captured (bwrap, NULL, NULL, cancellable, error); -} - -GSubprocess * -rpmostree_bwrap_execute (RpmOstreeBwrap *bwrap, GError **error) -{ - g_autoptr(GSubprocessLauncher) launcher = util::move_nullify (bwrap->launcher); - g_assert (!bwrap->executed); - bwrap->executed = TRUE; - - /* Add the final NULL */ - g_ptr_array_add (bwrap->argv, NULL); - - g_subprocess_launcher_set_child_setup (launcher, bwrap_child_setup, bwrap, NULL); - return g_subprocess_launcher_spawnv (launcher, (const char *const*)bwrap->argv->pdata, error); -} - -/* Execute /bin/true inside a bwrap container on the host */ -gboolean -rpmostree_bwrap_selftest (GError **error) -{ - glnx_autofd int host_root_dfd = -1; - g_autoptr(RpmOstreeBwrap) bwrap = NULL; - - if (!glnx_opendirat (AT_FDCWD, "/", TRUE, &host_root_dfd, error)) - return FALSE; - - bwrap = rpmostree_bwrap_new (host_root_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error); - if (!bwrap) - return FALSE; - - rpmostree_bwrap_append_child_argv (bwrap, "true", NULL); - - if (!rpmostree_bwrap_run (bwrap, NULL, error)) - { - g_prefix_error (error, "bwrap test failed, see : "); - return FALSE; - } - - return TRUE; -} - -namespace rpmostreecxx { - -// High level interface to bwrap, currently used by the postprocess code. -// This deals with renaming `/etc` to `/usr/etc` for example. -void -bwrap_run_mutable(int32_t rootfs_fd, rust::Str binpath, - const rust::Vec &child_argv, - bool unified_core_mode) -{ - g_autoptr(GError) local_error = NULL; - /* For scripts, it's /etc, not /usr/etc */ - if (!glnx_fstatat_allow_noent (rootfs_fd, "etc", NULL, 0, &local_error)) - util::throw_gerror(local_error); - const bool renamed_usretc = (errno == ENOENT); - if (renamed_usretc) - { - if (!glnx_renameat (rootfs_fd, "usr/etc", rootfs_fd, "etc", &local_error)) - util::throw_gerror(local_error); - /* But leave a compat symlink, as we used to bind mount, so scripts - * could still use that too. - */ - if (symlinkat ("../etc", rootfs_fd, "usr/etc") < 0) - { - (void)glnx_throw_errno_prefix (&local_error, "symlinkat"); - util::throw_gerror(local_error); - } - } - - RpmOstreeBwrapMutability mut = - unified_core_mode ? RPMOSTREE_BWRAP_MUTATE_ROFILES : RPMOSTREE_BWRAP_MUTATE_FREELY; - g_autoptr(RpmOstreeBwrap) bwrap = rpmostree_bwrap_new (rootfs_fd, mut, &local_error); - if (!bwrap) - util::throw_gerror(local_error); - - if (unified_core_mode) - rpmostree_bwrap_bind_read (bwrap, "var", "/var"); - else - rpmostree_bwrap_bind_readwrite (bwrap, "var", "/var"); - - { auto binpath_s = std::string(binpath); - rpmostree_bwrap_append_child_argv (bwrap, binpath_s.c_str(), NULL); - } - - /* https://github.com/projectatomic/bubblewrap/issues/91 */ - { bool first = true; - for (auto &elt : child_argv) - { - if (first) - first = false; - else - { - auto s = std::string(elt); // NUL terminate - rpmostree_bwrap_append_child_argv (bwrap, s.c_str(), NULL); - } - } - } - - if (!rpmostree_bwrap_run (bwrap, NULL, &local_error)) - util::throw_gerror(local_error); - - /* Remove the symlink and swap back */ - if (renamed_usretc) - { - if (!glnx_unlinkat (rootfs_fd, "usr/etc", 0, &local_error)) - util::throw_gerror(local_error); - if (!glnx_renameat (rootfs_fd, "etc", rootfs_fd, "usr/etc", &local_error)) - util::throw_gerror(local_error); - } -} - -rust::Vec -bwrap_run_captured(int32_t rootfs_dfd, - const rust::Vec &child_argv) -{ - g_autoptr(GError) local_error = NULL; - g_autoptr(RpmOstreeBwrap) bwrap = - rpmostree_bwrap_new (rootfs_dfd, RPMOSTREE_BWRAP_IMMUTABLE, &local_error); - if (!bwrap) - util::throw_gerror(local_error); - - for (const auto & arg : child_argv) - { - auto arg_c = std::string(arg); - rpmostree_bwrap_append_child_argv (bwrap, arg_c.c_str(), NULL); - } - - g_autoptr(GBytes) out = NULL; - if (!rpmostree_bwrap_run_captured (bwrap, &out, NULL, NULL, &local_error)) - util::throw_gerror(local_error); - gsize len; - auto cbuf = (const guint8*)g_bytes_get_data(out, &len); - rust::Vec r; - r.reserve(len); - for (auto i = 0; i < len; i++) - r.push_back(cbuf[i]); - return r; -} - -} /* namespace */ diff --git a/src/libpriv/rpmostree-bwrap.h b/src/libpriv/rpmostree-bwrap.h deleted file mode 100644 index b6fc7250..00000000 --- a/src/libpriv/rpmostree-bwrap.h +++ /dev/null @@ -1,105 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2016 Colin Walters - * - * 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. - */ - -#pragma once - -#include - -#include "libglnx.h" - -#include "rust/cxx.h" - -namespace rpmostreecxx { - -rust::Vec -bwrap_run_captured(int32_t rootfs_dfd, - const rust::Vec &child_argv); - -void bwrap_run_mutable(int32_t rootfs_dfd, rust::Str binpath, - const rust::Vec &child_argv, - bool unified_core_mode); - -} - -G_BEGIN_DECLS - -typedef enum { - RPMOSTREE_BWRAP_IMMUTABLE = 0, - RPMOSTREE_BWRAP_MUTATE_ROFILES, - RPMOSTREE_BWRAP_MUTATE_FREELY -} RpmOstreeBwrapMutability; - -typedef struct RpmOstreeBwrap RpmOstreeBwrap; -RpmOstreeBwrap *rpmostree_bwrap_ref (RpmOstreeBwrap *bwrap); -void rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap); -G_DEFINE_AUTOPTR_CLEANUP_FUNC(RpmOstreeBwrap, rpmostree_bwrap_unref) - -/* TODO - move this utility elsewhere */ -void rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...) G_GNUC_NULL_TERMINATED; - -RpmOstreeBwrap *rpmostree_bwrap_new_base (int rootfs, GError **error); - -RpmOstreeBwrap *rpmostree_bwrap_new (int rootfs, - RpmOstreeBwrapMutability is_mutable, - GError **error); - -void rpmostree_bwrap_set_inherit_stdin (RpmOstreeBwrap *bwrap); -void rpmostree_bwrap_var_tmp_tmpfs (RpmOstreeBwrap *bwrap); -void rpmostree_bwrap_bind_read (RpmOstreeBwrap *bwrap, const char *src, const char *dest); -void rpmostree_bwrap_bind_readwrite (RpmOstreeBwrap *bwrap, const char *src, const char *dest); -void rpmostree_bwrap_append_bwrap_argv (RpmOstreeBwrap *bwrap, ...) G_GNUC_NULL_TERMINATED; -void rpmostree_bwrap_append_child_argv (RpmOstreeBwrap *bwrap, ...) G_GNUC_NULL_TERMINATED; -void rpmostree_bwrap_append_child_argva (RpmOstreeBwrap *bwrap, int argc, char **argv); - -void rpmostree_bwrap_setenv (RpmOstreeBwrap *bwrap, const char *name, const char *value); - -void rpmostree_bwrap_take_fd (RpmOstreeBwrap *bwrap, - int source_fd, - int target_fd); - -void rpmostree_bwrap_take_stdin_fd (RpmOstreeBwrap *bwrap, - int source_fd); -void rpmostree_bwrap_take_stdout_fd (RpmOstreeBwrap *bwrap, - int source_fd); -void rpmostree_bwrap_take_stderr_fd (RpmOstreeBwrap *bwrap, - int source_fd); - -void rpmostree_bwrap_take_stdout_and_stderr_fd (RpmOstreeBwrap *bwrap, - int source_fd); - -void rpmostree_bwrap_set_child_setup (RpmOstreeBwrap *bwrap, - GSpawnChildSetupFunc func, - gpointer data); - -gboolean rpmostree_bwrap_run_captured (RpmOstreeBwrap *bwrap, - GBytes **stdout_buf, - GBytes **stderr_buf, - GCancellable *cancellable, - GError **error); - -GSubprocess * rpmostree_bwrap_execute (RpmOstreeBwrap *bwrap, GError **error); - -gboolean rpmostree_bwrap_run (RpmOstreeBwrap *bwrap, - GCancellable *cancellable, - GError **error); - -gboolean rpmostree_bwrap_selftest (GError **error); - -G_END_DECLS diff --git a/src/libpriv/rpmostree-kernel.cxx b/src/libpriv/rpmostree-kernel.cxx index ed8c7eef..984d9b88 100644 --- a/src/libpriv/rpmostree-kernel.cxx +++ b/src/libpriv/rpmostree-kernel.cxx @@ -36,7 +36,6 @@ #include "rpmostree-core.h" #include "rpmostree-kernel.h" -#include "rpmostree-bwrap.h" #include "rpmostree-cxxrs.h" #include "rpmostree-util.h" @@ -510,7 +509,6 @@ rpmostree_run_dracut (int rootfs_dfd, "mkdir -p /tmp/dracut && dracut $extra_argv -v --add ostree --tmpdir=/tmp/dracut -f /tmp/initramfs.img \"$@\"\n" "cat /tmp/initramfs.img >/proc/self/fd/3\n", destdir.c_str()); - g_autoptr(RpmOstreeBwrap) bwrap = NULL; g_autoptr(GPtrArray) rebuild_argv = NULL; g_auto(GLnxTmpfile) tmpf = { 0, }; @@ -567,42 +565,39 @@ rpmostree_run_dracut (int rootfs_dfd, &tmpf, error)) return FALSE; + auto bwrap = rpmostreecxx::bubblewrap_new(rootfs_dfd); if (use_root_etc) { - bwrap = rpmostree_bwrap_new_base (rootfs_dfd, error); - if (!bwrap) - return FALSE; - rpmostree_bwrap_bind_read (bwrap, "/etc", "/etc"); - rpmostree_bwrap_bind_read (bwrap, "usr", "/usr"); + bwrap->bind_read("/etc", "/etc"); + bwrap->bind_read("usr", "/usr"); } else { - bwrap = rpmostree_bwrap_new_base (rootfs_dfd, error); - if (!bwrap) - return FALSE; - rpmostree_bwrap_bind_read (bwrap, "usr/etc", "/etc"); - rpmostree_bwrap_bind_read (bwrap, "usr", "/usr"); + bwrap->bind_read("usr/etc", "/etc"); + bwrap->bind_read("usr", "/usr"); } if (dracut_host_tmpdir) - rpmostree_bwrap_bind_readwrite (bwrap, dracut_host_tmpdir->path, "/tmp/dracut"); + bwrap->bind_readwrite(dracut_host_tmpdir->path, "/tmp/dracut"); /* Set up argv and run */ - rpmostree_bwrap_append_child_argv (bwrap, (char*)glnx_basename (rpmostree_dracut_wrapper_path), NULL); + bwrap->append_child_arg((const char*)glnx_basename (rpmostree_dracut_wrapper_path)); for (char **iter = (char**)argv; iter && *iter; iter++) - rpmostree_bwrap_append_child_argv (bwrap, *iter, NULL); + bwrap->append_child_arg(*iter); if (kver) - rpmostree_bwrap_append_child_argv (bwrap, "--kver", kver, NULL); + { + bwrap->append_child_arg("--kver"); + bwrap->append_child_arg(kver); + } // Pass the tempfile to the child as fd 3 glnx_autofd int tmpf_child = fcntl (tmpf.fd, F_DUPFD_CLOEXEC, 3); if (tmpf_child < 0) return glnx_throw_errno_prefix (error, "fnctl"); - rpmostree_bwrap_take_fd (bwrap, glnx_steal_fd (&tmpf_child), 3); + bwrap->take_fd (glnx_steal_fd (&tmpf_child), 3); - if (!rpmostree_bwrap_run (bwrap, cancellable, error)) - return FALSE; + bwrap->run(*cancellable); /* For FIPS mode we need /dev/urandom pre-created because the FIPS * standards authors require that randomness is tested in a diff --git a/src/libpriv/rpmostree-postprocess.cxx b/src/libpriv/rpmostree-postprocess.cxx index c5967a43..7d74e2ae 100644 --- a/src/libpriv/rpmostree-postprocess.cxx +++ b/src/libpriv/rpmostree-postprocess.cxx @@ -42,7 +42,6 @@ #include "rpmostree-postprocess.h" #include "rpmostree-kernel.h" -#include "rpmostree-bwrap.h" #include "rpmostree-output.h" #include "rpmostree-rpm-util.h" #include "rpmostree-core.h" @@ -213,7 +212,7 @@ rpmostree_postprocess_run_depmod (int rootfs_dfd, GError **error) { rust::Vec child_argv = { rust::String("depmod"), rust::String("-a"), rust::String(kver) }; - rpmostreecxx::bwrap_run_mutable (rootfs_dfd, "depmod", child_argv, (bool)unified_core_mode); + rpmostreecxx::bubblewrap_run_sync(rootfs_dfd, child_argv, false, (bool)unified_core_mode); return TRUE; } @@ -822,7 +821,7 @@ rpmostree_postprocess_final (int rootfs_dfd, /* Now regenerate SELinux policy so that postprocess scripts from users and from us * (e.g. the /etc/default/useradd incision) that affect it are baked in. */ rust::Vec child_argv = { rust::String("semodule"), rust::String("-nB") }; - rpmostreecxx::bwrap_run_mutable (rootfs_dfd, "semodule", child_argv, (bool)unified_core_mode); + rpmostreecxx::bubblewrap_run_sync (rootfs_dfd, child_argv, false, (bool)unified_core_mode); } gboolean container = FALSE; diff --git a/src/libpriv/rpmostree-scripts.cxx b/src/libpriv/rpmostree-scripts.cxx index 039fbb87..5b52803e 100644 --- a/src/libpriv/rpmostree-scripts.cxx +++ b/src/libpriv/rpmostree-scripts.cxx @@ -25,7 +25,6 @@ #include "rpmostree-output.h" #include "rpmostree-util.h" #include "rpmostree-cxxrs.h" -#include "rpmostree-bwrap.h" #include #include #include "libglnx.h" @@ -341,33 +340,37 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd, */ gboolean is_glibc_locales = strcmp (pkg_script, "glibc-all-langpacks.posttrans") == 0 || strcmp (pkg_script, "glibc-common.post") == 0; - RpmOstreeBwrapMutability mutability = - (is_glibc_locales || !enable_fuse) ? RPMOSTREE_BWRAP_MUTATE_FREELY : RPMOSTREE_BWRAP_MUTATE_ROFILES; - g_autoptr(RpmOstreeBwrap) bwrap = rpmostree_bwrap_new (rootfs_fd, mutability, error); - if (!bwrap) - return FALSE; + rpmostreecxx::BubblewrapMutability mutability = + (is_glibc_locales || !enable_fuse) ? rpmostreecxx::BubblewrapMutability::MutateFreely : rpmostreecxx::BubblewrapMutability::RoFiles; + auto bwrap = rpmostreecxx::bubblewrap_new_with_mutability (rootfs_fd, mutability); /* Scripts can see a /var with compat links like alternatives */ - rpmostree_bwrap_var_tmp_tmpfs (bwrap); + bwrap->var_tmp_tmpfs(); struct stat stbuf; if (glnx_fstatat (rootfs_fd, "usr/lib/opt", &stbuf, AT_SYMLINK_NOFOLLOW, NULL) && S_ISDIR(stbuf.st_mode)) - rpmostree_bwrap_append_bwrap_argv (bwrap, "--symlink", "usr/lib/opt", "/opt", NULL); + { + bwrap->append_bwrap_arg("--symlink"); + bwrap->append_bwrap_arg("usr/lib/opt"); + bwrap->append_bwrap_arg("/opt"); + } /* Don't let scripts see the base rpm database by default */ - rpmostree_bwrap_bind_read (bwrap, "usr/share/empty", "usr/share/rpm"); + bwrap->bind_read("usr/share/empty", "usr/share/rpm"); /* Add ostree-booted API; some scriptlets may work differently on OSTree systems; e.g. * akmods. Just create it manually; /run is usually tmpfs, but scriptlets shouldn't be * adding stuff there anyway. */ if (!glnx_shutil_mkdir_p_at (rootfs_fd, "run", 0755, cancellable, error)) return FALSE; - rpmostree_bwrap_bind_readwrite (bwrap, "./run", "/run"); + bwrap->bind_readwrite("./run", "/run"); - rpmostree_bwrap_take_fd (bwrap, glnx_steal_fd (&devnull_fd), devnull_target_fd); - rpmostree_bwrap_append_bwrap_argv (bwrap, "--ro-bind-data", bwrap_devnull_fd, "/run/ostree-booted", NULL); + bwrap->take_fd(glnx_steal_fd (&devnull_fd), devnull_target_fd); + bwrap->append_bwrap_arg("--ro-bind-data"); + bwrap->append_bwrap_arg(bwrap_devnull_fd); + bwrap->append_bwrap_arg("/run/ostree-booted"); if (var_lib_rpm_statedir) - rpmostree_bwrap_bind_readwrite (bwrap, var_lib_rpm_statedir->path, "/var/lib/rpm-state"); + bwrap->bind_readwrite(var_lib_rpm_statedir->path, "/var/lib/rpm-state"); gboolean debugging_script = g_strcmp0 (g_getenv ("RPMOSTREE_SCRIPT_DEBUG"), pkg_script) == 0; @@ -375,7 +378,7 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd, * "systemctl,verbs: Introduce SYSTEMD_OFFLINE environment variable" * https://github.com/systemd/systemd/commit/f38951a62837a00a0b1ff42d007e9396b347742d */ - rpmostree_bwrap_setenv (bwrap, "SYSTEMD_OFFLINE", "1"); + bwrap->setenv("SYSTEMD_OFFLINE", "1"); /* FDs that need to be held open until we exec; they're * owned by the GSubprocessLauncher instance. @@ -390,15 +393,15 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd, stdin_fd = fcntl (provided_stdin_fd, F_DUPFD_CLOEXEC, 3); if (stdin_fd == -1) return glnx_throw_errno_prefix (error, "fcntl"); - rpmostree_bwrap_take_stdin_fd (bwrap, stdin_fd); + bwrap->take_stdin_fd(stdin_fd); } - GLnxTmpfile buffered_output = { 0, }; + g_auto(GLnxTmpfile) buffered_output = { 0, }; const char *id = glnx_strjoina ("rpm-ostree(", pkg_script, ")"); if (debugging_script || stdin_fd == STDIN_FILENO) { - rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/bash", NULL); - rpmostree_bwrap_set_inherit_stdin (bwrap); + bwrap->append_child_arg ("/usr/bin/bash"); + bwrap->set_inherit_stdin(); } else { @@ -416,12 +419,12 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd, stdout_fd = sd_journal_stream_fd (id, LOG_INFO, 0); if (stdout_fd < 0) return glnx_prefix_error (error, "While creating stdout stream fd"); - rpmostree_bwrap_take_stdout_fd (bwrap, stdout_fd); + bwrap->take_stdout_fd(stdout_fd); stderr_fd = sd_journal_stream_fd (id, LOG_ERR, 0); if (stderr_fd < 0) return glnx_prefix_error (error, "While creating stderr stream fd"); - rpmostree_bwrap_take_stderr_fd (bwrap, stderr_fd); + bwrap->take_stderr_fd(stderr_fd); } else { @@ -431,46 +434,31 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd, buffered_output_fd_child = fcntl (buffered_output.fd, F_DUPFD_CLOEXEC, 3); if (buffered_output_fd_child < 0) return glnx_throw_errno_prefix (error, "fcntl"); - rpmostree_bwrap_take_stdout_and_stderr_fd (bwrap, buffered_output_fd_child); - } - - const char *script_trace = g_getenv ("RPMOSTREE_SCRIPT_TRACE"); - if (script_trace) - { - int trace_argc = 0; - char **trace_argv = NULL; - if (!g_shell_parse_argv (script_trace, &trace_argc, &trace_argv, error)) - return glnx_prefix_error (error, "Parsing '%s'", script_trace); - rpmostree_bwrap_append_child_argva (bwrap, trace_argc, trace_argv); - g_strfreev (trace_argv); + bwrap->take_stdout_and_stderr_fd(buffered_output_fd_child); } const int script_child_fd = 5; - rpmostree_bwrap_take_fd (bwrap, glnx_steal_fd (&script_memfd), script_child_fd); + bwrap->take_fd(glnx_steal_fd (&script_memfd), script_child_fd); g_autofree char *procpath = g_strdup_printf ("/proc/self/fd/%d", script_child_fd); - rpmostree_bwrap_append_child_argv (bwrap, - interp, - procpath, - script_arg, - NULL); + bwrap->append_child_arg(interp); + bwrap->append_child_arg(procpath); + if (script_arg != nullptr) + bwrap->append_child_arg(script_arg); } - - if (!rpmostree_bwrap_run (bwrap, cancellable, error)) - { - dump_buffered_output_noerr (pkg_script, &buffered_output); + try { + g_assert(cancellable); + bwrap->run(*cancellable); + } catch (std::exception&e) { + dump_buffered_output_noerr(pkg_script, &buffered_output); + auto msg = e.what(); /* If errors go to the journal, help the user/admin find them there */ - if (error && rpmostreecxx::running_in_systemd()) - { - g_assert (*error); - g_autofree char *errmsg = (*error)->message; - (*error)->message = - g_strdup_printf ("%s; run `journalctl -t '%s'` for more information", errmsg, id); - } - return FALSE; - } - else - dump_buffered_output_noerr (pkg_script, &buffered_output); + if (rpmostreecxx::running_in_systemd()) + return glnx_throw (error, "%s; run `journalctl -t '%s'` for more information", msg, id); + else + return glnx_throw (error, "%s", msg); + } + dump_buffered_output_noerr(pkg_script, &buffered_output); return TRUE; } @@ -971,16 +959,14 @@ rpmostree_deployment_sanitycheck_true (int rootfs_fd, if (getenv ("RPMOSTREE_SKIP_SANITYCHECK")) return TRUE; - GLNX_AUTO_PREFIX_ERROR ("Sanity-checking final rootfs", error); - g_autoptr(RpmOstreeBwrap) bwrap = - rpmostree_bwrap_new (rootfs_fd, RPMOSTREE_BWRAP_IMMUTABLE, error); - - if (!bwrap) - return FALSE; - rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/true", NULL); - if (!rpmostree_bwrap_run (bwrap, cancellable, error)) - return FALSE; - + g_assert(cancellable); + auto bwrap = rpmostreecxx::bubblewrap_new_with_mutability(rootfs_fd, rpmostreecxx::BubblewrapMutability::Immutable); + bwrap->append_child_arg("/usr/bin/true"); + try { + bwrap->run(*cancellable); + } catch (std::exception& e) { + util::rethrow_prefixed(e, "Sanity-checking final rootfs"); + } sd_journal_print (LOG_INFO, "sanitycheck(/usr/bin/true) successful"); return TRUE; } diff --git a/tests/vmcheck/test-layering-scripts.sh b/tests/vmcheck/test-layering-scripts.sh index 06a908a0..ef0de579 100755 --- a/tests/vmcheck/test-layering-scripts.sh +++ b/tests/vmcheck/test-layering-scripts.sh @@ -223,7 +223,7 @@ if vm_rpmostree install rmrf 2>err.txt; then fi vm_cmd test -f /home/core/somedata -a -f /etc/passwd -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 'error: Sanity-checking final rootfs: Executing bwrap(/usr/bin/true)' +assert_file_has_content err.txt 'error: Sanity-checking final rootfs:.*bwrap(/usr/bin/true)' echo "ok impervious to rm -rf post" cursor=$(vm_get_journal_cursor)