Skip to content

Commit

Permalink
feat: add landlock based access restriction functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
vlaci committed Jun 2, 2023
1 parent 65988e1 commit df08473
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 5 deletions.
78 changes: 73 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ crate-type = [
]

[dependencies]
log = "0.4.18"
pyo3 = "0.18.3"
pyo3-log = "0.8.1"

[target.'cfg(target_os = "linux")'.dependencies]
landlock = "0.2.0"

[dev-dependencies]
approx = "0.5.0"
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
pub mod math_tools;
pub mod sandbox;

use pyo3::prelude::*;

/// Performance-critical functionality
#[pymodule]
fn _native(py: Python, m: &PyModule) -> PyResult<()> {
math_tools::init_module(py, m)?;
sandbox::init_module(py, m)?;

pyo3_log::init();

Ok(())
}
120 changes: 120 additions & 0 deletions src/sandbox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#[cfg(target_os = "linux")]
use landlock::{
path_beneath_rules, Access, AccessFs, Ruleset, RulesetAttr, RulesetCreatedAttr, ABI,
};
use pyo3::{create_exception, exceptions::PyException, prelude::*, types::PyTuple};

use log;

#[derive(Clone, Debug)]
pub enum FSAccess {
Read(String),
ReadWrite(String),
}

#[cfg(target_os = "linux")]
impl FSAccess {
fn read(&self) -> Option<&str> {
if let Self::Read(path) = self {
Some(path)
} else {
None
}
}
fn read_write(&self) -> Option<&str> {
if let Self::ReadWrite(path) = self {
Some(path)
} else {
None
}
}
}

pub fn restrict_access(access_rules: &[FSAccess]) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_os = "linux")]
{
let abi = ABI::V1;

let read_only: Vec<&str> = access_rules.iter().filter_map(FSAccess::read).collect();

let read_write: Vec<&str> = access_rules
.iter()
.filter_map(FSAccess::read_write)
.collect();

let status = Ruleset::new()
.handle_access(AccessFs::from_all(abi))?
.create()?
.add_rules(path_beneath_rules(read_only, AccessFs::from_read(abi)))?
.add_rules(path_beneath_rules(read_write, AccessFs::from_all(abi)))?
.restrict_self()?;

log::info!(
"Activated FS access restrictions; rules={:?}, status={:?}",
access_rules,
status.ruleset
);
}

#[cfg(not(target_os = "linux"))]
log::warn!("Sandboxing FS access is unavailable on this system");

Ok(())
}

/// Enforces access restrictions
#[pyfunction(name = "restrict_access", signature=(*rules))]
fn py_restrict_access(_py: Python, rules: &PyTuple) -> PyResult<()> {
restrict_access(
&rules
.iter()
.map(|r| Ok(r.extract::<PyFSAccess>()?.access))
.collect::<PyResult<Vec<_>>>()?,
)
.map_err(|err| SandboxError::new_err(err.to_string()))
}

create_exception!(unblob_native.sandbox, SandboxError, PyException);

#[pyclass(name = "FSAccess", module = "unblob_native.sandbox")]
#[derive(Clone)]
struct PyFSAccess {
access: FSAccess,
}

impl PyFSAccess {
fn new(access: FSAccess) -> Self {
Self { access }
}
}

#[pymethods]
impl PyFSAccess {
#[staticmethod]
fn read(dir: String) -> Self {
Self::new(FSAccess::Read(dir))
}

#[staticmethod]
fn read_write(dir: String) -> Self {
Self::new(FSAccess::ReadWrite(dir))
}
}

pub fn init_module(py: Python, root_module: &PyModule) -> PyResult<()> {
let module = PyModule::new(py, "sandbox")?;
module.add_function(wrap_pyfunction!(py_restrict_access, module)?)?;
module.add_class::<PyFSAccess>()?;

root_module.add_submodule(module)?;

let sys = PyModule::import(py, "sys")?;
let modules = sys.getattr("modules")?;
modules.call_method(
"__setitem__",
("unblob_native.sandbox".to_string(), module),
None,
)?;

Ok(())
}

0 comments on commit df08473

Please sign in to comment.