1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-03-15 19:31:29 -07:00

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.
This commit is contained in:
Gareth Rees 2022-12-22 19:12:53 +00:00
parent e1af227429
commit 0b682800bc
7 changed files with 171 additions and 9 deletions

View file

@ -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 */

View file

@ -41,13 +41,25 @@
#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).
@ -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
* <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;
}

View file

@ -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 */
@ -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));
@ -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
* <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;
}

View file

@ -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 <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
----------------

View file

@ -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
......................

View file

@ -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).

View file

@ -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