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 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 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..fa6541913b 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 = (int)prot_all; /* potential narrowing cast, but safe */ 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..167ab614fa 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, (int)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