From d315bc5663b04f2b6a3812b8d07d945323d72e85 Mon Sep 17 00:00:00 2001 From: atagen Date: Mon, 3 Nov 2025 15:16:44 +1100 Subject: [PATCH 1/4] expand permissions system, allow escapes --- src/main.rs | 60 +++++++++++++++++++++++++++++++++++----------------- src/parse.rs | 31 +++++++++++++++++++-------- src/types.rs | 19 +++++++++-------- 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/main.rs b/src/main.rs index f315ec4..cfcc4b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,6 @@ use landlock::{ RulesetCreatedAttr, Scope, make_bitflags, path_beneath_rules, }; -use crate::types::BasePermission; - fn main() -> Result<()> { let opts = parse::parse_args()?; if opts.exec.is_empty() { @@ -43,13 +41,18 @@ rule types resolve process dependencies and add to sandbox: --ldd | -l + unsandbox: + --no-fs | -nf + --no-tcp | -nt + specifiers ------------ fs: - r - read (implies execute) - w - write (implies read+execute) - i - allow ioctls (implies nothing - may require read or write) + r - read + w - write + x - execute + i - ioctl tcp: i - in/bind @@ -76,16 +79,14 @@ examples .scope(Scope::AbstractUnixSocket) .context("scoping sockets")?; } - let is_fs = !opts.fs.is_empty(); - let is_tcp = !opts.tcp.is_empty(); - preempt = if is_fs { + preempt = if !opts.unsandbox.fs { preempt .handle_access(AccessFs::from_all(ABI::V6)) .context("handling fs access")? } else { preempt }; - preempt = if is_tcp { + preempt = if !opts.unsandbox.tcp { preempt .handle_access(AccessNet::BindTcp) .context("handling tcp bind access")? @@ -96,13 +97,32 @@ examples }; let mut ruleset = preempt.create().context("creating ruleset")?; for (perms, paths) in opts.fs { - let mut access = match perms.base { - BasePermission::Unset => BitFlags::empty(), - BasePermission::Read => AccessFs::from_read(ABI::V6), - BasePermission::Write => AccessFs::from_write(ABI::V6), - }; + let mut access = BitFlags::empty(); + if perms.read { + access.insert(make_bitflags!(AccessFs::{ReadFile | ReadDir})); + } + if perms.write { + access.insert(make_bitflags!( + AccessFs::{ WriteFile + | RemoveDir + | RemoveFile + | MakeChar + | MakeDir + | MakeReg + | MakeSock + | MakeFifo + | MakeBlock + | MakeSym + | Refer + | Truncate + } + )); + } + if perms.execute { + access.insert(AccessFs::Execute); + } if perms.ioctl { - access.insert(make_bitflags!(AccessFs::IoctlDev)); + access.insert(AccessFs::IoctlDev); } if access == BitFlags::empty() { return Err(anyhow!("invalid filesystem permissions requested")); @@ -126,10 +146,12 @@ examples } } let fullpath = which::which(&opts.exec[0]).context("finding executable")?; - ruleset = ruleset.add_rules(path_beneath_rules( - std::slice::from_ref(&fullpath), - AccessFs::from_read(ABI::V6), - ))?; + if !opts.unsandbox.fs { + ruleset = ruleset.add_rules(path_beneath_rules( + std::slice::from_ref(&fullpath), + AccessFs::from_read(ABI::V6), + ))?; + } if opts.ldd { let loader = DynamicLoader::options() .search_dirs(glibc::get_hard_coded_search_dirs(None)?) diff --git a/src/parse.rs b/src/parse.rs index 6e38e62..a369689 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,5 +1,5 @@ -use crate::types::{BasePermission, Direction, Permissions, Yoke}; -use anyhow::{Context, Result, anyhow}; +use crate::types::{Direction, Permissions, Yoke}; +use anyhow::{anyhow, Context, Result}; use std::{collections::HashMap, path::PathBuf, str::FromStr}; #[derive(PartialEq)] @@ -32,17 +32,18 @@ fn fs_parse(pairs: &[String]) -> Result>> { .ok_or(anyhow!("invalid filesystem pair"))?; let mut perms = Permissions::default(); - - use BasePermission::*; for c in s_perm.chars() { match c { - 'r' | 'R' if perms.base == Unset => { - perms.base = Read; + 'r' => { + perms.read = true; } - 'w' | 'W' => { - perms.base = Write; + 'w' => { + perms.write = true; } - 'i' | 'I' => { + 'x' => { + perms.execute = true; + } + 'i' => { perms.ioctl = true; } s => return Err(anyhow!("invalid access specifier {}", s)), @@ -150,6 +151,18 @@ pub fn parse_args() -> Result { cur_arg = Unset; yoke.ldd = true; } + "--no-fs" | "-nf" => { + collect_args(&mut yoke, &collector, &cur_arg)?; + collector.clear(); + cur_arg = Unset; + yoke.unsandbox.fs = true; + } + "--no-tcp" | "-nt" => { + collect_args(&mut yoke, &collector, &cur_arg)?; + collector.clear(); + cur_arg = Unset; + yoke.unsandbox.tcp = true; + } "--" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); diff --git a/src/types.rs b/src/types.rs index 50876bc..fffb4be 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,11 @@ use std::{collections::HashMap, path::PathBuf}; +#[derive(Debug, Default)] +pub struct Unsandbox { + pub fs: bool, + pub tcp: bool, +} + #[derive(Debug, Default)] pub struct Yoke { pub fs: HashMap>, @@ -8,21 +14,16 @@ pub struct Yoke { pub retain_env: bool, pub signals: bool, pub sockets: bool, + pub unsandbox: Unsandbox, pub ldd: bool, pub exec: Vec, } -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)] -pub enum BasePermission { - #[default] - Unset, - Read, - Write, -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)] pub struct Permissions { - pub base: BasePermission, + pub read: bool, + pub write: bool, + pub execute: bool, pub ioctl: bool, } From 5766b0cdfe9bf43b84dd1cc662bd8c3ef1a246c3 Mon Sep 17 00:00:00 2001 From: atagen Date: Mon, 3 Nov 2025 16:24:15 +1100 Subject: [PATCH 2/4] comment main rountine actions --- src/main.rs | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) 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(()) From ab083b07e4246f398565f1294250db5c7f2381dc Mon Sep 17 00:00:00 2001 From: atagen Date: Mon, 3 Nov 2025 16:41:38 +1100 Subject: [PATCH 3/4] make ldd / where features optional --- Cargo.toml | 14 ++++++++++---- src/main.rs | 24 +++++++++++++++--------- src/parse.rs | 5 ++++- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5124b9..ceb4fdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,17 @@ name = "yoke" version = "0.1.0" authors = [ "atagen" ] -description = "A simple sandboxing tool, similar to bwrap" +description = "CLI sandboxing tool similar to landrun or bwrap" repository = "https://git.atagen.co/atagen/yoke" license = "GPL-3.0-or-later" edition = "2024" +[features] +default = [] +ldd = ["dep:elb-dl"] +which = ["dep:which"] + + [profile.release] strip = true opt-level = "s" @@ -14,7 +20,7 @@ codegen-units = 1 [dependencies] anyhow = "1.0.100" -elb-dl = { version = "0.3.2", features = ["glibc"], default-features = false } -exec = "0.3.1" landlock = "0.4.3" -which = "8.0.0" +exec = "0.3.1" +elb-dl = { version = "0.3.2", features = ["glibc"], default-features = false, optional = true } +which = { version = "8.0.0", optional = true } diff --git a/src/main.rs b/src/main.rs index f003e6c..279dc90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,28 @@ mod parse; mod types; -use std::{collections::VecDeque, path::PathBuf, str::FromStr}; use anyhow::{Context, Result, anyhow}; -use elb_dl::{DependencyTree, DynamicLoader, glibc}; use landlock::{ ABI, Access, AccessFs, AccessNet, BitFlags, Compatible, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr, Scope, make_bitflags, path_beneath_rules, }; +#[cfg(feature = "ldd")] +use elb_dl::{DependencyTree, DynamicLoader, glibc}; +#[cfg(feature = "ldd")] +use std::{collections::VecDeque, path::PathBuf, str::FromStr}; + fn main() -> Result<()> { let opts = parse::parse_args()?; if opts.exec.is_empty() { // print help eprintln!( " -yoke -- simple command sandboxer +yoke -- simple sandboxer use: yoke [ruletype] [space separated rules] -- [command] -rule types +rules ------------ filesystem: --fs | -f [access]=/path:/another/path @@ -39,7 +42,8 @@ rule types allow sending signals to other processes: --signals | -k - resolve process dependencies and add to sandbox: + resolve process dependencies and add to sandbox + (with `ldd` feature): --ldd | -l unsandbox: @@ -47,7 +51,7 @@ rule types --no-tcp | -nt -specifiers +access specifiers ------------ fs: r - read @@ -165,7 +169,11 @@ examples } // locate our executable + #[cfg(feature = "which")] let fullpath = which::which(&opts.exec[0]).context("finding executable")?; + #[cfg(not(feature = "which"))] + let fullpath = &opts.exec[0]; + // add executeable as read+execute if !opts.unsandbox.fs { ruleset = ruleset.add_rules(path_beneath_rules( @@ -175,12 +183,10 @@ examples } // if requested, trace dependencies and add as read+execute + #[cfg(feature = "ldd")] 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(); diff --git a/src/parse.rs b/src/parse.rs index a369689..0ce3731 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,5 +1,5 @@ use crate::types::{Direction, Permissions, Yoke}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use std::{collections::HashMap, path::PathBuf, str::FromStr}; #[derive(PartialEq)] @@ -145,12 +145,15 @@ pub fn parse_args() -> Result { cur_arg = Unset; yoke.retain_env = true; } + + #[cfg(feature = "ldd")] "--ldd" | "-l" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); cur_arg = Unset; yoke.ldd = true; } + "--no-fs" | "-nf" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); From 8a9b61099409e08478ecf1c42eaf74eb25ea109e Mon Sep 17 00:00:00 2001 From: atagen Date: Thu, 6 Nov 2025 17:06:03 +1100 Subject: [PATCH 4/4] rework features, add abi support --- Cargo.toml | 16 +++++-- flake.nix | 32 +++++-------- nix/package.nix | 45 ++++++++++++++++++ src/main.rs | 118 +++++++++++++++++++++++++++++++++++------------- src/parse.rs | 14 ++++-- src/types.rs | 10 ++++ 6 files changed, 174 insertions(+), 61 deletions(-) create mode 100644 nix/package.nix diff --git a/Cargo.toml b/Cargo.toml index ceb4fdd..f7e987c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,21 +2,27 @@ name = "yoke" version = "0.1.0" authors = [ "atagen" ] -description = "CLI sandboxing tool similar to landrun or bwrap" +description = "CLI sandboxing tool (similar to landrun or bwrap)" repository = "https://git.atagen.co/atagen/yoke" license = "GPL-3.0-or-later" edition = "2024" [features] -default = [] -ldd = ["dep:elb-dl"] -which = ["dep:which"] - +default = [ "abi-6", "cli" ] +cli = ["dep:elb-dl", "dep:which"] +nix = [] +abi-1 = [] +abi-2 = [] +abi-3 = [] +abi-4 = [] +abi-5 = [] +abi-6 = [] [profile.release] strip = true opt-level = "s" codegen-units = 1 +lto = true [dependencies] anyhow = "1.0.100" diff --git a/flake.nix b/flake.nix index bced813..faa6da5 100644 --- a/flake.nix +++ b/flake.nix @@ -26,26 +26,18 @@ }); packages = forAllSystems (pkgs: { default = self.packages.${pkgs.system}.yoke; - yoke = - let - details = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package; - lib = pkgs.lib; - in - pkgs.rustPlatform.buildRustPackage (finalAttrs: { - pname = details.name; - inherit (details) version; - src = ./.; - cargoLock.lockFile = ./Cargo.lock; - RUSTFLAGS = "-C prefer-dynamic=yes"; - - meta = { - description = details.description; - homepage = details.repository; - license = lib.licenses.gpl3Plus; - maintainers = [ lib.maintainers.atagen ]; - mainProgram = details.name; - }; - }); + yoke = pkgs.rustPlatform.callPackage ./nix/package.nix { + features = [ + "cli" + ]; + }; + yoke-lite = pkgs.rustPlatform.callPackage ./nix/package.nix { }; }); + nixosModules.default = + { pkgs, ... }: + { + imports = [ ./nix/module.nix ]; + programs.yoke.package = self.packages.${pkgs.system}.yoke; + }; }; } diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..a2c9cd6 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,45 @@ +{ + lib, + rustPlatform, + features ? [ "cli" ], + abi ? 6, + ... +}: +let + details = (builtins.fromTOML (builtins.readFile ../Cargo.toml)).package; +in +rustPlatform.buildRustPackage (finalAttrs: { + pname = details.name; + inherit (details) version; + src = lib.cleanSourceWith { + src = lib.cleanSource ../.; + filter = + path_t: type: + let + path = toString path_t; + baseName = baseNameOf path; + parentDir = baseNameOf (dirOf path); + matchesSuffix = lib.any (suffix: lib.hasSuffix suffix baseName) [ + ".rs" + ".toml" + ]; + isCargoFile = baseName == "Cargo.lock"; + isCargoConf = parentDir == ".cargo" && baseName == "config"; + in + type == "directory" || matchesSuffix || isCargoFile || isCargoConf; + }; + cargoLock.lockFile = ../Cargo.lock; + buildNoDefaultFeatures = true; + buildFeatures = features ++ [ + "nix" + "abi-${toString abi}" + ]; + + meta = { + description = details.description; + homepage = details.repository; + license = lib.licenses.gpl3Plus; + maintainers = [ lib.maintainers.atagen ]; + mainProgram = details.name; + }; +}) diff --git a/src/main.rs b/src/main.rs index 279dc90..3260a38 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,26 @@ use landlock::{ RulesetCreatedAttr, Scope, make_bitflags, path_beneath_rules, }; -#[cfg(feature = "ldd")] +#[cfg(feature = "cli")] use elb_dl::{DependencyTree, DynamicLoader, glibc}; -#[cfg(feature = "ldd")] +#[cfg(feature = "cli")] use std::{collections::VecDeque, path::PathBuf, str::FromStr}; +use std::{fs::File, io::Read, os::fd::FromRawFd}; fn main() -> Result<()> { - let opts = parse::parse_args()?; + 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.exec.is_empty() { // print help eprintln!( @@ -50,6 +63,9 @@ rules --no-fs | -nf --no-tcp | -nt + accept additional rules from fd 3: + --fd-args | -fd + access specifiers ------------ @@ -77,16 +93,26 @@ examples // 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); + #[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 { + if !opts.signals && abi >= ABI::V6 { preempt = preempt.scope(Scope::Signal).context("scoping signals")?; } // disallow connections to unix domain sockets - if !opts.sockets { + if !opts.sockets && abi >= ABI::V6 { preempt = preempt .scope(Scope::AbstractUnixSocket) .context("scoping sockets")?; @@ -95,14 +121,14 @@ examples // lock down fs access preempt = if !opts.unsandbox.fs { preempt - .handle_access(AccessFs::from_all(ABI::V6)) + .handle_access(AccessFs::from_all(abi)) .context("handling fs access")? } else { preempt }; // lock down tcp access - preempt = if !opts.unsandbox.tcp { + preempt = if !(opts.unsandbox.tcp || abi < ABI::V3) { preempt .handle_access(AccessNet::BindTcp) .context("handling tcp bind access")? @@ -122,8 +148,9 @@ examples access.insert(make_bitflags!(AccessFs::{ReadFile | ReadDir})); } if perms.write { - access.insert(make_bitflags!( - AccessFs::{ WriteFile + let mut flags = make_bitflags!( + AccessFs::{ + WriteFile | RemoveDir | RemoveFile | MakeChar @@ -133,19 +160,33 @@ examples | MakeFifo | MakeBlock | MakeSym - | Refer - | Truncate } - )); + ); + match abi { + ABI::V2 => { + flags.insert(AccessFs::Refer); + } + _ if abi >= ABI::V3 => { + flags.insert(AccessFs::Refer | AccessFs::Truncate); + } + _ => (), + }; + access.insert(flags); } if perms.execute { access.insert(AccessFs::Execute); } if perms.ioctl { - access.insert(AccessFs::IoctlDev); + if abi >= ABI::V6 { + access.insert(AccessFs::IoctlDev); + } else { + return Err(anyhow!( + "ioctl is only available on Landlock ABI 6 or higher" + )); + } } if access == BitFlags::empty() { - return Err(anyhow!("invalid filesystem permissions requested")); + return Err(anyhow!("invalid/empty filesystem permissions requested")); } ruleset = ruleset .add_rules(path_beneath_rules(paths, access)) @@ -153,41 +194,54 @@ examples } // allow each tcp action specified, grouped by access specifier - 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")?; + 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" + )); } // locate our executable - #[cfg(feature = "which")] + #[cfg(feature = "cli")] let fullpath = which::which(&opts.exec[0]).context("finding executable")?; - #[cfg(not(feature = "which"))] + #[cfg(not(feature = "cli"))] let fullpath = &opts.exec[0]; // 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), + AccessFs::from_read(abi), ))?; } // if requested, trace dependencies and add as read+execute - #[cfg(feature = "ldd")] + #[cfg(feature = "cli")] if opts.ldd { + #[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()); @@ -201,7 +255,7 @@ examples acc.extend(deps); acc }), - AccessFs::from_read(ABI::V6), + AccessFs::from_read(abi), )) .context("tracking dependencies")?; } diff --git a/src/parse.rs b/src/parse.rs index 0ce3731..cd005d0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -94,7 +94,7 @@ fn tcp_parse(pairs: &[String]) -> Result>> { Ok(rules) } -pub fn parse_args() -> Result { +pub fn parse_args(args: impl Iterator) -> Result { let mut yoke = Yoke::default(); let mut collector = Vec::new(); let mut cur_arg = Unset; @@ -109,7 +109,7 @@ pub fn parse_args() -> Result { } Ok(()) } - for mut arg in std::env::args().skip(1) { + for mut arg in args { arg.make_ascii_lowercase(); match arg.as_str() { "--fs" | "-f" => { @@ -146,7 +146,7 @@ pub fn parse_args() -> Result { yoke.retain_env = true; } - #[cfg(feature = "ldd")] + #[cfg(feature = "cli")] "--ldd" | "-l" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); @@ -166,13 +166,19 @@ pub fn parse_args() -> Result { cur_arg = Unset; yoke.unsandbox.tcp = true; } + "--fd-args" | "-fd" => { + collect_args(&mut yoke, &collector, &cur_arg)?; + collector.clear(); + cur_arg = Unset; + yoke.fd_args = true; + } "--" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); cur_arg = Exec; } _ if cur_arg != Unset => { - collector.push(arg.clone()); + collector.push(arg.to_string()); } a => { return Err(anyhow!("invalid argument: {}", a)); diff --git a/src/types.rs b/src/types.rs index fffb4be..3cdf8d0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use std::{collections::HashMap, path::PathBuf}; #[derive(Debug, Default)] @@ -16,6 +17,7 @@ pub struct Yoke { pub sockets: bool, pub unsandbox: Unsandbox, pub ldd: bool, + pub fd_args: bool, pub exec: Vec, } @@ -32,3 +34,11 @@ pub struct Direction { pub inbound: bool, pub outbound: bool, } + +impl Yoke { + pub fn merge(&mut self, other: Yoke) { + self.fs.extend(other.fs); + self.tcp.extend(other.tcp); + self.env.extend(other.env); + } +}