Files
nltk--nltk/nltk/test/unit/test_boxer_security.py
wehub-resource-sync 3454a55636
cffconvert / validate (push) Has been cancelled
ci-workflow / pre-commit (push) Has been cancelled
ci-workflow / Minimal NLTK Download Test (macos-latest) (push) Has been cancelled
ci-workflow / Minimal NLTK Download Test (ubuntu-latest) (push) Has been cancelled
ci-workflow / Minimal NLTK Download Test (windows-latest) (push) Has been cancelled
ci-workflow / Python 3.10 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.11 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.12 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.13 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.14 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.14t on macos-latest (push) Has been cancelled
ci-workflow / Python 3.10 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.11 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.12 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.13 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.14 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.14t on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.10 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.11 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.12 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.13 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.14 on windows-latest (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:46:15 +08:00

92 lines
3.4 KiB
Python

"""Regression tests for the untrusted-search-path fix in the Boxer wrapper.
``Boxer`` resolves its external ``candc`` / ``boxer`` executables with
``nltk.internals.find_binary`` and then runs them with ``subprocess.Popen``.
``find_binary`` also matches a binary relative to the current working directory:
* a relative ``bin_dir`` (e.g. ".") yields ``./candc`` (a path with a separator),
and
* even the default ``bin_dir=None`` matches a ``candc/candc`` directory in the
CWD (``find_file_iter`` joins the name with itself).
Either way the resolved path contains a separator, so ``Popen`` runs it directly
from the CWD without consulting ``$PATH`` -- an attacker who can plant a
``candc``/``boxer`` file there gets code execution (CWE-426/CWE-427).
The wrapper now requires the resolved binary to be an *absolute* path, so a
CWD-relative result is refused. An absolute ``bin_dir`` (or ``CANDC`` env var, or
a ``$PATH`` lookup) keeps working.
"""
import os
import pytest
from nltk.sem.boxer import Boxer
_BIN_NAMES = ("candc", "boxer")
def _plant_binaries(directory):
"""Create an (executable) file for candc and boxer in *directory*."""
os.makedirs(directory, exist_ok=True)
for name in _BIN_NAMES:
target = os.path.join(directory, name)
with open(target, "wb"):
pass
os.chmod(target, 0o755)
return directory
def test_relative_bin_dir_is_rejected(tmp_path, monkeypatch):
"""A relative bin_dir resolving the binary in the CWD must be refused."""
_plant_binaries(str(tmp_path)) # ./candc, ./boxer attacker-planted
monkeypatch.chdir(tmp_path)
monkeypatch.delenv("CANDC", raising=False)
with pytest.raises(LookupError):
Boxer(bin_dir=".")
def test_cwd_nested_dir_with_default_bin_dir_is_rejected(tmp_path, monkeypatch):
"""The default bin_dir=None must not pick up a ./<name>/<name> in the CWD."""
# find_binary("candc", path_to_bin=None) joins the name with itself, so a
# CWD directory "<name>" containing an executable "<name>" would be run.
# Plant both so an unfixed wrapper would resolve *both* binaries and succeed.
for name in _BIN_NAMES:
nested = os.path.join(str(tmp_path), name, name)
os.makedirs(os.path.dirname(nested), exist_ok=True)
with open(nested, "wb"):
pass
os.chmod(nested, 0o755)
monkeypatch.chdir(tmp_path)
monkeypatch.delenv("CANDC", raising=False)
with pytest.raises(LookupError):
Boxer()
def test_cwd_does_not_override_configured_candc(tmp_path, monkeypatch):
"""A CWD binary must not shadow an absolute CANDC environment variable."""
_plant_binaries(str(tmp_path / "cwd")) # attacker-planted in the CWD
trusted = _plant_binaries(str(tmp_path / "trusted")) # configured location
monkeypatch.chdir(tmp_path / "cwd")
monkeypatch.setenv("CANDC", trusted)
boxer = Boxer(bin_dir=".")
assert os.path.realpath(boxer._candc_bin) == os.path.realpath(
os.path.join(trusted, "candc")
), "CWD binary overrode the trusted CANDC location"
def test_absolute_bin_dir_is_accepted(tmp_path, monkeypatch):
"""An explicit absolute bin_dir is still used as-is."""
abs_dir = _plant_binaries(str(tmp_path / "candc-1.00"))
monkeypatch.delenv("CANDC", raising=False)
boxer = Boxer(bin_dir=abs_dir)
assert os.path.isabs(boxer._candc_bin)
assert os.path.realpath(boxer._candc_bin) == os.path.realpath(
os.path.join(abs_dir, "candc")
)