diff --git a/pep-0551.rst b/pep-0551.rst index b40a3f344..48eb80d4c 100644 --- a/pep-0551.rst +++ b/pep-0551.rst @@ -268,58 +268,53 @@ integrate with these when launching scripts or importing modules. The new public C API for the verified open hook is:: # Set the handler - typedef PyObject *(*handler_func)(const char *narrow, - const wchar_t *wide) - int PyOS_SetOpenForExecuteHandler(handler_func handler) + typedef PyObject *(*hook_func)(PyObject *path) + int PyImport_SetOpenForImportHook(void *handler) # Open a file using the handler - PyObject *PyOS_OpenForExec(PyObject *path) + PyObject *PyImport_OpenForImport(const char *path) The new public Python API for the verified open hook is:: # Open a file using the handler - os.open_for_exec(pathlike) + _imp.open_for_import(path) -The ``os.open_for_exec()`` function is a drop-in replacement for -``open(pathlike, 'rb')``. Its default behaviour is to open a file for -raw, binary access - any more restrictive behaviour requires the use of -a custom handler. (Aside: since ``importlib`` requires access to this -function before the ``os`` module has been imported, it will be -available on the ``nt``/``posix`` modules, but the intent is that other -users will access it through the ``os`` module.) +The ``_imp.open_for_import()`` function is a drop-in replacement for +``open(str(pathlike), 'rb')``. Its default behaviour is to open a file +for raw, binary access - any more restrictive behaviour requires the +use of a custom handler. Only ``str`` arguments are accepted. -A custom handler may be set by calling ``Py_SetOpenForExecuteHandler()`` +A custom handler may be set by calling ``PyImport_SetOpenForImportHook()`` from C at any time, including before ``Py_Initialize()``. However, if a -handler has already been set then the call will fail. When -``open_for_exec()`` is called with a handler set, the handler will be -passed the processed narrow or wide path, depending on platform, and its -return value will be returned directly. The returned object should be an -open file-like object that supports reading raw bytes. This is -explicitly intended to allow a ``BytesIO`` instance if the open handler -has already had to read the file into memory in order to perform -whatever verification is necessary to determine whether the content is -permitted to be executed. +hook has already been set then the call will fail. When +``open_for_import()`` is called with a hook set, the hook will be passed +the path and its return value will be returned directly. The returned +object should be an open file-like object that supports reading raw +bytes. This is explicitly intended to allow a ``BytesIO`` instance if +the open handler has already had to read the file into memory in order +to perform whatever verification is necessary to determine whether the +content is permitted to be executed. -Note that these handlers can import and call the ``_io.open()`` function -on CPython without triggering themselves. +Note that these hooks can import and call the ``_io.open()`` function on +CPython without triggering themselves. -If the handler determines that the file is not suitable for execution, -it should raise an exception of its choice, as well as raising any other +If the hook determines that the file is not suitable for execution, it +should raise an exception of its choice, as well as raising any other auditing events or notifications. All import and execution functionality involving code from a file will -be changed to use ``open_for_exec()`` unconditionally. It is important +be changed to use ``open_for_import()`` unconditionally. It is important to note that calls to ``compile()``, ``exec()`` and ``eval()`` do not go through this function - an audit hook that includes the code from these calls will be added and is the best opportunity to validate code that is read from the file. Given the current decoupling between import and execution in Python, most imported code will go through both -``open_for_exec()`` and the log hook for ``compile``, and so care should -be taken to avoid repeating verification steps. +``open_for_import()`` and the log hook for ``compile``, and so care +should be taken to avoid repeating verification steps. .. note:: - The use of ``open_for_exec()`` by ``importlib`` is a valuable first - defence, but should not be relied upon to prevent misuse. In + The use of ``open_for_import()`` by ``importlib`` is a valuable + first defence, but should not be relied upon to prevent misuse. In particular, it is easy to monkeypatch ``importlib`` in order to bypass the call. Auditing hooks are the primary way to achieve security transparency, and are essential for detecting attempts to @@ -341,10 +336,10 @@ call will have any effect. (Including existence tests in security-critical code allows another vector to bypass auditing, so it is preferable that the function always exist.) -``os.open_for_exec(pathlike)`` should at a minimum always return -``_io.open(pathlike, 'rb')``. Code using the function should make no -further assumptions about what may occur, and implementations other than -CPython are not required to let developers override the behavior of this +``_imp.open_for_import(path)`` should at a minimum always return +``_io.open(path, 'rb')``. Code using the function should make no further +assumptions about what may occur, and implementations other than CPython +are not required to let developers override the behavior of this function with a hook. Audit Hook Locations @@ -366,14 +361,14 @@ should be considered part of the rationale for including the hook. hooks they are being cleaned up, mainly in case the event is triggered unexpectedly. This event cannot be aborted. " - ``Py_SetOpenForExecuteHandler``, ``setopenforexecutehandler``, "", " - Detects any attempt to set the ``open_for_execute`` handler. + ``PyImport_SetOpenForImportHook``, ``setopenforimporthook``, "", " + Detects any attempt to set the ``open_for_import`` hook. " "``compile``, ``exec``, ``eval``, ``PyAst_CompileString``, ``PyAST_obj2mod``", ``compile``, "``(code, filename_or_none)``", " Detect dynamic code compilation, where ``code`` could be a string or AST. Note that this will be called for regular imports of source - code, including those that were opened with ``open_for_exec``. + code, including those that were opened with ``open_for_import``. " "``exec``, ``eval``, ``run_mod``", ``exec``, "``(code_object,)``", " Detect dynamic execution of code objects. This only occurs for @@ -540,11 +535,11 @@ preventing any other hooks from being added. **Restrict importable modules** -Also before initialization, ``spython`` will set an open-for-execute -hook that validates all files opened with ``os.open_for_exec``. This +Also before initialization, ``spython`` will set an open-for-import +hook that validates all files opened with ``os.open_for_import``. This implementation will require all files to have a ``.py`` suffix (thereby blocking the use of cached bytecode), and will raise a custom audit -event ``spython.open_for_exec`` containing ``(filename, +event ``spython.open_for_import`` containing ``(filename, True_if_allowed)``. On Windows, the hook will also open the file with flags that prevent any @@ -622,7 +617,7 @@ for every file in a Python deployment, ideally verified using a private certificate. For example, Windows supports embedding signatures in executable files and using catalogs for others, and can use DeviceGuard [4]_ to validate signatures either automatically or using an -``open_for_exec`` hook. +``open_for_import`` hook. Sysadmins **should** log as many audited events as possible, and **should** copy logs off of local machines frequently. Even if logs are @@ -646,7 +641,7 @@ than to prevent them. Sysadmins **should** identify correlations between events, as a change to correlated events may indicate misuse. For example, module imports will typically trigger the ``import`` auditing event, followed by an -``open_for_exec`` call and usually a ``compile`` event. Attempts to +``open_for_import`` call and usually a ``compile`` event. Attempts to bypass auditing will often suppress some but not all of these events. So if the log contains ``import`` events but not ``compile`` events, investigation may be necessary. @@ -660,14 +655,14 @@ To prevent audit hooks being added on non-production machines, an entry point **may** add an audit hook that aborts the ``sys.addloghook`` event but otherwise does nothing. -On production machines, a non-validating ``open_for_exec`` hook **may** -be set in C code before ``Py_Initialize`` is called. This prevents later -code from overriding the hook, however, logging the +On production machines, a non-validating ``open_for_import`` hook +**may** be set in C code before ``Py_Initialize`` is called. This +prevents later code from overriding the hook, however, logging the ``setopenforexecutehandler`` event is useful since no code should ever -need to call it. Using at least the sample ``open_for_exec`` hook +need to call it. Using at least the sample ``open_for_import`` hook implementation from ``spython`` is recommended. -Since ``importlib``'s use of ``open_for_exec`` may be easily bypassed +Since ``importlib``'s use of ``open_for_import`` may be easily bypassed with monkeypatching, an audit hook **should** be used to detect attribute changes on type objects. @@ -719,8 +714,8 @@ section is the first time the word "secure" has been used. Security **transparency** does not result in any changed behaviour, so there is no appropriate reason for applications to modify their behaviour. -Both application-level APIs ``sys.audit`` and ``os.open_for_exec`` are -always present and functional, regardless of whether the regular +Both application-level APIs ``sys.audit`` and ``_imp.open_for_import`` +are always present and functional, regardless of whether the regular ``python`` entry point or some alternative entry point is used. Callers cannot determine whether any hooks have been added (except by performing side-channel analysis), nor do they need to. The calls should be fast