use std::ffi::{CStr, CString};
use std::io;
use std::os::unix::process::CommandExt;
use lex_extension::schema::Capabilities;
use super::{Sandbox, SandboxError};
#[derive(Debug, Default, Clone, Copy)]
pub struct MacosSandbox;
const PURE_PROFILE: &str = "(version 1)\n\
(allow default)\n\
(deny network*)\n\
(deny file-read* (subpath \"/etc\"))\n";
impl Sandbox for MacosSandbox {
fn apply_to(
&self,
cmd: &mut std::process::Command,
caps: Capabilities,
) -> Result<(), SandboxError> {
if !caps.is_pure() {
return Err(SandboxError::new(format!(
"MacosSandbox only enforces pure capabilities (fs=false, net=false); got {caps:?}"
)));
}
unsafe {
cmd.pre_exec(install_pure_policy);
}
Ok(())
}
fn supports(&self, _caps: Capabilities) -> bool {
false
}
}
extern "C" {
fn sandbox_init(
profile: *const libc::c_char,
flags: u64,
errorbuf: *mut *mut libc::c_char,
) -> libc::c_int;
fn sandbox_free_error(errorbuf: *mut libc::c_char);
}
fn install_pure_policy() -> io::Result<()> {
let profile = CString::new(PURE_PROFILE).expect("profile has no interior nul");
let mut errbuf: *mut libc::c_char = std::ptr::null_mut();
let ret = unsafe { sandbox_init(profile.as_ptr(), 0, &mut errbuf) };
if ret == 0 {
return Ok(());
}
let detail = if errbuf.is_null() {
"sandbox_init failed without a diagnostic message".to_owned()
} else {
let s = unsafe { CStr::from_ptr(errbuf) }
.to_string_lossy()
.into_owned();
unsafe { sandbox_free_error(errbuf) };
s
};
let err = io::Error::other(format!("sandbox_init: {detail}"));
write_diag("sandbox_init", &err);
Err(err)
}
fn write_diag(stage: &str, err: &io::Error) {
let msg = format!("lex-extension-host sandbox: {stage} failed: {err}\n");
let bytes = msg.as_bytes();
unsafe {
libc::write(2, bytes.as_ptr() as *const _, bytes.len());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn supports_returns_false_for_every_capability_shape() {
let s = MacosSandbox;
assert!(!s.supports(Capabilities::default()));
assert!(!s.supports(Capabilities {
fs: true,
net: false,
}));
assert!(!s.supports(Capabilities {
fs: false,
net: true,
}));
assert!(!s.supports(Capabilities {
fs: true,
net: true,
}));
}
#[test]
fn apply_to_rejects_non_pure_capabilities() {
let s = MacosSandbox;
let mut cmd = std::process::Command::new("/usr/bin/true");
let err = s
.apply_to(
&mut cmd,
Capabilities {
fs: true,
net: false,
},
)
.expect_err("non-pure caps must be rejected before spawn");
assert!(
err.to_string().contains("pure capabilities"),
"unexpected error message: {err}"
);
}
}