mirror of https://github.com/python/peps
748 lines
55 KiB
HTML
748 lines
55 KiB
HTML
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="color-scheme" content="light dark">
|
||
<title>PEP 578 – Python Runtime Audit Hooks | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-0578/">
|
||
<link rel="stylesheet" href="../_static/style.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
|
||
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
|
||
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
|
||
<meta property="og:title" content='PEP 578 – Python Runtime Audit Hooks | peps.python.org'>
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-0578/">
|
||
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
|
||
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
|
||
<meta property="og:image:alt" content="Python PEPs">
|
||
<meta property="og:image:width" content="200">
|
||
<meta property="og:image:height" content="200">
|
||
<meta name="description" content="Python Enhancement Proposals (PEPs)">
|
||
<meta name="theme-color" content="#3776ab">
|
||
</head>
|
||
<body>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Following system colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="9"></circle>
|
||
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected dark colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected light colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||
</svg>
|
||
</symbol>
|
||
</svg>
|
||
<script>
|
||
|
||
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
|
||
</script>
|
||
<section id="pep-page-section">
|
||
<header>
|
||
<h1>Python Enhancement Proposals</h1>
|
||
<ul class="breadcrumbs">
|
||
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li>
|
||
<li><a href="../pep-0000/">PEP Index</a> » </li>
|
||
<li>PEP 578</li>
|
||
</ul>
|
||
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
|
||
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
|
||
</button>
|
||
</header>
|
||
<article>
|
||
<section id="pep-content">
|
||
<h1 class="page-title">PEP 578 – Python Runtime Audit Hooks</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Steve Dower <steve.dower at python.org></dd>
|
||
<dt class="field-even">BDFL-Delegate<span class="colon">:</span></dt>
|
||
<dd class="field-even">Christian Heimes <christian at python.org></dd>
|
||
<dt class="field-odd">Status<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><abbr title="Normative proposal accepted for implementation">Accepted</abbr></dd>
|
||
<dt class="field-even">Type<span class="colon">:</span></dt>
|
||
<dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
|
||
<dt class="field-odd">Created<span class="colon">:</span></dt>
|
||
<dd class="field-odd">16-Jun-2018</dd>
|
||
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
|
||
<dd class="field-even">3.8</dd>
|
||
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-odd">28-Mar-2019, 07-May-2019</dd>
|
||
</dl>
|
||
<hr class="docutils" />
|
||
<section id="contents">
|
||
<details><summary>Table of Contents</summary><ul class="simple">
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#background">Background</a></li>
|
||
<li><a class="reference internal" href="#overview-of-changes">Overview of Changes</a><ul>
|
||
<li><a class="reference internal" href="#audit-hook">Audit Hook</a></li>
|
||
<li><a class="reference internal" href="#verified-open-hook">Verified Open Hook</a></li>
|
||
<li><a class="reference internal" href="#api-availability">API Availability</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#suggested-audit-hook-locations">Suggested Audit Hook Locations</a></li>
|
||
<li><a class="reference internal" href="#performance-impact">Performance Impact</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#separate-module-for-audit-hooks">Separate module for audit hooks</a></li>
|
||
<li><a class="reference internal" href="#flag-in-sys-flags-to-indicate-audited-mode">Flag in sys.flags to indicate “audited” mode</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#why-not-a-sandbox">Why Not A Sandbox</a></li>
|
||
<li><a class="reference internal" href="#relationship-to-pep-551">Relationship to PEP 551</a></li>
|
||
<li><a class="reference internal" href="#references">References</a></li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
</details></section>
|
||
<section id="abstract">
|
||
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
|
||
<p>This PEP describes additions to the Python API and specific behaviors
|
||
for the CPython implementation that make actions taken by the Python
|
||
runtime visible to auditing tools. Visibility into these actions
|
||
provides opportunities for test frameworks, logging frameworks, and
|
||
security tools to monitor and optionally limit actions taken by the
|
||
runtime.</p>
|
||
<p>This PEP proposes adding two APIs to provide insights into a running
|
||
Python application: one for arbitrary events, and another specific to
|
||
the module import system. The APIs are intended to be available in all
|
||
Python implementations, though the specific messages and values used
|
||
are unspecified here to allow implementations the freedom to determine
|
||
how best to provide information to their users. Some examples likely
|
||
to be used in CPython are provided for explanatory purposes.</p>
|
||
<p>See <a class="pep reference internal" href="../pep-0551/" title="PEP 551 – Security transparency in the Python runtime">PEP 551</a> for discussion and recommendations on enhancing the
|
||
security of a Python runtime making use of these auditing APIs.</p>
|
||
</section>
|
||
<section id="background">
|
||
<h2><a class="toc-backref" href="#background" role="doc-backlink">Background</a></h2>
|
||
<p>Python provides access to a wide range of low-level functionality on
|
||
many common operating systems. While this is incredibly useful for
|
||
“write-once, run-anywhere” scripting, it also makes monitoring of
|
||
software written in Python difficult. Because Python uses native system
|
||
APIs directly, existing monitoring tools either suffer from limited
|
||
context or auditing bypass.</p>
|
||
<p>Limited context occurs when system monitoring can report that an
|
||
action occurred, but cannot explain the sequence of events leading to
|
||
it. For example, network monitoring at the OS level may be able to
|
||
report “listening started on port 5678”, but may not be able to
|
||
provide the process ID, command line, parent process, or the local
|
||
state in the program at the point that triggered the action. Firewall
|
||
controls to prevent such an action are similarly limited, typically
|
||
to process names or some global state such as the current user, and
|
||
in any case rarely provide a useful log file correlated with other
|
||
application messages.</p>
|
||
<p>Auditing bypass can occur when the typical system tool used for an
|
||
action would ordinarily report its use, but accessing the APIs via
|
||
Python do not trigger this. For example, invoking “curl” to make HTTP
|
||
requests may be specifically monitored in an audited system, but
|
||
Python’s “urlretrieve” function is not.</p>
|
||
<p>Within a long-running Python application, particularly one that
|
||
processes user-provided information such as a web app, there is a risk
|
||
of unexpected behavior. This may be due to bugs in the code, or
|
||
deliberately induced by a malicious user. In both cases, normal
|
||
application logging may be bypassed resulting in no indication that
|
||
anything out of the ordinary has occurred.</p>
|
||
<p>Additionally, and somewhat unique to Python, it is very easy to affect
|
||
the code that is run in an application by manipulating either the
|
||
import system’s search path or placing files earlier on the path than
|
||
intended. This is often seen when developers create a script with the
|
||
same name as the module they intend to use - for example, a
|
||
<code class="docutils literal notranslate"><span class="pre">random.py</span></code> file that attempts to import the standard library
|
||
<code class="docutils literal notranslate"><span class="pre">random</span></code> module.</p>
|
||
<p>This is not sandboxing, as this proposal does not attempt to prevent
|
||
malicious behavior (though it enables some new options to do so).
|
||
See the <a class="reference internal" href="#why-not-a-sandbox">Why Not A Sandbox</a> section below for further discussion.</p>
|
||
</section>
|
||
<section id="overview-of-changes">
|
||
<h2><a class="toc-backref" href="#overview-of-changes" role="doc-backlink">Overview of Changes</a></h2>
|
||
<p>The aim of these changes is to enable both application developers and
|
||
system administrators to integrate Python into their existing
|
||
monitoring systems without dictating how those systems look or behave.</p>
|
||
<p>We propose two API changes to enable this: an Audit Hook and Verified
|
||
Open Hook. Both are available from Python and native code, allowing
|
||
applications and frameworks written in pure Python code to take
|
||
advantage of the extra messages, while also allowing embedders or
|
||
system administrators to deploy builds of Python where auditing is
|
||
always enabled.</p>
|
||
<p>Only CPython is bound to provide the native APIs as described here.
|
||
Other implementations should provide the pure Python APIs, and
|
||
may provide native versions as appropriate for their underlying
|
||
runtimes. Auditing events are likewise considered implementation
|
||
specific, but are bound by normal feature compatibility guarantees.</p>
|
||
<section id="audit-hook">
|
||
<h3><a class="toc-backref" href="#audit-hook" role="doc-backlink">Audit Hook</a></h3>
|
||
<p>In order to observe actions taken by the runtime (on behalf of the
|
||
caller), an API is required to raise messages from within certain
|
||
operations. These operations are typically deep within the Python
|
||
runtime or standard library, such as dynamic code compilation, module
|
||
imports, DNS resolution, or use of certain modules such as <code class="docutils literal notranslate"><span class="pre">ctypes</span></code>.</p>
|
||
<p>The following new C APIs allow embedders and CPython implementors to
|
||
send and receive audit hook messages:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Add an auditing hook</span>
|
||
<span class="n">typedef</span> <span class="nb">int</span> <span class="p">(</span><span class="o">*</span><span class="n">hook_func</span><span class="p">)(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="n">event</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span>
|
||
<span class="n">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">);</span>
|
||
<span class="nb">int</span> <span class="n">PySys_AddAuditHook</span><span class="p">(</span><span class="n">hook_func</span> <span class="n">hook</span><span class="p">,</span> <span class="n">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">);</span>
|
||
|
||
<span class="c1"># Raise an event with all auditing hooks</span>
|
||
<span class="nb">int</span> <span class="n">PySys_Audit</span><span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="n">event</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="n">args</span><span class="p">);</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The new Python APIs for receiving and raising audit hooks are:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Add an auditing hook</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">addaudithook</span><span class="p">(</span><span class="n">hook</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">tuple</span><span class="p">]])</span>
|
||
|
||
<span class="c1"># Raise an event with all auditing hooks</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">audit</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Hooks are added by calling <code class="docutils literal notranslate"><span class="pre">PySys_AddAuditHook()</span></code> from C at any time,
|
||
including before <code class="docutils literal notranslate"><span class="pre">Py_Initialize()</span></code>, or by calling
|
||
<code class="docutils literal notranslate"><span class="pre">sys.addaudithook()</span></code> from Python code. Hooks cannot be removed or
|
||
replaced. For CPython, hooks added from C are global, while hooks added
|
||
from Python are only for the current interpreter. Global hooks are
|
||
executed before interpreter hooks.</p>
|
||
<p>When events of interest are occurring, code can either call
|
||
<code class="docutils literal notranslate"><span class="pre">PySys_Audit()</span></code> from C (while the GIL is held) or <code class="docutils literal notranslate"><span class="pre">sys.audit()</span></code>. The
|
||
string argument is the name of the event, and the tuple contains
|
||
arguments. A given event name should have a fixed schema for arguments,
|
||
which should be considered a public API (for each x.y version release),
|
||
and thus should only change between feature releases with updated
|
||
documentation. To minimize overhead and simplify handling in native code
|
||
hook implementations, named arguments are not supported.</p>
|
||
<p>For maximum compatibility, events using the same name as an event in
|
||
the reference interpreter CPython should make every attempt to use
|
||
compatible arguments. Including the name or an abbreviation of the
|
||
implementation in implementation-specific event names will also help
|
||
prevent collisions. For example, a <code class="docutils literal notranslate"><span class="pre">pypy.jit_invoked</span></code> event is clearly
|
||
distinguished from an <code class="docutils literal notranslate"><span class="pre">ipy.jit_invoked</span></code> event. Events raised from
|
||
Python modules should include their module or package name in the event
|
||
name.</p>
|
||
<p>While event names may be arbitrary UTF-8 strings, for consistency across
|
||
implementations it is recommended to use valid Python dotted names and
|
||
avoid encoding specific details in the name. For example, an <code class="docutils literal notranslate"><span class="pre">import</span></code>
|
||
event with the module name <code class="docutils literal notranslate"><span class="pre">spam</span></code> as an argument is preferable to a
|
||
<code class="docutils literal notranslate"><span class="pre">spam</span> <span class="pre">module</span> <span class="pre">imported</span></code> event with no arguments. Avoid using embedded
|
||
null characters or you may upset those who implement hooks using C.</p>
|
||
<p>When an event is audited, each hook is called in the order it was added
|
||
(as much as is possible), passing the event name and arguments. If any
|
||
hook returns with an exception set, later hooks are ignored and <em>in
|
||
general</em> the Python runtime should terminate - exceptions from hooks are
|
||
not intended to be handled or treated as expected occurrences. This
|
||
allows hook implementations to decide how to respond to any particular
|
||
event. The typical responses will be to log the event, abort the
|
||
operation with an exception, or to immediately terminate the process with
|
||
an operating system exit call.</p>
|
||
<p>When an event is audited but no hooks have been set, the <code class="docutils literal notranslate"><span class="pre">audit()</span></code>
|
||
function should impose minimal overhead. Ideally, each argument is a
|
||
reference to existing data rather than a value calculated just for the
|
||
auditing call.</p>
|
||
<p>As hooks may be Python objects, they need to be freed during
|
||
interpreter or runtime finalization. These should not be triggered at
|
||
any other time, and should raise an event hook to ensure that any
|
||
unexpected calls are observed.</p>
|
||
<p>Below in <a class="reference internal" href="#suggested-audit-hook-locations">Suggested Audit Hook Locations</a>, we recommend some important
|
||
operations that should raise audit events. In general, events should be
|
||
raised at the lowest possible level. Given the choice between raising an
|
||
event from Python code or native code, raising from native code should be
|
||
preferred.</p>
|
||
<p>Python implementations should document which operations will raise
|
||
audit events, along with the event schema. It is intentional that
|
||
<code class="docutils literal notranslate"><span class="pre">sys.addaudithook(print)</span></code> is a trivial way to display all messages.</p>
|
||
</section>
|
||
<section id="verified-open-hook">
|
||
<h3><a class="toc-backref" href="#verified-open-hook" role="doc-backlink">Verified Open Hook</a></h3>
|
||
<p>Most operating systems have a mechanism to distinguish between files
|
||
that can be executed and those that can not. For example, this may be an
|
||
execute bit in the permissions field, a verified hash of the file
|
||
contents to detect potential code tampering, or file system path
|
||
restrictions. These are an important security mechanism for ensuring
|
||
that only code that has been approved for a given environment is
|
||
executed.</p>
|
||
<p>Most kernels offer ways to restrict or audit binaries loaded and executed
|
||
by the kernel. File types owned by Python appear as regular data and
|
||
these features do not apply. This open hook allows Python embedders to
|
||
integrate with operating system support when launching scripts or
|
||
importing Python code.</p>
|
||
<p>The new public C API for the verified open hook is:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Set the handler</span>
|
||
<span class="n">typedef</span> <span class="n">PyObject</span> <span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="n">hook_func</span><span class="p">)(</span><span class="n">PyObject</span> <span class="o">*</span><span class="n">path</span><span class="p">,</span> <span class="n">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">)</span>
|
||
<span class="nb">int</span> <span class="n">PyFile_SetOpenCodeHook</span><span class="p">(</span><span class="n">hook_func</span> <span class="n">handler</span><span class="p">,</span> <span class="n">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Open a file using the handler</span>
|
||
<span class="n">PyObject</span> <span class="o">*</span><span class="n">PyFile_OpenCode</span><span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="n">path</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The new public Python API for the verified open hook is:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Open a file using the handler</span>
|
||
<span class="n">io</span><span class="o">.</span><span class="n">open_code</span><span class="p">(</span><span class="n">path</span> <span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="n">io</span><span class="o">.</span><span class="n">IOBase</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">io.open_code()</span></code> function is a drop-in replacement for
|
||
<code class="docutils literal notranslate"><span class="pre">open(abspath(str(pathlike)),</span> <span class="pre">'rb')</span></code>. Its default behaviour is to
|
||
open a file for raw, binary access. To change the behaviour a new
|
||
handler should be set. Handler functions only accept <code class="docutils literal notranslate"><span class="pre">str</span></code> arguments.
|
||
The C API <code class="docutils literal notranslate"><span class="pre">PyFile_OpenCode</span></code> function assumes UTF-8 encoding. Paths
|
||
must be absolute, and it is the responsibility of the caller to ensure
|
||
the full path is correctly resolved.</p>
|
||
<p>A custom handler may be set by calling <code class="docutils literal notranslate"><span class="pre">PyFile_SetOpenCodeHook()</span></code> from
|
||
C at any time, including before <code class="docutils literal notranslate"><span class="pre">Py_Initialize()</span></code>. However, if a hook
|
||
has already been set then the call will fail. When <code class="docutils literal notranslate"><span class="pre">open_code()</span></code> 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 <code class="docutils literal notranslate"><span class="pre">BytesIO</span></code> instance if the open handler has already
|
||
read the entire file into memory.</p>
|
||
<p>Note that these hooks can import and call the <code class="docutils literal notranslate"><span class="pre">_io.open()</span></code> function on
|
||
CPython without triggering themselves. They can also use <code class="docutils literal notranslate"><span class="pre">_io.BytesIO</span></code>
|
||
to return a compatible result using an in-memory buffer.</p>
|
||
<p>If the hook determines that the file should not be loaded, it should
|
||
raise an exception of its choice, as well as performing any other
|
||
logging.</p>
|
||
<p>All import and execution functionality involving code from a file will
|
||
be changed to use <code class="docutils literal notranslate"><span class="pre">open_code()</span></code> unconditionally. It is important to
|
||
note that calls to <code class="docutils literal notranslate"><span class="pre">compile()</span></code>, <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code> do not go
|
||
through this function - an audit hook that includes the code from these
|
||
calls 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 <code class="docutils literal notranslate"><span class="pre">open_code()</span></code> and the
|
||
log hook for <code class="docutils literal notranslate"><span class="pre">compile</span></code>, and so care should be taken to avoid
|
||
repeating verification steps.</p>
|
||
<p>File accesses that are not intentionally planning to execute code are
|
||
not expected to use this function. This includes loading pickles, XML
|
||
or YAML files, where code execution is generally considered malicious
|
||
rather than intentional. These operations should provide their own
|
||
auditing events, preferably distinguishing between normal functionality
|
||
(for example, <code class="docutils literal notranslate"><span class="pre">Unpickler.load</span></code>) and code execution
|
||
(<code class="docutils literal notranslate"><span class="pre">Unpickler.find_class</span></code>).</p>
|
||
<p>A few examples: if the file type normally requires an execute bit (on
|
||
POSIX) or would warn when marked as having been downloaded from the
|
||
internet (on Windows), it should probably use <code class="docutils literal notranslate"><span class="pre">open_code()</span></code> rather
|
||
than plain <code class="docutils literal notranslate"><span class="pre">open()</span></code>. Opening ZIP files using the <code class="docutils literal notranslate"><span class="pre">ZipFile</span></code> class
|
||
should use <code class="docutils literal notranslate"><span class="pre">open()</span></code>, while opening them via <code class="docutils literal notranslate"><span class="pre">zipimport</span></code> should use
|
||
<code class="docutils literal notranslate"><span class="pre">open_code()</span></code> to signal the correct intent. Code that uses the wrong
|
||
function for a particular context may bypass the hook, which in CPython
|
||
and the standard library should be considered a bug. Using a combination
|
||
of <code class="docutils literal notranslate"><span class="pre">open_code</span></code> hooks and auditing hooks is necessary to trace all
|
||
executed sources in the presence of arbitrary code.</p>
|
||
<p>There is no Python API provided for changing the open hook. To modify
|
||
import behavior from Python code, use the existing functionality
|
||
provided by <code class="docutils literal notranslate"><span class="pre">importlib</span></code>.</p>
|
||
</section>
|
||
<section id="api-availability">
|
||
<h3><a class="toc-backref" href="#api-availability" role="doc-backlink">API Availability</a></h3>
|
||
<p>While all the functions added here are considered public and stable API,
|
||
the behavior of the functions is implementation specific. Most
|
||
descriptions here refer to the CPython implementation, and while other
|
||
implementations should provide the functions, there is no requirement
|
||
that they behave the same.</p>
|
||
<p>For example, <code class="docutils literal notranslate"><span class="pre">sys.addaudithook()</span></code> and <code class="docutils literal notranslate"><span class="pre">sys.audit()</span></code> should exist but
|
||
may do nothing. This allows code to make calls to <code class="docutils literal notranslate"><span class="pre">sys.audit()</span></code>
|
||
without having to test for existence, but it should not assume that its
|
||
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.)</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">io.open_code(path)</span></code> should at a minimum always return
|
||
<code class="docutils literal notranslate"><span class="pre">_io.open(path,</span> <span class="pre">'rb')</span></code>. 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.</p>
|
||
</section>
|
||
</section>
|
||
<section id="suggested-audit-hook-locations">
|
||
<h2><a class="toc-backref" href="#suggested-audit-hook-locations" role="doc-backlink">Suggested Audit Hook Locations</a></h2>
|
||
<p>The locations and parameters in calls to <code class="docutils literal notranslate"><span class="pre">sys.audit()</span></code> or
|
||
<code class="docutils literal notranslate"><span class="pre">PySys_Audit()</span></code> are to be determined by individual Python
|
||
implementations. This is to allow maximum freedom for implementations
|
||
to expose the operations that are most relevant to their platform,
|
||
and to avoid or ignore potentially expensive or noisy events.</p>
|
||
<p>Table 1 acts as both suggestions of operations that should trigger
|
||
audit events on all implementations, and examples of event schemas.</p>
|
||
<p>Table 2 provides further examples that are not required, but are
|
||
likely to be available in CPython.</p>
|
||
<p>Refer to the documentation associated with your version of Python to
|
||
see which operations provide audit events.</p>
|
||
<table class="docutils align-default" id="id5">
|
||
<caption><span class="caption-text">Table 1: Suggested Audit Hooks</span></caption>
|
||
<colgroup>
|
||
<col style="width: 15.4%" />
|
||
<col style="width: 15.4%" />
|
||
<col style="width: 23.1%" />
|
||
<col style="width: 46.2%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="row-odd"><th class="head">API Function</th>
|
||
<th class="head">Event Name</th>
|
||
<th class="head">Arguments</th>
|
||
<th class="head">Rationale</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">PySys_AddAuditHook</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">sys.addaudithook</span></code></td>
|
||
<td></td>
|
||
<td>Detect when new
|
||
audit hooks are being added.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">PyFile_SetOpenCodeHook</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">cpython.PyFile_SetOpenCodeHook</span></code></td>
|
||
<td></td>
|
||
<td>Detects any attempt to set the <code class="docutils literal notranslate"><span class="pre">open_code</span></code> hook.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">compile</span></code>, <code class="docutils literal notranslate"><span class="pre">exec</span></code>, <code class="docutils literal notranslate"><span class="pre">eval</span></code>, <code class="docutils literal notranslate"><span class="pre">PyAst_CompileString</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">PyAST_obj2mod</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">compile</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(code,</span> <span class="pre">filename_or_none)</span></code></td>
|
||
<td>Detect dynamic code compilation, where <code class="docutils literal notranslate"><span class="pre">code</span></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 <code class="docutils literal notranslate"><span class="pre">open_code</span></code>.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">exec</span></code>, <code class="docutils literal notranslate"><span class="pre">eval</span></code>, <code class="docutils literal notranslate"><span class="pre">run_mod</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">exec</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(code_object,)</span></code></td>
|
||
<td>Detect dynamic execution of code objects. This only occurs for
|
||
explicit calls, and is not raised for normal function invocation.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">import</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">import</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(module,</span> <span class="pre">filename,</span> <span class="pre">sys.path,</span>
|
||
<span class="pre">sys.meta_path,</span> <span class="pre">sys.path_hooks)</span></code></td>
|
||
<td>Detect when modules are
|
||
imported. This is raised before the module name is resolved to a
|
||
file. All arguments other than the module name may be <code class="docutils literal notranslate"><span class="pre">None</span></code> if
|
||
they are not used or available.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">open</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">io.open</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(path,</span> <span class="pre">mode,</span> <span class="pre">flags)</span></code></td>
|
||
<td>Detect when a
|
||
file is about to be opened. <em>path</em> and <em>mode</em> are the usual parameters
|
||
to <code class="docutils literal notranslate"><span class="pre">open</span></code> if available, while <em>flags</em> is provided instead of <em>mode</em>
|
||
in some cases.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">PyEval_SetProfile</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">sys.setprofile</span></code></td>
|
||
<td></td>
|
||
<td>Detect when code is
|
||
injecting trace functions. Because of the implementation, exceptions
|
||
raised from the hook will abort the operation, but will not be
|
||
raised in Python code. Note that <code class="docutils literal notranslate"><span class="pre">threading.setprofile</span></code> eventually
|
||
calls this function, so the event will be audited for each thread.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">PyEval_SetTrace</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">sys.settrace</span></code></td>
|
||
<td></td>
|
||
<td>Detect when code is
|
||
injecting trace functions. Because of the implementation, exceptions
|
||
raised from the hook will abort the operation, but will not be
|
||
raised in Python code. Note that <code class="docutils literal notranslate"><span class="pre">threading.settrace</span></code> eventually
|
||
calls this function, so the event will be audited for each thread.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">_PyObject_GenericSetAttr</span></code>, <code class="docutils literal notranslate"><span class="pre">check_set_special_type_attr</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">object_set_class</span></code>, <code class="docutils literal notranslate"><span class="pre">func_set_code</span></code>, <code class="docutils literal notranslate"><span class="pre">func_set_[kw]defaults</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">object.__setattr__</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(object,</span> <span class="pre">attr,</span> <span class="pre">value)</span></code></td>
|
||
<td>Detect monkey
|
||
patching of types and objects. This event
|
||
is raised for the <code class="docutils literal notranslate"><span class="pre">__class__</span></code> attribute and any attribute on
|
||
<code class="docutils literal notranslate"><span class="pre">type</span></code> objects.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">_PyObject_GenericSetAttr</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">object.__delattr__</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(object,</span>
|
||
<span class="pre">attr)</span></code></td>
|
||
<td>Detect deletion of object attributes. This event is raised
|
||
for any attribute on <code class="docutils literal notranslate"><span class="pre">type</span></code> objects.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">Unpickler.find_class</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">pickle.find_class</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(module_name,</span>
|
||
<span class="pre">global_name)</span></code></td>
|
||
<td>Detect imports and global name lookup when
|
||
unpickling.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<table class="docutils align-default" id="id6">
|
||
<caption><span class="caption-text">Table 2: Potential CPython Audit Hooks</span></caption>
|
||
<colgroup>
|
||
<col style="width: 15.4%" />
|
||
<col style="width: 15.4%" />
|
||
<col style="width: 23.1%" />
|
||
<col style="width: 46.2%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="row-odd"><th class="head">API Function</th>
|
||
<th class="head">Event Name</th>
|
||
<th class="head">Arguments</th>
|
||
<th class="head">Rationale</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">_PySys_ClearAuditHooks</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">sys._clearaudithooks</span></code></td>
|
||
<td></td>
|
||
<td>Notifies
|
||
hooks they are being cleaned up, mainly in case the event is
|
||
triggered unexpectedly. This event cannot be aborted.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">code_new</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">code.__new__</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(bytecode,</span> <span class="pre">filename,</span> <span class="pre">name)</span></code></td>
|
||
<td>Detect dynamic creation of code objects. This only occurs for
|
||
direct instantiation, and is not raised for normal compilation.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">func_new_impl</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">function.__new__</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(code,)</span></code></td>
|
||
<td>Detect
|
||
dynamic creation of function objects. This only occurs for direct
|
||
instantiation, and is not raised for normal compilation.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">_ctypes.dlopen</span></code>, <code class="docutils literal notranslate"><span class="pre">_ctypes.LoadLibrary</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">ctypes.dlopen</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(module_or_path,)</span></code></td>
|
||
<td>Detect when native modules are used.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">_ctypes._FuncPtr</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">ctypes.dlsym</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(lib_object,</span> <span class="pre">name)</span></code></td>
|
||
<td>Collect information about specific symbols retrieved from native
|
||
modules.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">_ctypes._CData</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">ctypes.cdata</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(ptr_as_int,)</span></code></td>
|
||
<td>Detect
|
||
when code is accessing arbitrary memory using <code class="docutils literal notranslate"><span class="pre">ctypes</span></code>.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">new_mmap_object</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">mmap.__new__</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(fileno,</span> <span class="pre">map_size,</span> <span class="pre">access,</span>
|
||
<span class="pre">offset)</span></code></td>
|
||
<td>Detects creation of mmap objects. On POSIX, access may
|
||
have been calculated from the <code class="docutils literal notranslate"><span class="pre">prot</span></code> and <code class="docutils literal notranslate"><span class="pre">flags</span></code> arguments.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">sys._getframe</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">sys._getframe</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(frame_object,)</span></code></td>
|
||
<td>Detect
|
||
when code is accessing frames directly.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">sys._current_frames</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">sys._current_frames</span></code></td>
|
||
<td></td>
|
||
<td>Detect when
|
||
code is accessing frames directly.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">socket.bind</span></code>, <code class="docutils literal notranslate"><span class="pre">socket.connect</span></code>, <code class="docutils literal notranslate"><span class="pre">socket.connect_ex</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">socket.getaddrinfo</span></code>, <code class="docutils literal notranslate"><span class="pre">socket.getnameinfo</span></code>, <code class="docutils literal notranslate"><span class="pre">socket.sendmsg</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">socket.sendto</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">socket.address</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(socket,</span> <span class="pre">address,)</span></code></td>
|
||
<td>Detect access to network resources. The address is unmodified from
|
||
the original call.</td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">member_get</span></code>, <code class="docutils literal notranslate"><span class="pre">func_get_code</span></code>, <code class="docutils literal notranslate"><span class="pre">func_get_[kw]defaults</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">object.__getattr__</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(object,</span> <span class="pre">attr)</span></code></td>
|
||
<td>Detect access to
|
||
restricted attributes. This event is raised for any built-in
|
||
members that are marked as restricted, and members that may allow
|
||
bypassing imports.</td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">urllib.urlopen</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">urllib.Request</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">(url,</span> <span class="pre">data,</span> <span class="pre">headers,</span>
|
||
<span class="pre">method)</span></code></td>
|
||
<td>Detects URL requests.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
<section id="performance-impact">
|
||
<h2><a class="toc-backref" href="#performance-impact" role="doc-backlink">Performance Impact</a></h2>
|
||
<p>The important performance impact is the case where events are being
|
||
raised but there are no hooks attached. This is the unavoidable case -
|
||
once a developer has added audit hooks they have explicitly chosen to
|
||
trade performance for functionality. Performance impact with hooks added
|
||
are not of interest here, since this is opt-in functionality.</p>
|
||
<p>Analysis using the Python Performance Benchmark Suite <a class="footnote-reference brackets" href="#id3" id="id1">[1]</a> shows no
|
||
significant impact, with the vast majority of benchmarks showing
|
||
between 1.05x faster to 1.05x slower.</p>
|
||
<p>In our opinion, the performance impact of the set of auditing points
|
||
described in this PEP is negligible.</p>
|
||
</section>
|
||
<section id="rejected-ideas">
|
||
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
|
||
<section id="separate-module-for-audit-hooks">
|
||
<h3><a class="toc-backref" href="#separate-module-for-audit-hooks" role="doc-backlink">Separate module for audit hooks</a></h3>
|
||
<p>The proposal is to add a new module for audit hooks, hypothetically
|
||
<code class="docutils literal notranslate"><span class="pre">audit</span></code>. This would separate the API and implementation from the
|
||
<code class="docutils literal notranslate"><span class="pre">sys</span></code> module, and allow naming the C functions <code class="docutils literal notranslate"><span class="pre">PyAudit_AddHook</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">PyAudit_Audit</span></code> rather than the current variations.</p>
|
||
<p>Any such module would need to be a built-in module that is guaranteed to
|
||
always be present. The nature of these hooks is that they must be
|
||
callable without condition, as any conditional imports or calls provide
|
||
opportunities to intercept and suppress or modify events.</p>
|
||
<p>Given it is one of the most core modules, the <code class="docutils literal notranslate"><span class="pre">sys</span></code> module is somewhat
|
||
protected against module shadowing attacks. Replacing <code class="docutils literal notranslate"><span class="pre">sys</span></code> with a
|
||
sufficiently functional module that the application can still run is a
|
||
much more complicated task than replacing a module with only one
|
||
function of interest. An attacker that has the ability to shadow the
|
||
<code class="docutils literal notranslate"><span class="pre">sys</span></code> module is already capable of running arbitrary code from files,
|
||
whereas an <code class="docutils literal notranslate"><span class="pre">audit</span></code> module could be replaced with a single line in a
|
||
<code class="docutils literal notranslate"><span class="pre">.pth</span></code> file anywhere on the search path:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">sys</span><span class="p">;</span> <span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="s1">'audit'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="s1">'audit'</span><span class="p">,</span> <span class="p">(</span><span class="nb">object</span><span class="p">,),</span>
|
||
<span class="p">{</span><span class="s1">'audit'</span><span class="p">:</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">a</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="s1">'addhook'</span><span class="p">:</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">a</span><span class="p">:</span> <span class="kc">None</span><span class="p">})</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Multiple layers of protection already exist for monkey patching attacks
|
||
against either <code class="docutils literal notranslate"><span class="pre">sys</span></code> or <code class="docutils literal notranslate"><span class="pre">audit</span></code>, but assignments or insertions to
|
||
<code class="docutils literal notranslate"><span class="pre">sys.modules</span></code> are not audited.</p>
|
||
<p>This idea is rejected because it makes it trivial to suppress all calls
|
||
to <code class="docutils literal notranslate"><span class="pre">audit</span></code>.</p>
|
||
</section>
|
||
<section id="flag-in-sys-flags-to-indicate-audited-mode">
|
||
<h3><a class="toc-backref" href="#flag-in-sys-flags-to-indicate-audited-mode" role="doc-backlink">Flag in sys.flags to indicate “audited” mode</a></h3>
|
||
<p>The proposal is to add a value in <code class="docutils literal notranslate"><span class="pre">sys.flags</span></code> to indicate when Python
|
||
is running in a “secure” or “audited” mode. This would allow
|
||
applications to detect when some features are enabled or when hooks
|
||
have been added and modify their behaviour appropriately.</p>
|
||
<p>Currently, we are not aware of any legitimate reasons for a program to
|
||
behave differently in the presence of audit hooks.</p>
|
||
<p>Both application-level APIs <code class="docutils literal notranslate"><span class="pre">sys.audit</span></code> and <code class="docutils literal notranslate"><span class="pre">io.open_code</span></code> are
|
||
always present and functional, regardless of whether the regular
|
||
<code class="docutils literal notranslate"><span class="pre">python</span></code> 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
|
||
enough that callers do not need to avoid them, and the program is
|
||
responsible for ensuring that any added hooks are fast enough to not
|
||
affect application performance.</p>
|
||
<p>The argument that this is “security by obscurity” is valid, but
|
||
irrelevant. Security by obscurity is only an issue when there are no
|
||
other protective mechanisms; obscurity as the first step in avoiding
|
||
attack is strongly recommended (see <a class="reference external" href="https://danielmiessler.com/study/security-by-obscurity/">this article</a> for
|
||
discussion).</p>
|
||
<p>This idea is rejected because there are no appropriate reasons for an
|
||
application to change its behaviour based on whether these APIs are in
|
||
use.</p>
|
||
</section>
|
||
</section>
|
||
<section id="why-not-a-sandbox">
|
||
<h2><a class="toc-backref" href="#why-not-a-sandbox" role="doc-backlink">Why Not A Sandbox</a></h2>
|
||
<p>Sandboxing CPython has been attempted many times in the past, and each
|
||
past attempt has failed. Fundamentally, the problem is that certain
|
||
functionality has to be restricted when executing the sandboxed code,
|
||
but otherwise needs to be available for normal operation of Python. For
|
||
example, completely removing the ability to compile strings into
|
||
bytecode also breaks the ability to import modules from source code, and
|
||
if it is not completely removed then there are too many ways to get
|
||
access to that functionality indirectly. There is not yet any feasible
|
||
way to generically determine whether a given operation is “safe” or not.
|
||
Further information and references available at <a class="footnote-reference brackets" href="#id4" id="id2">[2]</a>.</p>
|
||
<p>This proposal does not attempt to restrict functionality, but simply
|
||
exposes the fact that the functionality is being used. Particularly for
|
||
intrusion scenarios, detection is significantly more important than
|
||
early prevention (as early prevention will generally drive attackers to
|
||
use an alternate, less-detectable, approach). The availability of audit
|
||
hooks alone does not change the attack surface of Python in any way, but
|
||
they enable defenders to integrate Python into their environment in ways
|
||
that are currently not possible.</p>
|
||
<p>Since audit hooks have the ability to safely prevent an operation
|
||
occurring, this feature does enable the ability to provide some level of
|
||
sandboxing. In most cases, however, the intention is to enable logging
|
||
rather than creating a sandbox.</p>
|
||
</section>
|
||
<section id="relationship-to-pep-551">
|
||
<h2><a class="toc-backref" href="#relationship-to-pep-551" role="doc-backlink">Relationship to PEP 551</a></h2>
|
||
<p>This API was originally presented as part of
|
||
<a class="pep reference internal" href="../pep-0551/" title="PEP 551 – Security transparency in the Python runtime">PEP 551</a> Security
|
||
Transparency in the Python Runtime.</p>
|
||
<p>For simpler review purposes, and due to the broader applicability of
|
||
these APIs beyond security, the API design is now presented separately.</p>
|
||
<p><a class="pep reference internal" href="../pep-0551/" title="PEP 551 – Security transparency in the Python runtime">PEP 551</a> is an informational PEP discussing how to integrate Python into
|
||
a secure or audited environment.</p>
|
||
</section>
|
||
<section id="references">
|
||
<h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2>
|
||
<aside class="footnote-list brackets">
|
||
<aside class="footnote brackets" id="id3" role="doc-footnote">
|
||
<dt class="label" id="id3">[<a href="#id1">1</a>]</dt>
|
||
<dd>Python Performance Benchmark Suite <a class="reference external" href="https://github.com/python/performance">https://github.com/python/performance</a></aside>
|
||
<aside class="footnote brackets" id="id4" role="doc-footnote">
|
||
<dt class="label" id="id4">[<a href="#id2">2</a>]</dt>
|
||
<dd>Python Security model - Sandbox <a class="reference external" href="https://python-security.readthedocs.io/security.html#sandbox">https://python-security.readthedocs.io/security.html#sandbox</a></aside>
|
||
</aside>
|
||
</section>
|
||
<section id="copyright">
|
||
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
|
||
<p>Copyright (c) 2019 by Microsoft Corporation. This material may be
|
||
distributed only subject to the terms and conditions set forth in the
|
||
Open Publication License, v1.0 or later (the latest version is presently
|
||
available at <a class="reference external" href="http://www.opencontent.org/openpub/">http://www.opencontent.org/openpub/</a>).</p>
|
||
</section>
|
||
</section>
|
||
<hr class="docutils" />
|
||
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0578.rst">https://github.com/python/peps/blob/main/peps/pep-0578.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0578.rst">2023-09-09 17:39:29 GMT</a></p>
|
||
|
||
</article>
|
||
<nav id="pep-sidebar">
|
||
<h2>Contents</h2>
|
||
<ul>
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#background">Background</a></li>
|
||
<li><a class="reference internal" href="#overview-of-changes">Overview of Changes</a><ul>
|
||
<li><a class="reference internal" href="#audit-hook">Audit Hook</a></li>
|
||
<li><a class="reference internal" href="#verified-open-hook">Verified Open Hook</a></li>
|
||
<li><a class="reference internal" href="#api-availability">API Availability</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#suggested-audit-hook-locations">Suggested Audit Hook Locations</a></li>
|
||
<li><a class="reference internal" href="#performance-impact">Performance Impact</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#separate-module-for-audit-hooks">Separate module for audit hooks</a></li>
|
||
<li><a class="reference internal" href="#flag-in-sys-flags-to-indicate-audited-mode">Flag in sys.flags to indicate “audited” mode</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#why-not-a-sandbox">Why Not A Sandbox</a></li>
|
||
<li><a class="reference internal" href="#relationship-to-pep-551">Relationship to PEP 551</a></li>
|
||
<li><a class="reference internal" href="#references">References</a></li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
|
||
<br>
|
||
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0578.rst">Page Source (GitHub)</a>
|
||
</nav>
|
||
</section>
|
||
<script src="../_static/colour_scheme.js"></script>
|
||
<script src="../_static/wrap_tables.js"></script>
|
||
<script src="../_static/sticky_banner.js"></script>
|
||
</body>
|
||
</html> |