guard: kind of works

This commit is contained in:
Matthew LeVan 2024-01-23 14:22:03 -05:00
parent 7ff1657687
commit 95d4023d72
4 changed files with 170 additions and 79 deletions

View File

@ -15,11 +15,12 @@ use crate::noun::{Atom, Cell, IndirectAtom, Noun, Slots, D, T};
use crate::serf::TERMINATOR; use crate::serf::TERMINATOR;
use crate::trace::{write_nock_trace, TraceInfo, TraceStack}; use crate::trace::{write_nock_trace, TraceInfo, TraceStack};
use crate::unifying_equality::unifying_equality; use crate::unifying_equality::unifying_equality;
use ares_guard::{guard, guard_err}; use ares_guard::*;
use ares_macros::tas; use ares_macros::tas;
use assert_no_alloc::{assert_no_alloc, ensure_alloc_counters}; use assert_no_alloc::{assert_no_alloc, ensure_alloc_counters};
use bitvec::prelude::{BitSlice, Lsb0}; use bitvec::prelude::{BitSlice, Lsb0};
use either::Either::*; use either::*;
use std::convert::TryFrom;
use std::ffi::c_void; use std::ffi::c_void;
use std::result; use std::result;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -319,11 +320,25 @@ pub enum Error {
} }
pub enum GuardError { pub enum GuardError {
GuardSound = 0, GuardSound = GUARD_SOUND as isize,
GuardArmor = 1, GuardArmor = GUARD_ARMOR as isize,
GuardWeird = 2, GuardWeird = GUARD_WEIRD as isize,
GuardSpent = 3, GuardSpent = GUARD_SPENT as isize,
GuardErupt = 4, GuardErupt = GUARD_ERUPT as isize,
}
impl TryFrom<u32> for GuardError {
type Error = ();
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
match value {
GUARD_SOUND => Ok(GuardError::GuardSound),
GUARD_ARMOR => Ok(GuardError::GuardArmor),
GUARD_WEIRD => Ok(GuardError::GuardWeird),
GUARD_SPENT => Ok(GuardError::GuardSpent),
GUARD_ERUPT => Ok(GuardError::GuardErupt),
_ => Err(()),
}
}
} }
impl Preserve for Error { impl Preserve for Error {
@ -367,48 +382,89 @@ fn debug_assertions(stack: &mut NockStack, noun: Noun) {
assert_no_junior_pointers!(stack, noun); assert_no_junior_pointers!(stack, noun);
} }
extern "C" fn rust_callback(arg: *mut c_void) -> *mut c_void { use std::marker::PhantomData;
let closure: &mut Box<dyn FnMut() -> *mut c_void> =
unsafe { &mut *(arg as *mut Box<dyn FnMut() -> *mut c_void>) }; pub struct CCallback<'closure> {
closure() pub function: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
pub user_data: *mut c_void,
// without this it's too easy to accidentally drop the closure too soon
_lifetime: PhantomData<&'closure mut c_void>,
} }
impl<'closure> CCallback<'closure> {
pub fn new<F>(closure: &'closure mut F) -> Self where F: FnMut() -> Result {
let function: unsafe extern "C" fn(*mut c_void) -> *mut c_void = Self::call_closure::<F>;
debug_assert_eq!(std::mem::size_of::<&'closure mut F>(), std::mem::size_of::<*const c_void>());
debug_assert_eq!(std::mem::size_of_val(&function), std::mem::size_of::<*const c_void>());
Self {
function,
user_data: closure as *mut F as *mut c_void,
_lifetime: PhantomData,
}
}
unsafe extern "C" fn call_closure<F>(user_data: *mut c_void) -> *mut c_void where F: FnMut() -> Result {
let cb: &mut F = user_data.cast::<F>().as_mut().unwrap();
let mut v = (*cb)();
let v_ptr = &mut v as *mut _ as *mut c_void;
v_ptr
}
}
// fn main() {
// let mut v = Vec::new();
// // must assign to a variable to ensure it lives until the end of scope
// let closure = &mut |x: i32| { v.push(x) };
// let c = CCallback::new(closure);
// unsafe { (c.function)(c.user_data, 123) };
// assert_eq!(v, [123]);
// }
pub fn call_with_guard<F: FnMut() -> Result>( pub fn call_with_guard<F: FnMut() -> Result>(
f: F, f: &mut F,
stack: *const *mut c_void, stack: *const *mut c_void,
alloc: *const *mut c_void, alloc: *const *mut c_void,
) -> Result { ) -> Result {
let boxed_f = Box::new(f); let c = CCallback::new(f);
let res: Result = Err(Error::Deterministic(D(0))); let mut result: Result = Ok(D(0));
let mut result = Box::new(res); let result_ptr = &mut result as *mut _ as *mut c_void;
unsafe { unsafe {
let raw_f = Box::into_raw(Box::new(boxed_f));
let result_ptr = &mut result as *mut _ as *mut c_void;
guard( let err = guard(
Some(rust_callback), Some(c.function as unsafe extern "C" fn(*mut c_void) -> *mut c_void),
raw_f as *mut c_void, c.user_data as *mut c_void,
stack, stack,
alloc, alloc,
result_ptr, result_ptr,
); );
let _ = Box::from_raw(raw_f); if let Ok(err) = GuardError::try_from(err) {
if !*result.is_null() {
let err = *(result as *const guard_err);
match err { match err {
0 => { // sound GuardError::GuardSound => {
let res = result as *mut Result; return result;
*res }
}, GuardError::GuardArmor => {
// TODO: handle other errors explicitly. return Err(Error::Deterministic(D(0)));
_ => Err(Error::Deterministic(D(0))), }
GuardError::GuardWeird => {
return Err(Error::Deterministic(D(0)));
}
GuardError::GuardSpent => {
return Err(Error::NonDeterministic(D(0)));
}
GuardError::GuardErupt => {
return Err(Error::NonDeterministic(D(0)));
}
} }
} } else {
else { return Err(Error::Deterministic(D(0)));
(Box::from_raw(raw_f))()
} }
} }
} }
@ -450,8 +506,10 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
// (See https://docs.rs/assert_no_alloc/latest/assert_no_alloc/#advanced-use) // (See https://docs.rs/assert_no_alloc/latest/assert_no_alloc/#advanced-use)
let nock = assert_no_alloc(|| { let nock = assert_no_alloc(|| {
ensure_alloc_counters(|| { ensure_alloc_counters(|| {
call_with_guard(|| unsafe { let work_closure = &mut || unsafe {
eprint!("ares: entered closure\r\n");
push_formula(&mut context.stack, formula, true)?; push_formula(&mut context.stack, formula, true)?;
eprint!("ares: pushed formula\r\n");
loop { loop {
let work: NockWork = *context.stack.top(); let work: NockWork = *context.stack.top();
@ -989,11 +1047,8 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
}, },
}; };
} }
}, };
stack_ptr_ptr, call_with_guard(work_closure, stack_ptr_ptr, alloc_ptr_ptr)
alloc_ptr_ptr,
std::ptr::null_mut() as *mut *mut c_void,
)
}) })
}); });

View File

@ -1,4 +1,4 @@
#include <pthread.h> #include <setjmp.h>
#include <signal.h> #include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -15,12 +15,13 @@
static uint64_t *guard_p = 0; static uint64_t *guard_p = 0;
static uint64_t **stack = 0; static uint64_t **stack = 0;
static uint64_t **alloc = 0; static uint64_t **alloc = 0;
static jmp_buf env_buffer;
volatile sig_atomic_t err = guard_sound; volatile sig_atomic_t err = guard_sound;
// Center the guard page. // Center the guard page.
guard_err _focus_guard() { guard_err _focus_guard()
{
uint64_t *stack_p = *stack; uint64_t *stack_p = *stack;
uint64_t *alloc_p = *alloc; uint64_t *alloc_p = *alloc;
@ -61,8 +62,8 @@ guard_err _focus_guard() {
} }
guard_err _slash_guard(void *si_addr) { guard_err _slash_guard(void *si_addr) {
// fprintf(stderr, "guard: slash at %p\r\n", si_addr); fprintf(stderr, "guard: slash at %p\r\n", si_addr);
// fprintf(stderr, "guard: guard at %p\r\n", (void *) guard_p); fprintf(stderr, "guard: guard at %p\r\n", (void *) guard_p);
if (si_addr >= (void *)guard_p && si_addr < (void *)guard_p + GD_PAGESIZE) { if (si_addr >= (void *)guard_p && si_addr < (void *)guard_p + GD_PAGESIZE) {
return _focus_guard(); return _focus_guard();
@ -71,17 +72,24 @@ guard_err _slash_guard(void *si_addr) {
return guard_weird; return guard_weird;
} }
void _signal_handler(int sig, siginfo_t *si, void *unused) { void _signal_handler(int sig, siginfo_t *si, void *unused)
{
switch (sig) { switch (sig) {
case SIGSEGV: case SIGSEGV:
fprintf(stderr, "guard: sigsegv\r\n");
err = _slash_guard(si->si_addr); err = _slash_guard(si->si_addr);
break; break;
case SIGINT: case SIGINT:
fprintf(stderr, "guard: sigint\r\n");
err = guard_erupt; err = guard_erupt;
break; break;
default: default:
break; break;
} }
if (err != guard_sound) {
longjmp(env_buffer, 1);
}
} }
guard_err _register_handler() { guard_err _register_handler() {
@ -95,18 +103,27 @@ guard_err _register_handler() {
return guard_weird; return guard_weird;
} }
fprintf(stderr, "guard: registered handler\r\n");
return guard_sound; return guard_sound;
} }
void guard(void *(*f)(void *), void *arg, void *const *stack_pp, void *const *alloc_pp, void *ret) { guard_err guard(
void *(*f)(void *),
void *user_data,
void *const *stack_pp,
void *const *alloc_pp,
void *ret
)
{
stack = (uint64_t**) stack_pp; stack = (uint64_t**) stack_pp;
alloc = (uint64_t**) alloc_pp; alloc = (uint64_t**) alloc_pp;
fprintf(stderr, "guard: f pointer at %p\r\n", (void *) f);
fprintf(stderr, "guard: stack at %p\r\n", (void *) stack); fprintf(stderr, "guard: stack at %p\r\n", (void *) stack);
fprintf(stderr, "guard: alloc at %p\r\n", (void *) alloc); fprintf(stderr, "guard: alloc at %p\r\n", (void *) alloc);
fprintf(stderr, "guard: stack pointer at %p\r\n", (void *) *stack); fprintf(stderr, "guard: stack pointer at %p\r\n", (void *) *stack);
fprintf(stderr, "guard: alloc pointer at %p\r\n", (void *) *alloc); fprintf(stderr, "guard: alloc pointer at %p\r\n", (void *) *alloc);
fprintf(stderr, "guard: ret pointer at %p\r\n", (void *) ret);
if (guard_p == 0) { if (guard_p == 0) {
fprintf(stderr, "guard: installing guard page\r\n"); fprintf(stderr, "guard: installing guard page\r\n");
@ -124,27 +141,21 @@ void guard(void *(*f)(void *), void *arg, void *const *stack_pp, void *const *al
goto fail; goto fail;
} }
pthread_t thread; void *result;
int thread_err; if (setjmp(env_buffer) == 0) {
thread_err = pthread_create(&thread, NULL, f, arg); fprintf(stderr, "guard: calling f\r\n");
if (thread_err != 0) { result = f(user_data);
err = guard_weird;
goto fail;
} }
else {
while (err == guard_sound) { fprintf(stderr, "guard: jump buffer already set\r\n");
if (pthread_kill(thread, 0) != 0) { if (err != guard_sound) {
fprintf(stderr, "guard: not sound\r\n");
goto fail; goto fail;
} }
sleep(1); else {
} fprintf(stderr, "guard: assigning ret\r\n");
*(void **)ret = result;
void *thread_result; }
pthread_join(thread, &thread_result);
*(void **)ret = thread_result;
if (err != guard_sound) {
goto fail;
} }
if (mprotect(guard_p, GD_PAGESIZE, PROT_READ | PROT_WRITE) == -1) { if (mprotect(guard_p, GD_PAGESIZE, PROT_READ | PROT_WRITE) == -1) {
@ -152,13 +163,25 @@ void guard(void *(*f)(void *), void *arg, void *const *stack_pp, void *const *al
goto fail; goto fail;
} }
return; return guard_sound;
fail: fail:
if (mprotect(guard_p, GD_PAGESIZE, PROT_READ | PROT_WRITE) == -1) { if (mprotect(guard_p, GD_PAGESIZE, PROT_READ | PROT_WRITE) == -1) {
fprintf(stderr, "guard: failed to remove guard page\r\n"); fprintf(stderr, "guard: failed to uninstall guard page\r\n");
} }
fprintf(stderr, "guard: error %d\r\n", err); switch (err) {
*(void **)ret = (void *) &err; case guard_armor:
return; fprintf(stderr, "guard: armor error\r\n");
break;
case guard_weird:
fprintf(stderr, "guard: weird error\r\n");
break;
case guard_spent:
fprintf(stderr, "guard: spent error\r\n");
break;
case guard_erupt:
fprintf(stderr, "guard: erupt error\r\n");
break;
}
return err;
} }

View File

@ -3,6 +3,15 @@
#include <stdint.h> #include <stdint.h>
typedef enum {
guard_sound = 0, // job's done
guard_armor = 1, // mprotect
guard_weird = 2, // strange state
guard_spent = 3, // out of memory (bail:meme)
guard_erupt = 4, // sigint
} guard_err;
/** /**
* Execute the given closure `f` within the memory arena between the * Execute the given closure `f` within the memory arena between the
* `stack` and `alloc` pointers, with guard page protection. Write either * `stack` and `alloc` pointers, with guard page protection. Write either
@ -31,15 +40,13 @@
* error will be written to the `ret` pointer. The caller is then responsible * error will be written to the `ret` pointer. The caller is then responsible
* for handling this error and aborting with a `bail:meme`. * for handling this error and aborting with a `bail:meme`.
*/ */
void guard(void *(*f)(void *), void *arg, void *const *stack_pp, void *const *alloc_pp, void *ret); guard_err guard(
void *(*f)(void *),
typedef enum { void *user_data,
guard_sound = 0, // job's done void *const *stack_pp,
guard_armor = 1, // mprotect void *const *alloc_pp,
guard_weird = 2, // strange state void *ret
guard_spent = 3, // out of memory (bail:meme) );
guard_erupt = 4, // sigint
} guard_err;
#endif #endif

View File

@ -3,3 +3,9 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs")); include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub const GUARD_SOUND: u32 = guard_err_guard_sound;
pub const GUARD_ARMOR: u32 = guard_err_guard_armor;
pub const GUARD_WEIRD: u32 = guard_err_guard_weird;
pub const GUARD_SPENT: u32 = guard_err_guard_spent;
pub const GUARD_ERUPT: u32 = guard_err_guard_erupt;