expand permissions system, allow escapes

This commit is contained in:
atagen 2025-11-03 15:16:44 +11:00
parent 36d52304cb
commit 39ecba0170
3 changed files with 72 additions and 42 deletions

View file

@ -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")?
@ -97,18 +98,31 @@ examples
let mut ruleset = preempt.create().context("creating ruleset")?;
for (perms, paths) in opts.fs {
let mut access = BitFlags::empty();
match perms.base {
BasePermission::Unset => {}
BasePermission::Read => {
access = AccessFs::from_read(ABI::V6);
}
BasePermission::Write => {
access = AccessFs::from_write(ABI::V6);
access.insert(AccessFs::from_read(ABI::V6));
}
};
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"));
@ -132,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)?)

View file

@ -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<HashMap<Permissions, Vec<PathBuf>>> {
.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<Yoke> {
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();

View file

@ -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<Permissions, Vec<PathBuf>>,
@ -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<String>,
}
#[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,
}