diff --git a/Cargo.toml b/Cargo.toml index f7e987c..ceb4fdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,27 +2,21 @@ 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 = [ "abi-6", "cli" ] -cli = ["dep:elb-dl", "dep:which"] -nix = [] -abi-1 = [] -abi-2 = [] -abi-3 = [] -abi-4 = [] -abi-5 = [] -abi-6 = [] +default = [] +ldd = ["dep:elb-dl"] +which = ["dep:which"] + [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 faa6da5..bced813 100644 --- a/flake.nix +++ b/flake.nix @@ -26,18 +26,26 @@ }); packages = forAllSystems (pkgs: { default = self.packages.${pkgs.system}.yoke; - yoke = pkgs.rustPlatform.callPackage ./nix/package.nix { - features = [ - "cli" - ]; - }; - yoke-lite = pkgs.rustPlatform.callPackage ./nix/package.nix { }; + 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; + }; + }); }); - 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 deleted file mode 100644 index a2c9cd6..0000000 --- a/nix/package.nix +++ /dev/null @@ -1,45 +0,0 @@ -{ - 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 3260a38..b37cd4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,26 +7,13 @@ use landlock::{ RulesetCreatedAttr, Scope, make_bitflags, path_beneath_rules, }; -#[cfg(feature = "cli")] +#[cfg(feature = "ldd")] use elb_dl::{DependencyTree, DynamicLoader, glibc}; -#[cfg(feature = "cli")] +#[cfg(feature = "ldd")] use std::{collections::VecDeque, path::PathBuf, str::FromStr}; -use std::{fs::File, io::Read, os::fd::FromRawFd}; 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); - } + let opts = parse::parse_args()?; if opts.exec.is_empty() { // print help eprintln!( @@ -63,9 +50,6 @@ rules --no-fs | -nf --no-tcp | -nt - accept additional rules from fd 3: - --fd-args | -fd - access specifiers ------------ @@ -93,26 +77,16 @@ 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 && abi >= ABI::V6 { + if !opts.signals { preempt = preempt.scope(Scope::Signal).context("scoping signals")?; } // disallow connections to unix domain sockets - if !opts.sockets && abi >= ABI::V6 { + if !opts.sockets { preempt = preempt .scope(Scope::AbstractUnixSocket) .context("scoping sockets")?; @@ -121,14 +95,14 @@ examples // lock down fs access preempt = if !opts.unsandbox.fs { preempt - .handle_access(AccessFs::from_all(abi)) + .handle_access(AccessFs::from_all(ABI::V6)) .context("handling fs access")? } else { preempt }; // lock down tcp access - preempt = if !(opts.unsandbox.tcp || abi < ABI::V3) { + preempt = if !opts.unsandbox.tcp { preempt .handle_access(AccessNet::BindTcp) .context("handling tcp bind access")? @@ -148,9 +122,8 @@ examples access.insert(make_bitflags!(AccessFs::{ReadFile | ReadDir})); } if perms.write { - let mut flags = make_bitflags!( - AccessFs::{ - WriteFile + access.insert(make_bitflags!( + AccessFs::{ WriteFile | RemoveDir | RemoveFile | MakeChar @@ -160,33 +133,19 @@ 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 { - if abi >= ABI::V6 { - access.insert(AccessFs::IoctlDev); - } else { - return Err(anyhow!( - "ioctl is only available on Landlock ABI 6 or higher" - )); - } + access.insert(AccessFs::IoctlDev); } if access == BitFlags::empty() { - return Err(anyhow!("invalid/empty filesystem permissions requested")); + return Err(anyhow!("invalid filesystem permissions requested")); } ruleset = ruleset .add_rules(path_beneath_rules(paths, access)) @@ -194,54 +153,44 @@ examples } // 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")?; - } + 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 = "cli")] + #[cfg(feature = "which")] let fullpath = which::which(&opts.exec[0]).context("finding executable")?; - #[cfg(not(feature = "cli"))] + #[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( std::slice::from_ref(&fullpath), - AccessFs::from_read(abi), + AccessFs::from_read(ABI::V6), ))?; } // if requested, trace dependencies and add as read+execute - #[cfg(feature = "cli")] + #[cfg(feature = "ldd")] 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)?) + .search_dirs(glibc::get_search_dirs( + PathBuf::from_str("/").context("finding root")?, + )?) .new_loader(); - let mut tree = DependencyTree::new(); let mut queue = VecDeque::new(); queue.push_back(fullpath.clone()); @@ -255,7 +204,7 @@ examples acc.extend(deps); acc }), - AccessFs::from_read(abi), + AccessFs::from_read(ABI::V6), )) .context("tracking dependencies")?; } diff --git a/src/parse.rs b/src/parse.rs index cd005d0..0ce3731 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(args: impl Iterator) -> Result { +pub fn parse_args() -> Result { let mut yoke = Yoke::default(); let mut collector = Vec::new(); let mut cur_arg = Unset; @@ -109,7 +109,7 @@ pub fn parse_args(args: impl Iterator) -> Result { } Ok(()) } - for mut arg in args { + for mut arg in std::env::args().skip(1) { arg.make_ascii_lowercase(); match arg.as_str() { "--fs" | "-f" => { @@ -146,7 +146,7 @@ pub fn parse_args(args: impl Iterator) -> Result { yoke.retain_env = true; } - #[cfg(feature = "cli")] + #[cfg(feature = "ldd")] "--ldd" | "-l" => { collect_args(&mut yoke, &collector, &cur_arg)?; collector.clear(); @@ -166,19 +166,13 @@ pub fn parse_args(args: impl Iterator) -> 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.to_string()); + collector.push(arg.clone()); } a => { return Err(anyhow!("invalid argument: {}", a)); diff --git a/src/types.rs b/src/types.rs index 3cdf8d0..fffb4be 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use std::{collections::HashMap, path::PathBuf}; #[derive(Debug, Default)] @@ -17,7 +16,6 @@ pub struct Yoke { pub sockets: bool, pub unsandbox: Unsandbox, pub ldd: bool, - pub fd_args: bool, pub exec: Vec, } @@ -34,11 +32,3 @@ 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); - } -}