From b4d635df4e5a59e2429bd839dc74c97c15159d60 Mon Sep 17 00:00:00 2001 From: slipher Date: Wed, 22 Jul 2026 17:50:03 -0500 Subject: [PATCH 1/5] mach_crash_forwarding_test.c: disable output buffering --- .../osx_crash_forwarding/mach_crash_forwarding_test.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/trusted_crash/osx_crash_forwarding/mach_crash_forwarding_test.c b/tests/trusted_crash/osx_crash_forwarding/mach_crash_forwarding_test.c index cfebb83cb..2073a964f 100644 --- a/tests/trusted_crash/osx_crash_forwarding/mach_crash_forwarding_test.c +++ b/tests/trusted_crash/osx_crash_forwarding/mach_crash_forwarding_test.c @@ -118,6 +118,10 @@ void RegisterExceptionHandler(void) { int main(int argc, char **argv) { struct NaClApp app; + /* Turn off buffering to aid debugging. */ + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + /* Register file-local handler first (so it ends up second in the chain). */ if (strcmp(argv[1], "unforwarded_trusted") != 0) { RegisterExceptionHandler(); From 081f44fab7825b1e3251d2362fec8fdf09c6d20c Mon Sep 17 00:00:00 2001 From: slipher Date: Wed, 22 Jul 2026 17:47:53 -0500 Subject: [PATCH 2/5] osx/run_mig.py: use argparse correctly --- src/trusted/service_runtime/osx/run_mig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trusted/service_runtime/osx/run_mig.py b/src/trusted/service_runtime/osx/run_mig.py index 171ff5c12..678c71d58 100644 --- a/src/trusted/service_runtime/osx/run_mig.py +++ b/src/trusted/service_runtime/osx/run_mig.py @@ -103,7 +103,7 @@ def Main(args): parser.add_argument('dst_header') parser.add_argument('dst_server') parsed = parser.parse_args(args) - Generate(args[0], args[1], args[2], parsed.sdk, parsed.mig_path, + Generate(parsed.src_defs, parsed.dst_header, parsed.dst_server, parsed.sdk, parsed.mig_path, parsed.clang_path, parsed.migcom_path) From 08d2a5124caa63452205ffbd075c572de82d574c Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 23 Jul 2026 22:28:44 -0500 Subject: [PATCH 3/5] Fix Mach crash forwarding on ARM Macs The system's user library for the 'exc' Mach message doesn't work on my ARM Mac (version 15.6.1): upon sending a message it crashes with a GUARD_TYPE_MACH_PORT exception. Fix that by building the MIG-generated user code also (in addition to the server code) and using that instead of the system's exception_raise. I find that the error occurred due to mach_msg2_trap unexpectedly being called with the MACH64_SEND_KOBJECT_CALL option. If I use the debugger to replace that flag with MACH64_SEND_MQ_CALL, the send can succeed. Or at least that's how it goes with a native ARM binary. I didn't repeat these experiments with a Rosetta'd one. --- src/trusted/service_runtime/build.scons | 3 ++- .../service_runtime/osx/mach_exception_handler.c | 6 +++--- src/trusted/service_runtime/osx/run_mig.py | 15 ++++++++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/trusted/service_runtime/build.scons b/src/trusted/service_runtime/build.scons index 70833a48e..9b6212a58 100644 --- a/src/trusted/service_runtime/build.scons +++ b/src/trusted/service_runtime/build.scons @@ -160,11 +160,12 @@ elif env.Bit('mac'): "echo '#include '" + ' | ${CC} ${CCFLAGS} ${CFLAGS} -E - > ${TARGET}') env.Command( - [GENERATED + '/nacl_exc.h', GENERATED + '/nacl_exc_server.c'], + [GENERATED + '/nacl_exc.h', GENERATED + '/nacl_exc_server.c', GENERATED + '/nacl_exc_user.c'], ['osx/run_mig.py', GENERATED + '/exc.defs'], '${PYTHON} ${SOURCES} ${TARGETS}') ldr_inputs += [ GENERATED + '/nacl_exc_server.c', + GENERATED + '/nacl_exc_user.c', 'osx/crash_filter.c', 'osx/mach_exception_handler.c', 'osx/mach_thread_map.c', diff --git a/src/trusted/service_runtime/osx/mach_exception_handler.c b/src/trusted/service_runtime/osx/mach_exception_handler.c index 2cf50910f..def6e3f8c 100644 --- a/src/trusted/service_runtime/osx/mach_exception_handler.c +++ b/src/trusted/service_runtime/osx/mach_exception_handler.c @@ -383,7 +383,7 @@ static kern_return_t ForwardException( CHECK(target_behavior == EXCEPTION_DEFAULT); /* Forward the exception. */ - kr = exception_raise(target_port, thread, task, exception, code, code_count); + kr = nacl_exception_raise(target_port, thread, task, exception, code, code_count); /* * Don't set the thread state. See the comment in @@ -527,11 +527,11 @@ static void *MachExceptionHandlerThread(void *arg) { kern_return_t result; union { mach_msg_header_t header; - union __RequestUnion__nacl_exc_subsystem nacl_exc_subsystem_request; + union __RequestUnion__nacl_nacl_exc_subsystem nacl_exc_subsystem_request; } request; union { mach_msg_header_t header; - union __ReplyUnion__nacl_exc_subsystem nacl_exc_subsystem_reply; + union __ReplyUnion__nacl_nacl_exc_subsystem nacl_exc_subsystem_reply; } reply; for (;;) { diff --git a/src/trusted/service_runtime/osx/run_mig.py b/src/trusted/service_runtime/osx/run_mig.py index 678c71d58..e41e8e66f 100644 --- a/src/trusted/service_runtime/osx/run_mig.py +++ b/src/trusted/service_runtime/osx/run_mig.py @@ -23,8 +23,8 @@ def MkDirs(path): pass -def Generate(src_defs, dst_header, dst_server, sdk, mig_path, clang_path, - migcom_path): +def Generate(src_defs, dst_header, dst_server, dst_user, + sdk, mig_path, clang_path, migcom_path): """Generate interface headers and server from a .defs file using MIG. Args: @@ -35,9 +35,11 @@ def Generate(src_defs, dst_header, dst_server, sdk, mig_path, clang_path, """ dst_header = os.path.abspath(dst_header) dst_server = os.path.abspath(dst_server) + dst_user = os.path.abspath(dst_user) # Create directories containing output. MkDirs(os.path.dirname(dst_header)) MkDirs(os.path.dirname(dst_server)) + MkDirs(os.path.dirname(dst_user)) # Load exc.defs (input) fh = open(src_defs, 'r') @@ -49,6 +51,8 @@ def Generate(src_defs, dst_header, dst_server, sdk, mig_path, clang_path, (defs, count) = re.subn('ServerPrefix catch_;', 'ServerPrefix nacl_catch_;', defs) assert count == 1 + assert 'userprefix' not in defs.lower() + defs = 'UserPrefix nacl_;\n' + defs # Change the message name from exc to nacl_exc to avoid other collisions. # (But keep the message id base 2401 the same to match the OS.) (defs, count) = re.subn('exc 2401;', @@ -66,7 +70,7 @@ def Generate(src_defs, dst_header, dst_server, sdk, mig_path, clang_path, # Run the 'Mach Interface Generator'. args = [mig_path, '-server', dst_server, - '-user', '/dev/null', + '-user', dst_user, '-header', dst_header] # If SDKROOT is set to an SDK that Xcode doesn't know about, it might @@ -102,9 +106,10 @@ def Main(args): parser.add_argument('src_defs') parser.add_argument('dst_header') parser.add_argument('dst_server') + parser.add_argument('dst_user') parsed = parser.parse_args(args) - Generate(parsed.src_defs, parsed.dst_header, parsed.dst_server, parsed.sdk, parsed.mig_path, - parsed.clang_path, parsed.migcom_path) + Generate(parsed.src_defs, parsed.dst_header, parsed.dst_server, parsed.dst_user, + parsed.sdk, parsed.mig_path, parsed.clang_path, parsed.migcom_path) if __name__ == '__main__': From b023e66f62d495a56e866537c954fa3a8bf4a3df Mon Sep 17 00:00:00 2001 From: slipher Date: Fri, 24 Jul 2026 03:10:06 -0500 Subject: [PATCH 4/5] Prevent NaClLog from changing errno Save and restore errno around the I/O calls in the NaClLog implementation. This prevents a class of bugs caused by carelessly inserting log messages between an erroring function call and further errno accesses. Such a bug in NaClHostDescMap is revealed by nacl_desc_io_alloc_ctor_test on Mac OS 15.x. The culprit there was a successful localtime_r call modifying errno the first time NaClTimeStampString is called. But any of the actual I/O functions could also fail and set errno. Or even succeed and set errno, which is generally allowed by the relevant standards. The alternative would be fixing each logging call site individually. But that would be annoying, particularly in the case of some ultra-verbose log calls in untrusted code that log syscall entry/exit. Doing it inside the logger seems better since then the push/pop can be done only in the case that the message is actually printed. --- src/shared/platform/nacl_log.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/platform/nacl_log.c b/src/shared/platform/nacl_log.c index 4bbd3f3de..e086fd3c8 100644 --- a/src/shared/platform/nacl_log.c +++ b/src/shared/platform/nacl_log.c @@ -355,6 +355,7 @@ void NaClLogDoLogV_mu(int detail_level, char const *fmt, va_list ap) { struct Gio *s; + int saved_errno = errno; if (0 == g_abort_count) { s = NaClLogGetGio_mu(); @@ -368,6 +369,8 @@ void NaClLogDoLogV_mu(int detail_level, (void) fflush(stderr); } + errno = saved_errno; + if (LOG_FATAL == detail_level) { ++g_abort_count; } From 7f6c8420f242f75bfa4ac36260781ec3c729b251 Mon Sep 17 00:00:00 2001 From: slipher Date: Sat, 25 Jul 2026 01:35:44 -0500 Subject: [PATCH 5/5] Mac: switch clock_gettime impl to POSIX backend The NACL_CLOCK_PROCESS_CPUTIME_ID timer was broken on ARM Macs, as revealed by run_clock_cputime_test and run_nacl_clock_cputime_test. As described in https://eclecticlight.co/2020/11/27/inside-m1-macs-time-and-logs/, ARM Macs have a system clock tick duration of 125/3 ns. But for Rosetta-translated process, the system pretends that the tick duration is 1 ns as it was for Intel Macs. Accordingly, APIs that return ticks are scaled up by the 125/3 factor. However Apple missed a spot: task_info() with the TASK_ABSOLUTETIME_INFO query still returns a time based on the real tick rate, making the timer too slow by a factor of ~42. It is rather inconvenient to find out the real tick rate from inside a translated process. Fortunately, in Mac OS 10.12 the POSIX clock_gettime API was added. So solve the problem by removing the entire MacOS-specific NaClClockGetTime and NaClClockGetRes implemenations and using the Linux implementation which is a simple pass-through to the host clock_gettime and clock_getres. --- SConstruct | 2 +- src/shared/platform/build.scons | 3 +- src/shared/platform/osx/nacl_clock.c | 156 ------------------ .../platform/{linux => posix}/nacl_clock.c | 0 4 files changed, 2 insertions(+), 159 deletions(-) delete mode 100644 src/shared/platform/osx/nacl_clock.c rename src/shared/platform/{linux => posix}/nacl_clock.c (100%) diff --git a/SConstruct b/SConstruct index bf6d48505..d15cd7a9a 100755 --- a/SConstruct +++ b/SConstruct @@ -2477,7 +2477,7 @@ def MakeMacEnv(platform=None): # This should be kept in synch with mac_deployment_target # in build/common.gypi, which in turn should be kept in synch # with chromium/src/build/common.gypi. - mac_deployment_target = '10.6' + mac_deployment_target = '10.12' sdk_flags = ['-isysroot', mac_sdk_sysroot, '-mmacosx-version-min=' + mac_deployment_target] diff --git a/src/shared/platform/build.scons b/src/shared/platform/build.scons index 719c595ed..30d7c5904 100644 --- a/src/shared/platform/build.scons +++ b/src/shared/platform/build.scons @@ -60,7 +60,6 @@ if env.Bit('windows'): ) elif env.Bit('linux'): platform_inputs += [ - 'linux/nacl_clock.c', 'linux/nacl_host_dir.c', 'linux/nacl_semaphore.c', ] @@ -69,7 +68,6 @@ elif env.Bit('linux'): cputime_test_enabled = False elif env.Bit('mac'): platform_inputs += [ - 'osx/nacl_clock.c', 'osx/nacl_host_dir.c', 'osx/nacl_semaphore.c', ] @@ -80,6 +78,7 @@ if env.Bit('posix'): 'posix/aligned_malloc.c', 'posix/condition_variable.c', 'posix/lock.c', + 'posix/nacl_clock.c', 'posix/nacl_error.c', 'posix/nacl_exit.c', 'posix/nacl_fast_mutex.c', diff --git a/src/shared/platform/osx/nacl_clock.c b/src/shared/platform/osx/nacl_clock.c deleted file mode 100644 index b94121b5d..000000000 --- a/src/shared/platform/osx/nacl_clock.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2012 The Native Client Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "native_client/src/shared/platform/nacl_clock.h" - -#include "native_client/src/include/nacl_macros.h" -#include "native_client/src/include/portability.h" -#include "native_client/src/shared/platform/nacl_host_desc.h" -#include "native_client/src/shared/platform/nacl_log.h" -#include "native_client/src/shared/platform/nacl_time.h" -#include "native_client/src/trusted/service_runtime/include/sys/errno.h" - -/* - * OSX does not include POSIX.1-2011 functions, so we emulate using - * Mach calls. - */ -#include -#include -#include -#include -#include -#include - -static int g_NaClClock_is_initialized = 0; -static mach_timebase_info_data_t g_NaCl_time_base_info; - -int NaClClockInit(void) { - g_NaClClock_is_initialized = (mach_timebase_info(&g_NaCl_time_base_info) - == KERN_SUCCESS); - - return g_NaClClock_is_initialized; -} - -void NaClClockFini(void) {} - -int NaClClockGetRes(nacl_clockid_t clk_id, - struct nacl_abi_timespec *res) { - int rv = -NACL_ABI_EINVAL; - uint64_t t_resolution_ns; - struct timespec host_res; - - if (!g_NaClClock_is_initialized) { - NaClLog(LOG_FATAL, - "NaClClockGetRes invoked without successful NaClClockInit\n"); - } - switch (clk_id) { - case NACL_CLOCK_REALTIME: - t_resolution_ns = NaClTimerResolutionNanoseconds(); - host_res.tv_sec = (time_t) (t_resolution_ns / NACL_NANOS_PER_UNIT); - host_res.tv_nsec = (long) (t_resolution_ns % NACL_NANOS_PER_UNIT); - rv = 0; - break; - case NACL_CLOCK_MONOTONIC: - host_res.tv_sec = 0; - /* round up */ - host_res.tv_nsec = ((g_NaCl_time_base_info.numer - + g_NaCl_time_base_info.denom - 1) - / g_NaCl_time_base_info.denom); - rv = 0; - break; - case NACL_CLOCK_PROCESS_CPUTIME_ID: - case NACL_CLOCK_THREAD_CPUTIME_ID: - host_res.tv_sec = 0; - host_res.tv_nsec = 1; - rv = 0; - break; - } - if (0 == rv) { - res->tv_sec = host_res.tv_sec; - res->tv_nsec = host_res.tv_nsec; - } - return rv; -} - -int NaClClockGetTime(nacl_clockid_t clk_id, - struct nacl_abi_timespec *tp) { - int rv = -NACL_ABI_EINVAL; - struct nacl_abi_timeval tv; - uint64_t tick_cur; - uint64_t tick_ns; - struct task_absolutetime_info absolutetime_info; - thread_basic_info_data_t _basic_info; - thread_basic_info_t basic_info = &_basic_info; - mach_msg_type_number_t count; - - if (!g_NaClClock_is_initialized) { - NaClLog(LOG_FATAL, - "NaClClockGetTime invoked without successful NaClClockInit\n"); - } - switch (clk_id) { - case NACL_CLOCK_REALTIME: - rv = NaClGetTimeOfDay(&tv); - if (0 == rv) { - tp->tv_sec = tv.nacl_abi_tv_sec; - tp->tv_nsec = tv.nacl_abi_tv_usec * 1000; - } - break; - case NACL_CLOCK_MONOTONIC: - tick_cur = mach_absolute_time(); - /* - * mach_absolute_time() returns ticks since boot, with enough - * bits for several hundred years if the ticks occur at one per - * nanosecond. numer/denom gives ns/tick, and the scaling - * arithmetic should not result in over/underflows. - */ - tick_ns = (tick_cur * g_NaCl_time_base_info.numer - / g_NaCl_time_base_info.denom); - tp->tv_sec = tick_ns / 1000000000; - tp->tv_nsec = tick_ns % 1000000000; - rv = 0; - break; - case NACL_CLOCK_PROCESS_CPUTIME_ID: - count = TASK_ABSOLUTETIME_INFO_COUNT; - - if (KERN_SUCCESS != task_info(mach_task_self(), - TASK_ABSOLUTETIME_INFO, - (task_info_t) &absolutetime_info, - &count)) { - break; - } - tp->tv_sec = ((absolutetime_info.total_user - + absolutetime_info.total_system) - / NACL_NANOS_PER_UNIT); - tp->tv_nsec = ((absolutetime_info.total_user - + absolutetime_info.total_system) - % NACL_NANOS_PER_UNIT); - rv = 0; - break; - case NACL_CLOCK_THREAD_CPUTIME_ID: - count = THREAD_BASIC_INFO_COUNT; - - /* - * Don't use mach_thread_self() because it requires a separate - * mach_port_deallocate() system call to release it. Instead, rely on - * pthread's cached copy of the port. - */ - if (KERN_SUCCESS == thread_info(pthread_mach_thread_np(pthread_self()), - THREAD_BASIC_INFO, - (thread_info_t) basic_info, - &count)) { - tick_ns = ((basic_info->user_time.microseconds - + basic_info->system_time.microseconds) - * NACL_NANOS_PER_MICRO); - tp->tv_sec = (basic_info->user_time.seconds - + basic_info->system_time.seconds - + (tick_ns / NACL_NANOS_PER_UNIT)); - tp->tv_nsec = tick_ns % NACL_NANOS_PER_UNIT; - rv = 0; - } - break; - } - return rv; -} diff --git a/src/shared/platform/linux/nacl_clock.c b/src/shared/platform/posix/nacl_clock.c similarity index 100% rename from src/shared/platform/linux/nacl_clock.c rename to src/shared/platform/posix/nacl_clock.c