infix
A JIT-Powered FFI Library for C
Loading...
Searching...
No Matches
executor.c
Go to the documentation of this file.
1
56#include "common/utility.h"
57#include <stdio.h>
58#include <stdlib.h>
59#include <string.h>
60#include <time.h>
61
62#if defined(INFIX_OS_WINDOWS)
63#include <windows.h>
64#else // Linux, macOS, BSDs
65#include <errno.h>
66#include <fcntl.h>
67#include <sys/mman.h>
68#include <sys/types.h>
69#include <unistd.h>
70#endif
71
72#if defined(INFIX_OS_MACOS)
73#include <dlfcn.h>
74#include <pthread.h>
75#endif
76
77// Portability shim for mmap anonymous flag.
78#if defined(INFIX_ENV_POSIX) && !defined(INFIX_OS_WINDOWS)
79#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
80#define MAP_ANON MAP_ANONYMOUS
81#endif
82#endif
83
84// Internal Helpers for macOS Runtime Linking
85#if defined(INFIX_OS_MACOS)
86// Define opaque types to avoid including the full framework headers, which is key
87// to avoiding the forced linker dependency.
88typedef const struct __CFString * CFStringRef;
89typedef const void * CFTypeRef;
90typedef struct __SecTask * SecTaskRef;
91typedef struct __CFError * CFErrorRef;
92#define kCFStringEncodingUTF8 0x08000100
93
94// A struct to hold dynamically loaded function pointers. It is populated once at runtime.
95static struct {
96 void (*CFRelease)(CFTypeRef);
97 bool (*CFBooleanGetValue)(CFTypeRef boolean);
98 CFStringRef (*CFStringCreateWithCString)(CFTypeRef allocator, const char * cStr, uint32_t encoding);
99 CFTypeRef kCFAllocatorDefault;
100 SecTaskRef (*SecTaskCreateFromSelf)(CFTypeRef allocator);
101 CFTypeRef (*SecTaskCopyValueForEntitlement)(SecTaskRef task, CFStringRef entitlement, CFErrorRef * error);
102} g_macos_apis;
103
112static void initialize_macos_apis(void) {
113 // Attempt to load the required system frameworks dynamically.
114 void * cf = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_LAZY);
115 void * sec = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY);
116
117 // If either fails, we cannot proceed with the secure path. Clean up and exit.
118 if (!cf || !sec) {
119 INFIX_DEBUG_PRINTF("Warning: Could not dlopen macOS frameworks. JIT security features will be degraded.");
120 if (cf)
121 dlclose(cf);
122 if (sec)
123 dlclose(sec);
124 // Ensure all pointers are null to signal failure.
125 memset(&g_macos_apis, 0, sizeof(g_macos_apis));
126 return;
127 }
128
129 // Use dlsym to find the address of each function and global constant we need.
130 g_macos_apis.CFRelease = dlsym(cf, "CFRelease");
131 g_macos_apis.CFBooleanGetValue = dlsym(cf, "CFBooleanGetValue");
132 g_macos_apis.CFStringCreateWithCString = dlsym(cf, "CFStringCreateWithCString");
133 void ** pAlloc = (void **)dlsym(cf, "kCFAllocatorDefault");
134 if (pAlloc)
135 g_macos_apis.kCFAllocatorDefault = *pAlloc;
136
137 g_macos_apis.SecTaskCreateFromSelf = dlsym(sec, "SecTaskCreateFromSelf");
138 g_macos_apis.SecTaskCopyValueForEntitlement = dlsym(sec, "SecTaskCopyValueForEntitlement");
139
140 // The handles can be closed; the OS keeps the libraries in memory and the pointers remain valid.
141 dlclose(cf);
142 dlclose(sec);
143}
144
165static bool has_jit_entitlement(void) {
166 // Use pthread_once to ensure the framework APIs are loaded exactly once in a thread-safe manner.
167 static pthread_once_t init_once = PTHREAD_ONCE_INIT;
168 pthread_once(&init_once, initialize_macos_apis);
169
170 // If we failed to load the necessary APIs, we cannot check, so we must return false.
171 if (!g_macos_apis.SecTaskCopyValueForEntitlement || !g_macos_apis.CFStringCreateWithCString)
172 return false;
173
174 bool result = false;
175 // Get a reference to the current process's security properties.
176 SecTaskRef task = g_macos_apis.SecTaskCreateFromSelf(g_macos_apis.kCFAllocatorDefault);
177 if (!task)
178 return false;
179
180 // Create a CoreFoundation string for the entitlement key we're looking for.
181 CFStringRef key = g_macos_apis.CFStringCreateWithCString(
182 g_macos_apis.kCFAllocatorDefault, "com.apple.security.cs.allow-jit", kCFStringEncodingUTF8);
183 CFTypeRef value = NULL;
184 if (key) { // Query the OS for the entitlement value.
185 value = g_macos_apis.SecTaskCopyValueForEntitlement(task, key, NULL);
186 g_macos_apis.CFRelease(key); // Must release objects we create.
187 }
188 g_macos_apis.CFRelease(task); // Must release the task reference.
189
190 if (value) {
191 // A JIT entitlement is boolean. We must check that its value is explicitly `true`.
192 if (g_macos_apis.CFBooleanGetValue && g_macos_apis.CFBooleanGetValue(value))
193 result = true;
194 g_macos_apis.CFRelease(value); // Must release the retrieved value object.
195 }
196 return result;
197}
198#endif
199
200#if !defined(INFIX_OS_WINDOWS) && !defined(INFIX_OS_MACOS) && !defined(INFIX_OS_ANDROID) && !defined(INFIX_OS_OPENBSD)
201#include <fcntl.h>
202#include <stdint.h>
203
224static int shm_open_anonymous() {
225 char shm_name[64];
226 uint64_t random_val = 0;
227
228 // Use /dev/urandom to generate a highly unpredictable name to avoid collisions
229 // and prevent predictable name attacks.
230 int rand_fd = open("/dev/urandom", O_RDONLY);
231 if (rand_fd < 0)
232 return -1;
233
234 ssize_t bytes_read = read(rand_fd, &random_val, sizeof(random_val));
235 close(rand_fd);
236 if (bytes_read != sizeof(random_val))
237 return -1;
238
239 snprintf(shm_name, sizeof(shm_name), "/infix-jit-%d-%llx", getpid(), (unsigned long long)random_val);
240
241 // Create the shared memory object.
242 // - O_CREAT | O_EXCL: Atomically create the object, failing if it already exists.
243 // This prevents race conditions and symlink attacks.
244 int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0600);
245 if (fd >= 0) {
246 shm_unlink(shm_name);
247 return fd;
248 }
249
250 return -1;
251}
252#endif
253
254// Executable Memory Management
287#if defined(INFIX_OS_WINDOWS)
288 infix_executable_t exec = {.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .handle = NULL};
289#else
290 infix_executable_t exec = {.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1};
291#endif
292
293 if (size == 0)
294 return exec;
295
296#if defined(INFIX_OS_WINDOWS)
297 void * code = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
298 if (code == nullptr)
299 return exec;
300 exec.rw_ptr = code;
301 exec.rx_ptr = code;
302
303#elif defined(INFIX_OS_MACOS) || defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
304 void * code = MAP_FAILED;
305#if defined(MAP_ANON)
306 int flags = MAP_PRIVATE | MAP_ANON;
307#if defined(INFIX_OS_MACOS)
308 static bool g_use_secure_jit_path = false;
309 static bool g_checked_jit_support = false;
310 if (!g_checked_jit_support) {
311 g_use_secure_jit_path = has_jit_entitlement();
312 INFIX_DEBUG_PRINTF("macOS JIT check: Entitlement found = %s. Using %s API.",
313 g_use_secure_jit_path ? "yes" : "no",
314 g_use_secure_jit_path ? "secure (MAP_JIT)" : "legacy/insecure (mprotect)");
315 g_checked_jit_support = true;
316 }
317 // If the check determined the secure path is viable, add the MAP_JIT flag.
318 // Otherwise, we proceed without it, using the legacy path.
319 if (g_use_secure_jit_path)
320 flags |= MAP_JIT;
321#endif
322 code = mmap(nullptr, size, PROT_READ | PROT_WRITE, flags, -1, 0);
323#endif
324 if (code == MAP_FAILED) { // Fallback for systems without MAP_ANON (like DragonflyBSD)
325 int fd = open("/dev/zero", O_RDWR);
326 if (fd != -1) {
327 code = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
328 close(fd);
329 }
330 }
331 if (code == MAP_FAILED)
332 return exec;
333 exec.rw_ptr = code;
334 exec.rx_ptr = code;
335
336#else
337 exec.shm_fd = shm_open_anonymous();
338 if (exec.shm_fd < 0)
339 return exec;
340 if (ftruncate(exec.shm_fd, size) != 0) {
341 close(exec.shm_fd);
342 return exec;
343 }
344 exec.rw_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, exec.shm_fd, 0);
345 exec.rx_ptr = mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_SHARED, exec.shm_fd, 0);
346 if (exec.rw_ptr == MAP_FAILED || exec.rx_ptr == MAP_FAILED) {
347 if (exec.rw_ptr != MAP_FAILED)
348 munmap(exec.rw_ptr, size);
349 if (exec.rx_ptr != MAP_FAILED)
350 munmap(exec.rx_ptr, size);
351 close(exec.shm_fd);
352 return (infix_executable_t){.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1};
353 }
354#endif
355
356 exec.size = size;
357 INFIX_DEBUG_PRINTF("Allocated JIT memory. RW at %p, RX at %p", exec.rw_ptr, exec.rx_ptr);
358 return exec;
359}
360
389 if (exec.size == 0)
390 return;
391
392#if defined(INFIX_OS_WINDOWS)
393 if (exec.rw_ptr) {
394 // Arm the guard page by revoking all access.
395 if (!VirtualProtect(exec.rw_ptr, exec.size, PAGE_NOACCESS, &(DWORD){0}))
396 INFIX_DEBUG_PRINTF("WARNING: VirtualProtect failed to set PAGE_NOACCESS.");
397 // Now, release the memory.
398 VirtualFree(exec.rw_ptr, 0, MEM_RELEASE);
399 }
400#elif defined(INFIX_OS_MACOS)
401 if (exec.rw_ptr) {
402#if INFIX_MACOS_SECURE_JIT_AVAILABLE
403 // We must check the same static bool to know which path was taken during allocation.
404 static bool g_use_secure_jit_path = false;
405 if (g_use_secure_jit_path)
406 // If the secure path was used, the memory must be made writable again
407 // before it can be unmapped.
408 pthread_jit_write_protect_np(true);
409#endif
410 mprotect(exec.rw_ptr, exec.size, PROT_NONE);
411 munmap(exec.rw_ptr, exec.size);
412 }
413#elif defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
414 if (exec.rw_ptr) {
415 mprotect(exec.rw_ptr, exec.size, PROT_NONE);
416 munmap(exec.rw_ptr, exec.size);
417 }
418#else
419 if (exec.rx_ptr)
420 mprotect(exec.rx_ptr, exec.size, PROT_NONE);
421 if (exec.rw_ptr)
422 munmap(exec.rw_ptr, exec.size);
423 if (exec.rx_ptr && exec.rx_ptr != exec.rw_ptr)
424 munmap(exec.rx_ptr, exec.size);
425 if (exec.shm_fd >= 0)
426 close(exec.shm_fd);
427#endif
428}
429
454 if (exec.rw_ptr == nullptr || exec.size == 0)
455 return false;
456
457#if defined(INFIX_ARCH_AARCH64)
458#if defined(_MSC_VER)
459 FlushInstructionCache(GetCurrentProcess(), exec.rw_ptr, exec.size);
460#else
461 __builtin___clear_cache((char *)exec.rw_ptr, (char *)exec.rw_ptr + exec.size);
462#endif
463#endif
464
465 bool result = false;
466#if defined(INFIX_OS_WINDOWS)
467 result = VirtualProtect(exec.rw_ptr, exec.size, PAGE_EXECUTE_READ, &(DWORD){0});
468#elif defined(INFIX_OS_MACOS)
469#if INFIX_MACOS_SECURE_JIT_AVAILABLE
470 static bool g_use_secure_jit_path = false;
471 if (g_use_secure_jit_path) {
472 pthread_jit_write_protect_np(false);
473 result = true;
474 }
475 else // Fallback to the legacy, insecure method.
476#endif
477 result = (mprotect(exec.rw_ptr, exec.size, PROT_READ | PROT_EXEC) == 0);
478#elif defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
479 result = (mprotect(exec.rw_ptr, exec.size, PROT_READ | PROT_EXEC) == 0);
480#else
481 result = true; // No-op for dual-map platforms
482#endif
483
484 if (result)
485 INFIX_DEBUG_PRINTF("Memory at %p is ready for execution.", exec.rx_ptr);
486 return result;
487}
488
489// Protected Data Memory Management
508 infix_protected_t prot = {.rw_ptr = nullptr, .size = 0};
509 if (size == 0)
510 return prot;
511#if defined(INFIX_OS_WINDOWS)
512 prot.rw_ptr = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
513#else
514#if defined(MAP_ANON)
515 prot.rw_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
516#else
517 int fd = open("/dev/zero", O_RDWR);
518 if (fd == -1)
519 prot.rw_ptr = MAP_FAILED;
520 else {
521 prot.rw_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
522 close(fd);
523 }
524#endif
525 if (prot.rw_ptr == MAP_FAILED)
526 prot.rw_ptr = nullptr;
527#endif
528 if (prot.rw_ptr)
529 prot.size = size;
530 return prot;
531}
532
547 if (prot.size == 0)
548 return;
549#if defined(INFIX_OS_WINDOWS)
550 VirtualFree(prot.rw_ptr, 0, MEM_RELEASE);
551#else
552 munmap(prot.rw_ptr, prot.size);
553#endif
554}
555
578 if (prot.size == 0)
579 return false;
580 bool result = false;
581#if defined(INFIX_OS_WINDOWS)
582 result = VirtualProtect(prot.rw_ptr, prot.size, PAGE_READONLY, &(DWORD){0});
583#else
584 // On all POSIX platforms (Linux, macOS, BSDs), this works as expected for data pages.
585 result = (mprotect(prot.rw_ptr, prot.size, PROT_READ) == 0);
586#endif
587 return result;
588}
589
610void infix_internal_dispatch_callback_fn_impl(infix_reverse_t * context, void * return_value_ptr, void ** args_array) {
611 INFIX_DEBUG_PRINTF("Dispatching callback. Context: %p, User Fn: %p", (void *)context, context->user_callback_fn);
612
613 if (context->user_callback_fn == nullptr) {
614 // This is a fatal internal error, likely from a failed allocation during setup.
615 if (return_value_ptr && context->return_type->size > 0)
616 infix_memset(return_value_ptr, 0, context->return_type->size);
617 return;
618 }
619
620 // Check if this is a high-level "callback" or a low-level "closure".
621 if (context->cached_forward_trampoline != nullptr) {
622 // High-Level, Type-Safe "Callback" Path
623 // Use the cached forward trampoline to call the user's type-safe C handler.
624 // The user's handler has a "clean" signature and does not expect the context.
626 cif_func(return_value_ptr, args_array);
627 }
628 else {
629 // Low-Level, Generic "Closure" Path
630 // Directly call the user's generic handler, which expects the context.
632 handler(context, return_value_ptr, args_array);
633 }
634
635 INFIX_DEBUG_PRINTF("Exiting callback dispatcher.");
636}
#define c23_nodiscard
Definition compat_c23.h:93
void infix_protected_free(infix_protected_t prot)
Frees a block of protected data memory.
Definition executor.c:546
c23_nodiscard bool infix_executable_make_executable(infix_executable_t exec)
Makes a JIT memory region readable and executable (and non-writable).
Definition executor.c:453
void infix_executable_free(infix_executable_t exec)
Frees executable memory, creating a guard page to prevent use-after-free.
Definition executor.c:388
c23_nodiscard infix_protected_t infix_protected_alloc(size_t size)
Allocates a page-aligned block of data memory.
Definition executor.c:507
c23_nodiscard infix_executable_t infix_executable_alloc(size_t size)
Allocates a page-aligned block of W^X-compliant executable memory.
Definition executor.c:286
c23_nodiscard bool infix_protected_make_readonly(infix_protected_t prot)
Hardens a block of protected data memory to be read-only.
Definition executor.c:577
static int shm_open_anonymous()
Definition executor.c:224
void infix_internal_dispatch_callback_fn_impl(infix_reverse_t *context, void *return_value_ptr, void **args_array)
The high-level C dispatcher function called by reverse trampoline stubs.
Definition executor.c:610
c23_nodiscard infix_cif_func infix_forward_get_code(infix_forward_t *)
Retrieves the executable code pointer from a bound forward trampoline.
Definition trampoline.c:227
#define infix_memset
A macro for setting a block of memory to a specific value.
Definition infix.h:300
void(* infix_closure_handler_fn)(infix_context_t *context, void *return_value, void **args)
The signature for a generic "closure" handler.
Definition infix.h:346
void(* infix_cif_func)(void *, void **)
The signature for a "bound" forward-call trampoline.
Definition infix.h:335
Declarations for internal-only functions, types, and constants.
Definition infix_internals.h:42
size_t size
The total size of the allocated memory region in bytes.
Definition infix_internals.h:50
void * rw_ptr
Pointer with Read+Write permissions (where code is written).
Definition infix_internals.h:49
void * rx_ptr
Pointer with Read+Execute permissions (the callable address).
Definition infix_internals.h:48
int shm_fd
File descriptor for the shared memory object on POSIX systems that use dual-mapping.
Definition infix_internals.h:46
Definition infix_internals.h:60
size_t size
The size of the allocated memory region in bytes.
Definition infix_internals.h:62
void * rw_ptr
A pointer to the read-write data memory.
Definition infix_internals.h:61
Definition infix_internals.h:102
infix_type * return_type
The infix_type of the callback's return value.
Definition infix_internals.h:106
void * user_callback_fn
Pointer to the user's actual C callback handler function.
Definition infix_internals.h:111
infix_forward_t * cached_forward_trampoline
A pre-compiled forward trampoline for calling a type-safe user C callback.
Definition infix_internals.h:123
size_t size
The total size of the type in bytes, per sizeof.
Definition infix.h:163
A header for conditionally compiled debugging utilities.
#define INFIX_DEBUG_PRINTF(...)
Definition utility.h:100