.. mode: -*- rst -*-

POSIX implementation of protection module
=========================================

:Tag: design.mps.protix
:Author: Tony Mann
:Date: 2000-02-03
:Status: incomplete document
:Revision: $Id$
:Copyright: See `Copyright and License`_.
:Index terms:
   pair: POSIX; protection interface design
   pair: POSIX protection interface; design


Introduction
------------

_`.readership`: Any MPS developer

_`.intro`: This is the design of the POSIX implementation of the
protection module. It makes use of various services provided by POSIX.
It is intended to work with POSIX Threads.


Requirements
------------

_`.req.general`: Required to implement the general protection
interface defined in design.mps.prot.if_.

.. _design.mps.prot.if: prot#.if


Data structures
---------------

_`.data.signext`: If the SIGSEGV signal is not handled by any MPS
arena, ``sigHandle()`` needs to forward the signal to the next signal
handler in the chain (the signal handler that was installed when the
``ProtSetup()`` was called), by temporarily reinstalling the old
signal handler and calling ``kill()``. The only way to pass the next
signal handler to the current signal handler is via a global variable,
in this case the variable ``sigNext``.


Functions
---------

_`.fun.setup`: ``ProtSetup()`` installs a signal handler for the
signal ``SIGSEGV`` to catch and handle protection faults (this handler
is the function ``sigHandle()``).

_`.fun.setup.previous`: The previous handler is recorded (in the
variable ``sigNext``, see `.data.signext`_) so that it can be reached
from ``sigHandle()`` if it fails to handle the fault.

_`.fun.setup.restart`: We set the ``SA_RESTART`` flag when installing
the signal handler so that if the mutator gets a protection fault
while blocked in a system call, the system call is automatically
restarted after the signal is handled, instead of failing with
``EINTR``. Note that unlike the corresponding case in the thread
management subsystem (see design.mps.thread-manager.req.thread.intr_)
we are unsure if this case can actually arise: the ``SIGSEGV`` from a
protection fault is delivered to the thread that accessed the
protected memory, but in all the cases we have checked, if this access
occurred during a blocking system call such as a ``read()`` with the
buffer in protected memory, the system call fails with ``EFAULT`` and
is not restarted. However, it costs us nothing to set the
``SA_RESTART`` flag.

.. _design.mps.thread-manager.req.thread.intr: thread-manager#.req.thread.intr

_`.fun.set`: ``ProtSet()`` uses ``mprotect()`` to adjust the
protection for pages.

_`.fun.set.convert`: The requested protection (which is expressed in
the ``mode`` parameter, see design.mps.prot.if.set_) is translated into
an operating system protection. If read accesses are to be forbidden
then all accesses are forbidden, this is done by setting the
protection of the page to ``PROT_NONE``. If write accesses are to be
forbidden (and not read accesses) then write accesses are forbidden
and read accesses are allowed, this is done by setting the protection
of the page to ``PROT_READ|PROT_EXEC``. Otherwise (all access are
okay), the protection is set to ``PROT_READ|PROT_WRITE|PROT_EXEC``.

.. _design.mps.prot.if.set: prot#.if.set

_`.fun.set.assume.mprotect`: We assume that the call to ``mprotect()``
always succeeds.  We should always call the function with valid
arguments (aligned, references to mapped pages, and with an access
that is compatible with the access of the underlying object).

_`.fun.sync`: ``ProtSync()`` does nothing in this implementation as
``ProtSet()`` sets the protection without any delay.


Threads
-------

_`.threads`: The design must operate in a multi-threaded environment
(with POSIX Threads) and cooperate with the POSIX support for locks
(see design.mps.lock_) and the thread suspension mechanism (see
design.mps.pthreadext_ ).

.. _design.mps.pthreadext: pthreadext
.. _design.mps.lock: lock

_`.threads.suspend`: The ``SIGSEGV`` signal handler does not mask out
any signals, so a thread may be suspended while the handler is active,
as required by the design (see
design.mps.pthreadext.req.suspend.protection_). The signal handlers
simply nest at top of stack.

.. _design.mps.pthreadext.req.suspend.protection: pthreadext#.req.suspend.protection

_`.threads.async`: POSIX imposes some restrictions on signal handler
functions (see design.mps.pthreadext.analysis.signal.safety_). Basically
the rules say the behaviour of almost all POSIX functions inside a
signal handler is undefined, except for a handful of functions which
are known to be "async-signal safe". However, if it's known that the
signal didn't happen inside a POSIX function, then it is safe to call
arbitrary POSIX functions inside a handler.

.. _design.mps.pthreadext.analysis.signal.safety: pthreadext#.analysis.signal.safety

_`.threads.async.protection`: If the signal handler is invoked because
of an MPS access, then we know the access must have been caused by
client code, because the client is not allowed to permit access to
protectable memory to arbitrary foreign code. In these circumstances,
it's OK to call arbitrary POSIX functions inside the handler.

.. note::

    Need a reference for "the client is not allowed to permit access
    to protectable memory to arbitrary foreign code".

_`.threads.async.other`: If the signal handler is invoked for some
other reason (that is, one we are not prepared to handle) then there
is less we can say about what might have caused the SIGSEGV. In
general it is not safe to call arbitrary POSIX functions inside the
handler in this case.

_`.threads.async.choice`: The signal handler calls ``ArenaAccess()``
to determine whether the segmentation fault was the result of an MPS
access. ``ArenaAccess()`` will claim various MPS locks (that is, the
arena ring lock and some arena locks). The code calls no other POSIX
functions in the case where the segmentation fault is not an MPS
access. The locks are implemented as mutexes and are claimed by
calling ``pthread_mutex_lock()``, which is not defined to be
async-signal safe.

_`.threads.async.choice.ok`: However, despite the fact that POSIX
Threads documentation doesn't define the behaviour of
``pthread_mutex_lock()`` in these circumstances, we expect the POSIX
Threads implementation will be well-behaved unless the segmentation
fault occurs while while in the process of locking or unlocking one of
the MPS locks. But we can assume that a segmentation fault will not
happen then (because we use the locks correctly, and generally must
assume that they work). Hence we conclude that it is OK to call
``ArenaAccess()`` directly from the signal handler.

_`.threads.async.improve`: In future it would be preferable to not
have to assume reentrant mutex locking and unlocking functions. An
alternative approach would be necessary anyway when supporting another
platform which doesn't offer reentrant locks (if such a platform does
exist).

_`.threads.async.improve.how`: We could avoid the assumption if we had
a means of testing whether an address lies within an arena chunk
without the need to claim any locks. Such a test might actually be
possible. For example, arenas could update a global datastructure
describing the ranges of all chunks, using atomic updates rather than
locks; the handler code would be allowed to read this without locking.
However, this is somewhat tricky; a particular consideration is that
it's not clear when it's safe to deallocate stale portions of the
datastructure.

_`.threads.sig-stack`: We do not handle signals on a separate signal
stack. Separate signal stacks apparently don't work properly with
POSIX Threads.


Document History
----------------

- 2000-02-03 Tony Mann. Incomplete document.

- 2002-06-07 RB_ Converted from MMInfo database design document.

- 2013-05-23 GDR_ Converted to reStructuredText.

- 2016-10-13 GDR_ Generalise to POSIX, not just Linux.

.. _RB: https://www.ravenbrook.com/consultants/rb/
.. _GDR: https://www.ravenbrook.com/consultants/gdr/


Copyright and License
---------------------

Copyright © 2013–2020 `Ravenbrook Limited <https://www.ravenbrook.com/>`_.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
