Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions crates/sandlock-ffi/src/handler/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,6 @@ fn block_on_run(
handlers: Vec<(i64, FfiHandler)>,
interactive: bool,
) -> Option<Result<RunResult, SandlockError>> {
// Use a fresh runtime — sandlock-core already pulls in tokio with
// rt-multi-thread; this matches the pattern used by the existing
// `sandlock_run` path. A panic in an `extern "C"`-reachable path is
// UB, so we report runtime-build failure to the caller via `None`
// instead of unwrapping.
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.ok()?;
let cmd_refs: Vec<&str> = cmd.iter().map(String::as_str).collect();
// Apply `name` via the builder method on a clone — mirrors the
// pattern used by `sandlock_run` in lib.rs. A `None` here means
Expand All @@ -124,7 +115,11 @@ fn block_on_run(
Some(n) => sandbox.clone().with_name(n),
None => sandbox.clone(),
};
Some(rt.block_on(async move {
// Drives the supervisor on the shared per-thread runtime; see
// `crate::runtime` for why this is `current_thread`. This path is
// reached from `extern "C-unwind"` entry points, so user callback
// panics are intentionally allowed to propagate.
crate::runtime::with_runtime_unwind(|rt| rt.block_on(async move {
if interactive {
sb.run_interactive_with_extra_handlers(&cmd_refs, handlers).await
} else {
Expand Down
167 changes: 85 additions & 82 deletions crates/sandlock-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use sandlock_core::{Sandbox, RunResult};

pub mod handler;
pub mod notif_repr;
mod runtime;

use runtime::{block_on_runtime, build_live_runtime, build_runtime, with_runtime};

// ----------------------------------------------------------------
// Opaque wrapper types
Expand Down Expand Up @@ -691,17 +694,13 @@ pub unsafe extern "C" fn sandlock_run(
let args = read_argv(argv, argc);
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};
let mut sb = match name {
Some(ref n) => policy.clone().with_name(n.clone()),
None => policy.clone(),
};
match rt.block_on(sb.run(&arg_refs)) {
Ok(result) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Err(_) => ptr::null_mut(),
match with_runtime(|rt| rt.block_on(sb.run(&arg_refs))) {
Some(Ok(result)) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
_ => ptr::null_mut(),
}
}

Expand All @@ -710,7 +709,10 @@ pub unsafe extern "C" fn sandlock_run(
// ----------------------------------------------------------------

/// Opaque handle for a live sandbox.
/// Owns both the Sandbox and the tokio Runtime that drives its supervisor.
///
/// Owns both the Sandbox and a small Tokio runtime that drives its
/// supervisor. Live handles need a runtime whose spawned tasks keep
/// progressing after `sandlock_start` returns.
#[allow(non_camel_case_types)]
pub struct sandlock_handle_t {
sandbox: Sandbox,
Expand All @@ -725,12 +727,12 @@ pub struct sandlock_handle_t {
/// `policy` must be a valid policy pointer. `name` may be NULL to
/// auto-generate a sandbox name, or a valid NUL-terminated string.
/// `argv` must point to `argc` C strings.
#[no_mangle]
pub unsafe extern "C" fn sandlock_create(
unsafe fn sandlock_create_with_runtime(
policy: *const sandlock_sandbox_t,
name: *const c_char,
argv: *const *const c_char,
argc: c_uint,
build_rt: fn() -> Option<tokio::runtime::Runtime>,
) -> *mut sandlock_handle_t {
if policy.is_null() || argv.is_null() { return ptr::null_mut(); }
let policy = &(*policy)._private;
Expand All @@ -741,23 +743,54 @@ pub unsafe extern "C" fn sandlock_create(
let args = read_argv(argv, argc);
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
let rt = match build_rt() {
Some(rt) => rt,
None => return ptr::null_mut(),
};

let mut sb = match name {
Some(ref n) => policy.clone().with_name(n.clone()),
None => policy.clone(),
};

if rt.block_on(sb.create(&arg_refs)).is_err() {
if !matches!(block_on_runtime(&rt, sb.create(&arg_refs)), Some(Ok(()))) {
return ptr::null_mut();
}

Box::into_raw(Box::new(sandlock_handle_t { sandbox: sb, runtime: rt }))
}

#[no_mangle]
pub unsafe extern "C" fn sandlock_create(
policy: *const sandlock_sandbox_t,
name: *const c_char,
argv: *const *const c_char,
argc: c_uint,
) -> *mut sandlock_handle_t {
sandlock_create_with_runtime(policy, name, argv, argc, build_live_runtime)
}

/// Create a sandbox handle for immediate start+wait use on the calling
/// FFI thread. Unlike `sandlock_create`, this uses the thread-local
/// `current_thread` runtime and does not create Tokio worker threads.
///
/// This is intended for blocking one-shot wrappers that call
/// `sandlock_start` and `sandlock_handle_wait*` immediately from the
/// same thread. Long-lived handles should use `sandlock_create` so the
/// supervisor keeps progressing between FFI calls.
///
/// # Safety
/// Same constraints as `sandlock_create`.
#[no_mangle]
pub unsafe extern "C" fn sandlock_create_for_run(
policy: *const sandlock_sandbox_t,
name: *const c_char,
argv: *const *const c_char,
argc: c_uint,
) -> *mut sandlock_handle_t {
sandlock_create_with_runtime(policy, name, argv, argc, build_runtime)
}

/// Release a previously `sandlock_create`d child to execve. Returns 0 on
/// success, -1 on error.
///
Expand Down Expand Up @@ -789,9 +822,9 @@ pub unsafe extern "C" fn sandlock_handle_pid(h: *const sandlock_handle_t) -> i32
pub unsafe extern "C" fn sandlock_handle_wait(h: *mut sandlock_handle_t) -> *mut sandlock_result_t {
if h.is_null() { return ptr::null_mut(); }
let h = &mut *h;
match h.runtime.block_on(h.sandbox.wait()) {
Ok(result) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Err(_) => ptr::null_mut(),
match block_on_runtime(&h.runtime, h.sandbox.wait()) {
Some(Ok(result)) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
_ => ptr::null_mut(),
}
}

Expand All @@ -811,19 +844,19 @@ pub unsafe extern "C" fn sandlock_handle_wait_timeout(

if timeout_ms == 0 {
// No timeout -- same as sandlock_handle_wait.
return match h.runtime.block_on(h.sandbox.wait()) {
Ok(result) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Err(_) => ptr::null_mut(),
return match block_on_runtime(&h.runtime, h.sandbox.wait()) {
Some(Ok(result)) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
_ => ptr::null_mut(),
};
}

let dur = Duration::from_millis(timeout_ms);
match h.runtime.block_on(async {
match block_on_runtime(&h.runtime, async {
tokio::time::timeout(dur, h.sandbox.wait()).await
}) {
Ok(Ok(result)) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Ok(Err(_)) => ptr::null_mut(),
Err(_) => {
Some(Ok(Ok(result))) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Some(Ok(Err(_))) | None => ptr::null_mut(),
Some(Err(_)) => {
// Timeout -- kill the process and return a timeout result.
let _ = h.sandbox.kill();
let result = RunResult::timeout();
Expand All @@ -844,7 +877,10 @@ pub unsafe extern "C" fn sandlock_handle_port_mappings(
) -> *mut c_char {
if h.is_null() { return ptr::null_mut(); }
let h = &*h;
let map = h.runtime.block_on(h.sandbox.port_mappings());
let map = match block_on_runtime(&h.runtime, h.sandbox.port_mappings()) {
Some(map) => map,
None => return ptr::null_mut(),
};
if map.is_empty() { return ptr::null_mut(); }
let json = serde_json::to_string(&map).unwrap_or_default();
match CString::new(json) {
Expand Down Expand Up @@ -887,17 +923,13 @@ pub unsafe extern "C" fn sandlock_run_interactive(
let args = read_argv(argv, argc);
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return -1,
};
let mut sb = match name {
Some(ref n) => policy.clone().with_name(n.clone()),
None => policy.clone(),
};
match rt.block_on(sb.run_interactive(&arg_refs)) {
Ok(result) => result.code().unwrap_or(-1),
Err(_) => -1,
match with_runtime(|rt| rt.block_on(sb.run_interactive(&arg_refs))) {
Some(Ok(result)) => result.code().unwrap_or(-1),
_ => -1,
}
}

Expand Down Expand Up @@ -1039,17 +1071,13 @@ pub unsafe extern "C" fn sandlock_dry_run(
let args = read_argv(argv, argc);
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};
let mut sb = match name {
Some(ref n) => policy.clone().with_name(n.clone()),
None => policy.clone(),
};
match rt.block_on(sb.dry_run(&arg_refs)) {
Ok(result) => Box::into_raw(Box::new(sandlock_dry_run_result_t { _private: result })),
Err(_) => ptr::null_mut(),
match with_runtime(|rt| rt.block_on(sb.dry_run(&arg_refs))) {
Some(Ok(result)) => Box::into_raw(Box::new(sandlock_dry_run_result_t { _private: result })),
_ => ptr::null_mut(),
}
}

Expand Down Expand Up @@ -1221,14 +1249,9 @@ pub unsafe extern "C" fn sandlock_pipeline_run(
None
};

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};

match rt.block_on(pipeline.run(timeout)) {
Ok(result) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Err(_) => ptr::null_mut(),
match with_runtime(|rt| rt.block_on(pipeline.run(timeout))) {
Some(Ok(result)) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
_ => ptr::null_mut(),
}
}

Expand Down Expand Up @@ -1327,14 +1350,9 @@ pub unsafe extern "C" fn sandlock_gather_run(
None
};

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};

match rt.block_on(gather.run(timeout)) {
Ok(result) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Err(_) => ptr::null_mut(),
match with_runtime(|rt| rt.block_on(gather.run(timeout))) {
Some(Ok(result)) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
_ => ptr::null_mut(),
}
}

Expand Down Expand Up @@ -1625,14 +1643,9 @@ pub unsafe extern "C" fn sandlock_fork(
if sb.is_null() { return ptr::null_mut(); }
let sb = &mut *sb;

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};

match rt.block_on(sb.fork(n)) {
Ok(clones) => Box::into_raw(Box::new(sandlock_fork_result_t { clones })),
Err(_) => ptr::null_mut(),
match with_runtime(|rt| rt.block_on(sb.fork(n))) {
Some(Ok(clones)) => Box::into_raw(Box::new(sandlock_fork_result_t { clones })),
_ => ptr::null_mut(),
}
}

Expand Down Expand Up @@ -1676,19 +1689,14 @@ pub unsafe extern "C" fn sandlock_reduce(
let args = read_argv(argv, argc);
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};

let reducer = match name {
Some(ref n) => policy.clone().with_name(n.clone()),
None => policy.clone(),
};

match rt.block_on(reducer.reduce(&arg_refs, &mut fr.clones.as_mut_slice())) {
Ok(result) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
Err(_) => ptr::null_mut(),
match with_runtime(|rt| rt.block_on(reducer.reduce(&arg_refs, &mut fr.clones.as_mut_slice()))) {
Some(Ok(result)) => Box::into_raw(Box::new(sandlock_result_t { _private: result })),
_ => ptr::null_mut(),
}
}

Expand All @@ -1707,14 +1715,9 @@ pub unsafe extern "C" fn sandlock_wait(sb: *mut Sandbox) -> c_int {
if sb.is_null() { return -1; }
let sb = &mut *sb;

let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return -1,
};

match rt.block_on(sb.wait()) {
Ok(r) => r.code().unwrap_or(-1),
Err(_) => -1,
match with_runtime(|rt| rt.block_on(sb.wait())) {
Some(Ok(r)) => r.code().unwrap_or(-1),
_ => -1,
}
}

Expand Down Expand Up @@ -1749,9 +1752,9 @@ pub unsafe extern "C" fn sandlock_handle_checkpoint(
) -> *mut sandlock_checkpoint_t {
if h.is_null() { return ptr::null_mut(); }
let h = &mut *h;
match h.runtime.block_on(h.sandbox.checkpoint()) {
Ok(cp) => Box::into_raw(Box::new(sandlock_checkpoint_t { _private: cp })),
Err(_) => ptr::null_mut(),
match block_on_runtime(&h.runtime, h.sandbox.checkpoint()) {
Some(Ok(cp)) => Box::into_raw(Box::new(sandlock_checkpoint_t { _private: cp })),
_ => ptr::null_mut(),
}
}

Expand Down
Loading
Loading