fix seccomp and add tracing-mode project feature

This commit is contained in:
guochao 2023-11-01 21:53:20 +08:00
parent 51064a503b
commit 3305f70f77
11 changed files with 298 additions and 1069 deletions

4
.vscode/launch.json vendored
View File

@ -20,7 +20,9 @@
"kind": "bin"
}
},
"args": [],
"args": [
"/usr/bin/ls"
],
"cwd": "${workspaceFolder}",
"env": {
"RUST_LOG": "DEBUG"

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"rust-analyzer.cargo.features": [
"tracing-mode"
]
}

124
Cargo.lock generated
View File

@ -11,6 +11,54 @@ dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.75"
@ -35,6 +83,52 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "env_logger"
version = "0.10.0"
@ -58,6 +152,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.3"
@ -200,6 +300,23 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.3.0"
@ -215,6 +332,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "winapi"
version = "0.3.9"
@ -317,6 +440,7 @@ name = "x2t-sandbox"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger",
"libseccomp",
"log",

View File

@ -12,12 +12,16 @@ members = [
[dependencies]
anyhow = "1.0.75"
clap = { version = "4.4.7", features = ["derive"] }
env_logger = "0.10.0"
libseccomp = "0.3.0"
log = "0.4.20"
nix = { version = "0.27.1", features = [ "process" ] }
nix = { version = "0.27.1", features = [ "process", "ptrace", "signal" ] }
x2t-sandbox-rulegen = { path = "./x2t-sandbox-rulegen" }
[build-dependencies]
anyhow = "1.0.75"
pkg-config = "0.3.27"
[features]
tracing-mode = ["x2t-sandbox-rulegen/no-fail"]

View File

@ -22,6 +22,20 @@ cargo build
cargo run
```
### Generate syscalls with strace
```
strace -f --output x2t-syscalls.txt /path/to/x2t some.xml
```
### Generate syscalls with tracing mode
cargo 开启 tracing-mode 后,宏找不到环境变量和文件不会失败,可以直接生成一个。
```
cargo run --features tracing-mode -- -l x2t-syscalls.txt /path/to/x2t some.xml
```
## 项目结构
- [项目](/)

View File

@ -1,5 +1,9 @@
fn main() -> anyhow::Result<()> {
pkg_config::probe_library("libseccomp")?;
println!("cargo:rerun-if-changed=x2t-syscalls.txt");
println!("cargo:rerun-if-env-changed=X2T_SYSCALLS_FILE");
println!("cargo:rerun-if-env-changed=X2T_SYSCALLS");
Ok(())
}

View File

@ -45,7 +45,7 @@
};
buildWithPackages = pkgs: pkgsStatic: (buildRustPlatform pkgsStatic).buildRustPackage rec {
pname = "hello";
pname = "x2t-sandbox";
version = "1.0.0";
nativeBuildInputs = buildTools pkgs;
@ -72,9 +72,10 @@
pkgs = import nixpkgs { inherit system; };
in
rec {
hello = buildWithPackages pkgs pkgs.pkgsStatic;
x2t-sandbox-musl = buildWithPackages pkgs pkgs.pkgsStatic;
x2t-sandbox-glibc = buildWithPackages pkgs pkgs;
default = hello;
default = x2t-sandbox-musl;
});
devShells = foreachSystem
(system:

View File

@ -1,38 +1,149 @@
use std::{ffi::CString, ptr::null};
use nix::{
libc::{c_long, EPERM, ORIG_RAX},
sys::signal::Signal,
unistd::{getpid, getppid},
};
use std::{
ffi::{c_void, CString},
mem::size_of,
};
use std::io::Write;
use clap::*;
use libseccomp::*;
use nix::libc::EPERM;
#[derive(clap::Parser, Debug)]
struct Args {
#[cfg(feature = "tracing-mode")]
#[clap(short, long)]
log_failed_to: Option<String>,
command: Vec<String>,
}
fn main() -> anyhow::Result<()> {
env_logger::init();
log::info!("restrict myself by set_no_new_privs...");
nix::sys::prctl::set_no_new_privs()?;
let args = Args::parse();
log::info!("create filter context...");
log::trace!("args parsed: {args:?}");
let mut filter = ScmpFilterContext::new_filter(ScmpAction::Errno(EPERM))?;
log::info!("add architecture to filter context...");
filter.add_arch(ScmpArch::X8664)?;
#[cfg(feature = "tracing-mode")]
let tracing = args.log_failed_to != None;
let mut default_action = ScmpAction::Errno(EPERM);
#[cfg(feature = "tracing-mode")]
if let Some(log_fail_to) = args.log_failed_to {
use nix::sys::{
ptrace::Options,
wait::{waitpid, WaitStatus},
};
log::trace!("tracing mode. forking into a tracer...");
let fork_result = match unsafe { nix::unistd::fork() } {
Err(err) => panic!("failed to fork a child: {}", err),
Ok(pid) => pid,
};
log::trace!("fork result: {fork_result:?}");
if let nix::unistd::ForkResult::Parent { child } = fork_result {
// i'm parent, setting up ptrace
log::trace!("waiting for child to be ready...");
waitpid(child, None)?;
let mut output = match std::fs::OpenOptions::new().append(true).write(true).create(true).open(log_fail_to) {
Ok(file) => file,
Err(err) => {
nix::sys::ptrace::kill(child)?;
return Err(err.into());
}
};
nix::sys::ptrace::setoptions(child, Options::PTRACE_O_TRACESECCOMP)?;
nix::sys::ptrace::cont(child, None)?;
log::trace!("child is ready");
loop {
log::debug!("parent: waitpid...");
match waitpid(child, None)? {
WaitStatus::Exited(pid, ret) => {
log::info!("child {pid} exited with return code {ret}");
break;
}
WaitStatus::PtraceEvent(pid, sig,_) => {
let syscall_nr = nix::sys::ptrace::read_user(
pid,
(size_of::<c_long>() * ORIG_RAX as usize) as *mut c_void,
)? as i32;
let syscall = ScmpSyscall::from(syscall_nr);
let syscall_name = syscall.get_name().unwrap_or(format!("syscall({syscall_nr})"));
log::info!("parent: child {pid} received signal {sig:?} syscall: {syscall_name}({syscall_nr})");
writeln!(output, "{} {}", pid.as_raw(), syscall_name);
}
_ => {},
}
nix::sys::ptrace::cont(child, None);
}
return Ok(());
}
}
let command: Vec<_> = args
.command
.iter()
.map(|s| CString::new(s.as_str()).unwrap())
.collect();
log::trace!("command: {command:?}");
let executable = CString::new(args.command[0].clone()).unwrap();
log::trace!("executable: {executable:?}");
let env: Vec<CString> = std::env::vars()
.into_iter()
.map(|(k, v)| format!("{k}={v}"))
.map(|s| CString::new(s).unwrap())
.collect();
#[cfg(feature = "tracing-mode")]
if tracing {
// i'm child. trace me
log::debug!("child: traceme");
nix::sys::ptrace::traceme()?;
let me = getpid();
log::info!("waiting for parent to be ready...");
nix::sys::signal::kill(me, nix::sys::signal::Signal::SIGSTOP)?;
log::info!("parent is ready");
let parent = getppid();
default_action = ScmpAction::Trace(parent.as_raw() as u16);
log::info!("set default action to {default_action:?}");
}
log::trace!("create filter context...");
let mut filter = ScmpFilterContext::new_filter(default_action)?;
filter.add_rule(ScmpAction::Allow, ScmpSyscall::from(nix::libc::SYS_execve as i32))?;
x2t_sandbox_rulegen::generate! {
log::info!("accepting {}", syscall_name);
log::trace!("accepting {}({})", syscall_name, syscall_nr);
};
log::info!("load filter into kernel...");
log::debug!("restrict myself by set_no_new_privs...");
nix::sys::prctl::set_no_new_privs()?;
log::info!("loading filter into kernel...");
if let Err(err) = filter.load() {
log::error!("failed to load filter into kernel: {err}");
return Err(err.into());
}
log::trace!("loaded");
let args: Vec<_> = std::env::args().map(|s| CString::new(s).unwrap()).collect();
let command = std::env::args().next().unwrap();
let command = CString::new(command).unwrap();
let env: Vec<CString> = Vec::new();
log::info!("executing {:?}", args);
if let Err(err) = nix::unistd::execve(&command, args.as_slice(), env.as_slice()) {
log::debug!("executing {:?}", args.command);
if let Err(err) = nix::unistd::execve(&executable, command.as_slice(), env.as_slice()) {
panic!("failed to execve for {err}");
}

View File

@ -14,3 +14,5 @@ quote = "1"
nix = "0.27.1"
regex = "1.10.2"
[features]
no-fail = []

View File

@ -1,7 +1,7 @@
use std::{collections::HashSet, str::FromStr};
use proc_macro::*;
use quote::{quote, format_ident, TokenStreamExt};
use quote::{quote, format_ident};
#[proc_macro]
pub fn generate(input: TokenStream) -> TokenStream {
@ -15,7 +15,14 @@ pub fn generate(input: TokenStream) -> TokenStream {
} else if let Ok(syscalls) = std::fs::read_to_string(&syscall_filepath) {
generate_from(input.clone(), syscalls, "\n").into()
} else {
#[cfg(not(feature = "no-fail"))]
panic!("either specify a X2T_SYSCALLS environment variable with values seperated by colon or write the allowed syscalls line by line into {}", syscall_filepath.to_string_lossy());
#[cfg(feature = "no-fail")]
{
eprintln!("x2t syscalls not found. macro is not failing. but you program may fail. turn on tracing-mode to find out what happened");
TokenStream::new()
}
}
}
@ -50,7 +57,7 @@ fn generate_from(input: proc_macro::TokenStream, buf: String, sep: &str) -> proc
{
let syscall_nr = nix::libc::#libc_name;
let syscall_name = #syscall_name;
filter.add_rule_conditional(ScmpAction::Allow, syscall_nr as i32, &[])?;
filter.add_rule(ScmpAction::Allow, syscall_nr as i32)?;
#hook
}
}

File diff suppressed because it is too large Load Diff