diff --git a/Cargo.lock b/Cargo.lock index 97c0a79..eebd05b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,16 +21,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] -name = "cfg-if" -version = "1.0.4" +name = "cc" +version = "1.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +dependencies = [ + "find-msvc-tools", + "shlex", +] [[package]] name = "elb" @@ -82,6 +80,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + [[package]] name = "errno" version = "0.3.14" @@ -92,6 +101,32 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "exec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886b70328cba8871bfc025858e1de4be16b1d5088f2ba50b57816f4210672615" +dependencies = [ + "errno 0.2.8", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "glob" version = "0.3.3" @@ -127,18 +162,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "proc-macro2" version = "1.0.103" @@ -164,12 +187,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", - "errno", + "errno 0.3.14", "libc", "linux-raw-sys", "windows-sys", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" version = "2.0.108" @@ -218,6 +247,28 @@ dependencies = [ "winsafe", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.1" @@ -245,7 +296,7 @@ version = "0.1.0" dependencies = [ "anyhow", "elb-dl", + "exec", "landlock", - "nix", "which", ] diff --git a/Cargo.toml b/Cargo.toml index 318db26..f7e987c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "GPL-3.0-or-later" edition = "2024" [features] -default = [ "abi-6", "cli", "compat-besteffort" ] +default = [ "abi-6", "cli" ] cli = ["dep:elb-dl", "dep:which"] nix = [] abi-1 = [] @@ -17,8 +17,6 @@ abi-3 = [] abi-4 = [] abi-5 = [] abi-6 = [] -compat-besteffort = [] -compat-require = [] [profile.release] strip = true @@ -29,6 +27,6 @@ lto = true [dependencies] anyhow = "1.0.100" landlock = "0.4.3" +exec = "0.3.1" elb-dl = { version = "0.3.2", features = ["glibc"], default-features = false, optional = true } which = { version = "8.0.0", optional = true } -nix = { version = "0.30.1", features = ["fs", "mount", "process"] } diff --git a/nix/package.nix b/nix/package.nix index 8708b94..e7756da 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,5 +1,4 @@ { - pkgs, lib, rustPlatform, features ? [ ], @@ -36,9 +35,6 @@ rustPlatform.buildRustPackage (finalAttrs: { "abi-${toString abi}" ]; - LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; - C_INCLUDE_PATH = "${pkgs.linuxHeaders}/include"; - meta = { description = details.description; homepage = details.repository; diff --git a/src/main.rs b/src/main.rs index 7ea52ca..abee00c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,127 +1,148 @@ -mod namespaces; mod parse; mod types; use anyhow::{Context, Result, anyhow}; use landlock::{ ABI, Access, AccessFs, AccessNet, BitFlags, Compatible, NetPort, Ruleset, RulesetAttr, - RulesetCreated, RulesetCreatedAttr, Scope, make_bitflags, path_beneath_rules, + RulesetCreatedAttr, Scope, make_bitflags, path_beneath_rules, }; #[cfg(feature = "cli")] use elb_dl::{DependencyTree, DynamicLoader, glibc}; -use nix::{ - errno::Errno, - libc::{MOUNT_ATTR_IDMAP, mkdtemp, unshare}, - mount::mount, - sys::statvfs::fstatvfs, - unistd::pivot_root, -}; #[cfg(feature = "cli")] -use std::{ - collections::VecDeque, - path::{Path, PathBuf}, - str::FromStr, -}; -use std::{ - env::temp_dir, - ffi::{CString, c_char}, - fs::{File, create_dir_all}, - io::Read, - os::{fd::FromRawFd, unix::process::CommandExt}, - process::Command, -}; +use std::{collections::VecDeque, path::PathBuf, str::FromStr}; +use std::{fs::File, io::Read, os::fd::FromRawFd}; -use crate::types::Yoke; - -fn check_conflicts(opts: &Yoke) -> Result<()> { - if (!opts.fs.is_empty()) || opts.unshare.mount && opts.unsandbox.fs { - return Err(anyhow!( - "conflicting options provided (--fs or --namespace m & --no-fs)" - )); +fn main() -> Result<()> { + let args = std::env::args().skip(1); + let mut opts = parse::parse_args(args)?; + if opts.fd_args { + let mut fd = unsafe { File::from_raw_fd(3) }; + let mut raw_args = String::new(); + fd.read_to_string(&mut raw_args)?; + let args = raw_args + .split_ascii_whitespace() + .map(|s| s.to_string()) + .collect::>(); + let fd_opts = parse::parse_args(args.into_iter())?; + opts.merge(fd_opts); } - if (!opts.net.is_empty() || opts.unshare.net) && opts.unsandbox.net { - return Err(anyhow!( - "conflicting options provided (--net or --namespace n & --no-net)" - )); - } - // if opts.chroot.as_ref().is_some_and(|pb| !pb.is_absolute()) { - // return Err(anyhow!("chroot path must be absolute")); - // } - Ok(()) -} + if opts.exec.is_empty() { + // print help + eprintln!( + " +yoke -- simple sandboxer -#[cfg(not(feature = "cli"))] -fn trace_deps(mut ruleset: RulesetCreated, opts: &Yoke, abi: ABI) -> Result { - if opts.besteffort { - eprintln!("yoke was not compiled with the cli feature, cannot use --ldd"); - Ok(ruleset) +use: yoke [ruletype] [space separated rules] -- [command] + +rules +------------ + filesystem: + --fs | -f [access]=/path:/another/path + + tcp port: + --tcp | -t [access]=1234:5678 + + env vars: + --env | -e [key]=[value] + + retain inherited env vars: + --retain-env | -r + + allow use of external unix domain sockets: + --sockets | -s + + allow sending signals to other processes: + --signals | -k + + resolve process dependencies and add to sandbox + (with `ldd` feature): + --ldd | -l + + unsandbox: + --no-fs | -nf + --no-tcp | -nt + + accept additional rules from fd 3: + --fd-args | -fd + + +access specifiers +------------ + fs: + r - read + w - write + x - execute + i - ioctl + + tcp: + i - in/bind + o - out/connect + +examples +------------ + yoke --fs r=/etc -l -- ls /etc + + yoke --tcp io=80:443 --fs r=/srv/web --clear-env --env SERVE_FROM=/srv/web \ + -- myhttpserver + " + ); + std::process::exit(1); + } + + // set up scope of our intial ruleset + let mut preempt = Ruleset::default(); + + preempt = preempt.set_compatibility(landlock::CompatLevel::HardRequirement); + + #[cfg(feature = "abi-2")] + let abi = ABI::V2; + #[cfg(feature = "abi-3")] + let abi = ABI::V3; + #[cfg(feature = "abi-4")] + let abi = ABI::V4; + #[cfg(feature = "abi-5")] + let abi = ABI::V5; + #[cfg(feature = "abi-6")] + let abi = ABI::V6; + + // disallow signals to other processes + if !opts.signals && abi >= ABI::V6 { + preempt = preempt.scope(Scope::Signal).context("scoping signals")?; + } + + // disallow connections to unix domain sockets + if !opts.sockets && abi >= ABI::V6 { + preempt = preempt + .scope(Scope::AbstractUnixSocket) + .context("scoping sockets")?; + } + + // lock down fs access + preempt = if !opts.unsandbox.fs { + preempt + .handle_access(AccessFs::from_all(abi)) + .context("handling fs access")? } else { - Err(anyhow!( - "yoke was not compiled with the cli feature, cannot use --ldd" - )) - } -} + preempt + }; -#[cfg(feature = "cli")] -fn trace_deps(mut ruleset: RulesetCreated, abi: ABI, fullpath: &Path) -> Result { - #[cfg(not(feature = "nix"))] - let loader = DynamicLoader::options() - .search_dirs(glibc::get_search_dirs(PathBuf::from_str("/")?)?) - .new_loader(); + // lock down tcp access + preempt = if !(opts.unsandbox.tcp || abi < ABI::V3) { + preempt + .handle_access(AccessNet::BindTcp) + .context("handling tcp bind access")? + .handle_access(AccessNet::ConnectTcp) + .context("handling tcp conn access")? + } else { + preempt + }; - #[cfg(feature = "nix")] - let loader = DynamicLoader::options() - .search_dirs(glibc::get_hard_coded_search_dirs(None)?) - .new_loader(); + // create ruleset and begin inserting rules + let mut ruleset = preempt.create().context("creating ruleset")?; - let mut tree = DependencyTree::new(); - let mut queue = VecDeque::new(); - queue.push_back(fullpath.to_path_buf()); - while let Some(path) = queue.pop_front() { - let deps = loader.resolve_dependencies(&path, &mut tree)?; - queue.extend(deps); - } - ruleset = ruleset - .add_rules(path_beneath_rules( - tree.into_iter().fold(Vec::new(), |mut acc, (_, deps)| { - acc.extend(deps); - acc - }), - AccessFs::from_read(abi), - )) - .context("adding rules for dependencies")?; - Ok(ruleset) -} - -// TODO overhaul tcp -> net -fn insert_tcp_rules(mut ruleset: RulesetCreated, opts: &Yoke, abi: ABI) -> Result { - if abi >= ABI::V3 { - for (dir, ports) in opts.net.iter() { - let mut access = BitFlags::empty(); - if dir.inbound { - access.insert(make_bitflags!(AccessNet::BindTcp)); - } - if dir.outbound { - access.insert(make_bitflags!(AccessNet::ConnectTcp)) - } - for port in ports { - ruleset = ruleset - .add_rule(NetPort::new(*port, access)) - .context("adding tcp rule")?; - } - } - } else if !opts.net.is_empty() { - return Err(anyhow!( - "tcp controls are only supported with Landlock ABI 3 or higher" - )); - } - Ok(ruleset) -} - -fn insert_fs_rules(mut ruleset: RulesetCreated, opts: &Yoke, abi: ABI) -> Result { // allow each path specified, grouped by access specifier - for (perms, paths) in opts.fs.iter() { + for (perms, paths) in opts.fs { let mut access = BitFlags::empty(); if perms.read { access.insert(make_bitflags!(AccessFs::{ReadFile | ReadDir})); @@ -171,110 +192,36 @@ fn insert_fs_rules(mut ruleset: RulesetCreated, opts: &Yoke, abi: ABI) -> Result .add_rules(path_beneath_rules(paths, access)) .context("adding fs rule")?; } - Ok(ruleset) -} -fn recv_fd_args() -> Result { - let mut fd = unsafe { File::from_raw_fd(3) }; - let mut raw_args = String::new(); - fd.read_to_string(&mut raw_args)?; - let args = raw_args - .split_ascii_whitespace() - .map(|s| s.to_string()) - .collect::>(); - parse::parse_args(args.into_iter()) -} - -fn configure_landlock_access(mut preempt: Ruleset, opts: &Yoke, abi: ABI) -> Result { - // disallow signals to other processes - if !opts.signals && abi >= ABI::V6 { - preempt = preempt.scope(Scope::Signal).context("scoping signals")?; + // allow each tcp action specified, grouped by access specifier + if abi >= ABI::V3 { + for (dir, ports) in opts.tcp { + let mut access = BitFlags::empty(); + if dir.inbound { + access.insert(make_bitflags!(AccessNet::BindTcp)); + } + if dir.outbound { + access.insert(make_bitflags!(AccessNet::ConnectTcp)) + } + for port in ports { + ruleset = ruleset + .add_rule(NetPort::new(port, access)) + .context("adding tcp rule")?; + } + } + } else if !opts.tcp.is_empty() { + return Err(anyhow!( + "tcp controls are only supported with Landlock ABI 3 or higher" + )); } - // disallow connections to unix domain sockets - if !opts.sockets && abi >= ABI::V6 { - preempt = preempt - .scope(Scope::AbstractUnixSocket) - .context("scoping sockets")?; - } - - // lock down fs access - preempt = if !opts.unsandbox.fs { - preempt - .handle_access(AccessFs::from_all(abi)) - .context("handling fs access")? - } else { - preempt - }; - - // lock down tcp access - preempt = if !(opts.unsandbox.net || abi < ABI::V3) { - preempt - .handle_access(AccessNet::BindTcp) - .context("handling tcp bind access")? - .handle_access(AccessNet::ConnectTcp) - .context("handling tcp conn access")? - } else { - preempt - }; - - Ok(preempt) -} - -fn main() -> Result<()> { - let args = std::env::args().skip(1); - let mut opts = parse::parse_args(args)?; - - if opts.fd_args { - opts.merge(recv_fd_args()?); - } - - if opts.exec.is_empty() { - print_help(); - std::process::exit(1); - } - - // set up scope of our intial ruleset - let mut preempt = Ruleset::default(); - - let compat = if opts.besteffort { - landlock::CompatLevel::BestEffort - } else { - landlock::CompatLevel::HardRequirement - }; - preempt = preempt.set_compatibility(compat); - - #[cfg(feature = "abi-2")] - let abi = ABI::V2; - #[cfg(feature = "abi-3")] - let abi = ABI::V3; - #[cfg(feature = "abi-4")] - let abi = ABI::V4; - #[cfg(feature = "abi-5")] - let abi = ABI::V5; - #[cfg(feature = "abi-6")] - let abi = ABI::V6; - - // configure what landlock will restrict access to - preempt = configure_landlock_access(preempt, &opts, abi)?; - - // ensure no conflicting configuration options were provided - check_conflicts(&opts)?; - - // create ruleset and begin inserting rules - let mut ruleset = preempt.create().context("creating ruleset")?; - - ruleset = insert_fs_rules(ruleset, &opts, abi)?; - - ruleset = insert_tcp_rules(ruleset, &opts, abi)?; - // locate our executable #[cfg(feature = "cli")] let fullpath = which::which(&opts.exec[0]).context("finding executable")?; #[cfg(not(feature = "cli"))] let fullpath = &opts.exec[0]; - // add executable as read+execute + // add executeable as read+execute if !opts.unsandbox.fs { ruleset = ruleset.add_rules(path_beneath_rules( std::slice::from_ref(&fullpath), @@ -283,13 +230,45 @@ fn main() -> Result<()> { } // if requested, trace dependencies and add as read+execute + #[cfg(feature = "cli")] if opts.ldd { - ruleset = trace_deps(ruleset, abi, &fullpath)?; + #[cfg(not(feature = "nix"))] + let loader = DynamicLoader::options() + .search_dirs(glibc::get_search_dirs(PathBuf::from_str("/")?)?) + .new_loader(); + + #[cfg(feature = "nix")] + let loader = DynamicLoader::options() + .search_dirs(glibc::get_hard_coded_search_dirs(None)?) + .new_loader(); + + let mut tree = DependencyTree::new(); + let mut queue = VecDeque::new(); + queue.push_back(fullpath.clone()); + while let Some(path) = queue.pop_front() { + let deps = loader.resolve_dependencies(&path, &mut tree)?; + queue.extend(deps); + } + ruleset = ruleset + .add_rules(path_beneath_rules( + tree.into_iter().fold(Vec::new(), |mut acc, (_, deps)| { + acc.extend(deps); + acc + }), + AccessFs::from_read(abi), + )) + .context("tracking dependencies")?; } // enforce the ruleset on ourselves ruleset.restrict_self().context("enforcing ruleset")?; + // construct a command for the target program + let mut cmd = exec::Command::new(fullpath); + if opts.exec.len() > 1 { + cmd.args(&opts.exec[1..]); + } + // clear env unless retention is requested if !opts.retain_env { for (k, _) in std::env::vars() { @@ -308,158 +287,8 @@ fn main() -> Result<()> { } } - // TODO bind mounts, pivot_root, chroot - - // do we need to use commandext pre_exec, - // or copy unshare's fork_and_wait behaviour ? - let mut spaced = false; - - unsafe { - use nix::libc::*; - let ns = opts.unshare; - let mut flag = 0; - let pairs = [ - (ns.mount, CLONE_NEWNS), - (ns.uts, CLONE_NEWUTS), - (ns.ipc, CLONE_NEWIPC), - (ns.pid, CLONE_NEWPID), - (ns.net, CLONE_NEWNET), - (ns.user, CLONE_NEWUSER), - (ns.cgroup, CLONE_NEWCGROUP), - (ns.time, CLONE_NEWTIME), - ]; - for (_, cloneflag) in pairs.iter().filter(|(o, _)| *o) { - flag |= cloneflag; - } - if flag != 0 && unshare(flag) == -1 { - let e = Errno::last(); - if opts.besteffort { - eprintln!("failed to enter namespace: {e}") - } else { - return Err(anyhow!("failed to enter namespace: {e}")); - } - }; - } - - if opts.unshare.mount && opts.chroot { - let pid = std::process::id(); - let new_root = { - let mut template = CString::from_str(format!("yoke-{pid}-XXXXXX").as_str())?.into_raw(); - PathBuf::from_str(&(unsafe { CString::from_raw(mkdtemp(template)) }.into_string()?))? - }; - for (perms, paths) in opts.fs.iter() { - for path in paths { - let target = new_root.clone(); - target.push(path); - create_dir_all(&target)?; - mount(path, target); - } - } - // see bubblewrap.c lines 1260.. - // bind mount all the paths in fs - // mount(source, target, fstype, flags, data)?; - todo!(); - // pivot_root(new_root, put_old); - } - // execute and hopefully never return - let exec_path = CString::from_str( - fullpath - .to_str() - .ok_or(anyhow!("could not convert exec path to string"))?, - )?; - let argv = if opts.exec.len() > 1 { - let mut args = Vec::new(); - for arg in &opts.exec[1..] { - args.push(CString::from_str(arg)?); - } - args - } else { - Vec::new() - }; - - nix::unistd::execv(&exec_path, &argv)?; - let e = nix::errno::Errno::last(); - eprintln!("failed to run process: {e}"); + let err = cmd.exec(); + eprintln!("failed to run process: {}", err); Ok(()) } - -fn print_help() { - eprintln!( - " -yoke -- simple sandboxer - -use: yoke [ruletype] [space separated rules] -- [command] - -rules ------------- - filesystem: - --fs | -f [access]=/path:/another/path - - tcp port: - --tcp | -t [access]=1234:5678 - - env vars: - --env | -e [key]=[value] - - retain inherited env vars: - --retain-env | -r - - allow use of unix domain sockets: - --sockets | -s - - allow sending signals to other processes: - --signals | -k - - best-effort (no error on sandbox failures): - --best-effort | -b - - resolve process dependencies and add to sandbox (requires feature): - --ldd | -l - - unsandbox: - --no-fs | -nf - --no-net | -nn - - isolate namespace: - --namespace | -n [specifier] - where specifiers are: - m - mount - h - host/uts - i - ipc - n - network - p - pid - u - user - c - cgroup - t - time - a - all - - change root: - --chroot | -c - - accept additional rules from fd 3: - --fd-args | -fd - - -access specifiers ------------- - fs: - r - read - w - write - x - execute - i - ioctl - d - set as device (if using mount namespace) - - tcp: - i - in/bind - o - out/connect - -examples ------------- - yoke --fs r=/etc -l -- ls /etc - - yoke --tcp io=80:443 --fs r=/srv/web --clear-env --env SERVE_FROM=/srv/web \ - -- myhttpserver - " - ); -} diff --git a/src/namespaces.rs b/src/namespaces.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/namespaces.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/parse.rs b/src/parse.rs index 3acea4a..cd005d0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,4 +1,4 @@ -use crate::types::{Direction, Permissions, Unshare, Yoke}; +use crate::types::{Direction, Permissions, Yoke}; use anyhow::{Context, Result, anyhow}; use std::{collections::HashMap, path::PathBuf, str::FromStr}; @@ -6,67 +6,9 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; enum Arg { Unset, Filesystem, - Net, + Tcp, Env, Exec, - Namespace, - // Chroot, -} - -// fn chroot_parse(paths: &[String]) -> Result { -// if paths.len() > 1 { -// return Err(anyhow!("only one chroot path may be provided")); -// } -// Ok(PathBuf::from_str(&paths[0])?.canonicalize()?) -// } - -fn namespace_parse(flags: &[String]) -> Result { - let mut unshare = Unshare::default(); - for set in flags { - for c in set.chars() { - match c { - 'm' => { - unshare.mount = true; - } - 'h' => { - unshare.uts = true; - } - 'i' => { - unshare.ipc = true; - } - 'n' => { - unshare.net = true; - } - 'p' => { - unshare.pid = true; - } - 'u' => { - unshare.user = true; - } - 'c' => { - unshare.cgroup = true; - } - 't' => { - unshare.time = true; - } - 'a' => { - unshare.mount = true; - unshare.uts = true; - unshare.ipc = true; - unshare.net = true; - unshare.pid = true; - unshare.user = true; - unshare.cgroup = true; - unshare.time = true; - return Ok(unshare); - } - _ => { - return Err(anyhow!("invalid unshare flag specified")); - } - } - } - } - Ok(unshare) } fn env_parse(pairs: &[String]) -> Result> { @@ -104,9 +46,6 @@ fn fs_parse(pairs: &[String]) -> Result>> { 'i' => { perms.ioctl = true; } - 'd' => { - perms.dev = true; - } s => return Err(anyhow!("invalid access specifier {}", s)), } } @@ -124,10 +63,10 @@ fn fs_parse(pairs: &[String]) -> Result>> { Ok(rules) } -fn net_parse(pairs: &[String]) -> Result>> { +fn tcp_parse(pairs: &[String]) -> Result>> { let mut rules = HashMap::new(); for pair in pairs { - let (s_io, s_port) = pair.split_once('=').context("invalid net pair")?; + let (s_io, s_port) = pair.split_once('=').context("invalid tcp pair")?; let mut dir = Direction::default(); for c in s_io.chars() { match c { @@ -138,7 +77,7 @@ fn net_parse(pairs: &[String]) -> Result>> { dir.outbound = true; } _ => { - return Err(anyhow!("invalid net specifier")); + return Err(anyhow!("invalid tcp specifier")); } } } @@ -164,14 +103,9 @@ pub fn parse_args(args: impl Iterator) -> Result { match cur_arg { Unset => (), Filesystem => yoke.fs.extend(fs_parse(collector)?), - Net => yoke.net.extend(net_parse(collector)?), + Tcp => yoke.tcp.extend(tcp_parse(collector)?), Env => yoke.env.extend(env_parse(collector)?), Exec => yoke.exec.extend(collector.iter().cloned()), - Namespace => { - yoke.unshare = namespace_parse(collector)?; - } // Chroot => { - // yoke.chroot = Some(chroot_parse(collector)?); - // } } Ok(()) } @@ -186,18 +120,13 @@ pub fn parse_args(args: impl Iterator) -> Result { "--tcp" | "-t" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); - cur_arg = Net; + cur_arg = Tcp; } "--env" | "-e" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); cur_arg = Env; } - "--namespace" | "-n" => { - collect_args(&mut yoke, &collector, &cur_arg)?; - collector.clear(); - cur_arg = Namespace; - } "--sockets" | "-s" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); @@ -216,18 +145,7 @@ pub fn parse_args(args: impl Iterator) -> Result { cur_arg = Unset; yoke.retain_env = true; } - "--best-effort" | "-b" => { - collect_args(&mut yoke, &collector, &cur_arg)?; - collector.clear(); - cur_arg = Unset; - yoke.besteffort = true; - } - "--chroot" | "-c" => { - collect_args(&mut yoke, &collector, &cur_arg)?; - collector.clear(); - cur_arg = Unset; - yoke.chroot = true; - } + #[cfg(feature = "cli")] "--ldd" | "-l" => { collect_args(&mut yoke, &collector, &cur_arg)?; @@ -242,11 +160,11 @@ pub fn parse_args(args: impl Iterator) -> Result { cur_arg = Unset; yoke.unsandbox.fs = true; } - "--no-net" | "-nn" => { + "--no-tcp" | "-nt" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); cur_arg = Unset; - yoke.unsandbox.net = true; + yoke.unsandbox.tcp = true; } "--fd-args" | "-fd" => { collect_args(&mut yoke, &collector, &cur_arg)?; diff --git a/src/types.rs b/src/types.rs index ca54463..3cdf8d0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,27 +2,23 @@ use anyhow::Result; use std::{collections::HashMap, path::PathBuf}; #[derive(Debug, Default)] -pub struct Yoke { - pub fs: HashMap>, - pub net: HashMap>, - pub env: HashMap, - pub binds: HashMap>, - pub unshare: Unshare, - pub unsandbox: Unsandbox, - pub chroot: bool, - pub retain_env: bool, - pub signals: bool, - pub sockets: bool, - pub ldd: bool, - pub fd_args: bool, - pub exec: Vec, - pub besteffort: bool, +pub struct Unsandbox { + pub fs: bool, + pub tcp: bool, } #[derive(Debug, Default)] -pub struct Unsandbox { - pub fs: bool, - pub net: bool, +pub struct Yoke { + pub fs: HashMap>, + pub tcp: HashMap>, + pub env: HashMap, + pub retain_env: bool, + pub signals: bool, + pub sockets: bool, + pub unsandbox: Unsandbox, + pub ldd: bool, + pub fd_args: bool, + pub exec: Vec, } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)] @@ -31,7 +27,6 @@ pub struct Permissions { pub write: bool, pub execute: bool, pub ioctl: bool, - pub dev: bool, } #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Default)] @@ -40,22 +35,10 @@ pub struct Direction { pub outbound: bool, } -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Default)] -pub struct Unshare { - pub mount: bool, - pub uts: bool, - pub ipc: bool, - pub net: bool, - pub pid: bool, - pub user: bool, - pub cgroup: bool, - pub time: bool, -} - impl Yoke { pub fn merge(&mut self, other: Yoke) { self.fs.extend(other.fs); - self.net.extend(other.net); + self.tcp.extend(other.tcp); self.env.extend(other.env); } }