hashx: allow hashx_compile to fail, avoid segfault without changing API

This is a minimal portion of the fix for tor issue #40794, in which
hashx segfaults due to denial of mprotect() syscalls at runtime.

Prior to this fix, hashx makes the assumption that if the JIT is
supported on the current architecture, it will also be usable at
runtime. This isn't true if mprotect fails on linux, which it may for
various reasons: the tor built-in sandbox, the shadow simulator, or
external security software that implements a syscall filter.

The necessary error propagation was missing internally in hashx,
causing us to obliviously call into code which was never made
executable. With this fix, hashx_make() will instead fail by returning
zero.

A proper fix will require API changes so that callers can discern
between different types of failures. Zero already means that a program
couldn't be constructed, which requires a different response: choosing a
different seed, vs switching implementations. Callers would also benefit
from a way to use one context (with its already-built program) to
run in either compiled or interpreted mode.

Signed-off-by: Micah Elizabeth Scott <beth@torproject.org>
This commit is contained in:
Micah Elizabeth Scott 2023-05-24 13:25:54 -07:00
parent 941613c663
commit 6fd5ca4914
6 changed files with 34 additions and 25 deletions

View File

@ -10,9 +10,9 @@
#include "virtual_memory.h" #include "virtual_memory.h"
#include "program.h" #include "program.h"
HASHX_PRIVATE void hashx_compile_x86(const hashx_program* program, uint8_t* code); HASHX_PRIVATE bool hashx_compile_x86(const hashx_program* program, uint8_t* code);
HASHX_PRIVATE void hashx_compile_a64(const hashx_program* program, uint8_t* code); HASHX_PRIVATE bool hashx_compile_a64(const hashx_program* program, uint8_t* code);
#if defined(_M_X64) || defined(__x86_64__) #if defined(_M_X64) || defined(__x86_64__)
#define HASHX_COMPILER 1 #define HASHX_COMPILER 1
@ -24,7 +24,7 @@ HASHX_PRIVATE void hashx_compile_a64(const hashx_program* program, uint8_t* code
#define hashx_compile(p,c) hashx_compile_a64(p,c) #define hashx_compile(p,c) hashx_compile_a64(p,c)
#else #else
#define HASHX_COMPILER 0 #define HASHX_COMPILER 0
#define hashx_compile(p,c) #define hashx_compile(p,c) (false)
#endif #endif
HASHX_PRIVATE bool hashx_compiler_init(hashx_ctx* compiler); HASHX_PRIVATE bool hashx_compiler_init(hashx_ctx* compiler);

View File

@ -48,8 +48,9 @@ static const uint8_t a64_epilogue[] = {
0xc0, 0x03, 0x5f, 0xd6, /* ret */ 0xc0, 0x03, 0x5f, 0xd6, /* ret */
}; };
void hashx_compile_a64(const hashx_program* program, uint8_t* code) { bool hashx_compile_a64(const hashx_program* program, uint8_t* code) {
hashx_vm_rw(code, COMP_CODE_SIZE); if (!hashx_vm_rw(code, COMP_CODE_SIZE))
return false;
uint8_t* pos = code; uint8_t* pos = code;
uint8_t* target = NULL; uint8_t* target = NULL;
int creg = -1; int creg = -1;
@ -145,10 +146,12 @@ void hashx_compile_a64(const hashx_program* program, uint8_t* code) {
} }
} }
EMIT(pos, a64_epilogue); EMIT(pos, a64_epilogue);
hashx_vm_rx(code, COMP_CODE_SIZE); if (!hashx_vm_rx(code, COMP_CODE_SIZE))
return false;
#ifdef __GNUC__ #ifdef __GNUC__
__builtin___clear_cache(code, pos); __builtin___clear_cache(code, pos);
#endif #endif
return true;
} }
#endif #endif

View File

@ -81,8 +81,9 @@ static const uint8_t x86_epilogue[] = {
0xC3 /* ret */ 0xC3 /* ret */
}; };
void hashx_compile_x86(const hashx_program* program, uint8_t* code) { bool hashx_compile_x86(const hashx_program* program, uint8_t* code) {
hashx_vm_rw(code, COMP_CODE_SIZE); if (!hashx_vm_rw(code, COMP_CODE_SIZE))
return false;
uint8_t* pos = code; uint8_t* pos = code;
uint8_t* target = NULL; uint8_t* target = NULL;
EMIT(pos, x86_prologue); EMIT(pos, x86_prologue);
@ -145,7 +146,7 @@ void hashx_compile_x86(const hashx_program* program, uint8_t* code) {
} }
} }
EMIT(pos, x86_epilogue); EMIT(pos, x86_epilogue);
hashx_vm_rx(code, COMP_CODE_SIZE); return hashx_vm_rx(code, COMP_CODE_SIZE);
} }
#endif #endif

View File

@ -64,7 +64,9 @@ int hashx_make(hashx_ctx* ctx, const void* seed, size_t size) {
if (!initialize_program(ctx, &program, keys)) { if (!initialize_program(ctx, &program, keys)) {
return 0; return 0;
} }
hashx_compile(&program, ctx->code); if (!hashx_compile(&program, ctx->code)) {
return 0;
}
return 1; return 1;
} }
return initialize_program(ctx, ctx->program, keys); return initialize_program(ctx, ctx->program, keys);

View File

@ -22,7 +22,7 @@
#ifdef HASHX_WIN #ifdef HASHX_WIN
static int set_privilege(const char* pszPrivilege, BOOL bEnable) { static bool set_privilege(const char* pszPrivilege, BOOL bEnable) {
HANDLE hToken; HANDLE hToken;
TOKEN_PRIVILEGES tp; TOKEN_PRIVILEGES tp;
BOOL status; BOOL status;
@ -30,10 +30,10 @@ static int set_privilege(const char* pszPrivilege, BOOL bEnable) {
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY, &hToken)) | TOKEN_QUERY, &hToken))
return 0; return false;
if (!LookupPrivilegeValue(NULL, pszPrivilege, &tp.Privileges[0].Luid)) if (!LookupPrivilegeValue(NULL, pszPrivilege, &tp.Privileges[0].Luid))
return 0; return false;
tp.PrivilegeCount = 1; tp.PrivilegeCount = 1;
@ -64,31 +64,33 @@ void* hashx_vm_alloc(size_t bytes) {
return mem; return mem;
} }
static inline int page_protect(void* ptr, size_t bytes, int rules) { static inline bool page_protect(void* ptr, size_t bytes, int rules) {
#ifdef HASHX_WIN #ifdef HASHX_WIN
DWORD oldp; DWORD oldp;
if (!VirtualProtect(ptr, bytes, (DWORD)rules, &oldp)) { if (!VirtualProtect(ptr, bytes, (DWORD)rules, &oldp)) {
return 0; return false;
} }
#else #else
if (-1 == mprotect(ptr, bytes, rules)) if (mprotect(ptr, bytes, rules) != 0)
return 0; return false;
#endif #endif
return 1; return true;
} }
void hashx_vm_rw(void* ptr, size_t bytes) { bool hashx_vm_rw(void* ptr, size_t bytes) {
page_protect(ptr, bytes, PAGE_READWRITE); return page_protect(ptr, bytes, PAGE_READWRITE);
} }
void hashx_vm_rx(void* ptr, size_t bytes) { bool hashx_vm_rx(void* ptr, size_t bytes) {
page_protect(ptr, bytes, PAGE_EXECUTE_READ); return page_protect(ptr, bytes, PAGE_EXECUTE_READ);
} }
void* hashx_vm_alloc_huge(size_t bytes) { void* hashx_vm_alloc_huge(size_t bytes) {
void* mem; void* mem;
#ifdef HASHX_WIN #ifdef HASHX_WIN
set_privilege("SeLockMemoryPrivilege", 1); if (!set_privilege("SeLockMemoryPrivilege", 1)) {
/* Failed, but try the VirtualAlloc anyway */
}
SIZE_T page_min = GetLargePageMinimum(); SIZE_T page_min = GetLargePageMinimum();
if (page_min > 0) { if (page_min > 0) {
mem = VirtualAlloc(NULL, ALIGN_SIZE(bytes, page_min), MEM_COMMIT mem = VirtualAlloc(NULL, ALIGN_SIZE(bytes, page_min), MEM_COMMIT

View File

@ -6,13 +6,14 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdbool.h>
#include <hashx.h> #include <hashx.h>
#define ALIGN_SIZE(pos, align) ((((pos) - 1) / (align) + 1) * (align)) #define ALIGN_SIZE(pos, align) ((((pos) - 1) / (align) + 1) * (align))
HASHX_PRIVATE void* hashx_vm_alloc(size_t size); HASHX_PRIVATE void* hashx_vm_alloc(size_t size);
HASHX_PRIVATE void hashx_vm_rw(void* ptr, size_t size); HASHX_PRIVATE bool hashx_vm_rw(void* ptr, size_t size);
HASHX_PRIVATE void hashx_vm_rx(void* ptr, size_t size); HASHX_PRIVATE bool hashx_vm_rx(void* ptr, size_t size);
HASHX_PRIVATE void* hashx_vm_alloc_huge(size_t size); HASHX_PRIVATE void* hashx_vm_alloc_huge(size_t size);
HASHX_PRIVATE void hashx_vm_free(void* ptr, size_t size); HASHX_PRIVATE void hashx_vm_free(void* ptr, size_t size);