From 0b682800bc9ea5bc3d27c4929bc8541e1c9a2cc5 Mon Sep 17 00:00:00 2001 From: Gareth Rees Date: Thu, 22 Dec 2022 19:12:53 +0000 Subject: [PATCH] Handle errors from mmap and mprotect due to apple hardened runtime. On Apple Silicon, when Hardened Runtime is in force and the application lacks the "Allow Unsigned Executable Memory Entitlement", mmap and mprotect return EACCES when an attempt is made to create or modify memory so that it is simultaneously writable and executable. This commit handles these cases by retrying the operation without the PROT_EXEC flag, and setting global variables so that future operations omit the flag. --- mps/code/config.h | 13 ++++++++ mps/code/protix.c | 31 ++++++++++++++++--- mps/code/vmix.c | 31 ++++++++++++++++--- mps/design/prot.txt | 46 ++++++++++++++++++++++++++++ mps/design/vm.txt | 20 ++++++++++++ mps/manual/source/release.rst | 3 ++ mps/manual/source/topic/platform.rst | 36 ++++++++++++++++++++++ 7 files changed, 171 insertions(+), 9 deletions(-) diff --git a/mps/code/config.h b/mps/code/config.h index aa9b70c7cce..83f985a8859 100644 --- a/mps/code/config.h +++ b/mps/code/config.h @@ -710,6 +710,19 @@ #define WB_DEFER_HIT 1 /* boring scans after barrier hit */ +/* Apple Hardened Runtime + * + * The MAYBE_HARDENED_RUNTIME macro is true if Apple's "Hardened + * Runtime" feature may be enabled, and so calls to mmap() and + * mprotect() with PROT_WRITE | PROT_EXEC may fail with EACCES. + * See for details. + */ +#if defined(MPS_OS_XC) && defined(MPS_ARCH_A6) +#define MAYBE_HARDENED_RUNTIME 1 +#else +#define MAYBE_HARDENED_RUNTIME 0 +#endif + #endif /* config_h */ diff --git a/mps/code/protix.c b/mps/code/protix.c index 3c9f5203981..8d6ead62a9f 100644 --- a/mps/code/protix.c +++ b/mps/code/protix.c @@ -41,13 +41,25 @@ #include "vm.h" +#include #include +#include /* sig_atomic_t */ #include #include #include SRCID(protix, "$Id$"); + +/* Value for memory protection corresponding to AccessSetEMPTY. + * See .convert.access for an explanation of the conversion. + * We use a global variable and not a constant so that we can clear + * the executable flag from future requests if Apple Hardened Runtime + * is detected. See for details. */ + +static sig_atomic_t prot_all = PROT_READ | PROT_WRITE | PROT_EXEC; + + /* ProtSet -- set protection * * This is just a thin veneer on top of mprotect(2). @@ -55,7 +67,7 @@ SRCID(protix, "$Id$"); void ProtSet(Addr base, Addr limit, AccessSet mode) { - int flags; + int flags, result; AVER(sizeof(size_t) == sizeof(Addr)); AVER(base < limit); @@ -63,7 +75,7 @@ void ProtSet(Addr base, Addr limit, AccessSet mode) AVER(AddrOffset(base, limit) <= INT_MAX); /* should be redundant */ AVERT(AccessSet, mode); - /* Convert between MPS AccessSet and UNIX PROT thingies. + /* .convert.access: Convert between MPS AccessSet and UNIX PROT thingies. In this function, AccessREAD means protect against read accesses (disallow them). PROT_READ means allow read accesses. Notice that this follows a difference in contract as well as style. AccessREAD @@ -82,7 +94,7 @@ void ProtSet(Addr base, Addr limit, AccessSet mode) flags = PROT_READ | PROT_EXEC; break; case AccessSetEMPTY: - flags = PROT_READ | PROT_WRITE | PROT_EXEC; + flags = prot_all; break; default: NOTREACHED; @@ -90,7 +102,18 @@ void ProtSet(Addr base, Addr limit, AccessSet mode) } /* .assume.mprotect.base */ - if(mprotect((void *)base, (size_t)AddrOffset(base, limit), flags) != 0) + result = mprotect((void *)base, (size_t)AddrOffset(base, limit), flags); + if (MAYBE_HARDENED_RUNTIME && result != 0 && errno == EACCES + && (flags & PROT_WRITE) && (flags & PROT_EXEC)) + { + /* Apple Hardened Runtime is enabled, so that we cannot have + * memory that is simultaneously writable and executable. Handle + * this by dropping the executable part of the request. See + * for details. */ + prot_all = PROT_READ | PROT_WRITE; + result = mprotect((void *)base, (size_t)AddrOffset(base, limit), flags & prot_all); + } + if (result != 0) NOTREACHED; } diff --git a/mps/code/vmix.c b/mps/code/vmix.c index 418c26f7001..5a4b258aad1 100644 --- a/mps/code/vmix.c +++ b/mps/code/vmix.c @@ -47,6 +47,7 @@ #include "vm.h" #include /* errno */ +#include /* sig_atomic_t */ #include /* see .feature.li in config.h */ #include /* mmap, munmap */ #include /* getpagesize */ @@ -156,11 +157,20 @@ void VMFinish(VM vm) } +/* Value to use for protection of newly allocated pages. + * We use a global variable and not a constant so that we can clear + * the executable flag from future requests if Apple Hardened Runtime + * is detected. See for details. */ + +static sig_atomic_t vm_prot = PROT_READ | PROT_WRITE | PROT_EXEC; + + /* VMMap -- map the given range of memory */ Res VMMap(VM vm, Addr base, Addr limit) { Size size; + void *result; AVERT(VM, vm); AVER(sizeof(void *) == sizeof(Addr)); @@ -172,11 +182,22 @@ Res VMMap(VM vm, Addr base, Addr limit) size = AddrOffset(base, limit); - if(mmap((void *)base, (size_t)size, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANON | MAP_PRIVATE | MAP_FIXED, - -1, 0) - == MAP_FAILED) { + result = mmap((void *)base, (size_t)size, vm_prot, + MAP_ANON | MAP_PRIVATE | MAP_FIXED, + -1, 0); + if (MAYBE_HARDENED_RUNTIME && result == MAP_FAILED && errno == EACCES + && (vm_prot & PROT_WRITE) && (vm_prot & PROT_EXEC)) + { + /* Apple Hardened Runtime is enabled, so that we cannot have + * memory that is simultaneously writable and executable. Handle + * this by dropping the executable part of the request. See + * for details. */ + vm_prot = PROT_READ | PROT_WRITE; + result = mmap((void *)base, (size_t)size, vm_prot, + MAP_ANON | MAP_PRIVATE | MAP_FIXED, + -1, 0); + } + if (result == MAP_FAILED) { AVER(errno == ENOMEM); /* .assume.mmap.err */ return ResMEMORY; } diff --git a/mps/design/prot.txt b/mps/design/prot.txt index d3592b84c4b..ba642b6c20d 100644 --- a/mps/design/prot.txt +++ b/mps/design/prot.txt @@ -51,6 +51,14 @@ the MPS cannot take the correct action: that is, fixing references in a read-protected segment, and discarding the remembered set from a write-protected segment. See ``TraceSegAccess()``.) +_`.req.prot.exec`: The protection module should allow mutators to +write machine code into memory managed by the MPS and then execute +that code, for example, to implement just-in-time translation, or +other forms of dynamic compilation. Compare +design.mps.vm.req.prot.exec_. + +.. _design.mps.vm.req.prot.exec: vm#.req.prot.exec + Design ------ @@ -69,6 +77,10 @@ and calling ``ArenaAccess()``. .. _design.mps.prmc.req.fault.addr: prmc#.req.fault.addr .. _design.mps.prmc.req.fault.access: prmc#.req.fault.access +_`.sol.prot.exec`: The protection module makes memory executable +whenever it is readable by the mutator, if this is supported by the +platform. + Interface --------- @@ -141,6 +153,40 @@ _`.impl.w3`: Windows implementation. _`.impl.xc`: macOS implementation. +_`.impl.xc.prot.exec`: The approach in `.sol.prot.exec`_ of always +making memory executable causes a difficulty on macOS on Apple +Silicon. On this platform, programs may enable `Hardened Runtime`_. +This feature rejects attempts to map or protect memory so that it is +simultaneously writable and executable. Moreover, the feature is +enabled by default (as of macOS 13 Ventura), so that if you install +Xcode and then use it to compile the following program, the executable +fails when run with "mmap: Permission denied". :: + + #include + #include + + int main(void) + { + void *p = mmap(0, 1, PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (p == MAP_FAILED) perror("mmap"); + return 0; + } + +.. _Hardened Runtime: https://developer.apple.com/documentation/security/hardened_runtime + +_`.impl.xc.prot.exec.detect`: The protection module detects Hardened +Runtime if the operating system is macOS, the CPU architecture is +ARM64, a call to ``mprotect()`` fails, the call requested writable and +executable access, and the error code is ``EACCES``. + +_`.impl.xc.prot.exec.retry`: To avoid requiring developers who don't +need to allocate executable memory to figure out how to disable +Hardened Runtime, or enable the appropriate entitlement, the +protection module handles the ``EACCES`` error from ``mprotect()`` in +the Hardened Runtime case by retrying without the request for the +memory to be executable, and setting a global variable to prevent the +writable and executable combination being attempted again. + Document History ---------------- diff --git a/mps/design/vm.txt b/mps/design/vm.txt index 3287e094880..f4bc2e4588b 100644 --- a/mps/design/vm.txt +++ b/mps/design/vm.txt @@ -113,6 +113,14 @@ the client program to modify the behaviour of the virtual mapping implementation. (This is needed to implement the ``MPS_KEY_VMW3_MEM_TOP_DOWN`` keyword argument.) +_`.req.prot.exec`: The virtual mapping module should allow mutators to +write machine code into memory allocated by the MPS and then execute +that code, for example, to implement just-in-time translation, or +other forms of dynamic compilation. Compare +design.mps.prot.req.prot.exec_. + +.. _design.mps.prot.req.prot.exec: prot#.req.prot.exec + Design ------ @@ -144,6 +152,9 @@ statically determinable so that the caller can allocate it on the stack: it is given by the constant ``VMParamSize``. Since this is potentially platform-dependent it is defined in ``config.h``. +_`.sol.prot.exec`: The virtual mapping module maps memory as +executable, if this is supported by the platform. + Interface --------- @@ -287,6 +298,15 @@ _`.impl.ix.unmap`: Address space is unmapped from main memory by calling |mmap|_, passing ``PROT_NONE`` and ``MAP_ANON | MAP_PRIVATE | MAP_FIXED``. +_`.impl.xc.prot.exec`: The approach in `.sol.prot.exec`_ of always +making memory executable causes a difficulty on macOS on Apple +Silicon. The virtual mapping module uses the same solution as the +protection module, that is, detecting Apple Hardened Runtime, and +retrying without the request for the memory to be executable. See +design.mps.prot.impl.xc.prot.exec_ for details. + +.. _design.mps.prot.impl.xc.prot.exec: prot#.impl.xc.prot.exec + Windows implementation ...................... diff --git a/mps/manual/source/release.rst b/mps/manual/source/release.rst index 6ff3db29589..cba65bcc3ad 100644 --- a/mps/manual/source/release.rst +++ b/mps/manual/source/release.rst @@ -18,6 +18,9 @@ New features * ``lia6ll`` (Linux, ARM64, Clang/LLVM). * ``xca6ll`` (macOS, ARM64, Clang/LLVM). + See :ref:`topic-platform-limitations` for limitations in the + support for Apple Hardened Runtime on ``xca6ll``. + #. Support removed for platform: * ``xci3ll`` (macOS, IA-32, Clang/LLVM). diff --git a/mps/manual/source/topic/platform.rst b/mps/manual/source/topic/platform.rst index 3d72cd93c1d..eef48d280c0 100644 --- a/mps/manual/source/topic/platform.rst +++ b/mps/manual/source/topic/platform.rst @@ -416,3 +416,39 @@ Platform Status ``xci6ll`` Supported ``xcppgc`` *Not supported* ========== ======================= + + +.. index:: + pair: platform; limitations + single: Hardened Runtime + +.. _topic-platform-limitations: + +Platform limitations +-------------------- + +This section documents limitations that affect individual platforms. + +``xca6ll`` + + On macOS on Apple Silicon, programs may enable `Hardened Runtime`_. + This feature rejects attempts to map or protect memory so that it + is simultaneously writable and executable. Therefore, when Hardened + Runtime is enabled, memory managed by the MPS is not executable. + + .. _Hardened Runtime: https://developer.apple.com/documentation/security/hardened_runtime + + If your program needs to write executable code into memory managed + by the MPS (for example, it uses just-in-time translation or + dynamic compilation), then you must either disable Hardened + Runtime, or configure the `Allow Unsigned Executable Memory + Entitlement`_. + + .. _Allow Unsigned Executable Memory Entitlement: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-unsigned-executable-memory + + Note that the MPS has no support for Apple's :c:macro:`MAP_JIT` + flag. If your application is using the `Allow Execution of + JIT-compiled Code Entitlement`_ and needs support for this flag, + please :ref:`contact us `. + + .. _Allow Execution of JIT-compiled Code Entitlement: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-jit