@@ -10,7 +10,7 @@ use std::time::Duration;
1010
1111use sandlock_core:: pipeline:: Stage ;
1212use sandlock_core:: sandbox:: { BranchAction , ByteSize , SandboxBuilder } ;
13- use sandlock_core:: { Protection , RunResult , Sandbox } ;
13+ use sandlock_core:: { Protection , RunResult , Sandbox , StdioMode } ;
1414
1515pub mod handler;
1616pub mod notif_repr;
@@ -1021,41 +1021,61 @@ pub struct sandlock_handle_t {
10211021 runtime : tokio:: runtime:: Runtime ,
10221022}
10231023
1024- /// Fork the child and install policy; the child is parked between policy
1025- /// install and execve. Returns a live handle. Call `sandlock_start` to
1026- /// release the child to execve.
1024+ /// Shared entry-point prologue for the create/popen family: validate the
1025+ /// pointers, parse the optional name and argv, build the requested runtime,
1026+ /// and apply the name to a cloned policy. Returns the owned
1027+ /// `(Sandbox, Runtime, args)` triple, or `None` on any invalid input or
1028+ /// runtime-build failure (callers map `None` to a null handle).
1029+ ///
1030+ /// The tail stays with each caller: `sandlock_create*` drive `sb.create()`,
1031+ /// `sandlock_popen` drives `sb.popen()` + fd hand-off. Factoring the prologue
1032+ /// here keeps null/name/argv/runtime handling from drifting between them.
10271033///
10281034/// # Safety
10291035/// `policy` must be a valid policy pointer. `name` may be NULL to
10301036/// auto-generate a sandbox name, or a valid NUL-terminated string.
10311037/// `argv` must point to `argc` C strings.
1032- unsafe fn sandlock_create_with_runtime (
1038+ unsafe fn prepare (
10331039 policy : * const sandlock_sandbox_t ,
10341040 name : * const c_char ,
10351041 argv : * const * const c_char ,
10361042 argc : c_uint ,
10371043 build_rt : fn ( ) -> Option < tokio:: runtime:: Runtime > ,
1038- ) -> * mut sandlock_handle_t {
1044+ ) -> Option < ( Sandbox , tokio :: runtime :: Runtime , Vec < String > ) > {
10391045 if policy. is_null ( ) || argv. is_null ( ) {
1040- return ptr :: null_mut ( ) ;
1046+ return None ;
10411047 }
10421048 let policy = & ( * policy) . _private ;
1043- let name = match optional_name ( name) {
1044- Ok ( name) => name,
1045- Err ( _) => return ptr:: null_mut ( ) ,
1046- } ;
1049+ let name = optional_name ( name) . ok ( ) ?;
10471050 let args = read_argv ( argv, argc) ;
1048- let arg_refs: Vec < & str > = args. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
1049-
1050- let rt = match build_rt ( ) {
1051- Some ( rt) => rt,
1052- None => return ptr:: null_mut ( ) ,
1053- } ;
1054-
1055- let mut sb = match name {
1051+ let rt = build_rt ( ) ?;
1052+ let sb = match name {
10561053 Some ( ref n) => policy. clone ( ) . with_name ( n. clone ( ) ) ,
10571054 None => policy. clone ( ) ,
10581055 } ;
1056+ Some ( ( sb, rt, args) )
1057+ }
1058+
1059+ /// Fork the child and install policy; the child is parked between policy
1060+ /// install and execve. Returns a live handle. Call `sandlock_start` to
1061+ /// release the child to execve.
1062+ ///
1063+ /// # Safety
1064+ /// `policy` must be a valid policy pointer. `name` may be NULL to
1065+ /// auto-generate a sandbox name, or a valid NUL-terminated string.
1066+ /// `argv` must point to `argc` C strings.
1067+ unsafe fn sandlock_create_with_runtime (
1068+ policy : * const sandlock_sandbox_t ,
1069+ name : * const c_char ,
1070+ argv : * const * const c_char ,
1071+ argc : c_uint ,
1072+ build_rt : fn ( ) -> Option < tokio:: runtime:: Runtime > ,
1073+ ) -> * mut sandlock_handle_t {
1074+ let ( mut sb, rt, args) = match prepare ( policy, name, argv, argc, build_rt) {
1075+ Some ( t) => t,
1076+ None => return ptr:: null_mut ( ) ,
1077+ } ;
1078+ let arg_refs: Vec < & str > = args. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
10591079
10601080 if !matches ! ( block_on_runtime( & rt, sb. create( & arg_refs) ) , Some ( Ok ( ( ) ) ) ) {
10611081 return ptr:: null_mut ( ) ;
@@ -1098,6 +1118,130 @@ pub unsafe extern "C" fn sandlock_create_for_run(
10981118 sandlock_create_with_runtime ( policy, name, argv, argc, build_runtime)
10991119}
11001120
1121+ /// Map a raw `StdioMode` discriminant (0=inherit, 1=piped, 2=null) to the enum.
1122+ /// Returns `None` for any other value so callers can reject it loudly.
1123+ fn stdio_mode_from_raw ( v : u32 ) -> Option < StdioMode > {
1124+ match v {
1125+ 0 => Some ( StdioMode :: Inherit ) ,
1126+ 1 => Some ( StdioMode :: Piped ) ,
1127+ 2 => Some ( StdioMode :: Null ) ,
1128+ _ => None ,
1129+ }
1130+ }
1131+
1132+ /// Write `fd` through `out` if non-null; otherwise close it (a piped stream
1133+ /// whose fd the caller did not ask for must not leak).
1134+ unsafe fn write_or_close_fd ( out : * mut c_int , fd : c_int ) {
1135+ if out. is_null ( ) {
1136+ if fd >= 0 {
1137+ libc:: close ( fd) ;
1138+ }
1139+ } else {
1140+ * out = fd;
1141+ }
1142+ }
1143+
1144+ /// Spawn a confined process with per-stream stdio wiring and return a live
1145+ /// handle (the streaming counterpart of `sandlock_create` + `sandlock_start`).
1146+ ///
1147+ /// `stdin_mode`/`stdout_mode`/`stderr_mode` are `StdioMode` discriminants
1148+ /// (0=inherit, 1=piped, 2=null). For each stream set to *piped*, the matching
1149+ /// `out_*_fd` receives an owned fd the caller must `close()`; for inherit/null
1150+ /// it receives -1. Pass null for an `out_*_fd` to discard that fd (it is closed
1151+ /// rather than leaked). Returns null on error (unknown mode, build/fork
1152+ /// failure) with every `out_*_fd` left -1.
1153+ ///
1154+ /// The handle uses a multi-threaded runtime so the seccomp supervisor keeps
1155+ /// pumping while the caller does blocking IO on the returned fds. Wait for the
1156+ /// process with `sandlock_handle_wait`, terminate with `sandlock_handle_kill`,
1157+ /// and release with `sandlock_handle_free`.
1158+ ///
1159+ /// Deadlock warning: a piped fd is yours once returned — `close()` a piped
1160+ /// `out_stdin_fd` *before* `sandlock_handle_wait`, or a child that reads to EOF
1161+ /// (e.g. `cat`) never exits and the wait blocks forever. Likewise drain a piped
1162+ /// `out_stdout_fd`/`out_stderr_fd` before waiting: a child that fills the pipe
1163+ /// buffer blocks on write and never reaches exit.
1164+ ///
1165+ /// # Safety
1166+ /// As `sandlock_create`; `out_*_fd` must each be null or a valid `*mut c_int`.
1167+ #[ no_mangle]
1168+ pub unsafe extern "C" fn sandlock_popen (
1169+ policy : * const sandlock_sandbox_t ,
1170+ name : * const c_char ,
1171+ argv : * const * const c_char ,
1172+ argc : c_uint ,
1173+ stdin_mode : u32 ,
1174+ stdout_mode : u32 ,
1175+ stderr_mode : u32 ,
1176+ out_stdin_fd : * mut c_int ,
1177+ out_stdout_fd : * mut c_int ,
1178+ out_stderr_fd : * mut c_int ,
1179+ ) -> * mut sandlock_handle_t {
1180+ use std:: os:: fd:: IntoRawFd ;
1181+
1182+ if !out_stdin_fd. is_null ( ) {
1183+ * out_stdin_fd = -1 ;
1184+ }
1185+ if !out_stdout_fd. is_null ( ) {
1186+ * out_stdout_fd = -1 ;
1187+ }
1188+ if !out_stderr_fd. is_null ( ) {
1189+ * out_stderr_fd = -1 ;
1190+ }
1191+
1192+ let ( stdin, stdout, stderr) = match (
1193+ stdio_mode_from_raw ( stdin_mode) ,
1194+ stdio_mode_from_raw ( stdout_mode) ,
1195+ stdio_mode_from_raw ( stderr_mode) ,
1196+ ) {
1197+ ( Some ( a) , Some ( b) , Some ( c) ) => ( a, b, c) ,
1198+ _ => {
1199+ // Fail loud, not into an indistinguishable NULL: name the offending
1200+ // discriminant(s) so the binding author sees why the handle is null.
1201+ eprintln ! (
1202+ "sandlock: sandlock_popen rejected an unknown StdioMode \
1203+ (stdin={stdin_mode}, stdout={stdout_mode}, stderr={stderr_mode}); \
1204+ valid values are 0=inherit, 1=piped, 2=null"
1205+ ) ;
1206+ return ptr:: null_mut ( ) ;
1207+ }
1208+ } ;
1209+ // Shared prologue (null/name/argv/runtime) via `prepare`; popen keeps its own
1210+ // `sb.popen()` + fd-handoff tail. build_live_runtime: the multi-threaded
1211+ // runtime keeps the seccomp supervisor pumping during the caller's blocking IO.
1212+ let ( mut sb, rt, args) = match prepare ( policy, name, argv, argc, build_live_runtime) {
1213+ Some ( t) => t,
1214+ None => return ptr:: null_mut ( ) ,
1215+ } ;
1216+ let arg_refs: Vec < & str > = args. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
1217+
1218+ // popen returns a Process borrowing `sb`; take the owned fds and let it drop
1219+ // so `sb` can move into the handle. The process keeps running (owned by `sb`).
1220+ // Return OwnedFds (not raw ints) from the future: on the Err/None branch
1221+ // below they drop-close themselves instead of leaking a dangling raw fd.
1222+ let owned = block_on_runtime ( & rt, async {
1223+ let mut proc = sb. popen ( & arg_refs, stdin, stdout, stderr) . await ?;
1224+ Ok :: < _ , sandlock_core:: SandlockError > ( (
1225+ proc. take_stdin ( ) ,
1226+ proc. take_stdout ( ) ,
1227+ proc. take_stderr ( ) ,
1228+ ) )
1229+ } ) ;
1230+ let ( in_fd, out_fd, err_fd) = match owned {
1231+ Some ( Ok ( t) ) => t,
1232+ _ => return ptr:: null_mut ( ) ,
1233+ } ;
1234+ // Convert to raw only on the infallible side of the match.
1235+ write_or_close_fd ( out_stdin_fd, in_fd. map ( |f| f. into_raw_fd ( ) ) . unwrap_or ( -1 ) ) ;
1236+ write_or_close_fd ( out_stdout_fd, out_fd. map ( |f| f. into_raw_fd ( ) ) . unwrap_or ( -1 ) ) ;
1237+ write_or_close_fd ( out_stderr_fd, err_fd. map ( |f| f. into_raw_fd ( ) ) . unwrap_or ( -1 ) ) ;
1238+
1239+ Box :: into_raw ( Box :: new ( sandlock_handle_t {
1240+ sandbox : sb,
1241+ runtime : rt,
1242+ } ) )
1243+ }
1244+
11011245/// Release a previously `sandlock_create`d child to execve. Returns 0 on
11021246/// success, -1 on error.
11031247///
@@ -1127,10 +1271,33 @@ pub unsafe extern "C" fn sandlock_handle_pid(h: *const sandlock_handle_t) -> i32
11271271 ( * h) . sandbox . pid ( ) . unwrap_or ( 0 )
11281272}
11291273
1274+ /// Send SIGKILL to the handle's entire process group. Idempotent: a process
1275+ /// that already exited is not an error. Returns 0 on success, -1 on error.
1276+ /// The handle remains valid; call `sandlock_handle_wait` to collect the exit
1277+ /// status and `sandlock_handle_free` to release it.
1278+ ///
1279+ /// # Safety
1280+ /// `h` must be a valid handle from `sandlock_create` / `sandlock_popen`.
1281+ #[ no_mangle]
1282+ pub unsafe extern "C" fn sandlock_handle_kill ( h : * mut sandlock_handle_t ) -> c_int {
1283+ if h. is_null ( ) {
1284+ return -1 ;
1285+ }
1286+ let h = & mut * h;
1287+ if h. sandbox . kill ( ) . is_err ( ) {
1288+ return -1 ;
1289+ }
1290+ 0
1291+ }
1292+
11301293/// Wait for the sandbox to exit. Returns a result handle with stdout/stderr.
11311294///
1295+ /// For a `sandlock_popen` handle, close a piped `out_stdin_fd` and drain any
1296+ /// piped `out_stdout_fd`/`out_stderr_fd` *before* calling this, or a child that
1297+ /// reads to EOF or fills a pipe buffer never exits and this blocks forever.
1298+ ///
11321299/// # Safety
1133- /// `h` must be a valid handle from `sandlock_create`.
1300+ /// `h` must be a valid handle from `sandlock_create` / `sandlock_popen` .
11341301#[ no_mangle]
11351302pub unsafe extern "C" fn sandlock_handle_wait ( h : * mut sandlock_handle_t ) -> * mut sandlock_result_t {
11361303 if h. is_null ( ) {
@@ -1211,7 +1378,7 @@ pub unsafe extern "C" fn sandlock_handle_port_mappings(h: *const sandlock_handle
12111378/// Free a sandbox handle. Kills the process if still running.
12121379///
12131380/// # Safety
1214- /// `h` must be null or a valid handle from `sandlock_create`.
1381+ /// `h` must be null or a valid handle from `sandlock_create` / `sandlock_popen` .
12151382#[ no_mangle]
12161383pub unsafe extern "C" fn sandlock_handle_free ( h : * mut sandlock_handle_t ) {
12171384 if !h. is_null ( ) {
0 commit comments