Skip to content

Commit

Permalink
Merge branch 'branch/2022-12-23/hardened-runtime' for github pull req…
Browse files Browse the repository at this point in the history
…uest 93 <#93>
  • Loading branch information
thejayps committed Jan 13, 2023
2 parents 5051593 + cf6a0c9 commit 43a856c
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 12 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://docs.travis-ci.com/user/languages/c/>.
os:
- freebsd
- linux
- osx
# See "include" section for Windows
Expand Down
10 changes: 7 additions & 3 deletions Makefile.in
Original file line number Diff line number Diff line change
@@ -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@
Expand All @@ -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
Expand All @@ -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
Expand Down
13 changes: 13 additions & 0 deletions code/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <design/prot#impl.xc.prot.exec> 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 */


Expand Down
31 changes: 27 additions & 4 deletions code/protix.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,41 @@

#include "vm.h"

#include <errno.h>
#include <limits.h>
#include <signal.h> /* sig_atomic_t */
#include <stddef.h>
#include <sys/mman.h>
#include <sys/types.h>

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 <design/prot#impl.xc.prot.exec> 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).
*/

void ProtSet(Addr base, Addr limit, AccessSet mode)
{
int flags;
int flags, result;

AVER(sizeof(size_t) == sizeof(Addr));
AVER(base < limit);
AVER(base != 0);
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
Expand All @@ -82,15 +94,26 @@ 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;
flags = PROT_NONE;
}

/* .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
* <design/prot#impl.xc.prot.exec> for details. */
prot_all = PROT_READ | PROT_WRITE;
result = mprotect((void *)base, (size_t)AddrOffset(base, limit), flags & prot_all);
}
if (result != 0)
NOTREACHED;
}

Expand Down
31 changes: 26 additions & 5 deletions code/vmix.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "vm.h"

#include <errno.h> /* errno */
#include <signal.h> /* sig_atomic_t */
#include <sys/mman.h> /* see .feature.li in config.h */
#include <sys/types.h> /* mmap, munmap */
#include <unistd.h> /* getpagesize */
Expand Down Expand Up @@ -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 <design/vm#impl.xc.prot.exec> 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));
Expand All @@ -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
* <design/vm#impl.xc.prot.exec> 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;
}
Expand Down
46 changes: 46 additions & 0 deletions design/prot.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
------
Expand All @@ -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
---------
Expand Down Expand Up @@ -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 <stdio.h>
#include <sys/mman.h>

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
----------------
Expand Down
20 changes: 20 additions & 0 deletions design/vm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
------
Expand Down Expand Up @@ -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
---------
Expand Down Expand Up @@ -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
......................
Expand Down
3 changes: 3 additions & 0 deletions manual/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
36 changes: 36 additions & 0 deletions manual/source/topic/platform.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <contact>`.

.. _Allow Execution of JIT-compiled Code Entitlement: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-jit

0 comments on commit 43a856c

Please sign in to comment.