mirror of
https://github.com/urbit/ares.git
synced 2024-11-22 15:08:54 +03:00
guard: documentation
This commit is contained in:
parent
77ec530c7c
commit
b81ae6e406
@ -18,10 +18,10 @@
|
|||||||
static uintptr_t guard_p = 0;
|
static uintptr_t guard_p = 0;
|
||||||
static const uintptr_t *stack_pp = NULL;
|
static const uintptr_t *stack_pp = NULL;
|
||||||
static const uintptr_t *alloc_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 struct sigaction prev_sa;
|
||||||
|
|
||||||
static int32_t
|
static guard_result
|
||||||
_prot_page(void *address, int prot)
|
_prot_page(void *address, int prot)
|
||||||
{
|
{
|
||||||
if (mprotect(address, GD_PAGE_SIZE, prot)) {
|
if (mprotect(address, GD_PAGE_SIZE, prot)) {
|
||||||
@ -33,13 +33,13 @@ _prot_page(void *address, int prot)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t
|
static guard_result
|
||||||
_mark_page(void *address)
|
_mark_page(void *address)
|
||||||
{
|
{
|
||||||
return _prot_page(address, PROT_NONE);
|
return _prot_page(address, PROT_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t
|
static guard_result
|
||||||
_unmark_page(void *address)
|
_unmark_page(void *address)
|
||||||
{
|
{
|
||||||
return _prot_page(address, PROT_READ | PROT_WRITE);
|
return _prot_page(address, PROT_READ | PROT_WRITE);
|
||||||
@ -48,14 +48,14 @@ _unmark_page(void *address)
|
|||||||
// Center the guard page.
|
// Center the guard page.
|
||||||
// XX: could be a false positive if the new frame results in exact same 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
|
// solution: we only re-center from the signal handler
|
||||||
static int32_t
|
static guard_result
|
||||||
_focus_guard()
|
_focus_guard()
|
||||||
{
|
{
|
||||||
uintptr_t stack_p = *stack_pp;
|
uintptr_t stack_p = *stack_pp;
|
||||||
uintptr_t alloc_p = *alloc_pp;
|
uintptr_t alloc_p = *alloc_pp;
|
||||||
uintptr_t old_guard_p = guard_p;
|
uintptr_t old_guard_p = guard_p;
|
||||||
uintptr_t new_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: stack pointer at %p\r\n", (void *)stack_p);
|
||||||
fprintf(stderr, "guard: focus: alloc pointer at %p\r\n", (void *)alloc_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)
|
_signal_handler(int sig, siginfo_t *si, void *unused)
|
||||||
{
|
{
|
||||||
uintptr_t sig_addr;
|
uintptr_t sig_addr;
|
||||||
int32_t err = 0;
|
guard_result err = 0;
|
||||||
|
|
||||||
assert(guard_p);
|
assert(guard_p);
|
||||||
|
|
||||||
@ -112,8 +112,7 @@ _signal_handler(int sig, siginfo_t *si, void *unused)
|
|||||||
|
|
||||||
if (sig != SIGSEGV) {
|
if (sig != SIGSEGV) {
|
||||||
fprintf(stderr, "guard: sig_handle: invalid signal\r\n");
|
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
|
assert(0);
|
||||||
siglongjmp(buffer_list->buffer, guard_signal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sig_addr = (uintptr_t)si->si_addr;
|
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()
|
_register_handler()
|
||||||
{
|
{
|
||||||
struct sigaction sa;
|
struct sigaction sa;
|
||||||
@ -169,17 +168,17 @@ _register_handler()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t
|
guard_result
|
||||||
guard(
|
guard(
|
||||||
callback f,
|
void *(*f)(void *),
|
||||||
void *closure,
|
void *closure,
|
||||||
const uintptr_t *const s_pp,
|
const uintptr_t *const s_pp,
|
||||||
const uintptr_t *const a_pp,
|
const uintptr_t *const a_pp,
|
||||||
void ** ret
|
void ** ret
|
||||||
) {
|
) {
|
||||||
BufListNode *new_buffer;
|
GD_buflistnode *new_buffer;
|
||||||
int32_t err = 0;
|
guard_result err = 0;
|
||||||
int32_t td_err = 0;
|
guard_result td_err = 0;
|
||||||
|
|
||||||
fprintf(stderr, "guard: setup: stack pointer at %p\r\n", (void *)(*s_pp));
|
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));
|
fprintf(stderr, "guard: setup: alloc pointer at %p\r\n", (void *)(*a_pp));
|
||||||
@ -206,7 +205,7 @@ guard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup new longjmp buffer
|
// Setup new longjmp buffer
|
||||||
new_buffer = (BufListNode *)malloc(sizeof(BufListNode));
|
new_buffer = (GD_buflistnode *)malloc(sizeof(GD_buflistnode));
|
||||||
if (new_buffer == NULL) {
|
if (new_buffer == NULL) {
|
||||||
fprintf(stderr, "guard: malloc error\r\n");
|
fprintf(stderr, "guard: malloc error\r\n");
|
||||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||||
|
@ -5,60 +5,76 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Linked list stack of jump buffers.
|
||||||
*/
|
*/
|
||||||
typedef struct _buf_list_node {
|
typedef struct GD_buflistnode GD_buflistnode;
|
||||||
jmp_buf buffer;
|
struct GD_buflistnode {
|
||||||
struct _buf_list_node *next;
|
jmp_buf buffer;
|
||||||
} BufListNode;
|
struct GD_buflistnode *next;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error codes and flags.
|
* Return codes and flags.
|
||||||
*
|
*
|
||||||
* The flags are bitwise added to the errno of their respective errors.
|
* The flags are bitwise added to the errno of their respective errors.
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
guard_success = 0, // successful return
|
||||||
guard_null = 1, // null stack or alloc pointer
|
guard_null = 1, // null stack or alloc pointer
|
||||||
guard_signal, // invalid signal
|
guard_signal = 2, // invalid signal
|
||||||
guard_oom, // OOM
|
guard_oom = 3, // out of memory
|
||||||
guard_malloc = 0x10000000, // malloc error flag
|
guard_malloc = 0x10000000, // malloc error flag
|
||||||
guard_mprotect = 0x20000000, // mprotect error flag
|
guard_mprotect = 0x20000000, // mprotect error flag
|
||||||
guard_sigaction = 0x40000000, // sigaction error flag
|
guard_sigaction = 0x40000000, // sigaction error flag
|
||||||
} guard_err;
|
} guard_result;
|
||||||
|
|
||||||
typedef void *(*callback)(void *);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the given closure `f` within the memory arena between the
|
* @brief Executes the given callback function `f` within the memory arena
|
||||||
* `stack` and `alloc` pointers, with guard page protection. Write either
|
* between the stack and allocation pointers pointed to by `s_pp` and `a_pp`,
|
||||||
* `f`'s succesful result or a `guard_err` to the given `ret` pointer.
|
* 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
|
* Assumptions:
|
||||||
* ------
|
* - `NockStack` pages are marked `PROT_READ|PROT_WRITE` by default.
|
||||||
* The free memory arena between the `stack` and `alloc` pointers is part of a
|
* - All memory access patterns are outside-in.
|
||||||
* NockStack frame, which may either face east or west. If the frame faces
|
* - Callback functions are compatible with the C ABI.
|
||||||
* east, the `stack` pointer will be greater than the `alloc` pointer. If it
|
* - `NockStack` stack and allocation pointer locations are fixed.
|
||||||
* faces west, the `stack` pointer will be less than the `alloc` pointer.
|
* - 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`)
|
* Invariants:
|
||||||
* by default, with the exception of a single guard page in the middle of the
|
* - A single guard page is installed and maintained in the approximate center
|
||||||
* arena, which is marked with `PROT_NONE`.
|
* 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
|
* Enhancements:
|
||||||
* -----
|
* - Use only a single, static jump buffer variable instead of a linked list.
|
||||||
* This function protects the free memory arena between the `stack` and `alloc`
|
* We currently use a linked list of jump buffers because we don't have a
|
||||||
* pointers with a guard page. A guard page is simply a single page of memory
|
* function for preserving stack traces across `crate::interpreter::interpret`
|
||||||
* which is marked with `PROT_NONE`. Since all other pages are marked clean by
|
* calls.
|
||||||
* 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
|
* @param f The callback function to execute.
|
||||||
* the guard page in the remaining free space left in the arena. If there is no
|
* @param closure A pointer to the closure data for the callback function.
|
||||||
* more free space, then memory exhaustion has occurred and the `guard_spent`
|
* @param s_pp A pointer to the stack pointer location.
|
||||||
* error will be written to the `ret` pointer. The caller is then responsible
|
* @param a_pp A pointer to the allocation pointer location.
|
||||||
* for handling this error and aborting with a `bail:meme`.
|
* @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(
|
guard(
|
||||||
callback f,
|
void *(*f)(void *),
|
||||||
void *closure,
|
void *closure,
|
||||||
const uintptr_t *const s_pp,
|
const uintptr_t *const s_pp,
|
||||||
const uintptr_t *const a_pp,
|
const uintptr_t *const a_pp,
|
||||||
|
Loading…
Reference in New Issue
Block a user