expand permissions system, allow escapes
This commit is contained in:
parent
861de08a9b
commit
d315bc5663
3 changed files with 73 additions and 37 deletions
60
src/main.rs
60
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)?)
|
||||
|
|
|
|||
31
src/parse.rs
31
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<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();
|
||||
|
|
|
|||
19
src/types.rs
19
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<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,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue