rust: Introduce systemd-run based isolation mod, use in live
I was thinking about privilege separation today with systemd units, and that led me to the problem of "lifecycle binding". We really want e.g. `systemctl stop rpm-ostreed` to kill any separate systemd units we're managing. systemd already has a mechanism for this with `BindsTo=`. And then I realized we weren't doing this for the systemd-tmpfiles invocations in the `live.rs` code. Generalize this into a small `isolation` module that fixes this and several other things at the same time. I'd like to build on this to further improve our multi-process isolation story later.
This commit is contained in:
parent
445af087d6
commit
9126831b8b
52
rust/src/isolation.rs
Normal file
52
rust/src/isolation.rs
Normal file
@ -0,0 +1,52 @@
|
||||
//! APIs for multi-process isolation
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use fn_error_context::context;
|
||||
use std::process::Command;
|
||||
|
||||
const SELF_UNIT: &str = "rpm-ostreed.service";
|
||||
/// Run as a child process, synchronously.
|
||||
const BASE_ARGS: &[&str] = &["--wait", "--pipe", "--no-ask-password", "--quiet"];
|
||||
|
||||
/// Configuration for transient unit.
|
||||
pub(crate) struct UnitConfig<'a> {
|
||||
/// If provided, will be used as the name of the unit
|
||||
pub(crate) name: Option<&'a str>,
|
||||
/// Unit/Service properties, e.g. DynamicUser=yes
|
||||
pub(crate) properties: &'a [&'a str],
|
||||
/// The command to execute
|
||||
pub(crate) exec_args: &'a [&'a str],
|
||||
}
|
||||
|
||||
/// Create a child process via `systemd-run` and synchronously wait
|
||||
/// for its completion. This runs in `--pipe` mode, so e.g. stdout/stderr
|
||||
/// will go to the parent process.
|
||||
/// Use this for isolation, as well as to escape the parent rpm-ostreed.service
|
||||
/// isolation like `ProtectHome=true`.
|
||||
#[context("Running systemd worker")]
|
||||
pub(crate) fn run_systemd_worker_sync(cfg: &UnitConfig) -> Result<()> {
|
||||
if !systemd::daemon::booted()? {
|
||||
return Err(anyhow!("Not running under systemd"));
|
||||
}
|
||||
let mut cmd = Command::new("systemd-run");
|
||||
cmd.args(BASE_ARGS);
|
||||
if let Some(name) = cfg.name {
|
||||
cmd.arg("--unit");
|
||||
cmd.arg(name);
|
||||
}
|
||||
for prop in cfg.properties.iter() {
|
||||
cmd.arg("--property");
|
||||
cmd.arg(prop);
|
||||
}
|
||||
// This ensures that this unit won't escape our process.
|
||||
cmd.arg(format!("--property=BindsTo={}", SELF_UNIT));
|
||||
cmd.arg(format!("--property=After={}", SELF_UNIT));
|
||||
cmd.arg("--");
|
||||
cmd.args(cfg.exec_args);
|
||||
let status = cmd.status()?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("{}", status));
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -379,6 +379,7 @@ pub(crate) use extensions::*;
|
||||
mod fedora_integration;
|
||||
mod history;
|
||||
pub use self::history::*;
|
||||
mod isolation;
|
||||
mod journal;
|
||||
pub(crate) use self::journal::*;
|
||||
mod initramfs;
|
||||
|
@ -8,8 +8,10 @@
|
||||
*/
|
||||
|
||||
use crate::ffi::LiveApplyState;
|
||||
use crate::isolation;
|
||||
use crate::{cxxrsutil::*, variant_utils};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use fn_error_context::context;
|
||||
use nix::sys::statvfs;
|
||||
use openat_ext::OpenatDirExt;
|
||||
use ostree::DeploymentUnlockedState;
|
||||
@ -18,7 +20,6 @@ use std::borrow::Cow;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::process::Command;
|
||||
use variant_utils::{variant_dict_lookup_bool, variant_dict_lookup_str};
|
||||
|
||||
/// GVariant `s`: Choose a specific commit
|
||||
@ -301,50 +302,34 @@ fn update_etc(
|
||||
// we actually need to escape our mount namespace and affect
|
||||
// the "main" mount namespace so that other processes will
|
||||
// see the overlayfs.
|
||||
#[context("Creating overlayfs")]
|
||||
fn unlock_transient(sysroot: &ostree::Sysroot) -> Result<()> {
|
||||
// Temporarily drop the lock
|
||||
sysroot.unlock();
|
||||
let status = Command::new("systemd-run")
|
||||
.args(&[
|
||||
"-u",
|
||||
"rpm-ostree-unlock",
|
||||
"--wait",
|
||||
"--",
|
||||
"ostree",
|
||||
"admin",
|
||||
"unlock",
|
||||
"--transient",
|
||||
])
|
||||
.status();
|
||||
sysroot.lock()?;
|
||||
let status = status?;
|
||||
if !status.success() {
|
||||
bail!("Failed to unlock --transient");
|
||||
}
|
||||
isolation::run_systemd_worker_sync(&isolation::UnitConfig {
|
||||
name: Some("rpm-ostree-unlock"),
|
||||
properties: &[],
|
||||
exec_args: &["ostree", "admin", "unlock", "--transient"],
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `systemd-tmpfiles` via `systemd-run` so we escape our mount namespace.
|
||||
/// This allows our `ProtectHome=` in the unit file to work.
|
||||
/// Run `systemd-tmpfiles` as a separate systemd unit to escape
|
||||
/// our mount namespace.
|
||||
/// This allows our `ProtectHome=` in the unit file to work
|
||||
/// for example. Longer term I'd like to protect even more of `/var`.
|
||||
#[context("Running tmpfiles for /run and /var")]
|
||||
fn rerun_tmpfiles() -> Result<()> {
|
||||
for prefix in &["/run", "/var"] {
|
||||
let status = Command::new("systemd-run")
|
||||
.args(&[
|
||||
"-u",
|
||||
"rpm-ostree-tmpfiles",
|
||||
"--wait",
|
||||
"--",
|
||||
"systemd-tmpfiles",
|
||||
"--create",
|
||||
"--prefix",
|
||||
prefix,
|
||||
])
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
bail!("Failed to invoke systemd-tmpfiles");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
isolation::run_systemd_worker_sync(&isolation::UnitConfig {
|
||||
name: Some("rpm-ostree-tmpfiles"),
|
||||
properties: &[],
|
||||
exec_args: &[
|
||||
"systemd-tmpfiles",
|
||||
"--create",
|
||||
"--prefix=/run",
|
||||
"--prefix=/var",
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
fn get_required_booted_deployment(sysroot: &ostree::Sysroot) -> Result<ostree::Deployment> {
|
||||
@ -508,7 +493,7 @@ pub(crate) fn transaction_apply_live(
|
||||
&openat::Dir::open("/etc")?,
|
||||
)?;
|
||||
std::mem::drop(task);
|
||||
let task = crate::ffi::progress_begin_task("Running systemd-tmpfiles for /var");
|
||||
let task = crate::ffi::progress_begin_task("Running systemd-tmpfiles for /run and /var");
|
||||
rerun_tmpfiles()?;
|
||||
std::mem::drop(task);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user