Rewrite bwrap code in Rust

I tried to do this incrementally but it snowballed.
This commit is contained in:
Colin Walters 2021-03-21 13:30:48 +00:00
parent 40dc281e5e
commit fa81456cbf
13 changed files with 546 additions and 894 deletions

View File

@ -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 \

View File

@ -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<openat::Dir>,
pub(crate) rootfs_fd: openat::Dir,
executed: bool,
argv: Vec<String>,
child_argv0: Option<NonZeroUsize>,
launcher: gio::SubprocessLauncher, // 🚀
tempdirs: Vec<tempfile::TempDir>,
}
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<tempfile::TempDir> {
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<openat::Dir>) -> Self {
Self {
rootfs_fd: Rc::clone(rootfs_fd),
pub(crate) fn new(rootfs_fd: &openat::Dir) -> Result<Self> {
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<Self> {
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<glib::Bytes> {
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<T>` 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<Box<Bubblewrap>> {
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<Box<Bubblewrap>> {
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<String>,
capture_stdout: bool,
unified_core: bool,
) -> CxxResult<Vec<u8>> {
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 <https://github.com/coreos/rpm-ostree/pull/429>")]
/// 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(())
}

View File

@ -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<String> = ["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();

View File

@ -63,14 +63,48 @@ pub mod ffi {
fn client_handle_fd_argument(arg: &str, arch: &str) -> Result<Vec<i32>>;
}
#[derive(Debug)]
enum BubblewrapMutability {
Immutable,
RoFiles,
MutateFreely,
}
// bubblewrap.rs
extern "Rust" {
type Bubblewrap;
fn bubblewrap_new(rootfs_fd: i32) -> Result<Box<Bubblewrap>>;
fn get_rootfs_fd(&self) -> i32;
}
fn bubblewrap_selftest() -> Result<()>;
fn bubblewrap_run_sync(
rootfs_dfd: i32,
args: &Vec<String>,
capture_stdout: bool,
unified_core: bool,
) -> Result<Vec<u8>>;
fn bubblewrap_new(rootfs_fd: i32) -> Result<Box<Bubblewrap>>;
fn bubblewrap_new_with_mutability(
rootfs_fd: i32,
mutability: BubblewrapMutability,
) -> Result<Box<Bubblewrap>>;
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<String>,
unified_core_mode: bool,
) -> Result<()>;
fn bwrap_run_captured(rootfs_dfd: i32, child_argv: &Vec<String>) -> Result<Vec<u8>>;
}
unsafe extern "C++" {
include!("rpmostree-clientlib.h");
fn client_require_root() -> Result<()>;

View File

@ -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)
{

View File

@ -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)

View File

@ -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"

View File

@ -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 <err.h>
#include <stdio.h>
#include <systemd/sd-journal.h>
#include "rpmostree-util.h"
#include "rpmostree-cxxrs.h"
#include <utility>
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<rpmostreecxx::Bubblewrap> 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 <https://github.com/projectatomic/rpm-ostree/pull/429>: ");
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<rust::String> &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<uint8_t>
bwrap_run_captured(int32_t rootfs_dfd,
const rust::Vec<rust::String> &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<uint8_t> r;
r.reserve(len);
for (auto i = 0; i < len; i++)
r.push_back(cbuf[i]);
return r;
}
} /* namespace */

View File

@ -1,105 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2016 Colin Walters <walters@verbum.org>
*
* 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 <ostree.h>
#include "libglnx.h"
#include "rust/cxx.h"
namespace rpmostreecxx {
rust::Vec<uint8_t>
bwrap_run_captured(int32_t rootfs_dfd,
const rust::Vec<rust::String> &child_argv);
void bwrap_run_mutable(int32_t rootfs_dfd, rust::Str binpath,
const rust::Vec<rust::String> &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

View File

@ -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

View File

@ -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;

View File

@ -25,7 +25,6 @@
#include "rpmostree-output.h"
#include "rpmostree-util.h"
#include "rpmostree-cxxrs.h"
#include "rpmostree-bwrap.h"
#include <err.h>
#include <systemd/sd-journal.h>
#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;
}

View File

@ -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)