diff --git a/src/main.rs b/src/main.rs index cfcc4b3..f003e6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ mod parse; mod types; -use std::collections::VecDeque; +use std::{collections::VecDeque, path::PathBuf, str::FromStr}; use anyhow::{Context, Result, anyhow}; use elb_dl::{DependencyTree, DynamicLoader, glibc}; @@ -12,6 +12,7 @@ use landlock::{ fn main() -> Result<()> { let opts = parse::parse_args()?; if opts.exec.is_empty() { + // print help eprintln!( " yoke -- simple command sandboxer @@ -68,17 +69,26 @@ examples ); std::process::exit(1); } + + // set up scope of our intial ruleset let mut preempt = Ruleset::default(); + // TODO FIXME set up for lesser ABI versions preempt = preempt.set_compatibility(landlock::CompatLevel::HardRequirement); + + // disallow signals to other processes if !opts.signals { preempt = preempt.scope(Scope::Signal).context("scoping signals")?; } + + // disallow connections to unix domain sockets if !opts.sockets { preempt = preempt .scope(Scope::AbstractUnixSocket) .context("scoping sockets")?; } + + // lock down fs access preempt = if !opts.unsandbox.fs { preempt .handle_access(AccessFs::from_all(ABI::V6)) @@ -86,6 +96,8 @@ examples } else { preempt }; + + // lock down tcp access preempt = if !opts.unsandbox.tcp { preempt .handle_access(AccessNet::BindTcp) @@ -95,7 +107,11 @@ examples } else { preempt }; + + // create ruleset and begin inserting rules let mut ruleset = preempt.create().context("creating ruleset")?; + + // allow each path specified, grouped by access specifier for (perms, paths) in opts.fs { let mut access = BitFlags::empty(); if perms.read { @@ -131,6 +147,8 @@ examples .add_rules(path_beneath_rules(paths, access)) .context("adding fs rule")?; } + + // allow each tcp action specified, grouped by access specifier for (dir, ports) in opts.tcp { let mut access = BitFlags::empty(); if dir.inbound { @@ -145,16 +163,24 @@ examples .context("adding tcp rule")?; } } + + // locate our executable let fullpath = which::which(&opts.exec[0]).context("finding executable")?; + // add executeable as read+execute if !opts.unsandbox.fs { ruleset = ruleset.add_rules(path_beneath_rules( std::slice::from_ref(&fullpath), AccessFs::from_read(ABI::V6), ))?; } + + // if requested, trace dependencies and add as read+execute if opts.ldd { let loader = DynamicLoader::options() .search_dirs(glibc::get_hard_coded_search_dirs(None)?) + .search_dirs(glibc::get_search_dirs( + PathBuf::from_str("/").context("finding root")?, + )?) .new_loader(); let mut tree = DependencyTree::new(); let mut queue = VecDeque::new(); @@ -173,11 +199,17 @@ examples )) .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() { unsafe { @@ -185,6 +217,8 @@ examples } } } + + // add specified env vars if !opts.env.is_empty() { for (k, v) in opts.env { unsafe { @@ -192,6 +226,8 @@ examples } } } + + // execute and hopefully never return let err = cmd.exec(); eprintln!("failed to run process: {}", err); Ok(())