Affix
view release on metacpan or search on metacpan
infix/src/jit/executor.c view on Meta::CPAN
/**
* Copyright (c) 2025 Sanko Robinson
*
* This source code is dual-licensed under the Artistic License 2.0 or the MIT License.
* You may choose to use this code under the terms of either license.
*
* SPDX-License-Identifier: (Artistic-2.0 OR MIT)
*
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file executor.c
* @brief Implements platform-specific memory management for JIT code and execution.
* @ingroup internal_jit
*
* @details This module serves as the critical OS abstraction layer for the JIT engine.
* Its primary responsibilities are:
*
* 1. **Executable Memory Management:** It allocates, protects, and frees executable
* memory in a way that is secure and compliant with modern OS security features
* like **W^X (Write XOR Execute)**. It implements different strategies (single-
* vs. dual-mapping) depending on the platform's capabilities and security model.
*
* 2. **Security Hardening:** It provides mechanisms to make memory regions read-only,
* which is used to protect the `infix_reverse_t` context from runtime memory
* corruption. It also implements "guard pages" on freed memory to immediately
* catch use-after-free bugs.
*
* 3. **Universal Dispatch:** It contains the `infix_internal_dispatch_callback_fn_impl`,
* the universal C entry point that is the final target of all reverse trampoline
* stubs. This function is the bridge between the low-level JIT code and the
* high-level user-provided C handlers.
*/
#include "common/infix_internals.h"
#include "common/utility.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Platform-Specific Includes
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#if defined(INFIX_OS_MACOS)
#include <dlfcn.h>
#include <libkern/OSCacheControl.h>
#endif
// Polyfills for mmap flags for maximum POSIX compatibility.
#if defined(INFIX_ENV_POSIX) && !defined(INFIX_OS_WINDOWS)
#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
#define MAP_ANON MAP_ANONYMOUS
#endif
static pthread_mutex_t g_dwarf_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
#if defined(INFIX_OS_WINDOWS) && defined(INFIX_ARCH_X64)
// SEH Unwind Info Opcodes and Structures for JIT code on Windows x64.
// These are defined in winnt.h but we redefine them here for clarity and to ensure availability.
#define UWOP_PUSH_NONVOL 0
#define UWOP_ALLOC_LARGE 1
#define UWOP_ALLOC_SMALL 2
#define UWOP_SET_FPREG 3
#pragma pack(push, 1)
typedef struct _UNWIND_CODE {
uint8_t CodeOffset;
uint8_t UnwindOp : 4;
uint8_t OpInfo : 4;
} UNWIND_CODE;
typedef struct _UNWIND_INFO {
uint8_t Version : 3;
uint8_t Flags : 5;
uint8_t SizeOfPrologue;
uint8_t CountOfCodes;
uint8_t FrameRegister : 4;
uint8_t FrameOffset : 4;
UNWIND_CODE UnwindCode[1]; // Variable length array
} UNWIND_INFO;
// We reserve 512 bytes at the end of every JIT block for SEH metadata.
#define INFIX_SEH_METADATA_SIZE 256
#elif defined(INFIX_OS_WINDOWS) && defined(INFIX_ARCH_AARCH64)
#pragma pack(push, 1)
typedef struct _UNWIND_INFO_ARM64 {
uint32_t FunctionLength : 18;
uint32_t Version : 2;
uint32_t X : 1;
uint32_t E : 1;
uint32_t EpilogueCount : 5;
uint32_t CodeWords : 5;
} UNWIND_INFO_ARM64;
#pragma pack(pop)
#define INFIX_SEH_METADATA_SIZE 256
#else
#define INFIX_SEH_METADATA_SIZE 0
#endif
// macOS JIT Security Hardening Logic
#if defined(INFIX_OS_MACOS)
/**
* @internal
* @brief macOS-specific function pointers and types for checking JIT entitlements.
*
* @details To support hardened runtimes on Apple platforms (especially Apple Silicon),
* `infix` must use special APIs like `MAP_JIT` and `pthread_jit_write_protect_np`.
* However, these are only effective if the host application has been granted the
* `com.apple.security.cs.allow-jit` entitlement.
infix/src/jit/executor.c view on Meta::CPAN
// Strategy 3: shm_open with randomized name (Legacy POSIX)
char shm_name[64];
uint64_t random_val = 0;
// Generate a sufficiently random name to avoid collisions if multiple processes
// are running this code simultaneously. Using /dev/urandom is a robust way to do this.
int rand_fd = open("/dev/urandom", O_RDONLY);
if (rand_fd < 0)
return -1;
ssize_t bytes_read = read(rand_fd, &random_val, sizeof(random_val));
close(rand_fd);
if (bytes_read != sizeof(random_val))
return -1;
snprintf(shm_name, sizeof(shm_name), "/infix-jit-%d-%llx", getpid(), (unsigned long long)random_val);
// Create the shared memory object exclusively.
int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0) {
// Unlink immediately. The name is removed, but the inode persists until close().
shm_unlink(shm_name);
return fd;
}
return -1;
}
#endif
// Public API: Executable Memory Management
/**
* @internal
* @brief Allocates a block of memory suitable for holding JIT-compiled code,
* respecting platform-specific W^X (Write XOR Execute) security policies.
* @param size The number of bytes to allocate. Must be a multiple of the system page size.
* @return An `infix_executable_t` structure. On failure, its pointers will be `nullptr`.
*/
c23_nodiscard infix_executable_t infix_executable_alloc(size_t size) {
#if defined(INFIX_OS_WINDOWS)
infix_executable_t exec = {
.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .handle = nullptr, .seh_registration = nullptr};
#else
infix_executable_t exec = {.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1, .eh_frame_ptr = nullptr};
#endif
if (size == 0)
return exec;
#if defined(INFIX_OS_WINDOWS)
// Add headroom for SEH metadata on Windows.
size_t total_size = size + INFIX_SEH_METADATA_SIZE;
// Windows: Single-mapping W^X. Allocate as RW, later change to RX via VirtualProtect.
void * code = VirtualAlloc(nullptr, total_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (code == nullptr) {
_infix_set_system_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, GetLastError(), nullptr);
return exec;
}
exec.rw_ptr = code;
exec.rx_ptr = code;
#elif defined(INFIX_OS_MACOS) || defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
// Single-mapping POSIX platforms. Allocate as RW, later change to RX via mprotect.
void * code = MAP_FAILED;
#if defined(MAP_ANON)
int flags = MAP_PRIVATE | MAP_ANON;
#if defined(INFIX_OS_MACOS)
// On macOS, we perform a one-time check for JIT support.
static bool g_use_secure_jit_path = false;
static bool g_checked_jit_support = false;
if (!g_checked_jit_support) {
g_use_secure_jit_path = has_jit_entitlement();
INFIX_DEBUG_PRINTF("macOS JIT check: Entitlement found = %s. Using %s API.",
g_use_secure_jit_path ? "yes" : "no",
g_use_secure_jit_path ? "secure (MAP_JIT)" : "legacy (mprotect)");
g_checked_jit_support = true;
}
// If entitled, use the modern, more secure MAP_JIT flag.
if (g_use_secure_jit_path)
flags |= MAP_JIT;
#endif // INFIX_OS_MACOS
code = mmap(nullptr, size, PROT_READ | PROT_WRITE, flags, -1, 0);
#if defined(INFIX_OS_MACOS)
if (code != MAP_FAILED && g_use_secure_jit_path) {
// Switch thread to Write mode. enabled=0 means Write allowed.
g_macos_apis.pthread_jit_write_protect_np(0);
}
#endif
#endif // MAP_ANON
if (code == MAP_FAILED) { // Fallback for older systems without MAP_ANON
int fd = open("/dev/zero", O_RDWR);
if (fd != -1) {
code = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd);
}
}
if (code == MAP_FAILED) {
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, errno, nullptr);
return exec;
}
exec.rw_ptr = code;
exec.rx_ptr = code;
#else
// Dual-mapping POSIX platforms (e.g., Linux, FreeBSD). Create two separate views of the same memory.
exec.shm_fd = create_anonymous_file();
if (exec.shm_fd < 0) {
_infix_set_system_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, errno, "create_anonymous_file failed");
return exec;
}
if (ftruncate(exec.shm_fd, size) != 0) {
_infix_set_system_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, errno, "ftruncate failed");
close(exec.shm_fd);
exec.shm_fd = -1; // Ensure clean state
return exec;
}
// The RW mapping.
exec.rw_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, exec.shm_fd, 0);
// The RX mapping of the exact same physical memory.
exec.rx_ptr = mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_SHARED, exec.shm_fd, 0);
// If either mapping fails, clean up both and return an error.
if (exec.rw_ptr == MAP_FAILED || exec.rx_ptr == MAP_FAILED) {
int err = errno; // Capture errno before cleanup
if (exec.rw_ptr != MAP_FAILED)
munmap(exec.rw_ptr, size);
if (exec.rx_ptr != MAP_FAILED)
munmap(exec.rx_ptr, size);
close(exec.shm_fd);
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, err, "mmap failed");
return (infix_executable_t){.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1};
}
// The mmap mappings hold a reference to the shared memory object, so we don't
// need the FD anymore. Keeping it open consumes a file descriptor per trampoline,
// causing "shm_open failed" after ~1024 trampolines.
close(exec.shm_fd);
exec.shm_fd = -1;
#endif
exec.size = size;
INFIX_DEBUG_PRINTF("Allocated JIT memory. RW at %p, RX at %p", exec.rw_ptr, exec.rx_ptr);
return exec;
( run in 1.077 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )