# PYTHON-LANG-SEC-023: Dangerous subinterpreters run_string() Usage

> **Severity:** HIGH | **CWE:** CWE-95 | **OWASP:** A03:2021

- **Language:** Python
- **Category:** Python Core
- **URL:** https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-023
- **Detection:** `pathfinder scan --ruleset python/PYTHON-LANG-SEC-023 --project .`

## Description

Python's _xxsubinterpreters (also accessible as subinterpreters in some builds) module
provides low-level access to CPython's sub-interpreter API, allowing multiple Python
interpreters to run in the same process. The run_string() function executes a Python code
string inside a specified sub-interpreter.

Despite running in a separate interpreter instance, sub-interpreters do not provide a
security sandbox. Code running in a sub-interpreter can still access the filesystem, make
network connections, import arbitrary modules, and interact with the host system. If the
code string passed to run_string() is derived from untrusted input, an attacker achieves
full code execution in the context of the Python process.

This module is experimental and its API has changed between Python versions. Its use in
production code is rare and should be carefully audited.


## Vulnerable Code

```python
import subprocess
import asyncio

# SEC-023: subinterpreters
import _xxsubinterpreters
_xxsubinterpreters.run_string("print('hello')")
```

## Secure Code

```python
# There is no safe way to execute untrusted code strings in sub-interpreters.
# For sandboxed code execution, use process isolation instead:

import subprocess

# SECURE: Execute untrusted code in a separate process with restricted permissions
def execute_sandboxed(code: str, timeout: int = 5) -> str:
    """
    Execute code in a completely isolated subprocess.
    The subprocess should run as a restricted user with no network access.
    """
    result = subprocess.run(
        ["/usr/bin/python3", "-c", code],
        capture_output=True,
        text=True,
        timeout=timeout,
        # In production: use seccomp, namespaces, or containers for true isolation
    )
    return result.stdout

# SECURE: For dynamic computation, use a safe expression evaluator
from ast import literal_eval

def evaluate_safe_expression(expr: str):
    return literal_eval(expr)

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, flows, QueryType
from codepathfinder.presets import PropagationPresets


@python_rule(
    id="PYTHON-LANG-SEC-023",
    name="Dangerous subinterpreters run_string",
    severity="HIGH",
    category="lang",
    cwe="CWE-95",
    tags="python,subinterpreters,code-execution,CWE-95",
    message="subinterpreters.run_string() detected. Avoid executing untrusted code strings.",
    owasp="A03:2021",
)
def detect_subinterpreters():
    """Detects _xxsubinterpreters.run_string usage."""
    return calls("_xxsubinterpreters.run_string", "subinterpreters.run_string")
```

## How to Fix

- Never pass user-controlled strings to subinterpreters.run_string(); there is no security isolation between sub-interpreters and the host filesystem.
- For sandboxed execution of untrusted code, use process isolation with resource limits, seccomp filters, or container-level sandboxing rather than sub-interpreters.
- Use ast.literal_eval() for safe expression evaluation of Python literals without code execution.
- If dynamic computation is needed, use a purpose-built safe expression library that restricts available operations.
- Audit all uses of the subinterpreters module since it is experimental and rarely needed in production application code.

## Security Implications

- **Code Execution in Sub-interpreter:** Code executing in a sub-interpreter has full access to the filesystem, network, and
installed Python modules. It can spawn subprocesses, open sockets, read files, and
import os or subprocess. The sub-interpreter boundary provides isolation from the
parent interpreter's namespace but not from host system resources.

- **Inter-interpreter State Leakage:** Sub-interpreters share the same process memory space. Through ctypes, cffi, or direct
memory access, code in a sub-interpreter may be able to read or corrupt the parent
interpreter's memory, bypassing the namespace separation.

- **Experimental API Instability:** The subinterpreters API is experimental and has changed across Python versions. Code
relying on it may break silently or exhibit unexpected security behaviors on version
upgrades, making security properties difficult to reason about.

- **Resource Exhaustion:** Injected code running in a sub-interpreter can create threads, allocate large amounts
of memory, or perform CPU-intensive operations that consume process resources and
cause denial of service for the parent interpreter.


## FAQ

**Q: Do sub-interpreters provide any security isolation?**

Sub-interpreters provide namespace isolation — each interpreter has its own module
namespace and global state. However, they do NOT provide filesystem isolation, network
isolation, or memory isolation. Code in a sub-interpreter can still perform any OS
operation, access any file, and potentially access the parent interpreter's memory
via ctypes or direct pointer manipulation.


**Q: What is the intended use of sub-interpreters?**

Sub-interpreters are intended for multi-tenancy in frameworks where multiple Python
programs need to run in the same process with separate global state (e.g., Python
embedded in a web server). They are designed for trusted code isolation (preventing
global state pollution), not for security isolation of untrusted code.


**Q: How does run_string() compare to eval() for security?**

run_string() is equally dangerous to eval() for security purposes. Both execute
arbitrary Python code. run_string() adds the overhead of a sub-interpreter but
provides no additional security boundary against malicious code accessing host
resources.


**Q: What should I use for true sandboxed code execution?**

True sandboxing requires process isolation combined with OS-level resource restrictions.
Options include: subprocess with seccomp-bpf syscall filtering, Docker containers with
read-only filesystems and no network, WebAssembly runtimes (Pyodide, WASM), and
dedicated sandboxing services. No pure-Python or CPython-internal mechanism provides
true sandboxing.


**Q: Is this module stable enough to rely on in production?**

No. The _xxsubinterpreters module is explicitly experimental and marked as such in
CPython source. Its API has changed multiple times between minor Python versions.
Using it in production code creates both a security risk and a maintenance burden
from API instability.


**Q: What Python version introduced subinterpreters?**

Low-level sub-interpreter support has existed in CPython's C API since Python 3.x,
but the _xxsubinterpreters module exposing it to Python code was added as an
experimental feature in Python 3.9. PEP 554 proposes making it a proper public API,
but as of Python 3.12 it remains experimental.


## References

- [CWE-95: Eval Injection](https://cwe.mitre.org/data/definitions/95.html)
- [PEP 554: Multiple Interpreters in the Stdlib](https://peps.python.org/pep-0554/)
- [Python docs: _xxsubinterpreters](https://docs.python.org/3/c-api/init.html#sub-interpreter-support)
- [OWASP Top 10 A03:2021 Injection](https://owasp.org/Top10/A03_2021-Injection/)
- [OWASP Code Injection](https://owasp.org/www-community/attacks/Code_Injection)

---

Source: https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-023
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
