ADR-013 Phase 2: import guard hardening (round-3 review fixes)
This commit is contained in:
@@ -142,6 +142,52 @@ def test_tampered_submodule_import_raises(guarded):
|
||||
importlib.import_module(f'plugins.{name}.models')
|
||||
|
||||
|
||||
def test_planted_extension_module_refused(guarded):
|
||||
"""Finding (round 3): a planted .so standing in for a would-be submodule must
|
||||
be refused, not handed to the stdlib ExtensionFileLoader unverified."""
|
||||
import importlib.machinery
|
||||
guard, plugins_dir, priv, name = guarded
|
||||
pdir = _write_pkg_plugin(plugins_dir, name)
|
||||
_stamp(pdir, priv, name)
|
||||
|
||||
ext = importlib.machinery.EXTENSION_SUFFIXES[0]
|
||||
(pdir / 'models' / f'evil{ext}').write_bytes(b'\x7fELF-not-really')
|
||||
with pytest.raises(PluginVerificationError):
|
||||
guard.find_spec(f'plugins.{name}.models.evil')
|
||||
|
||||
|
||||
def test_planted_sourceless_pyc_refused(guarded):
|
||||
"""A sourceless .pyc for a name with no .py must be refused."""
|
||||
guard, plugins_dir, priv, name = guarded
|
||||
pdir = _write_pkg_plugin(plugins_dir, name)
|
||||
_stamp(pdir, priv, name)
|
||||
|
||||
(pdir / 'models' / 'evil.pyc').write_bytes(b'\x00garbage')
|
||||
with pytest.raises(PluginVerificationError):
|
||||
guard.find_spec(f'plugins.{name}.models.evil')
|
||||
|
||||
|
||||
def test_absent_module_defers(guarded):
|
||||
"""A genuinely-absent module returns None (normal ModuleNotFoundError)."""
|
||||
guard, plugins_dir, priv, name = guarded
|
||||
pdir = _write_pkg_plugin(plugins_dir, name)
|
||||
_stamp(pdir, priv, name)
|
||||
assert guard.find_spec(f'plugins.{name}.models.notthere') is None
|
||||
|
||||
|
||||
def test_top_level_plugins_body_neutralized(guarded):
|
||||
"""Finding (round 3): the top-level plugins/__init__.py runs first and is in
|
||||
no provenance; the guard must exec an EMPTY body so an attacker-overwritten
|
||||
plugins/__init__.py cannot execute."""
|
||||
guard, plugins_dir, priv, name = guarded
|
||||
(plugins_dir / '__init__.py').write_text('raise RuntimeError("attacker code")')
|
||||
spec = guard.find_spec('plugins')
|
||||
assert spec is not None
|
||||
# the loader carries empty source, not the on-disk body
|
||||
assert spec.loader._source == b''
|
||||
assert str(plugins_dir) in spec.submodule_search_locations
|
||||
|
||||
|
||||
def test_install_uninstall_idempotent(tmp_path, keypair):
|
||||
_, pub_path = keypair
|
||||
plugins_dir = tmp_path / 'plugins'
|
||||
|
||||
Reference in New Issue
Block a user