@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
@@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
members = ["bin/semios_sandbox_exec", "lib/semios_sandbox","lib/semios_sandbox_low","lib/semios_sandbox_parser"]
|
||||
resolver = "3"
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "semios_sandbox_exec"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4", default-features = false, features = ["std", "help", "usage", "derive"] }
|
||||
semios_sandbox = { path = "../../lib/semios_sandbox" }
|
||||
@@ -0,0 +1,58 @@
|
||||
use clap::Parser;
|
||||
use semios_sandbox::vm::Vm;
|
||||
use std::{ffi::OsString, os::unix::process::CommandExt, path::PathBuf};
|
||||
|
||||
macro_rules! errx {
|
||||
($($arg:tt)*) => {
|
||||
eprintln!("semios_sandbox_exec: {}", format!($($arg)*));
|
||||
std::process::exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
struct Cli {
|
||||
#[arg(short, long)]
|
||||
rule: PathBuf,
|
||||
|
||||
#[arg(long)]
|
||||
arg0: Option<OsString>,
|
||||
|
||||
cmd: Vec<OsString>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let rule = match std::fs::read_to_string(&cli.rule) {
|
||||
Ok(x) => x,
|
||||
Err(err) => {
|
||||
errx!("failed to read rule \"{}\": {}", cli.rule.display(), err);
|
||||
}
|
||||
};
|
||||
let ast = match semios_sandbox::parser::parse(&rule) {
|
||||
Ok(x) => x,
|
||||
Err(err) => {
|
||||
errx!("failed to parse rule \"{}\": {}", cli.rule.display(), err);
|
||||
}
|
||||
};
|
||||
|
||||
let mut vm = Vm::new();
|
||||
if let Err(err) = vm.exec(&ast) {
|
||||
errx!("failed to execute rule \"{}\": {}", cli.rule.display(), err);
|
||||
};
|
||||
if let Err(err) = vm.finish() {
|
||||
errx!("failed to apply rules: {err}");
|
||||
}
|
||||
|
||||
let Some(path) = cli.cmd.first() else {
|
||||
errx!("no program specified");
|
||||
};
|
||||
let arg0 = cli.arg0.as_ref().unwrap_or(path);
|
||||
let args = &cli.cmd[1..];
|
||||
|
||||
let err = std::process::Command::new(path)
|
||||
.arg0(arg0)
|
||||
.args(args)
|
||||
.exec();
|
||||
|
||||
errx!("failed to execute \"{}\": {}", path.display(), err);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "semios_sandbox"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
semios_sandbox_parser = { path = "../semios_sandbox_parser" }
|
||||
semios_sandbox_low = { path = "../semios_sandbox_low" }
|
||||
rustc-hash = "2"
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod vm;
|
||||
|
||||
pub use semios_sandbox_low as low;
|
||||
pub use semios_sandbox_parser as parser;
|
||||
@@ -0,0 +1,254 @@
|
||||
//! Virtual machine for executing rule code.
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use semios_sandbox_low::{FilesystemOps, FilesystemRule, Rules, apply};
|
||||
use semios_sandbox_parser::{Expr, LitPart, Stmt};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub struct Vm {
|
||||
native_mods: Vec<Box<dyn NativeMod>>,
|
||||
}
|
||||
impl Vm {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
native_mods: vec![Box::new(EnvMod), Box::new(SandboxMod::new())],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(&mut self, ast: &[Stmt]) -> Result<(), VmError> {
|
||||
let mut local_scope = LocalScope::new();
|
||||
for i in ast {
|
||||
match i {
|
||||
Stmt::Let(decl) => {
|
||||
local_scope.declare(&decl.name.0, self.eval(&local_scope, decl.value.clone())?);
|
||||
}
|
||||
Stmt::Expr(expr) => {
|
||||
self.eval(&local_scope, expr.clone())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finish(self) -> Result<(), VmError> {
|
||||
for mut i in self.native_mods {
|
||||
i.finish()
|
||||
.map_err(|err| VmError::NativeError("[anonymous] finish".into(), err))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eval(&mut self, local_scope: &LocalScope, expr: Expr) -> Result<Value, VmError> {
|
||||
match expr {
|
||||
Expr::Lit(x) => {
|
||||
let Some(parts) = semios_sandbox_parser::lit(&x) else {
|
||||
return Err(VmError::LitSyntaxError);
|
||||
};
|
||||
let mut s = String::new();
|
||||
for i in parts {
|
||||
match i {
|
||||
LitPart::Direct(y) => s.push_str(&y),
|
||||
LitPart::Variable(key) => match local_scope.get(&key.0)? {
|
||||
Value::Lit(z) => s.push_str(&z),
|
||||
_ => return Err(VmError::VarAbsent(key.0)),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(Value::Lit(s))
|
||||
}
|
||||
Expr::Var(x) => local_scope.get(&x.0),
|
||||
Expr::Call(x) => {
|
||||
let mut args = Vec::with_capacity(x.args.len());
|
||||
for i in x.args {
|
||||
args.push(self.eval(local_scope, i)?);
|
||||
}
|
||||
for i in &mut self.native_mods {
|
||||
match i.invoke(&x.name.0, &args) {
|
||||
Ok(ret) => return Ok(ret),
|
||||
Err(NativeModError::MethodAbsent) => continue,
|
||||
Err(NativeModError::StdError(err)) => {
|
||||
return Err(VmError::NativeError(x.name.0, err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(VmError::VarAbsent(x.name.0))
|
||||
}
|
||||
Expr::Set(x) => {
|
||||
let mut ret = Vec::with_capacity(x.len());
|
||||
for i in x {
|
||||
ret.push(self.eval(local_scope, i)?);
|
||||
}
|
||||
Ok(Value::Set(ret))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Debug for Vm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Vm").field("native_mods", &()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct LocalScope {
|
||||
map: FxHashMap<String, Value>,
|
||||
}
|
||||
impl LocalScope {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn declare(&mut self, key: &str, val: Value) {
|
||||
self.map.insert(key.into(), val);
|
||||
}
|
||||
|
||||
fn get(&self, key: &str) -> Result<Value, VmError> {
|
||||
self.map
|
||||
.get(key)
|
||||
.cloned()
|
||||
.ok_or_else(|| VmError::MethodAbsent(key.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NativeMod {
|
||||
fn invoke(&mut self, method: &str, args: &[Value]) -> Result<Value, NativeModError>;
|
||||
fn finish(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value {
|
||||
Set(Vec<Value>),
|
||||
Lit(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NativeModError {
|
||||
MethodAbsent,
|
||||
StdError(Box<dyn std::error::Error>),
|
||||
}
|
||||
impl<E: std::error::Error + 'static> From<E> for NativeModError {
|
||||
fn from(value: E) -> Self {
|
||||
Self::StdError(Box::new(value))
|
||||
}
|
||||
}
|
||||
impl Display for NativeModError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MethodAbsent => write!(f, "method is absent"),
|
||||
Self::StdError(err) => write!(f, "{err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VmError {
|
||||
MethodAbsent(String),
|
||||
VarAbsent(String),
|
||||
NativeError(String, Box<dyn std::error::Error>),
|
||||
LitSyntaxError,
|
||||
}
|
||||
impl Display for VmError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MethodAbsent(method) => write!(f, "method is absent: {method}"),
|
||||
Self::VarAbsent(var) => write!(f, "variable is absent: {var}"),
|
||||
Self::NativeError(method, err) => write!(f, "method `{method}` failed: {err}"),
|
||||
Self::LitSyntaxError => write!(f, "literal syntax error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for VmError {}
|
||||
|
||||
struct EnvMod;
|
||||
impl NativeMod for EnvMod {
|
||||
fn invoke(&mut self, method: &str, args: &[Value]) -> Result<Value, NativeModError> {
|
||||
if method == "env" {
|
||||
let Some(Value::Lit(key)) = args.get(0) else {
|
||||
return Err(NativeModError::MethodAbsent);
|
||||
};
|
||||
if let Ok(val) = std::env::var(key) {
|
||||
Ok(Value::Lit(val))
|
||||
} else if let Some(Value::Lit(default)) = args.get(1) {
|
||||
Ok(Value::Lit(default.into()))
|
||||
} else {
|
||||
Err(NativeModError::StdError(Box::from(format!(
|
||||
"Environment `{key}` not found"
|
||||
))))
|
||||
}
|
||||
} else {
|
||||
Err(NativeModError::MethodAbsent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SandboxMod {
|
||||
rules: Rules,
|
||||
}
|
||||
impl SandboxMod {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
rules: Rules::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fs_allow(&mut self, path: &str, sops: &[String]) -> Result<Value, NativeModError> {
|
||||
let fs_op = |x| match x {
|
||||
"read" => Ok(FilesystemOps::READ),
|
||||
"write" => Ok(FilesystemOps::WRITE),
|
||||
"exec" => Ok(FilesystemOps::EXEC),
|
||||
"browse" => Ok(FilesystemOps::BROWSE),
|
||||
"create_reg" => Ok(FilesystemOps::CREATE_REG),
|
||||
"create_dir" => Ok(FilesystemOps::CREATE_DIR),
|
||||
"create_dev" => Ok(FilesystemOps::CREATE_DEV),
|
||||
"create_sym" => Ok(FilesystemOps::CREATE_SYM),
|
||||
"create_ipc" => Ok(FilesystemOps::CREATE_IPC),
|
||||
"remove_file" => Ok(FilesystemOps::REMOVE_FILE),
|
||||
"remove_dir" => Ok(FilesystemOps::REMOVE_DIR),
|
||||
"control_io" => Ok(FilesystemOps::CONTROL_IO),
|
||||
"refer" => Ok(FilesystemOps::REFER),
|
||||
other => Err(NativeModError::StdError(Box::from(format!(
|
||||
"unrecognized filesystem op `{other}`"
|
||||
)))),
|
||||
};
|
||||
|
||||
let mut ops = FilesystemOps::empty();
|
||||
for sop in sops {
|
||||
ops |= fs_op(sop)?;
|
||||
}
|
||||
|
||||
self.rules.filesystem.push(FilesystemRule {
|
||||
path: PathBuf::from(path),
|
||||
ops,
|
||||
});
|
||||
|
||||
Ok(Value::Lit(String::new()))
|
||||
}
|
||||
}
|
||||
impl NativeMod for SandboxMod {
|
||||
fn invoke(&mut self, method: &str, args: &[Value]) -> Result<Value, NativeModError> {
|
||||
match (method, args) {
|
||||
("fs_allow", [Value::Lit(path), Value::Set(ops)]) => {
|
||||
let mut rops = Vec::with_capacity(ops.len());
|
||||
for op in ops {
|
||||
if let Value::Lit(op) = op {
|
||||
rops.push(op.into());
|
||||
} else {
|
||||
return Err(NativeModError::StdError(Box::from("invalid arguments")));
|
||||
}
|
||||
}
|
||||
self.fs_allow(path, &rops)
|
||||
}
|
||||
("fs_allow", [Value::Lit(path), Value::Lit(op)]) => self.fs_allow(path, &[op.into()]),
|
||||
_ => Err(NativeModError::MethodAbsent),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
apply(self.rules.clone()).map_err(Box::from)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "semios_sandbox_low"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
enumflags2 = "0.7"
|
||||
landlock = "0.4"
|
||||
@@ -0,0 +1,73 @@
|
||||
use bitflags::bitflags;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg_attr(target_os = "linux", path = "linux.rs")]
|
||||
mod imp;
|
||||
|
||||
/// Low-level sandbox rules.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rules {
|
||||
pub filesystem: Vec<FilesystemRule>,
|
||||
}
|
||||
impl Rules {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
filesystem: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Filesystem rule set.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FilesystemRule {
|
||||
pub path: PathBuf,
|
||||
pub ops: FilesystemOps,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct FilesystemOps: u32 {
|
||||
/// Read a file.
|
||||
const READ = 1;
|
||||
|
||||
/// Write to a file.
|
||||
const WRITE = 2;
|
||||
|
||||
/// Execute a file.
|
||||
const EXEC = 4;
|
||||
|
||||
/// Read / Browse a directory.
|
||||
const BROWSE = 8;
|
||||
|
||||
/// Create a regular file.
|
||||
const CREATE_REG = 16;
|
||||
|
||||
/// Create a directory.
|
||||
const CREATE_DIR = 32;
|
||||
|
||||
/// Create a symbolic link.
|
||||
const CREATE_SYM = 64;
|
||||
|
||||
/// Create a IPC primitive.
|
||||
const CREATE_IPC = 128;
|
||||
|
||||
/// Create a device file.
|
||||
const CREATE_DEV = 256;
|
||||
|
||||
/// Removes a directory.
|
||||
const REMOVE_DIR = 512;
|
||||
|
||||
/// Removes a file.
|
||||
const REMOVE_FILE = 1024;
|
||||
|
||||
/// Link or rename a file from or to a different directory.
|
||||
const REFER = 2048;
|
||||
|
||||
/// Controls device input / output.
|
||||
const CONTROL_IO = 4096;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply(rules: Rules) -> Result<(), Box<dyn std::error::Error>> {
|
||||
imp::apply_rules(rules)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use crate::{FilesystemOps, Rules};
|
||||
use enumflags2::BitFlag;
|
||||
use landlock::{
|
||||
ABI, Access, AccessFs, BitFlags, RulesetAttr, RulesetCreatedAttr, path_beneath_rules,
|
||||
};
|
||||
|
||||
pub fn apply_rules(rules: Rules) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut ruleset = landlock::Ruleset::default()
|
||||
.handle_access(AccessFs::from_all(ABI::V6))?
|
||||
.create()?;
|
||||
|
||||
for i in rules.filesystem.iter() {
|
||||
ruleset = ruleset.add_rules(path_beneath_rules(
|
||||
[i.path.clone()],
|
||||
landlock_fs_access(i.ops),
|
||||
))?;
|
||||
}
|
||||
|
||||
ruleset.restrict_self()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn landlock_fs_access(x: FilesystemOps) -> BitFlags<AccessFs> {
|
||||
let mut ret = AccessFs::empty();
|
||||
|
||||
if x.contains(FilesystemOps::READ) {
|
||||
ret |= AccessFs::ReadFile;
|
||||
}
|
||||
if x.contains(FilesystemOps::WRITE) {
|
||||
ret |= AccessFs::WriteFile | AccessFs::Truncate;
|
||||
}
|
||||
if x.contains(FilesystemOps::EXEC) {
|
||||
ret |= AccessFs::Execute;
|
||||
}
|
||||
if x.contains(FilesystemOps::BROWSE) {
|
||||
ret |= AccessFs::ReadDir;
|
||||
}
|
||||
if x.contains(FilesystemOps::CREATE_REG) {
|
||||
ret |= AccessFs::MakeReg;
|
||||
}
|
||||
if x.contains(FilesystemOps::CREATE_DIR) {
|
||||
ret |= AccessFs::MakeDir;
|
||||
}
|
||||
if x.contains(FilesystemOps::CREATE_SYM) {
|
||||
ret |= AccessFs::MakeSym;
|
||||
}
|
||||
if x.contains(FilesystemOps::CREATE_DEV) {
|
||||
ret |= AccessFs::MakeBlock | AccessFs::MakeChar;
|
||||
}
|
||||
if x.contains(FilesystemOps::CREATE_IPC) {
|
||||
ret |= AccessFs::MakeSock | AccessFs::MakeFifo;
|
||||
}
|
||||
if x.contains(FilesystemOps::REMOVE_FILE) {
|
||||
ret |= AccessFs::RemoveFile;
|
||||
}
|
||||
if x.contains(FilesystemOps::REMOVE_DIR) {
|
||||
ret |= AccessFs::RemoveDir;
|
||||
}
|
||||
if x.contains(FilesystemOps::CONTROL_IO) {
|
||||
ret |= AccessFs::IoctlDev;
|
||||
}
|
||||
if x.contains(FilesystemOps::REFER) {
|
||||
ret |= AccessFs::Refer;
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "semios_sandbox_parser"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
peg = "0.8"
|
||||
unicode-xid = "0.2"
|
||||
@@ -0,0 +1,179 @@
|
||||
use unicode_xid::UnicodeXID;
|
||||
|
||||
peg::parser! {
|
||||
grammar sandbox() for str {
|
||||
pub rule program() -> Vec<Stmt>
|
||||
= _* stmt:stmt() ** (_*) _* { stmt }
|
||||
|
||||
rule stmt() -> Stmt
|
||||
= x:let_stmt() { Stmt::Let(x) }
|
||||
/ x:expr() _* ";" { Stmt::Expr(x) }
|
||||
|
||||
rule let_stmt() -> StmtLet
|
||||
= "let" _+ name:ident() _* "=" _* value:expr() _* ";" { StmtLet { name, value } }
|
||||
|
||||
rule expr() -> Expr
|
||||
= x:expr_call() { Expr::Call(x) }
|
||||
/ x:lit() { Expr::Lit(x) }
|
||||
/ x:ident() { Expr::Var(x) }
|
||||
/ x:expr_set() { Expr::Set(x) }
|
||||
|
||||
rule expr_call() -> ExprCall
|
||||
= name:ident() _* "(" _* args:expr() ** (_* "," _*) _* ")" { ExprCall { name, args } }
|
||||
|
||||
rule expr_set() -> Vec<Expr>
|
||||
= "{" _* args:expr() ** (_* "," _*) _* "}" { args }
|
||||
|
||||
rule ident() -> Ident
|
||||
= f:([x if UnicodeXID::is_xid_start(x)]) s:([x if UnicodeXID::is_xid_continue(x)])*
|
||||
{ Ident(format!("{}{}", f, s.iter().collect::<String>())) }
|
||||
|
||||
rule lit() -> String
|
||||
= lit_str()
|
||||
/ lit_int()
|
||||
|
||||
rule lit_int() -> String
|
||||
= x:['0'..='9']+ { x.into_iter().collect() }
|
||||
/ "0x" x:['a'..='f' | 'A'..='F' | '0'..='9']+ { "0x".chars().chain(x.into_iter()).collect() }
|
||||
/ "0o" x:['0'..='7']+ { "0o".chars().chain(x.into_iter()).collect() }
|
||||
/ "0b" x:['0'..='1']+ { "0b".chars().chain(x.into_iter()).collect() }
|
||||
|
||||
rule lit_str() -> String
|
||||
= "\"" x:lit_str_double_part()* "\"" { x.iter().collect() }
|
||||
/ "'" x:lit_str_single_part()* "'" { x.iter().collect() }
|
||||
|
||||
rule lit_str_double_part() -> char
|
||||
= x:lit_str_part_escape() { x }
|
||||
/ x:[^ '"' | '\n' | '\r'] { x }
|
||||
|
||||
rule lit_str_single_part() -> char
|
||||
= x:lit_str_part_escape() { x }
|
||||
/ x:[^ '\'' | '\n' | '\r'] { x }
|
||||
|
||||
rule lit_str_part_escape() -> char
|
||||
= "\\\\" { '\\' }
|
||||
/ "\\'" { '\'' }
|
||||
/ "\\\"" { '"' }
|
||||
/ "\\n" { '\n' }
|
||||
/ "\\r" { '\r' }
|
||||
/ "\\t" { '\t' }
|
||||
|
||||
rule comment()
|
||||
= "//" [^ '\n']*
|
||||
|
||||
rule _()
|
||||
= [' ' | '\t' | '\n']
|
||||
/ comment()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ident(pub String);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Stmt {
|
||||
Let(StmtLet),
|
||||
Expr(Expr),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StmtLet {
|
||||
pub name: Ident,
|
||||
pub value: Expr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
Call(ExprCall),
|
||||
Lit(String),
|
||||
Var(Ident),
|
||||
Set(Vec<Expr>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExprCall {
|
||||
pub name: Ident,
|
||||
pub args: Vec<Expr>,
|
||||
}
|
||||
|
||||
pub use sandbox::program as parse;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LitPart {
|
||||
Direct(String),
|
||||
Variable(Ident),
|
||||
}
|
||||
|
||||
pub fn lit(s: &str) -> Option<Vec<LitPart>> {
|
||||
let mut ret = Vec::new();
|
||||
let mut current = String::new();
|
||||
let mut chars = s.chars().peekable();
|
||||
let mut in_ident = false;
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '{' {
|
||||
if let Some(&next_c) = chars.peek() {
|
||||
if next_c == '{' {
|
||||
current.push('{');
|
||||
chars.next();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if !in_ident {
|
||||
if !current.is_empty() {
|
||||
ret.push(LitPart::Direct(current.clone()));
|
||||
current.clear();
|
||||
}
|
||||
in_ident = true;
|
||||
|
||||
let mut ident = String::new();
|
||||
let mut closed = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
if ch == '}' {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
ident.push(ch);
|
||||
}
|
||||
|
||||
if !closed {
|
||||
return None;
|
||||
}
|
||||
|
||||
if ident.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
ret.push(LitPart::Variable(Ident(ident)));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else if c == '}' {
|
||||
if let Some(&next_c) = chars.peek() {
|
||||
if next_c == '}' {
|
||||
current.push('}');
|
||||
chars.next();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return None;
|
||||
} else {
|
||||
if in_ident {
|
||||
return None;
|
||||
}
|
||||
current.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
if in_ident {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !current.is_empty() {
|
||||
ret.push(LitPart::Direct(current));
|
||||
}
|
||||
|
||||
Some(ret)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Pre-defined Sandbox Rule: APPLICATION
|
||||
|
||||
let home = env('HOME');
|
||||
|
||||
// Allow linking to system libraries.
|
||||
fs_allow('/lib', { "read", "exec", "browse" });
|
||||
fs_allow('/lib64', { "read", "exec", "browse" });
|
||||
fs_allow('/usr/lib', { "read", "exec", "browse" });
|
||||
|
||||
// Allow executing system commands.
|
||||
fs_allow('/bin', { "read", "browse", "exec" });
|
||||
fs_allow('/usr/bin', { "read", "browse", "exec" });
|
||||
|
||||
// Allow accessing user home.
|
||||
fs_allow(home, { "read", "write", "browse" });
|
||||
Reference in New Issue
Block a user