Strengthen weak-assertion / false-confidence tests:
- test_alfcrypto: replace the tautological ctx_init determinism check
with an independent golden vector, and add a golden Topaz decrypt
vector that does not rely on the test's own inverse helper (so a
systematic cipher bug is caught, not just round-trip symmetry). Pin
the PC1 bad-key assertion to match="Bad key length", and load
alfcrypto via the dedrm package so the test exercises the same module
object that topazextract/mobidedrm import.
- test_mobidedrm: pin the PC1 bad-key assertion to the guard message.
- test_topazextract: make the path-traversal regression rely on the
depth-independent positive oracle (the sanitised file must land inside
outdir, which fails against the pre-fix code) plus an exact-contents
check, instead of brittle parent-path negatives.
- test_erdr2pml: narrow the import skip to only the missing-cgi case so
a genuinely broken module fails loudly instead of silently skipping.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
"""Tests for topazextract, including the path-traversal regression test."""
|
|
|
|
from io import BytesIO
|
|
|
|
import dedrm_test_utils as U
|
|
|
|
tz = U.load("topazextract", package="dedrm")
|
|
|
|
|
|
def test_book_read_encoded_number():
|
|
assert tz.bookReadEncodedNumber(BytesIO(bytes([0x05]))) == 5
|
|
assert tz.bookReadEncodedNumber(BytesIO(bytes([0x81, 0x00]))) == 128
|
|
|
|
|
|
def test_book_read_string():
|
|
assert tz.bookReadString(BytesIO(bytes([0x03]) + b"abc")) == b"abc"
|
|
|
|
|
|
def _bare_book(outdir):
|
|
"""A TopazBook instance with just the attributes extractFiles() needs."""
|
|
book = tz.TopazBook.__new__(tz.TopazBook)
|
|
book.outdir = str(outdir)
|
|
return book
|
|
|
|
|
|
def test_extractfiles_strips_path_traversal(tmp_path):
|
|
"""A malicious Topaz header tag must not write outside the output dir.
|
|
|
|
Regression test for the arbitrary-file-write fix: the record name is read
|
|
verbatim from the (untrusted) book file, so ``../../evil`` must be reduced
|
|
to a basename inside ``outdir`` rather than escaping it.
|
|
"""
|
|
outdir = tmp_path / "out"
|
|
outdir.mkdir()
|
|
book = _bare_book(outdir)
|
|
|
|
evil_tag = b"../../evil"
|
|
book.bookHeaderRecords = {evil_tag: [[0, 5, 0]]}
|
|
# Attacker-controlled payload bytes, no key required.
|
|
book.getBookPayloadRecord = lambda name, index: b"PWNED"
|
|
|
|
book.extractFiles()
|
|
|
|
# Primary oracle (independent of traversal depth): with the fix, basename()
|
|
# strips every "../" so the record always lands INSIDE outdir. The pre-fix
|
|
# code did os.path.join(outdir, "../../evil0000.dat"), writing OUTSIDE outdir,
|
|
# so this assertion fails without the fix no matter how deep tmp_path nests.
|
|
assert (outdir / "evil0000.dat").read_bytes() == b"PWNED"
|
|
# outdir contains exactly that one sanitised file, nothing unexpected.
|
|
assert sorted(p.name for p in outdir.iterdir()) == ["evil0000.dat"]
|
|
# Defense in depth: nothing escaped to the dirs the "../../" payload targets.
|
|
assert not (tmp_path / "evil0000.dat").exists()
|
|
assert not (tmp_path.parent / "evil0000.dat").exists()
|
|
|
|
|
|
def test_extractfiles_writes_normal_record(tmp_path):
|
|
outdir = tmp_path / "out"
|
|
outdir.mkdir()
|
|
book = _bare_book(outdir)
|
|
book.bookHeaderRecords = {b"spam": [[0, 4, 0]]}
|
|
book.getBookPayloadRecord = lambda name, index: b"eggs"
|
|
|
|
book.extractFiles()
|
|
|
|
assert (outdir / "spam0000.dat").read_bytes() == b"eggs"
|