From 2ab45803a9fd4dc6942be77d91d8d6250ff87f48 Mon Sep 17 00:00:00 2001 From: Gareth Rees Date: Thu, 22 Dec 2022 19:12:53 +0000 Subject: [PATCH 1/5] 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. --- code/config.h | 13 +++++++++ code/protix.c | 31 ++++++++++++++++++--- code/vmix.c | 31 +++++++++++++++++---- design/prot.txt | 46 ++++++++++++++++++++++++++++++++ design/vm.txt | 20 ++++++++++++++ manual/source/release.rst | 3 +++ manual/source/topic/platform.rst | 36 +++++++++++++++++++++++++ 7 files changed, 171 insertions(+), 9 deletions(-) diff --git a/code/config.h b/code/config.h index aa9b70c7cc..83f985a885 100644 --- a/code/config.h +++ b/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/code/protix.c b/code/protix.c index 3c9f520398..8d6ead62a9 100644 --- a/code/protix.c +++ b/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/code/vmix.c b/code/vmix.c index 418c26f700..5a4b258aad 100644 --- a/code/vmix.c +++ b/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/design/prot.txt b/design/prot.txt index d3592b84c4..ba642b6c20 100644 --- a/design/prot.txt +++ b/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/design/vm.txt b/design/vm.txt index 3287e09488..f4bc2e4588 100644 --- a/design/vm.txt +++ b/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/manual/source/release.rst b/manual/source/release.rst index 6ff3db2958..cba65bcc3a 100644 --- a/manual/source/release.rst +++ b/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/manual/source/topic/platform.rst b/manual/source/topic/platform.rst index 3d72cd93c1..eef48d280c 100644 --- a/manual/source/topic/platform.rst +++ b/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 From 1dff0009f02c798e37d3bf9addb1761bd00b0211 Mon Sep 17 00:00:00 2001 From: Richard Brooksby Date: Tue, 10 Jan 2023 17:07:58 +0000 Subject: [PATCH 2/5] Adding FreeBSD to Travis CI build matrix. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 001d1674e1..2153440b00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ if: NOT branch IN (branch/2023-01-07/pull-request-merge-procedure) # The main build matrix for POSIX-like systems. language: c # see . os: + - freebsd - linux - osx # See "include" section for Windows From 1eb0bf38a555c6fc970e160e00fb9231071ef56e Mon Sep 17 00:00:00 2001 From: Richard Brooksby Date: Tue, 10 Jan 2023 23:43:49 +0000 Subject: [PATCH 3/5] Reverting 3c27665 (changelist 181860) that introduced GNU syntax into the top-level Makefile, breaking FreeBSD builds. Adding warning to prevent similar mistakes. --- Makefile.in | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile.in b/Makefile.in index 524b04a6b4..a1e8759406 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,13 +1,17 @@ # Makefile.in -- source for autoconf Makefile # # $Id$ -# Copyright (C) 2012-2016 Ravenbrook Limited. See end of file for license. +# Copyright (C) 2012-2023 Ravenbrook Limited. See end of file for license. # # YOU DON'T NEED AUTOCONF TO BUILD THE MPS # This is just here for people who want or expect a configure script. # See [Building the Memory Pool System](manual/build.txt) for how best # to build and integrate the MPS. # +# THIS IS NOT A GNU MAKEFILE +# This makefile be compatible with the default make on every (Posix) +# target platform, e.g. BSD make. +# INSTALL=@INSTALL@ INSTALL_DATA=@INSTALL_DATA@ @@ -34,7 +38,7 @@ install-make-build: make-install-dirs build-via-make $(INSTALL_DATA) code/mps*.h $(prefix)/include/ $(INSTALL_DATA) code/$(MPS_TARGET_NAME)/cool/mps.a $(prefix)/lib/libmps-debug.a $(INSTALL_DATA) code/$(MPS_TARGET_NAME)/hot/mps.a $(prefix)/lib/libmps.a - $(INSTALL_PROGRAM) $(addprefix code/$(MPS_TARGET_NAME)/hot/,$(EXTRA_TARGETS)) $(prefix)/bin + for PROGRAM in $(EXTRA_TARGETS); do $(INSTALL_PROGRAM) code/$(MPS_TARGET_NAME)/hot/$$PROGRAM $(prefix)/bin/$$PROGRAM; done build-via-xcode: $(XCODEBUILD) -config Debug @@ -48,7 +52,7 @@ install-xcode-build: make-install-dirs build-via-xcode $(INSTALL_DATA) code/mps*.h $(prefix)/include/ $(INSTALL_DATA) code/xc/Debug/libmps.a $(prefix)/lib/libmps-debug.a $(INSTALL_DATA) code/xc/Release/libmps.a $(prefix)/lib/libmps.a - $(INSTALL_PROGRAM) $(addprefix code/xc/Release/,$(EXTRA_TARGETS)) $(prefix)/bin + for PROGRAM in $(EXTRA_TARGETS); do $(INSTALL_PROGRAM) code/xc/Release/$$PROGRAM $(prefix)/bin/$$PROGRAM; done Makefile: Makefile.in config.status ./config.status Makefile From 407fae80978e16826afae3aebce5196b7f8fd779 Mon Sep 17 00:00:00 2001 From: Richard Brooksby Date: Wed, 11 Jan 2023 15:58:50 +0000 Subject: [PATCH 4/5] Adding narrowing cast to suppress warning on fr86ll. --- code/protix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/protix.c b/code/protix.c index 8d6ead62a9..fa6541913b 100644 --- a/code/protix.c +++ b/code/protix.c @@ -94,7 +94,7 @@ void ProtSet(Addr base, Addr limit, AccessSet mode) flags = PROT_READ | PROT_EXEC; break; case AccessSetEMPTY: - flags = prot_all; + flags = (int)prot_all; /* potential narrowing cast, but safe */ break; default: NOTREACHED; From cf6a0c90666eb768adfc869c5d2350aa557e3f52 Mon Sep 17 00:00:00 2001 From: Richard Brooksby Date: Wed, 11 Jan 2023 18:18:51 +0000 Subject: [PATCH 5/5] Adding narrowing cast to suppress warning on fri6ll. --- code/vmix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/vmix.c b/code/vmix.c index 5a4b258aad..167ab614fa 100644 --- a/code/vmix.c +++ b/code/vmix.c @@ -182,7 +182,7 @@ Res VMMap(VM vm, Addr base, Addr limit) size = AddrOffset(base, limit); - result = mmap((void *)base, (size_t)size, vm_prot, + result = mmap((void *)base, (size_t)size, (int)vm_prot, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); if (MAYBE_HARDENED_RUNTIME && result == MAP_FAILED && errno == EACCES