Files
DeDRM_tools/tests/test_topazextract.py
JMR-devandClaude Opus 4.8 d6418996dd Harden tests flagged in code review
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>
2026-06-22 20:35:21 -05:00

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"