Compare commits
34 Commits
elf-packin
...
main
Author | SHA1 | Date | |
---|---|---|---|
5b16d240ea | |||
2ef3d46a5b | |||
efbf6be520 | |||
983cd53f13 | |||
afecbd9cd8 | |||
29fec1466e | |||
5d707bdde4 | |||
44380ba5be | |||
71888d40e7 | |||
20c2f8d5f7 | |||
a2cbe9dc54 | |||
5bad0766fd | |||
5d1076f975 | |||
2017d7c688 | |||
7bf1b6600d | |||
8f0b1a8083 | |||
b7abf4fa07 | |||
72e2b31629 | |||
8244e4ebf7 | |||
d365508cba | |||
3069f12da0 | |||
9fe48a7749 | |||
258ccc09b0 | |||
95d3c06b31 | |||
8943d56a46 | |||
01f8f096c6 | |||
4f0f74b859 | |||
491fb46cb9 | |||
ccdb1f3e7e | |||
60c7888937 | |||
c07d6df163 | |||
0c77cd47f7 | |||
00ace078b7 | |||
7da0e02235 |
231
Cargo.lock
generated
231
Cargo.lock
generated
@ -2,23 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
@ -28,6 +11,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@ -46,13 +35,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@ -89,9 +75,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@ -134,27 +120,6 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clone-shim"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"clap 3.1.15",
|
||||
"close_fds",
|
||||
"criterion",
|
||||
"env_logger",
|
||||
"exitcode",
|
||||
"ipnetwork",
|
||||
"libc",
|
||||
"log",
|
||||
"nix",
|
||||
"object",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "close_fds"
|
||||
version = "0.3.2"
|
||||
@ -165,15 +130,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.5"
|
||||
@ -311,29 +267,6 @@ dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
@ -345,9 +278,6 @@ name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
@ -358,6 +288,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@ -430,9 +366,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.117"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@ -458,23 +394,13 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.1"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||
checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
@ -499,24 +425,11 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.28.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.10.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@ -642,6 +555,21 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
@ -651,6 +579,27 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
@ -672,6 +621,16 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.6"
|
||||
@ -719,6 +678,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@ -817,10 +782,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "void-orchestrator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 3.1.15",
|
||||
"close_fds",
|
||||
"criterion",
|
||||
"env_logger",
|
||||
"exitcode",
|
||||
"httparse",
|
||||
"ipnetwork",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"nix",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
@ -833,12 +822,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.79"
|
||||
@ -903,6 +886,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
13
Cargo.toml
13
Cargo.toml
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "clone-shim"
|
||||
name = "void-orchestrator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
@ -14,18 +14,23 @@ exitcode = "1"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
bincode = "1.3"
|
||||
ipnetwork = "0.18"
|
||||
|
||||
libc = "0.2.117"
|
||||
nix = "0.23.1"
|
||||
nix = "0.24.1"
|
||||
|
||||
close_fds = "0.3.2"
|
||||
tempfile = "3.3"
|
||||
object = { version = "0.28", features = ["write_core"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
anyhow = "1"
|
||||
|
||||
# examples/tls
|
||||
httparse = "1"
|
||||
rustls = "0.20"
|
||||
rustls-pemfile = "1"
|
||||
lazy_static = "1"
|
||||
|
||||
[[bench]]
|
||||
name = "clone3"
|
||||
|
20
README.md
20
README.md
@ -2,6 +2,16 @@
|
||||
|
||||
## Running the examples
|
||||
|
||||
### examples/fib
|
||||
|
||||
The fib example performs fibonacci trivially on a fixed number. It is the most basic example of a process that requires no privilege, excluding `Stdout` to print the result.
|
||||
|
||||
To run this example:
|
||||
|
||||
cargo build
|
||||
cargo build --example fib
|
||||
target/debug/clone-shim -s examples/fib/spec.json target/debug/examples/fib
|
||||
|
||||
### examples/basic
|
||||
|
||||
The basic example instructs the shim to spawn two processes, each of which writes "hello from main{1,2}!" to stdout.
|
||||
@ -22,6 +32,16 @@ To run this example:
|
||||
cargo build --example pipes
|
||||
target/debug/clone-shim -s examples/pipes/spec.json target/debug/examples/pipes
|
||||
|
||||
### examples/pipes
|
||||
|
||||
The pipes example shows some of the power of the shim by using pipes. The process "pipe_sender" sends two messages down a pipe that it's given by the shim. These two messages each spawn a completely isolated process, "pipe_receiver", that receives that message.
|
||||
|
||||
To run this example:
|
||||
|
||||
cargo build
|
||||
cargo build --example tls
|
||||
target/debug/clone-shim -s examples/tls/spec.json target/debug/examples/tls
|
||||
|
||||
## Debugging the shim
|
||||
|
||||
The shim can be debugged as with most processes, but it is exceptionally forky. Breaking before a clone in `rust-gdb` then running `set follow-fork-mode child` is often necessary. The best approach is to go in with a plan of attack.
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clone_shim::clone::{clone3, CloneArgs, CloneFlags};
|
||||
use void_orchestrator::clone::{clone3, CloneArgs, CloneFlags};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
|
15
examples/fib/main.rs
Normal file
15
examples/fib/main.rs
Normal file
@ -0,0 +1,15 @@
|
||||
fn main() {
|
||||
println!("fib(1) = {}", fib(1));
|
||||
println!("fib(7) = {}", fib(7));
|
||||
println!("fib(19) = {}", fib(19));
|
||||
}
|
||||
|
||||
fn fib(i: u64) -> u64 {
|
||||
let (mut a, mut b) = (0, 1);
|
||||
|
||||
for _ in 0..i {
|
||||
(a, b) = (b, a + b);
|
||||
}
|
||||
|
||||
a
|
||||
}
|
27
examples/fib/spec.json
Normal file
27
examples/fib/spec.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"entrypoints": {
|
||||
"fib": {
|
||||
"environment": [
|
||||
"Stdout",
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
||||
"environment_path": "/lib/libgcc_s.so.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
||||
"environment_path": "/lib/libc.so.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
85
examples/tls/http.rs
Normal file
85
examples/tls/http.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub(super) fn handler(mut stream: UnixStream) -> i32 {
|
||||
println!("entered http handler");
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut buf_len = 0;
|
||||
|
||||
loop {
|
||||
buf.resize_with(buf_len + 4096, Default::default);
|
||||
|
||||
let read_bytes = stream.read(&mut buf[buf_len..]).unwrap();
|
||||
buf_len += read_bytes;
|
||||
|
||||
if read_bytes == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut headers = [httparse::EMPTY_HEADER; 64];
|
||||
let mut req = httparse::Request::new(&mut headers);
|
||||
let result = req.parse(&buf).unwrap();
|
||||
|
||||
if result.is_partial() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let filename = if req.method != Some("GET") {
|
||||
None
|
||||
} else {
|
||||
req.path
|
||||
};
|
||||
|
||||
if let Some(filename) = filename {
|
||||
if try_serve_file(&mut stream, filename).unwrap() {
|
||||
return exitcode::OK;
|
||||
}
|
||||
}
|
||||
|
||||
let status_line = "HTTP/1.1 404 NOT FOUND";
|
||||
let contents = "file not found\n";
|
||||
|
||||
let response = format!(
|
||||
"{}\r\nContent-Length: {}\r\n\r\n{}",
|
||||
status_line,
|
||||
contents.len(),
|
||||
contents
|
||||
);
|
||||
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
break;
|
||||
}
|
||||
|
||||
exitcode::OK
|
||||
}
|
||||
|
||||
fn try_serve_file(stream: &mut impl io::Write, filename: &str) -> io::Result<bool> {
|
||||
let mut fd = match OpenOptions::new()
|
||||
.read(true)
|
||||
.open(PathBuf::from("/var/www/html/").join(filename.strip_prefix('/').unwrap_or(filename)))
|
||||
{
|
||||
Ok(fd) => fd,
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::NotFound {
|
||||
return Ok(false);
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
let status_line = "HTTP/1.1 200 OK";
|
||||
|
||||
let response_header = format!(
|
||||
"{}\r\nContent-Length: {}\r\n\r\n",
|
||||
status_line,
|
||||
fd.metadata()?.len(),
|
||||
);
|
||||
|
||||
stream.write_all(response_header.as_bytes())?;
|
||||
io::copy(&mut fd, stream)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
55
examples/tls/listener.rs
Normal file
55
examples/tls/listener.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
use std::net::TcpListener;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use nix::poll::{poll, PollFd, PollFlags};
|
||||
use nix::sys::signal::{signal, SigHandler, Signal};
|
||||
use nix::Error as NixError;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
static ref RUNNING: AtomicBool = AtomicBool::new(true);
|
||||
}
|
||||
|
||||
pub(crate) fn handler(tls_handler_trigger: File, listener: TcpListener) -> i32 {
|
||||
println!("connection_listener entered");
|
||||
|
||||
// SAFETY: only unsafe if you use the result
|
||||
unsafe { signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)) }.unwrap();
|
||||
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
let mut to_poll = [PollFd::new(listener.as_raw_fd(), PollFlags::POLLIN)];
|
||||
while RUNNING.load(Ordering::Relaxed) {
|
||||
if let Err(e) = poll(&mut to_poll, 1000) {
|
||||
if e == NixError::EINTR {
|
||||
continue; // timed out
|
||||
}
|
||||
Err(e).unwrap()
|
||||
}
|
||||
|
||||
let stream = match listener.accept() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
Err(e).unwrap()
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("received a new connection");
|
||||
super::tls_handler(&tls_handler_trigger, stream.0);
|
||||
}
|
||||
|
||||
exitcode::OK
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigint(signal: libc::c_int) {
|
||||
let signal = Signal::try_from(signal).unwrap();
|
||||
RUNNING.store(signal != Signal::SIGINT, Ordering::Relaxed);
|
||||
}
|
149
examples/tls/main.rs
Normal file
149
examples/tls/main.rs
Normal file
@ -0,0 +1,149 @@
|
||||
mod http;
|
||||
mod listener;
|
||||
mod tls;
|
||||
|
||||
use std::fs::File;
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
fn main() {
|
||||
match std::env::args().next() {
|
||||
Some(s) => match s.as_str() {
|
||||
"connection_listener" => connection_listener_entrypoint(),
|
||||
"tls_handler" => tls_handler_entrypoint(),
|
||||
"http_handler" => http_handler_entrypoint(),
|
||||
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
None => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn connection_listener_entrypoint() {
|
||||
// imports
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
|
||||
// argument parsing
|
||||
let mut args = std::env::args();
|
||||
|
||||
let _entrypoint = args.next();
|
||||
|
||||
let tls_handler_trigger = args.next();
|
||||
let tls_handler_trigger: RawFd = tls_handler_trigger
|
||||
.expect("tls handler trigger required")
|
||||
.parse()
|
||||
.expect("tls handler trigger should be a file descriptor");
|
||||
let tls_handler_trigger = unsafe { File::from_raw_fd(tls_handler_trigger) };
|
||||
|
||||
let tcp_listener = args.next();
|
||||
let tcp_listener: RawFd = tcp_listener
|
||||
.expect("tcp listener required")
|
||||
.parse()
|
||||
.expect("tcp listener should be a file descriptor");
|
||||
let tcp_listener = unsafe { TcpListener::from_raw_fd(tcp_listener) };
|
||||
|
||||
// run function
|
||||
std::process::exit(listener::handler(tls_handler_trigger, tcp_listener));
|
||||
}
|
||||
|
||||
fn tls_handler(trigger_socket: &File, stream: TcpStream) {
|
||||
// imports
|
||||
use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
// send file descriptor(s)
|
||||
let sockfd = trigger_socket.as_raw_fd();
|
||||
let fds = [stream.as_raw_fd()];
|
||||
|
||||
sendmsg::<()>(
|
||||
sockfd,
|
||||
&[],
|
||||
&[ControlMessage::ScmRights(&fds)],
|
||||
MsgFlags::empty(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn tls_handler_entrypoint() {
|
||||
// imports
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
|
||||
// argument parsing
|
||||
let mut args = std::env::args();
|
||||
|
||||
let _entrypoint = args.next();
|
||||
|
||||
let http_handler_trigger = args.next();
|
||||
let http_handler_trigger: RawFd = http_handler_trigger
|
||||
.expect("http handler trigger required")
|
||||
.parse()
|
||||
.expect("http handler trigger should be a file descriptor");
|
||||
let http_handler_trigger = unsafe { File::from_raw_fd(http_handler_trigger) };
|
||||
|
||||
let tls_cert_file = args.next();
|
||||
let tls_cert_file: RawFd = tls_cert_file
|
||||
.expect("tls cert file required")
|
||||
.parse()
|
||||
.expect("tls cert file should be a file descriptor");
|
||||
let tls_cert_file = unsafe { File::from_raw_fd(tls_cert_file) };
|
||||
|
||||
let tls_key_file = args.next();
|
||||
let tls_key_file: RawFd = tls_key_file
|
||||
.expect("tls key file required")
|
||||
.parse()
|
||||
.expect("tls key file should be a file descriptor");
|
||||
let tls_key_file = unsafe { File::from_raw_fd(tls_key_file) };
|
||||
|
||||
let stream = args.next();
|
||||
let stream: RawFd = stream
|
||||
.expect("request stream required")
|
||||
.parse()
|
||||
.expect("request stream should be a file descriptor");
|
||||
let stream = unsafe { TcpStream::from_raw_fd(stream) };
|
||||
|
||||
std::process::exit(tls::handler(
|
||||
http_handler_trigger,
|
||||
tls_cert_file,
|
||||
tls_key_file,
|
||||
stream,
|
||||
));
|
||||
}
|
||||
|
||||
fn http_handler(trigger_socket: &File, stream: UnixStream) {
|
||||
// imports
|
||||
use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
// send file descriptor(s)
|
||||
let sockfd = trigger_socket.as_raw_fd();
|
||||
let fds = [stream.as_raw_fd()];
|
||||
|
||||
sendmsg::<()>(
|
||||
sockfd,
|
||||
&[],
|
||||
&[ControlMessage::ScmRights(&fds)],
|
||||
MsgFlags::empty(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn http_handler_entrypoint() {
|
||||
// imports
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
|
||||
// argument parsing
|
||||
let mut args = std::env::args();
|
||||
|
||||
let _entrypoint = args.next();
|
||||
|
||||
let stream = args.next();
|
||||
let stream: RawFd = stream
|
||||
.expect("request stream required")
|
||||
.parse()
|
||||
.expect("request stream should be a file descriptor");
|
||||
let stream = unsafe { UnixStream::from_raw_fd(stream) };
|
||||
|
||||
std::process::exit(http::handler(stream));
|
||||
}
|
114
examples/tls/spec.json
Normal file
114
examples/tls/spec.json
Normal file
@ -0,0 +1,114 @@
|
||||
{
|
||||
"entrypoints": {
|
||||
"connection_listener": {
|
||||
"args": [
|
||||
"Entrypoint",
|
||||
{
|
||||
"FileSocket": {
|
||||
"Tx": "tls"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TcpListener": {
|
||||
"addr": "0.0.0.0:8443"
|
||||
}
|
||||
}
|
||||
],
|
||||
"environment": [
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
||||
"environment_path": "/lib/libgcc_s.so.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
||||
"environment_path": "/lib/libc.so.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tls_handler": {
|
||||
"trigger": {
|
||||
"FileSocket": "tls"
|
||||
},
|
||||
"args": [
|
||||
"Entrypoint",
|
||||
{
|
||||
"FileSocket": {
|
||||
"Tx": "http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"File": "/etc/ssl/certs/example.com.pem"
|
||||
},
|
||||
{
|
||||
"File": "/etc/ssl/private/example.com.key"
|
||||
},
|
||||
"Trigger"
|
||||
],
|
||||
"environment": [
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
||||
"environment_path": "/lib/libgcc_s.so.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
||||
"environment_path": "/lib/libc.so.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"http_handler": {
|
||||
"trigger": {
|
||||
"FileSocket": "http"
|
||||
},
|
||||
"args": [
|
||||
"Entrypoint",
|
||||
"Trigger"
|
||||
],
|
||||
"environment": [
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/var/www/html",
|
||||
"environment_path": "/var/www/html"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
||||
"environment_path": "/lib/libgcc_s.so.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
||||
"environment_path": "/lib/libc.so.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Filesystem": {
|
||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
176
examples/tls/tls.rs
Normal file
176
examples/tls/tls.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, ErrorKind, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::Arc;
|
||||
|
||||
use nix::poll::{poll, PollFd, PollFlags};
|
||||
|
||||
use rustls::ServerConnection;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
|
||||
pub(crate) fn handler(
|
||||
http_trigger_socket: File,
|
||||
cert: File,
|
||||
key: File,
|
||||
mut stream: TcpStream,
|
||||
) -> i32 {
|
||||
let (mut socket, far_socket) = UnixStream::pair().unwrap();
|
||||
|
||||
let config = make_config(cert, key);
|
||||
let mut tls_conn = rustls::ServerConnection::new(config).unwrap();
|
||||
|
||||
super::http_handler(&http_trigger_socket, far_socket);
|
||||
|
||||
stream.set_nonblocking(true).unwrap();
|
||||
socket.set_nonblocking(true).unwrap();
|
||||
|
||||
let mut to_poll = [
|
||||
PollFd::new(stream.as_raw_fd(), PollFlags::POLLIN),
|
||||
PollFd::new(socket.as_raw_fd(), PollFlags::POLLIN),
|
||||
];
|
||||
|
||||
loop {
|
||||
println!("starting polling");
|
||||
poll(&mut to_poll, -1).unwrap();
|
||||
|
||||
if let Some(events) = to_poll[0].revents() {
|
||||
if events.contains(PollFlags::POLLIN) {
|
||||
handle_encrypted_data(&mut tls_conn, &mut stream, &mut socket).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(events) = to_poll[1].revents() {
|
||||
if events.contains(PollFlags::POLLIN) {
|
||||
handle_new_data(&mut tls_conn, &mut socket, &mut stream).unwrap();
|
||||
}
|
||||
|
||||
if events.contains(PollFlags::POLLHUP) {
|
||||
println!("response writer hung up, exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tls_conn.send_close_notify();
|
||||
tls_conn.write_tls(&mut stream).unwrap();
|
||||
|
||||
exitcode::OK
|
||||
}
|
||||
|
||||
fn handle_encrypted_data(
|
||||
tls_conn: &mut ServerConnection,
|
||||
stream: &mut (impl Read + Write),
|
||||
socket: &mut impl Write,
|
||||
) -> anyhow::Result<()> {
|
||||
println!("handling newly received encrypted data");
|
||||
|
||||
loop {
|
||||
let read = match tls_conn.read_tls(stream) {
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::WouldBlock {
|
||||
0
|
||||
} else {
|
||||
return Err(e).context("io error reading from stream");
|
||||
}
|
||||
}
|
||||
Ok(n) => n,
|
||||
};
|
||||
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let process_result = tls_conn.process_new_packets();
|
||||
let write_tls_result = tls_conn.write_tls(stream);
|
||||
|
||||
let io_state = process_result.context("tls processing failure")?;
|
||||
write_tls_result.context("tls write failure")?;
|
||||
|
||||
if io_state.plaintext_bytes_to_read() > 0 {
|
||||
let mut reader = tls_conn
|
||||
.reader()
|
||||
.take(io_state.plaintext_bytes_to_read() as u64);
|
||||
|
||||
std::io::copy(&mut reader, socket)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_new_data(
|
||||
tls_conn: &mut ServerConnection,
|
||||
socket: &mut impl Read,
|
||||
stream: &mut impl Write,
|
||||
) -> anyhow::Result<()> {
|
||||
println!("handling new data to encrypt");
|
||||
|
||||
let mut buf = [0_u8; BUFFER_SIZE];
|
||||
loop {
|
||||
let read = non_blocking_read(socket, &mut buf)?;
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tls_conn.writer().write_all(&buf[0..read])?;
|
||||
tls_conn.write_tls(stream)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn non_blocking_read(reader: &mut impl io::Read, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match reader.read(buf) {
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::WouldBlock {
|
||||
Ok(0)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
Ok(n) => Ok(n),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_config(cert: File, key: File) -> Arc<rustls::ServerConfig> {
|
||||
let certs = load_certs(cert);
|
||||
let privkey = load_private_key(key);
|
||||
|
||||
let config = rustls::ServerConfig::builder()
|
||||
.with_safe_default_cipher_suites()
|
||||
.with_safe_default_kx_groups()
|
||||
.with_safe_default_protocol_versions()
|
||||
.expect("inconsistent cipher-suites/versions specified")
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, privkey)
|
||||
.expect("bad certificates/private key");
|
||||
|
||||
Arc::new(config)
|
||||
}
|
||||
|
||||
fn load_certs(certfile: File) -> Vec<rustls::Certificate> {
|
||||
let mut reader = BufReader::new(certfile);
|
||||
|
||||
rustls_pemfile::certs(&mut reader)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|v| rustls::Certificate(v.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn load_private_key(keyfile: File) -> rustls::PrivateKey {
|
||||
let mut reader = BufReader::new(keyfile);
|
||||
|
||||
loop {
|
||||
match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
|
||||
Some(rustls_pemfile::Item::RSAKey(key)) => return rustls::PrivateKey(key),
|
||||
Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key),
|
||||
Some(rustls_pemfile::Item::ECKey(key)) => return rustls::PrivateKey(key),
|
||||
None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("no keys found (encrypted keys not supported)");
|
||||
}
|
16
src/error.rs
16
src/error.rs
@ -15,25 +15,13 @@ pub enum Error {
|
||||
#[error("json: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error("bincode: {0}")]
|
||||
Bincode(#[from] bincode::Error),
|
||||
|
||||
#[error("elf: read: {0}")]
|
||||
ElfRead(#[from] object::read::Error),
|
||||
|
||||
#[error("elf: write: {0}")]
|
||||
ElfWrite(#[from] object::write::Error),
|
||||
|
||||
#[error("bad pipe specification: a pipe must have exactly one reader and one writer: {0}")]
|
||||
BadPipe(String),
|
||||
|
||||
#[error("bad socket specification: a socket must have exactly one reader and one writer: {0}")]
|
||||
#[error("bad socket specification: a socket must have exactly one reader and one or more writers: {0}")]
|
||||
BadFileSocket(String),
|
||||
|
||||
#[error("no specification provided")]
|
||||
NoSpecification,
|
||||
|
||||
#[error("bad specification type: only json files are supported")]
|
||||
#[error("bad specification type: only .json files are supported")]
|
||||
BadSpecType,
|
||||
|
||||
#[error("bad trigger argument: this entrypoint is not triggered by something with arguments")]
|
||||
|
113
src/lib.rs
113
src/lib.rs
@ -2,51 +2,39 @@ use log::{debug, info};
|
||||
|
||||
pub mod clone;
|
||||
mod error;
|
||||
mod pack;
|
||||
mod spawner;
|
||||
mod specification;
|
||||
mod void;
|
||||
|
||||
use error::{Error, Result};
|
||||
use spawner::Spawner;
|
||||
use specification::Specification;
|
||||
use specification::{Environment, Specification};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::FromRawFd;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::path::Path;
|
||||
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::sys::socket;
|
||||
use nix::sys::wait::{waitid, Id, WaitPidFlag, WaitStatus};
|
||||
use nix::unistd;
|
||||
pub struct PackArgs<'a> {
|
||||
pub spec: &'a Path,
|
||||
pub binary: &'a Path,
|
||||
pub output: &'a Path,
|
||||
}
|
||||
|
||||
pub fn pack(args: &PackArgs) -> Result<()> {
|
||||
let spec: Specification = if args.spec.extension().map(|e| e == "json") == Some(true) {
|
||||
let f = std::fs::File::open(args.spec)?;
|
||||
Ok(serde_json::from_reader(f)?)
|
||||
} else {
|
||||
Err(Error::BadSpecType)
|
||||
}?;
|
||||
|
||||
pack::pack_binary(args.binary, &spec, args.output)
|
||||
}
|
||||
|
||||
pub struct RunArgs<'a> {
|
||||
pub spec: Option<&'a Path>,
|
||||
pub debug: bool,
|
||||
pub daemon: bool,
|
||||
|
||||
pub stdout: bool,
|
||||
pub stderr: bool,
|
||||
|
||||
pub binary: &'a Path,
|
||||
pub binary_args: Vec<&'a str>,
|
||||
}
|
||||
|
||||
pub fn run(args: &RunArgs) -> Result<()> {
|
||||
pub fn run(args: &RunArgs) -> Result<i32> {
|
||||
// parse the specification
|
||||
let spec: Specification = if let Some(m) = args.spec {
|
||||
let mut spec: Specification = if let Some(m) = args.spec {
|
||||
if m.extension().map(|e| e == "json") == Some(true) {
|
||||
let f = std::fs::File::open(m)?;
|
||||
Ok(serde_json::from_reader(f)?)
|
||||
@ -54,17 +42,26 @@ pub fn run(args: &RunArgs) -> Result<()> {
|
||||
Err(Error::BadSpecType)
|
||||
}
|
||||
} else {
|
||||
let spec = pack::extract_specification(args.binary)?;
|
||||
if let Some(s) = spec {
|
||||
Ok(s)
|
||||
} else {
|
||||
Err(Error::NoSpecification)
|
||||
}
|
||||
unimplemented!("reading spec from the elf is unimplemented")
|
||||
}?;
|
||||
|
||||
debug!("specification read: {:?}", &spec);
|
||||
spec.validate()?;
|
||||
|
||||
if args.stdout {
|
||||
debug!("forwarding stdout");
|
||||
for entrypoint in &mut spec.entrypoints.values_mut() {
|
||||
entrypoint.environment.insert(Environment::Stdout);
|
||||
}
|
||||
}
|
||||
|
||||
if args.stderr {
|
||||
debug!("forwarding stderr");
|
||||
for entrypoint in &mut spec.entrypoints.values_mut() {
|
||||
entrypoint.environment.insert(Environment::Stderr);
|
||||
}
|
||||
}
|
||||
|
||||
// create all the pipes
|
||||
let (pipes, _) = spec.pipes();
|
||||
let pipes = create_pipes(pipes)?;
|
||||
@ -84,7 +81,41 @@ pub fn run(args: &RunArgs) -> Result<()> {
|
||||
}
|
||||
.spawn()?;
|
||||
|
||||
Ok(())
|
||||
if args.daemon {
|
||||
return Ok(exitcode::OK);
|
||||
}
|
||||
|
||||
info!("spawned successfully, awaiting children exiting...");
|
||||
let mut exit_code = exitcode::OK;
|
||||
|
||||
loop {
|
||||
let status = match waitid(Id::All, WaitPidFlag::WEXITED) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(nix::Error::ECHILD) => {
|
||||
info!("all child processes have exited, exiting...");
|
||||
break;
|
||||
}
|
||||
Err(e) => Err(Error::Nix {
|
||||
msg: "waitpid",
|
||||
src: e,
|
||||
}),
|
||||
}?;
|
||||
|
||||
match status {
|
||||
WaitStatus::Exited(pid, code) => {
|
||||
if code != exitcode::OK {
|
||||
exit_code = code;
|
||||
}
|
||||
debug!("child {} exited with code {}", pid, code);
|
||||
}
|
||||
WaitStatus::Signaled(pid, sig, _coredump) => {
|
||||
debug!("child {} was terminated with signal {}", pid, sig);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
||||
fn create_pipes(names: Vec<&str>) -> Result<HashMap<String, PipePair>> {
|
||||
@ -121,10 +152,11 @@ impl PipePair {
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
// safe to create files given the successful return of pipe(2)
|
||||
Ok(PipePair {
|
||||
name: name.to_string(),
|
||||
// SAFETY: valid new fd as pipe2(2) returned successfully
|
||||
read: Some(unsafe { File::from_raw_fd(read) }),
|
||||
// SAFETY: valid new fd as pipe2(2) returned successfully
|
||||
write: Some(unsafe { File::from_raw_fd(write) }),
|
||||
})
|
||||
}
|
||||
@ -146,7 +178,7 @@ pub struct SocketPair {
|
||||
name: String,
|
||||
|
||||
read: Option<File>,
|
||||
write: Option<File>,
|
||||
write: File,
|
||||
}
|
||||
|
||||
impl SocketPair {
|
||||
@ -162,23 +194,30 @@ impl SocketPair {
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
// safe to create files given the successful return of socketpair(2)
|
||||
Ok(SocketPair {
|
||||
name: name.to_string(),
|
||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
||||
read: Some(unsafe { File::from_raw_fd(read) }),
|
||||
write: Some(unsafe { File::from_raw_fd(write) }),
|
||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
||||
write: unsafe { File::from_raw_fd(write) },
|
||||
})
|
||||
}
|
||||
|
||||
fn take_read(&mut self) -> Result<File> {
|
||||
self.read
|
||||
.take()
|
||||
.ok_or_else(|| Error::BadPipe(self.name.to_string()))
|
||||
.ok_or_else(|| Error::BadFileSocket(self.name.to_string()))
|
||||
}
|
||||
|
||||
fn take_write(&mut self) -> Result<File> {
|
||||
self.write
|
||||
.take()
|
||||
.ok_or_else(|| Error::BadPipe(self.name.to_string()))
|
||||
fn write(&self) -> Result<File> {
|
||||
let dup_fd = nix::unistd::dup(self.write.as_raw_fd())
|
||||
.map_err(|e| Error::Nix { msg: "dup", src: e })?;
|
||||
|
||||
// SAFETY: valid new fd as dup(2) returned successfully
|
||||
Ok(unsafe { File::from_raw_fd(dup_fd) })
|
||||
}
|
||||
|
||||
fn write_ref(&self) -> &File {
|
||||
&self.write
|
||||
}
|
||||
}
|
||||
|
92
src/main.rs
92
src/main.rs
@ -1,6 +1,6 @@
|
||||
use log::{error, info};
|
||||
use log::error;
|
||||
|
||||
use clone_shim::{pack, run, PackArgs, RunArgs};
|
||||
use void_orchestrator::{run, RunArgs};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
@ -12,39 +12,12 @@ fn main() {
|
||||
.version(env!("GIT_HASH"))
|
||||
.author("Jake Hillion <jake@hillion.co.uk>")
|
||||
.about("Launch a void process application.")
|
||||
.subcommand_negates_reqs(true)
|
||||
.trailing_var_arg(true)
|
||||
.subcommand(
|
||||
Command::new("pack")
|
||||
.arg(
|
||||
Arg::new("spec")
|
||||
.long("specification")
|
||||
.short('s')
|
||||
.help("Provide the specification to pack as an external JSON file.")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("binary")
|
||||
.long("binary")
|
||||
.short('b')
|
||||
.help("Provide the binary to pack.")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("output")
|
||||
.long("out")
|
||||
.short('o')
|
||||
.help("Location of the output file")
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("spec")
|
||||
.long("specification")
|
||||
.short('s')
|
||||
.help("Provide the specification to launch as an external JSON file.")
|
||||
.help("Provide the specification as an external JSON file.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
@ -61,6 +34,25 @@ fn main() {
|
||||
.help("Stop each spawned application process so that it can be attached to.")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("daemon")
|
||||
.long("daemon")
|
||||
.short('D')
|
||||
.help("Detach the shim from all child processes and exit immediately.")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("stdout")
|
||||
.long("stdout")
|
||||
.help("Allow all spawned processes access to stdout (useful for debugging).")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("stderr")
|
||||
.long("stderr")
|
||||
.help("Allow all spawned processes access to stderr (useful for debugging).")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("binary")
|
||||
.index(1)
|
||||
@ -82,30 +74,8 @@ fn main() {
|
||||
env_logger::init_from_env(env);
|
||||
|
||||
// launch process
|
||||
|
||||
let code = if let Some(matches) = matches.subcommand_matches("pack") {
|
||||
// execute binary packing procedure
|
||||
let args = PackArgs {
|
||||
spec: Path::new(matches.value_of("spec").expect("spec required")),
|
||||
binary: Path::new(matches.value_of("binary").expect("binary required")),
|
||||
output: matches
|
||||
.value_of("output")
|
||||
.map(Path::new)
|
||||
.unwrap_or_else(|| Path::new("a.out")),
|
||||
};
|
||||
|
||||
match pack(&args) {
|
||||
Ok(_) => {
|
||||
info!("binary packed successfully");
|
||||
exitcode::OK
|
||||
}
|
||||
Err(e) => {
|
||||
error!("error packing binary: {}", e);
|
||||
1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// execute shimmed process
|
||||
// execute shimmed process
|
||||
std::process::exit({
|
||||
let (binary, binary_args) = {
|
||||
let mut argv = matches.values_of("binary").unwrap();
|
||||
|
||||
@ -118,21 +88,21 @@ fn main() {
|
||||
let args = RunArgs {
|
||||
spec: matches.value_of("spec").map(Path::new),
|
||||
debug: matches.is_present("debug"),
|
||||
daemon: matches.is_present("daemon"),
|
||||
|
||||
stdout: matches.is_present("stdout"),
|
||||
stderr: matches.is_present("stderr"),
|
||||
|
||||
binary,
|
||||
binary_args,
|
||||
};
|
||||
|
||||
match run(&args) {
|
||||
Ok(_) => {
|
||||
info!("launched successfully");
|
||||
exitcode::OK
|
||||
}
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
error!("error: {}", e);
|
||||
-1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::process::exit(code);
|
||||
})
|
||||
}
|
||||
|
75
src/pack.rs
75
src/pack.rs
@ -1,75 +0,0 @@
|
||||
use crate::{Result, Specification};
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
use bincode::Options;
|
||||
use object::endian::Endianness;
|
||||
use object::read::ReadCache;
|
||||
use object::read::{Object, ObjectSection};
|
||||
use object::write::{StandardSegment, StreamingBuffer};
|
||||
use object::SectionKind;
|
||||
|
||||
const SPECIFICATION_SECTION_NAME: &str = "void_specification";
|
||||
|
||||
pub(crate) fn pack_binary(binary: &Path, spec: &Specification, output: &Path) -> Result<()> {
|
||||
let binary = File::open(binary)?;
|
||||
let binary = ReadCache::new(binary);
|
||||
|
||||
let output = File::create(output)?;
|
||||
let mut output = StreamingBuffer::new(output);
|
||||
|
||||
let input_object = object::File::parse(&binary)?;
|
||||
|
||||
let format = input_object.format();
|
||||
let architecture = input_object.architecture();
|
||||
let endianness = if input_object.is_little_endian() {
|
||||
Endianness::Little
|
||||
} else {
|
||||
Endianness::Big
|
||||
};
|
||||
|
||||
let mut output_object = object::write::Object::new(format, architecture, endianness);
|
||||
|
||||
for input_section in input_object.sections() {
|
||||
let output_section = output_object.add_section(
|
||||
input_section.segment_name_bytes()?.unwrap_or(&[]).to_vec(),
|
||||
input_section.name_bytes()?.to_vec(),
|
||||
input_section.kind(),
|
||||
);
|
||||
|
||||
output_object.set_section_data(output_section, input_section.data()?, 0);
|
||||
}
|
||||
|
||||
let spec_section = output_object.add_section(
|
||||
output_object.segment_name(StandardSegment::Debug).to_vec(),
|
||||
SPECIFICATION_SECTION_NAME.to_string().into(),
|
||||
SectionKind::Other,
|
||||
);
|
||||
|
||||
let spec = bincode_options().serialize(spec)?;
|
||||
output_object.set_section_data(spec_section, spec, 0);
|
||||
|
||||
output_object.emit(&mut output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn extract_specification(binary: &Path) -> Result<Option<Specification>> {
|
||||
let binary = File::open(binary)?;
|
||||
let binary = ReadCache::new(binary);
|
||||
let input_object = object::File::parse(&binary)?;
|
||||
|
||||
let spec_section = if let Some(s) = input_object.section_by_name(SPECIFICATION_SECTION_NAME) {
|
||||
s
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let spec_data = spec_section.data()?;
|
||||
|
||||
Ok(Some(bincode_options().deserialize(spec_data)?))
|
||||
}
|
||||
|
||||
fn bincode_options() -> impl bincode::Options {
|
||||
bincode::DefaultOptions::new()
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
use super::{Spawner, TriggerData};
|
||||
use log::info;
|
||||
|
||||
use super::{RpcHandler, Spawner, TriggerData};
|
||||
use crate::specification::{Arg, FileSocket, Pipe};
|
||||
use crate::void::VoidBuilder;
|
||||
use crate::{Error, Result};
|
||||
@ -7,7 +9,10 @@ use std::ffi::CString;
|
||||
use std::fs::File;
|
||||
use std::net::TcpListener;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::io::IntoRawFd;
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
|
||||
use nix::sys::socket;
|
||||
use nix::unistd::{fork, ForkResult};
|
||||
|
||||
pub struct PreparedArgs(Vec<PreparedArg>);
|
||||
|
||||
@ -37,11 +42,15 @@ impl PreparedArgs {
|
||||
* for things like network sockets. update the builder
|
||||
* with newly passed fds.
|
||||
*/
|
||||
pub fn prepare_ambient(builder: &mut VoidBuilder, args: &[Arg]) -> Result<Self> {
|
||||
pub fn prepare_ambient(
|
||||
spawner: &Spawner,
|
||||
builder: &mut VoidBuilder,
|
||||
args: &[Arg],
|
||||
) -> Result<Self> {
|
||||
let mut v = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
v.push(PreparedArg::prepare_ambient(builder, arg)?);
|
||||
v.push(PreparedArg::prepare_ambient(spawner, builder, arg)?);
|
||||
}
|
||||
|
||||
Ok(PreparedArgs(v))
|
||||
@ -85,6 +94,9 @@ enum PreparedArg {
|
||||
/// A TCP Listener
|
||||
TcpListener { socket: TcpListener },
|
||||
|
||||
/// RPC
|
||||
Rpc { socket: File },
|
||||
|
||||
/// The rest of argv[1..], 0 or more arguments
|
||||
Trailing,
|
||||
}
|
||||
@ -113,24 +125,28 @@ impl PreparedArg {
|
||||
PreparedArg::Pipe(pipe)
|
||||
}
|
||||
|
||||
Arg::FileSocket(s) => {
|
||||
let socket = match s {
|
||||
FileSocket::Rx(s) => spawner.sockets.get_mut(s).unwrap().take_read(),
|
||||
FileSocket::Tx(s) => spawner.sockets.get_mut(s).unwrap().take_write(),
|
||||
}?;
|
||||
Arg::FileSocket(FileSocket::Rx(s)) => {
|
||||
let socket = spawner.sockets.get_mut(s).unwrap().take_read()?;
|
||||
|
||||
builder.keep_fd(&socket);
|
||||
PreparedArg::FileSocket(socket)
|
||||
}
|
||||
|
||||
arg => Self::prepare_ambient(builder, arg)?,
|
||||
arg => Self::prepare_ambient(spawner, builder, arg)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_ambient(builder: &mut VoidBuilder, arg: &Arg) -> Result<Self> {
|
||||
fn prepare_ambient(spawner: &Spawner, builder: &mut VoidBuilder, arg: &Arg) -> Result<Self> {
|
||||
Ok(match arg {
|
||||
Arg::Pipe(p) => return Err(Error::BadPipe(p.get_name().to_string())),
|
||||
Arg::FileSocket(s) => return Err(Error::BadFileSocket(s.get_name().to_string())),
|
||||
Arg::FileSocket(FileSocket::Rx(s)) => return Err(Error::BadFileSocket(s.to_string())),
|
||||
|
||||
Arg::FileSocket(FileSocket::Tx(s)) => {
|
||||
let socket = spawner.sockets.get(s).unwrap().write()?;
|
||||
|
||||
builder.keep_fd(&socket);
|
||||
PreparedArg::FileSocket(socket)
|
||||
}
|
||||
|
||||
Arg::File(path) => {
|
||||
let fd = File::open(path)?;
|
||||
@ -146,6 +162,45 @@ impl PreparedArg {
|
||||
PreparedArg::TcpListener { socket }
|
||||
}
|
||||
|
||||
Arg::Rpc(specs) => {
|
||||
let (ambient, void) = socket::socketpair(
|
||||
socket::AddressFamily::Unix,
|
||||
socket::SockType::Datagram,
|
||||
None,
|
||||
socket::SockFlag::empty(),
|
||||
)
|
||||
.map_err(|e| Error::Nix {
|
||||
msg: "socketpair",
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
||||
let ambient = unsafe { File::from_raw_fd(ambient) };
|
||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
||||
let void = unsafe { File::from_raw_fd(void) };
|
||||
|
||||
// spawn this child with ambient authority
|
||||
// necessary as no void ever has outgoing network capability
|
||||
// SAFETY: this program is single-threaded so no safety issue
|
||||
let child = unsafe { fork() }.map_err(|e| Error::Nix {
|
||||
msg: "fork",
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
match child {
|
||||
ForkResult::Child => {
|
||||
let handler = RpcHandler::new(specs);
|
||||
handler.handle(ambient).unwrap();
|
||||
}
|
||||
ForkResult::Parent { child } => {
|
||||
info!("spawned rpc handler with pid {}", child);
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: safe as socketpair returned successfully
|
||||
PreparedArg::Rpc { socket: void }
|
||||
}
|
||||
|
||||
Arg::BinaryName => PreparedArg::BinaryName,
|
||||
Arg::Entrypoint => PreparedArg::Entrypoint,
|
||||
Arg::Trigger => PreparedArg::Trigger,
|
||||
@ -183,6 +238,10 @@ impl PreparedArg {
|
||||
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
||||
}
|
||||
|
||||
PreparedArg::Rpc { socket } => {
|
||||
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
||||
}
|
||||
|
||||
PreparedArg::Trailing => Ok(spawner
|
||||
.binary_args
|
||||
.iter()
|
||||
|
@ -1,10 +1,12 @@
|
||||
use log::{debug, error, info};
|
||||
|
||||
mod args;
|
||||
mod rpc;
|
||||
|
||||
use args::PreparedArgs;
|
||||
use rpc::RpcHandler;
|
||||
|
||||
use crate::specification::{Entrypoint, Environment, Specification, Trigger};
|
||||
use crate::specification::{Arg, Entrypoint, Environment, Specification, Trigger};
|
||||
use crate::void::VoidBuilder;
|
||||
use crate::{Error, Result};
|
||||
use crate::{PipePair, SocketPair};
|
||||
@ -13,14 +15,17 @@ use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use nix::sys::signal::{kill, Signal};
|
||||
use nix::sys::socket::{recvmsg, ControlMessageOwned, MsgFlags};
|
||||
use nix::unistd::{self, Pid};
|
||||
use nix::sys::wait::{waitid, Id, WaitPidFlag, WaitStatus};
|
||||
use nix::unistd::{self, fork, ForkResult, Pid};
|
||||
use nix::Error as NixError;
|
||||
|
||||
const BUFFER_SIZE: usize = 1024;
|
||||
const MAX_FILE_DESCRIPTORS: usize = 16;
|
||||
|
||||
pub struct Spawner<'a> {
|
||||
pub spec: &'a Specification,
|
||||
@ -64,10 +69,7 @@ impl<'a> Spawner<'a> {
|
||||
match &entrypoint.trigger {
|
||||
Trigger::Startup => {
|
||||
let mut builder = VoidBuilder::new();
|
||||
|
||||
let binary = PathBuf::from(self.binary).canonicalize()?;
|
||||
builder.mount(binary, "/entrypoint");
|
||||
|
||||
self.mount_entrypoint(&mut builder, self.binary)?;
|
||||
self.prepare_env(&mut builder, &entrypoint.environment);
|
||||
|
||||
let args =
|
||||
@ -100,70 +102,46 @@ impl<'a> Spawner<'a> {
|
||||
}
|
||||
|
||||
Trigger::Pipe(s) => {
|
||||
let pipe = self.pipes.get_mut(s).unwrap().take_read()?;
|
||||
let binary = PathBuf::from(self.binary).canonicalize()?;
|
||||
|
||||
let mut builder = VoidBuilder::new();
|
||||
builder.mount(binary, "/entrypoint");
|
||||
self.prepare_spawner(&mut builder, &entrypoint.environment, &entrypoint.args)?;
|
||||
|
||||
let pipe = self.pipes.get_mut(s).unwrap().take_read()?;
|
||||
builder.keep_fd(&pipe);
|
||||
|
||||
self.prepare_env(&mut builder, &entrypoint.environment);
|
||||
|
||||
for env in &entrypoint.environment {
|
||||
if let Environment::Filesystem {
|
||||
host_path,
|
||||
environment_path: _,
|
||||
} = env
|
||||
{
|
||||
builder.mount(host_path, host_path);
|
||||
}
|
||||
}
|
||||
|
||||
let closure = || match self.pipe_trigger(pipe, entrypoint, name) {
|
||||
Ok(()) => std::process::exit(exitcode::OK),
|
||||
Ok(()) => exitcode::OK,
|
||||
Err(e) => {
|
||||
error!("error in pipe_trigger: {}", e);
|
||||
std::process::exit(1)
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
let void = builder.spawn(closure)?;
|
||||
info!(
|
||||
"prepared pipe trigger for entrypoint `{}` as {}",
|
||||
"spawned pipe trigger for entrypoint `{}` as {}",
|
||||
name.as_str(),
|
||||
void
|
||||
);
|
||||
}
|
||||
|
||||
Trigger::FileSocket(s) => {
|
||||
let socket = self.sockets.get_mut(s).unwrap().take_read()?;
|
||||
let binary = PathBuf::from(self.binary).canonicalize()?;
|
||||
|
||||
let mut builder = VoidBuilder::new();
|
||||
builder.mount(binary, "/entrypoint");
|
||||
self.prepare_spawner(&mut builder, &entrypoint.environment, &entrypoint.args)?;
|
||||
|
||||
let socket = self.sockets.get_mut(s).unwrap().take_read()?;
|
||||
builder.keep_fd(&socket);
|
||||
|
||||
for env in &entrypoint.environment {
|
||||
if let Environment::Filesystem {
|
||||
host_path,
|
||||
environment_path: _,
|
||||
} = env
|
||||
{
|
||||
builder.mount(host_path, host_path);
|
||||
}
|
||||
}
|
||||
|
||||
let closure = || match self.file_socket_trigger(socket, entrypoint, name) {
|
||||
Ok(()) => std::process::exit(exitcode::OK),
|
||||
Ok(()) => exitcode::OK,
|
||||
Err(e) => {
|
||||
error!("error in file_socket_trigger: {}", e);
|
||||
std::process::exit(1)
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
let void = builder.spawn(closure)?;
|
||||
info!(
|
||||
"prepared socket trigger for entrypoint `{}` as {}",
|
||||
"spawned socket trigger for entrypoint `{}` as {}",
|
||||
name.as_str(),
|
||||
void
|
||||
);
|
||||
@ -175,9 +153,21 @@ impl<'a> Spawner<'a> {
|
||||
}
|
||||
|
||||
fn pipe_trigger(&self, mut pipe: File, spec: &Entrypoint, name: &str) -> Result<()> {
|
||||
// put the work in a forked process that can handle signals
|
||||
Self::fork_for_trigger()?;
|
||||
|
||||
let mut buf = [0_u8; BUFFER_SIZE];
|
||||
loop {
|
||||
let read_bytes = pipe.read(&mut buf)?;
|
||||
let read_bytes = match pipe.read(&mut buf) {
|
||||
Ok(n) => Ok(n),
|
||||
Err(e) => {
|
||||
if e.kind() == std::io::ErrorKind::Interrupted {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(e)
|
||||
}
|
||||
}?;
|
||||
if read_bytes == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
@ -189,7 +179,7 @@ impl<'a> Spawner<'a> {
|
||||
|
||||
self.prepare_env(&mut builder, &spec.environment);
|
||||
|
||||
let args = PreparedArgs::prepare_ambient(&mut builder, &spec.args)?;
|
||||
let args = PreparedArgs::prepare_ambient(self, &mut builder, &spec.args)?;
|
||||
|
||||
let closure =
|
||||
|| {
|
||||
@ -216,18 +206,36 @@ impl<'a> Spawner<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
builder.spawn(closure)?;
|
||||
let void = builder.spawn(closure)?;
|
||||
info!("spawned entrypoint `{}` as {}", name, void);
|
||||
}
|
||||
}
|
||||
|
||||
fn file_socket_trigger(&self, socket: File, spec: &Entrypoint, name: &str) -> Result<()> {
|
||||
// put the work in a forked process that can handle signals
|
||||
Self::fork_for_trigger()?;
|
||||
|
||||
let mut cmsg_buf = nix::cmsg_space!([RawFd; MAX_FILE_DESCRIPTORS]);
|
||||
|
||||
loop {
|
||||
let msg = recvmsg(socket.as_raw_fd(), &[], None, MsgFlags::empty()).map_err(|e| {
|
||||
Error::Nix {
|
||||
msg: "recvmsg",
|
||||
src: e,
|
||||
let msg = match recvmsg::<()>(
|
||||
socket.as_raw_fd(),
|
||||
&mut [],
|
||||
Some(&mut cmsg_buf),
|
||||
MsgFlags::empty(),
|
||||
) {
|
||||
Ok(m) => Ok(m),
|
||||
Err(e) => {
|
||||
if e == NixError::EINTR {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::Nix {
|
||||
msg: "recvmsg",
|
||||
src: e,
|
||||
})
|
||||
}
|
||||
})?;
|
||||
}?;
|
||||
|
||||
debug!("triggering from socket recvmsg");
|
||||
|
||||
@ -247,7 +255,7 @@ impl<'a> Spawner<'a> {
|
||||
|
||||
self.prepare_env(&mut builder, &spec.environment);
|
||||
|
||||
let args = PreparedArgs::prepare_ambient(&mut builder, &spec.args)?;
|
||||
let args = PreparedArgs::prepare_ambient(self, &mut builder, &spec.args)?;
|
||||
|
||||
let closure = || {
|
||||
if self.debug {
|
||||
@ -273,7 +281,8 @@ impl<'a> Spawner<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
builder.spawn(closure)?;
|
||||
let void = builder.spawn(closure)?;
|
||||
info!("spawned entrypoint `{}` as {}", name, void);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
@ -281,11 +290,39 @@ impl<'a> Spawner<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn fork_for_trigger() -> Result<()> {
|
||||
// SAFETY: only unsafe in a multi-threaded program
|
||||
if let ForkResult::Parent { child: _pid } = unsafe { fork() }.map_err(|e| Error::Nix {
|
||||
msg: "fork",
|
||||
src: e,
|
||||
})? {
|
||||
let status = waitid(Id::All, WaitPidFlag::WEXITED).map_err(|e| Error::Nix {
|
||||
msg: "waitpid",
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
match status {
|
||||
WaitStatus::Exited(_pid, code) => {
|
||||
std::process::exit(code);
|
||||
}
|
||||
WaitStatus::Signaled(pid, sig, _coredump) => {
|
||||
debug!(
|
||||
"trigger: forked child {} was terminated with signal {}",
|
||||
pid, sig
|
||||
);
|
||||
std::process::exit(-1);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop_self(name: &str) -> Result<()> {
|
||||
let pid = Pid::this();
|
||||
info!("stopping process `{}`", name);
|
||||
|
||||
kill(pid, Signal::SIGSTOP).map_err(|e| Error::Nix {
|
||||
kill(Pid::this(), Signal::SIGSTOP).map_err(|e| Error::Nix {
|
||||
msg: "kill",
|
||||
src: e,
|
||||
})?;
|
||||
@ -294,6 +331,67 @@ impl<'a> Spawner<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_entrypoint(&self, builder: &mut VoidBuilder, binary: &Path) -> Result<()> {
|
||||
let binary = PathBuf::from(binary).canonicalize()?;
|
||||
builder.mount(binary, "/entrypoint");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn forward_mounts<'b>(
|
||||
&self,
|
||||
builder: &mut VoidBuilder,
|
||||
environment: impl IntoIterator<Item = &'b Environment>,
|
||||
arguments: impl IntoIterator<Item = &'b Arg>,
|
||||
) {
|
||||
for env in environment {
|
||||
if let Environment::Filesystem {
|
||||
host_path,
|
||||
environment_path: _,
|
||||
} = env
|
||||
{
|
||||
builder.mount(host_path, host_path);
|
||||
}
|
||||
}
|
||||
|
||||
for arg in arguments {
|
||||
if let Arg::File(host_path) = arg {
|
||||
builder.mount(host_path, host_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn forward_files<'b>(
|
||||
&self,
|
||||
builder: &mut VoidBuilder,
|
||||
arguments: impl IntoIterator<Item = &'b Arg>,
|
||||
) {
|
||||
for arg in arguments {
|
||||
if let Arg::FileSocket(socket) = arg {
|
||||
builder.keep_fd(self.sockets.get(socket.get_name()).unwrap().write_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_spawner<'b>(
|
||||
&self,
|
||||
builder: &mut VoidBuilder,
|
||||
environment: impl IntoIterator<Item = &'b Environment>,
|
||||
args: impl IntoIterator<Item = &'b Arg> + Copy,
|
||||
) -> Result<()> {
|
||||
self.mount_entrypoint(builder, self.binary)?;
|
||||
self.forward_mounts(builder, environment, args);
|
||||
self.forward_files(builder, args);
|
||||
|
||||
builder.mount("/dev/null", "/dev/null");
|
||||
builder.mount("/proc", "/proc").remount_proc();
|
||||
|
||||
builder.keep_fd(&1);
|
||||
builder.keep_fd(&2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_env<'b>(
|
||||
&self,
|
||||
builder: &mut VoidBuilder,
|
||||
@ -314,6 +412,20 @@ impl<'a> Spawner<'a> {
|
||||
Environment::DomainName(name) => {
|
||||
builder.set_domain_name(name);
|
||||
}
|
||||
|
||||
Environment::Procfs => {
|
||||
builder.mount("/proc", "/proc").remount_proc();
|
||||
}
|
||||
|
||||
Environment::Stdin => {
|
||||
builder.keep_fd(&0);
|
||||
}
|
||||
Environment::Stdout => {
|
||||
builder.keep_fd(&1);
|
||||
}
|
||||
Environment::Stderr => {
|
||||
builder.keep_fd(&2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
300
src/spawner/rpc.rs
Normal file
300
src/spawner/rpc.rs
Normal file
@ -0,0 +1,300 @@
|
||||
use log::{debug, error};
|
||||
|
||||
use crate::specification::{AddressFamily as SpecAddressFamily, RpcSpecification};
|
||||
use crate::Error;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::net::{TcpStream, UdpSocket};
|
||||
use std::os::raw::c_char;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use nix::sys::socket::AddressFamily;
|
||||
use nix::sys::socket::{recv, send, sendmsg, ControlMessage, MsgFlags};
|
||||
|
||||
const MAX_MSG_LENGTH: usize = 4096;
|
||||
|
||||
pub struct RpcHandler<'a> {
|
||||
permitted_rpcs: &'a [RpcSpecification],
|
||||
}
|
||||
|
||||
impl<'a> RpcHandler<'a> {
|
||||
pub(super) fn new(permitted_rpcs: &'a [RpcSpecification]) -> Self {
|
||||
Self { permitted_rpcs }
|
||||
}
|
||||
|
||||
pub(super) fn handle(&self, socket: File) -> Result<(), Error> {
|
||||
let mut buf = vec![0; MAX_MSG_LENGTH];
|
||||
|
||||
loop {
|
||||
let read_bytes =
|
||||
recv(socket.as_raw_fd(), &mut buf, MsgFlags::empty()).map_err(|e| Error::Nix {
|
||||
msg: "recvmsg",
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
debug!("handling rpc");
|
||||
|
||||
if read_bytes < 4 {
|
||||
error!("received rpc too short");
|
||||
continue;
|
||||
}
|
||||
|
||||
// SAFETY: safe as the enum repr is non_exhaustive so any value is valid and the buffer is long enough
|
||||
let kind = unsafe { *(buf.as_ptr() as *const RpcKind) };
|
||||
|
||||
let fds = Vec::new();
|
||||
if kind.num_fds() > 0 {
|
||||
// get any fds to go alongside the message
|
||||
// nothing which requires this currently exists
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
let resp = handle_rpc(self.permitted_rpcs, kind, &buf[4..], &fds);
|
||||
|
||||
let (msg, fds) = RpcResultSend::new(resp);
|
||||
|
||||
// sendmsg first so its there when listening for the send
|
||||
if !fds.is_empty() {
|
||||
let fds: Box<[i32]> = fds.iter().map(|f| f.as_raw_fd()).collect();
|
||||
|
||||
sendmsg::<()>(
|
||||
socket.as_raw_fd(),
|
||||
&[],
|
||||
&[ControlMessage::ScmRights(&fds)],
|
||||
MsgFlags::empty(),
|
||||
None,
|
||||
)
|
||||
.map_err(|e| Error::Nix {
|
||||
msg: "sendmsg",
|
||||
src: e,
|
||||
})?;
|
||||
}
|
||||
|
||||
// SAFETY: safe as msg is of fixed size
|
||||
let msg = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
&msg as *const RpcResultSend as *const u8,
|
||||
std::mem::size_of_val(&msg),
|
||||
)
|
||||
};
|
||||
|
||||
send(socket.as_raw_fd(), msg, MsgFlags::empty()).map_err(|e| Error::Nix {
|
||||
msg: "send",
|
||||
src: e,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[non_exhaustive]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RpcKind {
|
||||
OpenTcpSocket,
|
||||
OpenUdpSocket,
|
||||
}
|
||||
|
||||
impl RpcKind {
|
||||
fn num_fds(&self) -> usize {
|
||||
match self {
|
||||
RpcKind::OpenTcpSocket => 0,
|
||||
RpcKind::OpenUdpSocket => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenSocket {
|
||||
pub family: AddressFamily,
|
||||
pub port: u16,
|
||||
pub host: [c_char],
|
||||
}
|
||||
|
||||
pub enum RpcResult {
|
||||
OpenTcpSocket { socket: TcpStream },
|
||||
OpenUdpSocket { socket: UdpSocket },
|
||||
|
||||
Error { error: RpcError },
|
||||
}
|
||||
|
||||
pub enum RpcResultSend {
|
||||
OpenTcpSocket,
|
||||
OpenUdpSocket,
|
||||
|
||||
Error { error: RpcError },
|
||||
}
|
||||
|
||||
impl RpcResultSend {
|
||||
fn new(from: RpcResult) -> (Self, Vec<Box<dyn AsRawFd>>) {
|
||||
match from {
|
||||
RpcResult::OpenTcpSocket { socket } => (Self::OpenTcpSocket, vec![Box::new(socket)]),
|
||||
RpcResult::OpenUdpSocket { socket } => (Self::OpenUdpSocket, vec![Box::new(socket)]),
|
||||
RpcResult::Error { error } => (Self::Error { error }, vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub enum RpcError {
|
||||
BadlyFormedRequest,
|
||||
OperationNotPermitted,
|
||||
Io { errno: i32 },
|
||||
}
|
||||
|
||||
fn handle_rpc(
|
||||
permitted_rpcs: &[RpcSpecification],
|
||||
kind: RpcKind,
|
||||
data: &[u8],
|
||||
_fds: &[File],
|
||||
) -> RpcResult {
|
||||
fn inner(
|
||||
permitted_rpcs: &[RpcSpecification],
|
||||
kind: RpcKind,
|
||||
data: &[u8],
|
||||
) -> Result<RpcResult, RpcError> {
|
||||
match kind {
|
||||
RpcKind::OpenTcpSocket => {
|
||||
let data = unsafe { &*(data as *const [u8] as *const OpenSocket) };
|
||||
if !validate_open_tcp_socket(permitted_rpcs, data)? {
|
||||
Ok(RpcResult::Error {
|
||||
error: RpcError::OperationNotPermitted,
|
||||
})
|
||||
} else {
|
||||
handle_open_tcp_socket(data)
|
||||
}
|
||||
}
|
||||
RpcKind::OpenUdpSocket => {
|
||||
let data = unsafe { &*(data as *const [u8] as *const OpenSocket) };
|
||||
if !validate_open_udp_socket(permitted_rpcs, data)? {
|
||||
Ok(RpcResult::Error {
|
||||
error: RpcError::OperationNotPermitted,
|
||||
})
|
||||
} else {
|
||||
handle_open_udp_socket(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match inner(permitted_rpcs, kind, data) {
|
||||
Ok(o) => o,
|
||||
Err(e) => RpcResult::Error { error: e },
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_open_tcp_socket(
|
||||
permitted_rpcs: &[RpcSpecification],
|
||||
req: &OpenSocket,
|
||||
) -> Result<bool, RpcError> {
|
||||
for each in permitted_rpcs {
|
||||
if let RpcSpecification::OpenTcpSocket { family, port, host } = each {
|
||||
let mut allowed = true;
|
||||
|
||||
allowed &= match family {
|
||||
None => true,
|
||||
Some(fam) => match req.family {
|
||||
AddressFamily::Inet => *fam == SpecAddressFamily::Inet,
|
||||
AddressFamily::Inet6 => *fam == SpecAddressFamily::Inet6,
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
allowed &= match port {
|
||||
None => true,
|
||||
Some(p) => req.port == *p,
|
||||
};
|
||||
|
||||
allowed &= match host {
|
||||
None => true,
|
||||
Some(h) => {
|
||||
CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||
.map_err(|_| RpcError::BadlyFormedRequest)?
|
||||
.to_string_lossy()
|
||||
.as_ref()
|
||||
== h
|
||||
}
|
||||
};
|
||||
|
||||
if allowed {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn handle_open_tcp_socket(req: &OpenSocket) -> Result<RpcResult, RpcError> {
|
||||
let host = CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||
.map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||
let host = host.to_str().map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||
|
||||
let socket = TcpStream::connect(host).map_err(|e| RpcError::Io {
|
||||
errno: e.raw_os_error().unwrap(),
|
||||
})?;
|
||||
|
||||
Ok(RpcResult::OpenTcpSocket { socket })
|
||||
}
|
||||
|
||||
fn validate_open_udp_socket(
|
||||
permitted_rpcs: &[RpcSpecification],
|
||||
req: &OpenSocket,
|
||||
) -> Result<bool, RpcError> {
|
||||
for each in permitted_rpcs {
|
||||
if let RpcSpecification::OpenUdpSocket { family, port, host } = each {
|
||||
let mut allowed = true;
|
||||
|
||||
allowed &= match family {
|
||||
None => true,
|
||||
Some(fam) => match req.family {
|
||||
AddressFamily::Inet => *fam == SpecAddressFamily::Inet,
|
||||
AddressFamily::Inet6 => *fam == SpecAddressFamily::Inet6,
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
allowed &= match port {
|
||||
None => true,
|
||||
Some(p) => req.port == *p,
|
||||
};
|
||||
|
||||
allowed &= match host {
|
||||
None => true,
|
||||
Some(h) => {
|
||||
CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||
.map_err(|_| RpcError::BadlyFormedRequest)?
|
||||
.to_string_lossy()
|
||||
.as_ref()
|
||||
== h
|
||||
}
|
||||
};
|
||||
|
||||
if allowed {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn handle_open_udp_socket(req: &OpenSocket) -> Result<RpcResult, RpcError> {
|
||||
let host = CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||
.map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||
let host = host.to_str().map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| RpcError::Io {
|
||||
errno: e.raw_os_error().unwrap(),
|
||||
})?;
|
||||
|
||||
socket.connect(host).map_err(|e| RpcError::Io {
|
||||
errno: e.raw_os_error().unwrap(),
|
||||
})?;
|
||||
|
||||
Ok(RpcResult::OpenUdpSocket { socket })
|
||||
}
|
||||
|
||||
fn as_u8_slice(s: &[c_char]) -> &[u8] {
|
||||
unsafe { std::slice::from_raw_parts(s.as_ptr() as *const u8, s.len()) }
|
||||
}
|
@ -68,6 +68,9 @@ pub enum Arg {
|
||||
/// A TCP Listener
|
||||
TcpListener { addr: SocketAddr },
|
||||
|
||||
/// An RPC socket that accepts specified commands
|
||||
Rpc(Vec<RpcSpecification>),
|
||||
|
||||
/// The rest of argv[1..], 0 or more arguments
|
||||
Trailing,
|
||||
}
|
||||
@ -78,6 +81,38 @@ impl Arg {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub enum RpcSpecification {
|
||||
/// Open a TCP socket
|
||||
///
|
||||
/// None for each value means that any value is allowed in the call.
|
||||
/// A specified value restricts to exactly that.
|
||||
OpenTcpSocket {
|
||||
family: Option<AddressFamily>,
|
||||
port: Option<u16>,
|
||||
host: Option<String>,
|
||||
},
|
||||
|
||||
/// Open a UDP socket
|
||||
///
|
||||
/// None for each value means that any value is allowed in the call.
|
||||
/// A specified value restricts to exactly that.
|
||||
OpenUdpSocket {
|
||||
family: Option<AddressFamily>,
|
||||
port: Option<u16>,
|
||||
host: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub enum AddressFamily {
|
||||
/// IPv4 address
|
||||
Inet,
|
||||
|
||||
/// IPv6 address
|
||||
Inet6,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub enum Pipe {
|
||||
Rx(String),
|
||||
@ -117,6 +152,12 @@ pub enum Environment {
|
||||
|
||||
Hostname(String),
|
||||
DomainName(String),
|
||||
|
||||
Procfs,
|
||||
|
||||
Stdin,
|
||||
Stdout,
|
||||
Stderr,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
||||
@ -204,6 +245,31 @@ impl Specification {
|
||||
return Err(Error::BadPipe(pipe.to_string()));
|
||||
}
|
||||
|
||||
// validate sockets match
|
||||
let (read, write) = self.sockets();
|
||||
let mut read_set = HashSet::with_capacity(read.len());
|
||||
|
||||
for socket in read {
|
||||
if !read_set.insert(socket) {
|
||||
return Err(Error::BadFileSocket(socket.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut write_set = HashSet::with_capacity(write.len());
|
||||
for socket in write {
|
||||
write_set.insert(socket);
|
||||
}
|
||||
|
||||
for socket in &read_set {
|
||||
if !write_set.contains(socket) {
|
||||
return Err(Error::BadFileSocket(socket.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(socket) = (&write_set - &read_set).into_iter().next() {
|
||||
return Err(Error::BadFileSocket(socket.to_string()));
|
||||
}
|
||||
|
||||
// validate trigger arguments make sense
|
||||
for entrypoint in self.entrypoints.values() {
|
||||
if entrypoint.args.contains(&Arg::Trigger) {
|
||||
|
133
src/void.rs
133
src/void.rs
@ -1,11 +1,12 @@
|
||||
use log::{debug, error};
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
use crate::clone::{clone3, CloneArgs, CloneFlags};
|
||||
use crate::{Error, Result};
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -13,7 +14,7 @@ use std::path::{Path, PathBuf};
|
||||
use nix::fcntl::{FcntlArg, FdFlag};
|
||||
use nix::mount::{mount, umount2, MntFlags, MsFlags};
|
||||
use nix::sys::signal::{signal, SigHandler, Signal};
|
||||
use nix::unistd::{close, getgid, getuid, pivot_root, sethostname, Gid, Pid, Uid};
|
||||
use nix::unistd::{close, dup2, getgid, getuid, pivot_root, sethostname, Gid, Pid, Uid};
|
||||
|
||||
use close_fds::CloseFdsBuilder;
|
||||
|
||||
@ -33,6 +34,8 @@ pub struct VoidBuilder {
|
||||
|
||||
mounts: HashMap<PathBuf, PathBuf>,
|
||||
fds: HashSet<RawFd>,
|
||||
|
||||
remount_proc: bool,
|
||||
}
|
||||
|
||||
impl VoidBuilder {
|
||||
@ -42,6 +45,7 @@ impl VoidBuilder {
|
||||
domain_name: None,
|
||||
mounts: HashMap::new(),
|
||||
fds: HashSet::new(),
|
||||
remount_proc: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +69,11 @@ impl VoidBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remount_proc(&mut self) -> &mut Self {
|
||||
self.remount_proc = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self, child_fn: impl FnOnce() -> i32) -> Result<VoidHandle> {
|
||||
let mut args = CloneArgs::new(
|
||||
CloneFlags::CLONE_NEWCGROUP
|
||||
@ -94,15 +103,23 @@ impl VoidBuilder {
|
||||
})?;
|
||||
|
||||
let result = {
|
||||
debug!("voiding user namespace...");
|
||||
self.void_user_namespace(parent_uid, parent_gid)?; // first to regain full capabilities
|
||||
|
||||
debug!("voiding mount namespace...");
|
||||
self.void_mount_namespace()?;
|
||||
debug!("voiding file descriptors..."); // occur after mount to unmount /dev/null
|
||||
self.void_file_descriptors()?;
|
||||
|
||||
debug!("voiding ipc namespace...");
|
||||
self.void_ipc_namespace()?;
|
||||
debug!("voiding uts namespace...");
|
||||
self.void_uts_namespace()?;
|
||||
debug!("voiding network namespace...");
|
||||
self.void_network_namespace()?;
|
||||
debug!("voiding pid namespace...");
|
||||
self.void_pid_namespace()?;
|
||||
self.void_mount_namespace()?;
|
||||
debug!("voiding cgroup namespace...");
|
||||
self.void_cgroup_namespace()?;
|
||||
|
||||
Ok::<(), Error>(())
|
||||
@ -112,19 +129,12 @@ impl VoidBuilder {
|
||||
error!("error preparing void: {}", e);
|
||||
std::process::exit(-1)
|
||||
} else {
|
||||
info!("successfully prepared void");
|
||||
std::process::exit(child_fn())
|
||||
}
|
||||
}
|
||||
|
||||
debug!("cloned child: {}", child);
|
||||
|
||||
// Leak the child function's resources in the parent process.
|
||||
// This avoids closing files that have been "moved" into the child.
|
||||
// It is also an over-approximation, and may cause actual memory leaks.
|
||||
// As the spawning process is normally short lived, this shouldn't
|
||||
// be a problem.
|
||||
std::mem::forget(child_fn);
|
||||
|
||||
Ok(VoidHandle { pid: child })
|
||||
}
|
||||
|
||||
@ -180,7 +190,7 @@ impl VoidBuilder {
|
||||
* unavailable after unmounting the old root.
|
||||
*/
|
||||
fn void_mount_namespace(&self) -> Result<()> {
|
||||
// change the propagation type of the old root to private
|
||||
trace!("changing the propagation type of the old root to private");
|
||||
mount(
|
||||
Option::<&str>::None,
|
||||
"/",
|
||||
@ -193,10 +203,21 @@ impl VoidBuilder {
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
// create and consume a tmpdir to mount a tmpfs into
|
||||
let new_root = tempfile::tempdir()?.into_path();
|
||||
trace!("creating tmpdir for new root");
|
||||
let tmp_base = {
|
||||
let env_dir = env::temp_dir();
|
||||
if env_dir.exists() {
|
||||
env_dir
|
||||
} else {
|
||||
debug!("env_dir does not exist, assuming `/` as the base");
|
||||
"/".into()
|
||||
}
|
||||
};
|
||||
|
||||
// mount a tmpfs as the new root
|
||||
// consume so it does not attempt to delete a folder which no longer exists
|
||||
let new_root = tempfile::tempdir_in(tmp_base)?.into_path();
|
||||
|
||||
trace!("mounting a new root tmpfs at `{:?}`", &new_root);
|
||||
mount(
|
||||
Some("tmpfs"),
|
||||
&new_root,
|
||||
@ -209,13 +230,12 @@ impl VoidBuilder {
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
// prepare a subdirectory to pivot the old root into
|
||||
let old_root = new_root.join("old_root/");
|
||||
debug!("new_root: {:?}; put_old: {:?}", &new_root, &old_root);
|
||||
fs::create_dir(&old_root)?;
|
||||
let put_old = new_root.join("old_root/");
|
||||
debug!("new_root: {:?}; put_old: {:?}", &new_root, &put_old);
|
||||
fs::create_dir_all(&put_old)?;
|
||||
|
||||
// pivot the old root into a subdirectory of the new root
|
||||
pivot_root(&new_root, &old_root).map_err(|e| Error::Nix {
|
||||
trace!("pivoting old root into a subdirectory of new root");
|
||||
pivot_root(&new_root, &put_old).map_err(|e| Error::Nix {
|
||||
msg: "pivot_root",
|
||||
src: e,
|
||||
})?;
|
||||
@ -223,11 +243,22 @@ impl VoidBuilder {
|
||||
let new_root = PathBuf::from("/");
|
||||
let old_root = PathBuf::from("/old_root/");
|
||||
|
||||
// chdir after
|
||||
trace!("changing root directory to new root");
|
||||
std::env::set_current_dir(&new_root)?;
|
||||
|
||||
// mount paths before unmounting old_root
|
||||
for (src, dst) in &self.mounts {
|
||||
trace!("creating bind mounts before unmounting");
|
||||
|
||||
let standard_dev_null = if self.mounts.contains_key(&PathBuf::from("/dev/null")) {
|
||||
None
|
||||
} else {
|
||||
Some((PathBuf::from("/dev/null"), PathBuf::from("/dev/null")))
|
||||
};
|
||||
|
||||
for (src, dst) in self
|
||||
.mounts
|
||||
.iter()
|
||||
.chain(standard_dev_null.as_ref().map(|(x, y)| (x, y)))
|
||||
{
|
||||
let mut src = old_root.join(src.strip_prefix("/").unwrap_or(src));
|
||||
let dst = new_root.join(dst.strip_prefix("/").unwrap_or(dst));
|
||||
|
||||
@ -252,12 +283,29 @@ impl VoidBuilder {
|
||||
fs::write(&dst, b"")?;
|
||||
}
|
||||
|
||||
// bind mount
|
||||
// rbind mount
|
||||
mount(
|
||||
Some(&src),
|
||||
&dst,
|
||||
Option::<&str>::None,
|
||||
MsFlags::MS_BIND,
|
||||
MsFlags::MS_BIND | MsFlags::MS_REC,
|
||||
Option::<&str>::None,
|
||||
)
|
||||
.map_err(|e| Error::Nix {
|
||||
msg: "mount",
|
||||
src: e,
|
||||
})?;
|
||||
}
|
||||
|
||||
// remount proc
|
||||
if self.remount_proc {
|
||||
debug!("remounting /proc`");
|
||||
|
||||
mount(
|
||||
Some("proc"),
|
||||
"/proc",
|
||||
Some("proc"),
|
||||
MsFlags::empty(),
|
||||
Option::<&str>::None,
|
||||
)
|
||||
.map_err(|e| Error::Nix {
|
||||
@ -347,6 +395,37 @@ impl VoidBuilder {
|
||||
closer.closefrom(3);
|
||||
}
|
||||
|
||||
// overwrite stdin/stdout/stderr without closing
|
||||
{
|
||||
let mut nullfd: Option<File> = None;
|
||||
for stdfd in &[0, 1, 2] {
|
||||
if !keep.contains(stdfd) {
|
||||
trace!("voiding stdfd {}", stdfd);
|
||||
|
||||
let fd = nullfd
|
||||
.take()
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| File::open("/dev/null"))?;
|
||||
|
||||
dup2(fd.as_raw_fd(), *stdfd).map_err(|e| Error::Nix {
|
||||
msg: "dup2",
|
||||
src: e,
|
||||
})?;
|
||||
|
||||
nullfd = Some(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.mounts.contains_key(&PathBuf::from("/dev/null")) {
|
||||
debug!("unmount /dev/null after voiding file descriptors");
|
||||
|
||||
umount2("/dev/null", MntFlags::MNT_DETACH).map_err(|e| Error::Nix {
|
||||
msg: "umount2",
|
||||
src: e,
|
||||
})?;
|
||||
}
|
||||
|
||||
for fd in keep.as_ref() {
|
||||
let mut flags = FdFlag::from_bits_truncate(
|
||||
nix::fcntl::fcntl(*fd, FcntlArg::F_GETFD).map_err(|e| Error::Nix {
|
||||
|
Loading…
Reference in New Issue
Block a user