use crate::error::BrainBitError;
#[cfg(target_os = "linux")]
pub fn block_internet() -> Result<(), BrainBitError> {
linux_sandbox::apply_seccomp_filter()
}
#[cfg(target_os = "macos")]
pub fn block_internet() -> Result<(), BrainBitError> {
macos_sandbox::apply_sandbox_profile()
}
#[cfg(target_os = "windows")]
pub fn block_internet() -> Result<(), BrainBitError> {
windows_sandbox::apply_firewall_rule()
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
pub fn block_internet() -> Result<(), BrainBitError> {
log::warn!("Network sandboxing not implemented for this platform");
Ok(())
}
#[cfg(target_os = "linux")]
pub fn is_sandboxed() -> bool {
linux_sandbox::is_seccomp_active()
}
#[cfg(target_os = "macos")]
pub fn is_sandboxed() -> bool {
macos_sandbox::is_sandboxed()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub fn is_sandboxed() -> bool {
false
}
#[cfg(target_os = "linux")]
mod linux_sandbox {
use crate::error::BrainBitError;
use std::sync::atomic::{AtomicBool, Ordering};
static SECCOMP_APPLIED: AtomicBool = AtomicBool::new(false);
const SYS_SOCKET: u32 = 41;
const AF_INET: u32 = 2;
const AF_INET6: u32 = 10;
const SECCOMP_SET_MODE_FILTER: libc::c_ulong = 1;
const SECCOMP_RET_ALLOW: u32 = 0x7fff_0000;
const SECCOMP_RET_ERRNO: u32 = 0x0005_0000;
const EPERM: u32 = 1;
const BPF_LD_W_ABS: u16 = 0x20; const BPF_JMP_JEQ_K: u16 = 0x15; const BPF_RET_K: u16 = 0x06;
const OFFSET_NR: u32 = 0; const OFFSET_ARG0: u32 = 16;
#[repr(C)]
struct SockFprog {
len: u16,
filter: *const SockFilter,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct SockFilter {
code: u16,
jt: u8,
jf: u8,
k: u32,
}
pub fn apply_seccomp_filter() -> Result<(), BrainBitError> {
if SECCOMP_APPLIED.load(Ordering::Relaxed) {
return Ok(());
}
let ret = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };
if ret != 0 {
return Err(BrainBitError::NotSupported(
"prctl(PR_SET_NO_NEW_PRIVS) failed".into(),
));
}
let filter: [SockFilter; 7] = [
SockFilter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: OFFSET_NR },
SockFilter { code: BPF_JMP_JEQ_K, jt: 0, jf: 3, k: SYS_SOCKET },
SockFilter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: OFFSET_ARG0 },
SockFilter { code: BPF_JMP_JEQ_K, jt: 2, jf: 0, k: AF_INET },
SockFilter { code: BPF_JMP_JEQ_K, jt: 1, jf: 0, k: AF_INET6 },
SockFilter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW },
SockFilter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ERRNO | EPERM },
];
let prog = SockFprog {
len: filter.len() as u16,
filter: filter.as_ptr(),
};
let ret = unsafe {
libc::syscall(
libc::SYS_seccomp,
SECCOMP_SET_MODE_FILTER,
0u64,
&prog as *const _,
)
};
if ret != 0 {
let err = std::io::Error::last_os_error();
return Err(BrainBitError::NotSupported(format!(
"seccomp(SET_MODE_FILTER) failed: {}", err
)));
}
SECCOMP_APPLIED.store(true, Ordering::Relaxed);
log::info!("seccomp: AF_INET/AF_INET6 socket creation blocked");
Ok(())
}
pub fn is_seccomp_active() -> bool {
SECCOMP_APPLIED.load(Ordering::Relaxed)
}
}
#[cfg(target_os = "macos")]
mod macos_sandbox {
use crate::error::BrainBitError;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
static SANDBOX_APPLIED: AtomicBool = AtomicBool::new(false);
extern "C" {
fn sandbox_init(
profile: *const c_char,
flags: u64,
errorbuf: *mut *mut c_char,
) -> i32;
fn sandbox_free_error(errorbuf: *mut c_char);
}
pub fn apply_sandbox_profile() -> Result<(), BrainBitError> {
if SANDBOX_APPLIED.load(Ordering::Relaxed) {
return Ok(());
}
let profile = CString::new(concat!(
"(version 1)\n",
"(allow default)\n",
"(deny network-outbound (remote ip))\n",
))
.map_err(|_| BrainBitError::NotSupported("Invalid sandbox profile".into()))?;
let mut errorbuf: *mut c_char = ptr::null_mut();
let ret = unsafe { sandbox_init(profile.as_ptr(), 0, &mut errorbuf) };
if ret != 0 {
let msg = if !errorbuf.is_null() {
let s = unsafe { CStr::from_ptr(errorbuf) }
.to_string_lossy()
.into_owned();
unsafe { sandbox_free_error(errorbuf) };
s
} else {
"unknown error".into()
};
return Err(BrainBitError::NotSupported(format!(
"sandbox_init failed: {}", msg
)));
}
SANDBOX_APPLIED.store(true, Ordering::Relaxed);
log::info!("macOS sandbox: remote network-outbound denied");
Ok(())
}
pub fn is_sandboxed() -> bool {
SANDBOX_APPLIED.load(Ordering::Relaxed)
}
}
#[cfg(target_os = "windows")]
mod windows_sandbox {
use crate::error::BrainBitError;
pub fn apply_firewall_rule() -> Result<(), BrainBitError> {
let exe = std::env::current_exe().map_err(|e| {
BrainBitError::NotSupported(format!("Cannot get exe path: {}", e))
})?;
let rule_name = format!("BrainBitSDK_Block_{}", std::process::id());
let output = std::process::Command::new("netsh")
.args([
"advfirewall", "firewall", "add", "rule",
&format!("name={}", rule_name),
"dir=out", "action=block",
&format!("program={}", exe.to_string_lossy()),
"enable=yes", "profile=any",
])
.output()
.map_err(|e| {
BrainBitError::NotSupported(format!(
"netsh failed (need Administrator?): {}", e
))
})?;
if !output.status.success() {
return Err(BrainBitError::NotSupported(format!(
"Firewall rule failed: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
log::info!("Windows Firewall: outbound blocked (rule '{}')", rule_name);
let name = rule_name.clone();
std::thread::spawn(move || {
let (tx, rx) = std::sync::mpsc::channel();
let _ = ctrlc::set_handler(move || { let _ = tx.send(()); });
let _ = rx.recv();
let _ = std::process::Command::new("netsh")
.args(["advfirewall", "firewall", "delete", "rule",
&format!("name={}", name)])
.output();
std::process::exit(0);
});
Ok(())
}
}