Optionally back /tmp and /var/tmp with stable host directories

This commit is contained in:
2026-05-15 01:36:58 +02:00
parent 28e68b0fff
commit 3fb0da0577
13 changed files with 839 additions and 52 deletions
+93 -27
View File
@@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
use serde::Deserialize;
use crate::cli::Args;
use crate::session_key::SessionKey;
use crate::{BindSpec, EnvEntry, SandboxConfig, SandboxError, SandboxMode};
pub fn build(args: Args, file_config: Option<FileConfig>) -> Result<SandboxConfig, SandboxError> {
@@ -13,72 +14,120 @@ pub fn build(args: Args, file_config: Option<FileConfig>) -> Result<SandboxConfi
let profile = c.resolve_profile(args.profile.as_deref())?;
(c.options, profile)
}
None => (Options::default(), Options::default()),
None => (
Options::default(),
ResolvedProfile {
name: args.profile.clone(),
options: Options::default(),
},
),
};
validate_mode(&globals)?;
validate_mode(&profile)?;
validate_mode(&profile.options)?;
globals.validate_paths()?;
profile.validate_paths()?;
profile.options.validate_paths()?;
let (command, command_args) =
resolve_command(args.entrypoint, args.command_and_args, &profile, &globals)?;
let mode = merge_mode(args.blacklist, args.whitelist, &profile.options, &globals);
let chdir = resolve_chdir(
args.chdir.clone(),
profile.options.chdir.clone(),
globals.chdir.clone(),
)?;
let persistent_tmp_key = resolve_persistent_tmp_key(&args, &profile, &globals, &mode, &chdir);
let (command, command_args) = resolve_command(
args.entrypoint,
args.command_and_args,
&profile.options,
&globals,
)?;
let command = resolve_binary(&command)
.ok_or_else(|| SandboxError::CommandNotFound(PathBuf::from(&command)))?;
let env = dedupe_env_last_wins(parse_env_entries(&merge_vecs(
args.env,
&profile.env,
&profile.options.env,
&globals.env,
))?);
let unsetenv = merge_vecs(args.unsetenv, &profile.unsetenv, &globals.unsetenv);
let unsetenv = merge_vecs(args.unsetenv, &profile.options.unsetenv, &globals.unsetenv);
reject_env_key_conflicts(&env, &unsetenv)?;
Ok(SandboxConfig {
mode: merge_mode(args.blacklist, args.whitelist, &profile, &globals),
mode,
hardened: merge_flag(
merge_flag_pair(args.hardened, args.no_hardened),
profile.hardened,
profile.options.hardened,
globals.hardened,
),
unshare_net: merge_flag(
merge_flag_pair(args.unshare_net, args.share_net),
profile.unshare_net,
profile.options.unshare_net,
globals.unshare_net,
),
seccomp: merge_flag_with_default(
merge_flag_pair(args.seccomp, args.no_seccomp),
profile.seccomp,
profile.options.seccomp,
globals.seccomp,
true,
),
env_filter: merge_flag_with_default(
merge_flag_pair(args.env_filter, args.no_env_filter),
profile.env_filter,
profile.options.env_filter,
globals.env_filter,
true,
),
dry_run: merge_flag(
merge_flag_pair(args.dry_run, args.no_dry_run),
profile.dry_run,
profile.options.dry_run,
globals.dry_run,
),
chdir: resolve_chdir(args.chdir, profile.chdir, globals.chdir)?,
extra_rw: merge_bind_specs(args.extra_rw, &profile.rw, &globals.rw)?,
extra_ro: merge_bind_specs(args.extra_ro, &profile.ro, &globals.ro)?,
mask: merge_vecs(args.mask, &profile.mask, &globals.mask),
chdir,
extra_rw: merge_bind_specs(args.extra_rw, &profile.options.rw, &globals.rw)?,
extra_ro: merge_bind_specs(args.extra_ro, &profile.options.ro, &globals.ro)?,
mask: merge_vecs(args.mask, &profile.options.mask, &globals.mask),
env,
unsetenv,
bwrap_args: split_bwrap_args(merge_vecs(
args.bwrap_args,
&profile.bwrap_args,
&profile.options.bwrap_args,
&globals.bwrap_args,
))?,
command,
command_args,
persistent_tmp_key,
})
}
fn resolve_persistent_tmp_key(
args: &Args,
profile: &ResolvedProfile,
globals: &Options,
mode: &SandboxMode,
chdir: &Path,
) -> Option<String> {
if matches!(mode, SandboxMode::Blacklist) {
return None;
}
let enabled = merge_flag(
merge_flag_pair(args.persistent_tmp, args.no_persistent_tmp),
profile.options.persistent_tmp,
globals.persistent_tmp,
);
if !enabled {
return None;
}
let label = args
.persistent_key
.clone()
.or_else(|| profile.options.persistent_key.clone())
.or_else(|| globals.persistent_key.clone());
Some(
label.unwrap_or_else(|| SessionKey::default().derive(profile.name.as_deref(), mode, chdir)),
)
}
fn merge_mode(
cli_blacklist: bool,
cli_whitelist: bool,
@@ -297,6 +346,12 @@ pub struct FileConfig {
_unknown: HashMap<String, toml::Value>,
}
#[derive(Default)]
pub struct ResolvedProfile {
pub name: Option<String>,
pub options: Options,
}
impl FileConfig {
pub fn load(path: &Path) -> Result<Self, SandboxError> {
Self::load_file(path)?.load_extra()
@@ -364,15 +419,22 @@ impl FileConfig {
Ok(config)
}
fn resolve_profile(&self, selected: Option<&str>) -> Result<Options, SandboxError> {
match selected.or(self.options.profile.as_deref()) {
Some(leaf) => self.resolve_chain(leaf),
None => Ok(Options::default()),
}
fn resolve_profile(&self, selected: Option<&str>) -> Result<ResolvedProfile, SandboxError> {
let Some(name) = selected
.map(String::from)
.or_else(|| self.options.profile.clone())
else {
return Ok(ResolvedProfile::default());
};
let options = self.resolve_chain(&name)?;
Ok(ResolvedProfile {
name: Some(name),
options,
})
}
fn resolve_chain(&self, leaf: &str) -> Result<Options, SandboxError> {
let chain = self.collect_chain(leaf)?;
fn resolve_chain(&self, name: &str) -> Result<Options, SandboxError> {
let chain = self.collect_chain(name)?;
let merged = chain
.into_iter()
@@ -382,10 +444,10 @@ impl FileConfig {
Ok(merged)
}
fn collect_chain(&self, leaf: &str) -> Result<Vec<Options>, SandboxError> {
fn collect_chain(&self, name: &str) -> Result<Vec<Options>, SandboxError> {
let mut visited: Vec<String> = Vec::new();
let mut chain: Vec<Options> = Vec::new();
let mut current = leaf.to_string();
let mut current = name.to_string();
loop {
if visited.iter().any(|seen| seen == &current) {
@@ -424,6 +486,8 @@ pub struct Options {
pub entrypoint: Option<CommandValue>,
pub command: Option<CommandValue>,
pub dry_run: Option<bool>,
pub persistent_tmp: Option<bool>,
pub persistent_key: Option<String>,
pub chdir: Option<PathBuf>,
#[serde(default)]
pub rw: Vec<String>,
@@ -453,6 +517,8 @@ impl Options {
entrypoint: extra.entrypoint.or(self.entrypoint),
command: extra.command.or(self.command),
dry_run: extra.dry_run.or(self.dry_run),
persistent_tmp: extra.persistent_tmp.or(self.persistent_tmp),
persistent_key: extra.persistent_key.or(self.persistent_key),
chdir: extra.chdir.or(self.chdir),
rw: append(self.rw, extra.rw),
ro: append(self.ro, extra.ro),