diff --git a/rust/ares_guard/c-src/guard.c b/rust/ares_guard/c-src/guard.c index ac37c05..8423cec 100644 --- a/rust/ares_guard/c-src/guard.c +++ b/rust/ares_guard/c-src/guard.c @@ -18,10 +18,10 @@ static uintptr_t guard_p = 0; static const uintptr_t *stack_pp = NULL; static const uintptr_t *alloc_pp = NULL; -static BufListNode *buffer_list = NULL; +static GD_buflistnode *buffer_list = NULL; static struct sigaction prev_sa; -static int32_t +static guard_result _prot_page(void *address, int prot) { if (mprotect(address, GD_PAGE_SIZE, prot)) { @@ -33,13 +33,13 @@ _prot_page(void *address, int prot) return 0; } -static int32_t +static guard_result _mark_page(void *address) { return _prot_page(address, PROT_NONE); } -static int32_t +static guard_result _unmark_page(void *address) { return _prot_page(address, PROT_READ | PROT_WRITE); @@ -48,14 +48,14 @@ _unmark_page(void *address) // Center the guard page. // XX: could be a false positive if the new frame results in exact same guard page // solution: we only re-center from the signal handler -static int32_t +static guard_result _focus_guard() { uintptr_t stack_p = *stack_pp; uintptr_t alloc_p = *alloc_pp; uintptr_t old_guard_p = guard_p; uintptr_t new_guard_p; - int32_t err = 0; + guard_result err = 0; fprintf(stderr, "guard: focus: stack pointer at %p\r\n", (void *)stack_p); fprintf(stderr, "guard: focus: alloc pointer at %p\r\n", (void *)alloc_p); @@ -104,7 +104,7 @@ static void _signal_handler(int sig, siginfo_t *si, void *unused) { uintptr_t sig_addr; - int32_t err = 0; + guard_result err = 0; assert(guard_p); @@ -112,8 +112,7 @@ _signal_handler(int sig, siginfo_t *si, void *unused) if (sig != SIGSEGV) { fprintf(stderr, "guard: sig_handle: invalid signal\r\n"); - // XX: do we even want to jump? if this is fatal error, maybe just die now - siglongjmp(buffer_list->buffer, guard_signal); + assert(0); } sig_addr = (uintptr_t)si->si_addr; @@ -143,7 +142,7 @@ _signal_handler(int sig, siginfo_t *si, void *unused) } } -static int32_t +static guard_result _register_handler() { struct sigaction sa; @@ -169,17 +168,17 @@ _register_handler() return 0; } -int32_t +guard_result guard( - callback f, + void *(*f)(void *), void *closure, const uintptr_t *const s_pp, const uintptr_t *const a_pp, void ** ret ) { - BufListNode *new_buffer; - int32_t err = 0; - int32_t td_err = 0; + GD_buflistnode *new_buffer; + guard_result err = 0; + guard_result td_err = 0; fprintf(stderr, "guard: setup: stack pointer at %p\r\n", (void *)(*s_pp)); fprintf(stderr, "guard: setup: alloc pointer at %p\r\n", (void *)(*a_pp)); @@ -206,7 +205,7 @@ guard( } // Setup new longjmp buffer - new_buffer = (BufListNode *)malloc(sizeof(BufListNode)); + new_buffer = (GD_buflistnode *)malloc(sizeof(GD_buflistnode)); if (new_buffer == NULL) { fprintf(stderr, "guard: malloc error\r\n"); fprintf(stderr, "%s\r\n", strerror(errno)); diff --git a/rust/ares_guard/c-src/guard.h b/rust/ares_guard/c-src/guard.h index a2f610f..3cef93c 100644 --- a/rust/ares_guard/c-src/guard.h +++ b/rust/ares_guard/c-src/guard.h @@ -5,60 +5,76 @@ #include /** - * + * Linked list stack of jump buffers. */ -typedef struct _buf_list_node { - jmp_buf buffer; - struct _buf_list_node *next; -} BufListNode; +typedef struct GD_buflistnode GD_buflistnode; +struct GD_buflistnode { + jmp_buf buffer; + struct GD_buflistnode *next; +}; /** - * Error codes and flags. + * Return codes and flags. * * The flags are bitwise added to the errno of their respective errors. */ typedef enum { + guard_success = 0, // successful return guard_null = 1, // null stack or alloc pointer - guard_signal, // invalid signal - guard_oom, // OOM + guard_signal = 2, // invalid signal + guard_oom = 3, // out of memory guard_malloc = 0x10000000, // malloc error flag guard_mprotect = 0x20000000, // mprotect error flag guard_sigaction = 0x40000000, // sigaction error flag -} guard_err; - -typedef void *(*callback)(void *); +} guard_result; /** - * Execute the given closure `f` within the memory arena between the - * `stack` and `alloc` pointers, with guard page protection. Write either - * `f`'s succesful result or a `guard_err` to the given `ret` pointer. + * @brief Executes the given callback function `f` within the memory arena + * between the stack and allocation pointers pointed to by `s_pp` and `a_pp`, + * with guard page protection. If `f`'s execution succeeds, its result is + * written to the return pointer `*ret`. If `f`'s execution triggers an + * out of memory error or any other `guard_result`, the `guard_result` is + * returned and `*ret` is left empty. In either case, cleanup is performed + * before returning. + * + * Definitions: + * - A guard page is marked `PROT_NONE`. * - * Memory - * ------ - * The free memory arena between the `stack` and `alloc` pointers is part of a - * NockStack frame, which may either face east or west. If the frame faces - * east, the `stack` pointer will be greater than the `alloc` pointer. If it - * faces west, the `stack` pointer will be less than the `alloc` pointer. + * Assumptions: + * - `NockStack` pages are marked `PROT_READ|PROT_WRITE` by default. + * - All memory access patterns are outside-in. + * - Callback functions are compatible with the C ABI. + * - `NockStack` stack and allocation pointer locations are fixed. + * - The caller is responsible for return value memory allocation. + * - The caller is responsible for managing any external state the callback + * function may mutate. + * - The callback function may be interrupted in the case of memory exhaustion + * or other `guard_result` error (failure to `mprotect`, `malloc`, etc.). + * - `SIGSEGV` signals are expected to be raised only on guard page accesses. * - * All the pages in the memory arena are marked clean (`PROT_READ | PROT_WRITE`) - * by default, with the exception of a single guard page in the middle of the - * arena, which is marked with `PROT_NONE`. + * Invariants: + * - A single guard page is installed and maintained in the approximate center + * until `crate::guard::call_with_guard` returns. + * - A return value is only written to `*ret` on successful callback execution. + * - A `guard_result` is returned, excepting panics or negative assertions. * - * Guard - * ----- - * This function protects the free memory arena between the `stack` and `alloc` - * pointers with a guard page. A guard page is simply a single page of memory - * which is marked with `PROT_NONE`. Since all other pages are marked clean by - * default, a SIGSEGV will only be raised if the `f` function attempts to write - * to the guard page. When it does, the signal handler will attempt to re-center - * the guard page in the remaining free space left in the arena. If there is no - * more free space, then memory exhaustion has occurred and the `guard_spent` - * error will be written to the `ret` pointer. The caller is then responsible - * for handling this error and aborting with a `bail:meme`. + * Enhancements: + * - Use only a single, static jump buffer variable instead of a linked list. + * We currently use a linked list of jump buffers because we don't have a + * function for preserving stack traces across `crate::interpreter::interpret` + * calls. + * + * @param f The callback function to execute. + * @param closure A pointer to the closure data for the callback function. + * @param s_pp A pointer to the stack pointer location. + * @param a_pp A pointer to the allocation pointer location. + * @param ret A pointer to a location where the callback's result can be stored. + * + * @return A `guard_result` return code. */ -int32_t +guard_result guard( - callback f, + void *(*f)(void *), void *closure, const uintptr_t *const s_pp, const uintptr_t *const a_pp,