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