From 136b2b60fcc02a3644c94b311b3472f3a5b8b1a3 Mon Sep 17 00:00:00 2001 From: Gareth Rees Date: Thu, 22 Dec 2022 19:12:53 +0000 Subject: [PATCH] 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. --- code/config.h | 17 +++++++++++++++++ code/protix.c | 22 +++++++++++++++++++--- code/vmix.c | 25 ++++++++++++++++++++----- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/code/config.h b/code/config.h index aa9b70c7cc..987f4a8034 100644 --- a/code/config.h +++ b/code/config.h @@ -710,6 +710,23 @@ #define WB_DEFER_HIT 1 /* boring scans after barrier hit */ +/* Apple Hardened Runtime + * + * .hardened-runtime: On Apple Silicon, applications may be compiled + * with Hardened Runtime enabled. These applications have restricted + * capabilities: in particular, unless the "Allow Unsigned Executable + * Memory Entitlement" is enabled, these applications cannot create + * memory that is simultaneously writable and executable. Attempts to + * do so using mmap() and mprotect() fail with EACCES. + * + * See + */ +#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..2f1ad1f62e 100644 --- a/code/protix.c +++ b/code/protix.c @@ -41,13 +41,21 @@ #include "vm.h" +#include #include +#include /* sig_atomic_t */ #include #include #include SRCID(protix, "$Id$"); + +/* Value for memory protection corresponding to AccessSetEMPTY. */ + +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 +63,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); @@ -82,7 +90,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 +98,15 @@ 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)) + { + /* See . */ + 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..941939f170 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,17 @@ void VMFinish(VM vm) } +/* Value to use for protection of newly allocated pages. */ + +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 +179,19 @@ 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)) + { + /* See . */ + 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; }