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:
parent
e1af227429
commit
0b682800bc
7 changed files with 171 additions and 9 deletions
|
|
@ -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 */
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
----------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
......................
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue