diff --git a/CMakeLists.txt b/CMakeLists.txt index 3887876..1e6a0e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,6 +215,7 @@ if(Python3_Interpreter_FOUND) test_extract test_human_tree test_partition + test_partition_overlap test_esp32_part test_esp32_nvs test_legacy_fs diff --git a/tests/run.sh b/tests/run.sh index 4285cfe..996c14e 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -18,6 +18,8 @@ python3 tests/test_extract.py python3 tests/test_human_tree.py # GPT/MBR partition-table identify + region map (synthetic + real-tool round-trip). python3 tests/test_partition.py +# Partition-boundary overrun: a stale finding must not hide a real partition (PR #33). +python3 tests/test_partition_overlap.py # ESP32 partition-table region map + NVS identify/extract (synthetic, self-contained). python3 tests/test_esp32_part.py python3 tests/test_esp32_nvs.py diff --git a/tests/test_partition_overlap.py b/tests/test_partition_overlap.py new file mode 100644 index 0000000..69befe9 --- /dev/null +++ b/tests/test_partition_overlap.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Regression for the partition-boundary overrun fix (PR #33). + +A finding's self-declared size can overrun a region the partition table already +accounts for (a stale superblock in unpartitioned space still recording its +pre-repartition size). The scanner used to skip the whole extent, stepping over +the real partition's superblock so it was never validated and went missing. The +fix clamps the skip (and the resolve owner-skip) at partition boundaries. + +Fully self-contained (hand-built GPT + synthetic ext superblocks, no mkfs tools). +It is a differential: with the GPT present the hidden partition is recovered; +with the GPT zeroed the same stale finding hides it — which proves the partition +table is what rescues it. Run: python3 tests/test_partition_overlap.py +""" +import json +import os +import struct +import subprocess +import sys +import tempfile +import zlib + +HERE = os.path.dirname(os.path.abspath(__file__)) +MORIA = os.path.join(HERE, "..", "build", "moria") + +SECT = 512 + + +def ext_sb(buf, off, size_bytes, block_size=4096): + """Plant a minimal ext superblock at image offset `off` (moria: ext consistent, + finding size = s_blocks_count_lo * (1024 << s_log_block_size)).""" + log = block_size.bit_length() - 1 - 10 # 4096 -> 2 + blocks = size_bytes // block_size + sb = off + 1024 + struct.pack_into(" extent 0x8000..0x48000, overrunning the + real userdata partition start at 0x40000. + A REAL ext (userdata) @0x40000 sized 256 KB -> extent 0x40000..0x80000, which + extends beyond the stale extent (so it is not merely interior noise).""" + disk_sectors = 4096 # 2 MB + buf = bytearray(disk_sectors * SECT) + + if with_gpt: + # protective MBR + struct.pack_into("