.. highlight:: none .. index:: pair: memory protection; design .. _design-prot: Memory protection ================= .. mps:prefix:: design.mps.prot Introduction ------------ :mps:tag:`intro` This is the design of the memory protection module. :mps:tag:`readership` Any MPS developer; anyone porting the MPS to a new platform. :mps:tag:`overview` The memory protection module ensures that the mutator sees a consistent view of memory during incremental collection, by applying protection to areas of memory, ensuring that attempts to read or write from those areas cause protection faults, and implementing the means for the MPS to handle these faults. Requirements ------------ :mps:tag:`req.consistent` Must ensure that the mutator sees a consistent view of memory during incremental collection: in particular, the mutator must never see objects in oldspace. (Otherwise there's no way for the MPS to interface with uncooperative code.) :mps:tag:`req.prot.read` Should allow collections to proceed incrementally, by read-protecting pages that are not consistent from the mutator's point of view. (This is the only way for the MPS to meet real-time requirements on pause times.) :mps:tag:`req.prot.write` Should allow the MPS to maintain remembered sets for segments that it has scanned, by write-protecting pages in these segments. (This improves performance by allowing the MPS to avoid scanning these segments again.) :mps:tag:`req.fault.handle` If the module implements protection, it must also provide a mechanism for handling protection faults. (Otherwise 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 :c:func:`TraceSegAccess()`.) :mps:tag:`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.html#design.mps.vm.req.prot.exec Design ------ :mps:tag:`sol.sync` If memory protection is not available, the only way to meet :mps:ref:`.req.consistent` is to ensure that no protection is required, by running the collector until it has no more incremental work to do. (This makes it impossible to meet real-time requirements on pause times, but may be the best that can be done.) :mps:tag:`sol.fault.handle` The protection module handles protection faults by decoding the context of the fault (see design.mps.prmc.req.fault.addr_ and design.mps.prmc.req.fault.access_) and calling :c:func:`ArenaAccess()`. .. _design.mps.prmc.req.fault.addr: prmc.html#design.mps.prmc.req.fault.addr .. _design.mps.prmc.req.fault.access: prmc.html#design.mps.prmc.req.fault.access :mps:tag:`sol.prot.exec` The protection module makes memory executable whenever it is readable by the mutator, if this is supported by the platform. Interface --------- .. c:function:: void ProtSetup(void) :mps:tag:`if.setup` Called exactly once (per process) as part of the initialization of the first arena that is created. It must arrange for the setup and initialization of any data structures or services that are necessary in order to implement the memory protection module. .. c:function:: Size ProtGranularity(void) :mps:tag:`if.granularity` Return the granularity of protection. The ``base`` and ``limit`` arguments to :c:func:`ProtSet()` must be multiples of the protection granularity. .. c:function:: void ProtSet(Addr base, Addr limit, AccessSet mode) :mps:tag:`if.set` Set the protection of the range of memory between ``base`` (inclusive) and ``limit`` (exclusive) to *forbid* the specified modes. The addresses ``base`` and ``limit`` are multiples of the protection granularity. The ``mode`` parameter contains the ``AccessWRITE`` bit if write accesses to the range are to be forbidden, and contains the ``AccessREAD`` bit if read accesses to the range are to be forbidden. :mps:tag:`if.set.read` If the request is to forbid read accesses (that is, ``AccessREAD`` is set) then the implemntation may also forbid write accesses, but read accesses must not be forbidden unless ``AccessREAD`` is set. :mps:tag:`if.set.noop` :c:func:`ProtSet()` is permitted to be a no-op if :c:func:`ProtSync()` is implemented. .. c:function:: void ProtSync(Arena arena) :mps:tag:`if.sync` Ensure that the actual protection (as determined by the operating system) of every segment in the arena matches the segment's protection mode (``seg->pm``). :mps:tag:`if.sync.noop` :c:func:`ProtSync()` is permitted to be a no-op if :c:func:`ProtSet()` is implemented. Implementations --------------- :mps:tag:`impl.an` Generic implementation in ``protan.c``. :mps:tag:`impl.an.set` :c:func:`ProtSet()` does nothing. :mps:tag:`impl.an.sync` :c:func:`ProtSync()` has no way of changing the protection of a segment, so it simulates faults on all segments that are supposed to be protected, by calling :c:func:`TraceSegAccess()`, until it determines that no segments require protection any more. This forces the trace to proceed until it is completed, preventing incremental collection. :mps:tag:`impl.an.sync.issue` This relies on the pool actually removing the protection, otherwise there is an infinite loop here. This is therefore not compatible with implementations of the protection mutator context module that support single-stepping of accesses (see design.mps.prmc.req.fault.step_). .. _design.mps.prmc.req.fault.step: prmc.html#design.mps.prmc.req.fault.step :mps:tag:`impl.ix` POSIX implementation. See design.mps.protix_. .. _design.mps.protix: protix.html :mps:tag:`impl.w3` Windows implementation. :mps:tag:`impl.xc` macOS implementation. :mps:tag:`impl.xc.prot.exec` The approach in :mps:ref:`.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 :mps:tag:`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 :c:func:`mprotect()` fails, the call requested writable and executable access, and the error code is :c:macro:`EACCES`. :mps:tag:`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 :c:macro:`EACCES` error from :c:func:`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.