Skip to content

Commit 1055fc9

Browse files
committed
fix(trampoline): MAP_JIT + pthread_jit_write_protect_np for macOS arm64
1 parent d990e10 commit 1055fc9

1 file changed

Lines changed: 33 additions & 8 deletions

File tree

runtime/interop_trampoline.c

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@
3838
#include <sys/mman.h>
3939
#include <pthread.h>
4040

41+
// On macOS arm64 (Apple Silicon), W+X mmap requires MAP_JIT and the
42+
// thread-local pthread_jit_write_protect_np guard to toggle between
43+
// writable and executable states on the same page.
44+
#ifdef __APPLE__
45+
# define JIT_MMAP_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT)
46+
# define JIT_WRITE_START() pthread_jit_write_protect_np(0)
47+
# define JIT_WRITE_END() pthread_jit_write_protect_np(1)
48+
#else
49+
# define JIT_MMAP_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
50+
# define JIT_WRITE_START() ((void)0)
51+
# define JIT_WRITE_END() ((void)0)
52+
#endif
53+
4154
// Forward decl from arc.c. Closure envs created by Tin are ARC blocks
4255
// with a destructor at offset 0; _tin_release_closure invokes the
4356
// destructor when rc reaches 0 to release captured RC values, then
@@ -227,8 +240,10 @@ static void atexit_release_all_pages(void) {
227240
void *env = data[1];
228241
// Scrub before release in case the dtor somehow re-enters
229242
// and re-walks this slot.
243+
JIT_WRITE_START();
230244
data[0] = NULL;
231245
data[1] = NULL;
246+
JIT_WRITE_END();
232247
_tin_release_closure(env);
233248
}
234249

@@ -241,11 +256,13 @@ static void atexit_release_all_pages(void) {
241256
static TrampPage *new_page(void) {
242257
void *raw = mmap(NULL, TRAMP_PAGE_SIZE,
243258
PROT_READ | PROT_WRITE | PROT_EXEC,
244-
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
259+
JIT_MMAP_FLAGS, -1, 0);
245260
if (raw == MAP_FAILED) {
246261
return NULL;
247262
}
248263

264+
JIT_WRITE_START();
265+
249266
TrampPage *p = (TrampPage *)raw;
250267
p->next = NULL;
251268
p->free_head = 0;
@@ -258,6 +275,8 @@ static TrampPage *new_page(void) {
258275
((i + 1) < TRAMP_SLOTS) ? (int32_t)(i + 1) : -1);
259276
}
260277

278+
JIT_WRITE_END();
279+
261280
// Append to the all-pages registry for atexit cleanup.
262281
if (_tramp_all_pages_len == _tramp_all_pages_cap) {
263282
size_t new_cap = _tramp_all_pages_cap ? _tramp_all_pages_cap * 2 : 16;
@@ -328,6 +347,8 @@ void *tin_make_trampoline(void *fn, void *env, void *dispatcher) {
328347
// Lay out the slot:
329348
// slot+0..15 : TinClosureData = { fn, env }
330349
// slot+16..31 : machine code
350+
JIT_WRITE_START();
351+
331352
void **data = (void **)slot;
332353
data[0] = fn;
333354
data[1] = env;
@@ -336,6 +357,8 @@ void *tin_make_trampoline(void *fn, void *env, void *dispatcher) {
336357
uint8_t *code = slot + TRAMP_DATA_BYTES;
337358
size_t n = emit_trampoline(code, closure_data_ptr, dispatcher);
338359

360+
JIT_WRITE_END();
361+
339362
icache_flush(code, n);
340363

341364
// The C caller's function pointer is the address of the CODE,
@@ -412,15 +435,15 @@ void tin_interop_closure_free(void *tramp) {
412435
return;
413436
}
414437

415-
p->in_use &= ~bit;
416-
417-
// Snapshot env BEFORE handing the slot back to the free-list, but
418-
// AFTER confirming this is a first free. Released outside the
419-
// mutex: the closure dtor may run user-defined Tin deinit code
420-
// which can allocate, take other locks, or transitively allocate
421-
// another trampoline.
438+
// Snapshot env (read) before toggling write protection; reads are
439+
// always safe from JIT pages regardless of write-protect state.
422440
void **data = (void **)slot;
423441
void *env = data[1];
442+
443+
JIT_WRITE_START();
444+
445+
p->in_use &= ~bit;
446+
424447
// Scrub so a use-after-free of the trampoline pointer crashes
425448
// loudly instead of jumping through stale bytes.
426449
data[0] = NULL;
@@ -442,6 +465,8 @@ void tin_interop_closure_free(void *tramp) {
442465
// a page from _tramp_all_pages and keeps the per-call free path
443466
// lock-and-mutate only.
444467

468+
JIT_WRITE_END();
469+
445470
pthread_mutex_unlock(&_tramp_mu);
446471

447472
// Release the captured env after dropping the lock.

0 commit comments

Comments
 (0)