⬢ DragonFlyBSD Kernel Audit
DF-2551 / gen_udf.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""gen_udf.py -- minimal mountable UDF 1.02 image for DF-2551.

Satisfies exactly what DragonFly's udf_mountfs/udf_vget/udf_readdir parse:
  sector 256 : anchor_vdp -> main_vds_ex
  MVDS       : logvol_desc (type-1 part map) + part_desc + term
  FSD        : fileset_desc -> rootdir_icb
  root FE    : file_entry file_type=4 (VDIR), short_ad -> FID stream
  FIDs       : parent ('..'-style) + 'regfile.txt' -> FE lb
  reg FE     : file_entry file_type=5 (VREG)  <-- v_object NEVER set
  data       : file content
udf_checktag() only verifies tag.id and the byte checksum (crc NOT checked).
"""
import struct, sys

BS = 2048
PART_START = 32            # partition begins at this image sector
LB_LVD     = 32            # inside partition? NO -- MVDS lives in image space
PART_LEN   = 4096

# image-space sectors (outside partition): MVDS at 16..19
SEC_LVD  = 16
SEC_PD   = 17
SEC_TERM = 18
# partition-relative LBNs
LB_FSD   = 0
LB_ROOTFE = 1
LB_DIRDATA = 2
LB_REGFE = 3
LB_FILEDATA = 4

img = bytearray(BS * 512)

def tag(tid, loc, ver=3, crc=0, crclen=0):
    t = bytearray(16)
    struct.pack_into('<HHBBHHHI', t, 0, tid, ver, 0, 0, 0, crc, crclen, loc)
    return t

def fix_cksum(buf, off=0):
    s = (sum(buf[off:off+15]) - buf[off+4]) & 0xFF
    buf[off+4] = s
    return buf

def put(sec, data):
    img[sec*BS:sec*BS+len(data)] = data

# ---------------- anchor (sector 256) ----------------
a = bytearray(BS)
a[0:16] = tag(2, 256)
struct.pack_into('<II', a, 16, 3*BS, SEC_LVD)   # main_vds_ex len,loc
struct.pack_into('<II', a, 24, 3*BS, SEC_LVD)   # reserve_vds_ex
put(256, fix_cksum(a))

# ---------------- logical volume descriptor ----------------
lvd = bytearray(BS)
lvd[0:16] = tag(6, SEC_LVD)
struct.pack_into('<I', lvd, 16, 1)              # seq_num
lvd[20] = 0                                     # charspec type=0
lvd[84:84+8] = b'LOGVOL0'                       # logvol_id
struct.pack_into('<I', lvd, 212, BS)            # lb_size
lvd[216] = 0                                    # domain_id flags
lvd[217:217+8] = b'*OSTA UDF '                  # domain regid
# _lvd_use.fsd_loc  (long_ad) at 248
struct.pack_into('<I', lvd, 248, BS)            # len
struct.pack_into('<IH', lvd, 252, LB_FSD, 0)    # lb_num, part_num
struct.pack_into('<H', lvd, 258, 0)             # ad_flags
struct.pack_into('<I', lvd, 260, 0)             # ad_id
struct.pack_into('<I', lvd, 264, 64)            # mt_l (1 x 64-byte map)
struct.pack_into('<I', lvd, 268, 1)             # n_pm = 1
# imp_id regid at 272 (32 bytes) zero
# imp_use[128] at 304
struct.pack_into('<II', lvd, 432, 0, 0)         # integrity_seq extent
# maps[0] type-1 part map at 440
lvd[440] = 1; lvd[441] = 6
struct.pack_into('<HH', lvd, 442, 1, 0)         # vol_seq_num, part_num
put(SEC_LVD, fix_cksum(lvd))

# ---------------- partition descriptor ----------------
pd = bytearray(BS)
pd[0:16] = tag(5, SEC_PD)
struct.pack_into('<I', pd, 16, 1)               # seq_num
struct.pack_into('<HH', pd, 20, 3, 0)           # flags=allocated, part_num=0
pd[24] = 0                                      # contents regid flags
pd[25:25+6] = b'+NSR02'                         # contents id
# contents_use[128] at 56..184 ; access_type @184
struct.pack_into('<I', pd, 184, 0)
struct.pack_into('<I', pd, 188, PART_START)     # start_loc
struct.pack_into('<I', pd, 192, PART_LEN)       # part_len
put(SEC_PD, fix_cksum(pd))

# ---------------- terminating descriptor ----------------
tm = bytearray(BS)
tm[0:16] = tag(8, SEC_TERM)
put(SEC_TERM, fix_cksum(tm))

# ---------------- file set descriptor (partition LB 0) ----------------
fsd = bytearray(BS)
fsd[0:16] = tag(256, PART_START + LB_FSD)
# timestamps/ids can stay zero
# rootdir_icb long_ad at offset: tag16+time12+ichg2+max2+charset4+charset4+
# fileset_num4+desc_num4+charspec64+logvol_id128+charspec64+fileset_id32+
# copyright32+abstract32 = 16+12+2+2+4+4+4+4+64+128+64+32+32+32 = 400
off = 400
struct.pack_into('<I', fsd, off, BS)            # len
struct.pack_into('<IH', fsd, off+4, LB_ROOTFE, 0)
struct.pack_into('<H', fsd, off+10, 0)
struct.pack_into('<I', fsd, off+12, 0)
put(PART_START + LB_FSD, fix_cksum(fsd))

# ---------------- root directory file entry ----------------
def file_entry(lb, ftype, inf_len, ads):
    fe = bytearray(BS)
    fe[0:16] = tag(261, PART_START + lb)
    # icb_tag at 16: prev@16 strat_type@20 strat_param@22 max@24 res@26
    # file_type@27 parent_icb@28 (lb_num@28,part_num@32) flags@34
    struct.pack_into('<I', fe, 16, 0)           # prev_num_dirs
    struct.pack_into('<H', fe, 20, 4)           # strat_type
    struct.pack_into('<H', fe, 24, 1)           # max_num_entries
    fe[27] = ftype                              # file_type
    struct.pack_into('<IH', fe, 28, 0, 0)       # parent_icb
    struct.pack_into('<H', fe, 34, 0)           # flags: short_ads
    struct.pack_into('<III', fe, 36, 0, 0, 0x1CE7)  # uid gid perm(rwx all)
    struct.pack_into('<H', fe, 48, 1)           # link_cnt
    struct.pack_into('<BB', fe, 50, 0, 0)       # rec_format, rec_disp_attr
    struct.pack_into('<I', fe, 52, 0)           # rec_len
    struct.pack_into('<Q', fe, 56, inf_len)     # inf_len
    struct.pack_into('<Q', fe, 64, (inf_len + BS - 1)//BS)  # logblks_rec
    # atime @72, mtime @84, attrtime @96 -- zero
    struct.pack_into('<I', fe, 108, 1)          # ckpoint
    # ex_attr_icb @112 (16) ; imp_id @128 (32) ; unique_id @160 (8)
    struct.pack_into('<Q', fe, 160, lb)
    struct.pack_into('<II', fe, 168, 0, 8*len(ads))  # l_ea=0, l_ad
    o = 176
    for (ln, pos) in ads:
        struct.pack_into('<II', fe, o, ln, pos)
        o += 8
    return fix_cksum(fe)

# directory FID stream
def fid(parent, name, lb):
    nm = name if isinstance(name, bytes) else name.encode()
    f = bytearray(38 + len(nm))
    f[0:16] = bytearray(16)                     # tag placeholder
    struct.pack_into('<H', f, 16, 0)            # file_num
    f[18] = 0x01 | (0x08 if parent else 0x00)   # file_char VIS|PAR
    f[19] = len(nm)                             # l_fi
    struct.pack_into('<I', f, 20, BS)           # icb len
    struct.pack_into('<IH', f, 24, lb, 0)
    struct.pack_into('<H', f, 30, 0)            # ad_flags
    struct.pack_into('<I', f, 32, 0)            # ad_id
    struct.pack_into('<H', f, 36, 0)            # l_iu
    f[38:38+len(nm)] = nm
    # real tag id + cksum
    struct.pack_into('<HH', f, 0, 257, 3)
    fix_cksum(f)
    # 4-byte align
    while len(f) % 4:
        f += b'\0'
    return f

# parent FID: l_fi must be 0 (kernel skips l_fi==0 && FILE_CHAR_PAR entries)
# regular FID: CS0 identifier = compID byte (8) + 8-bit chars
dirdata = fid(True, b'', LB_ROOTFE) + fid(False, b'\x08regfile.txt', LB_REGFE)
put(PART_START + LB_DIRDATA, dirdata)
put(PART_START + LB_ROOTFE, file_entry(LB_ROOTFE, 4, len(dirdata),
    [(len(dirdata), LB_DIRDATA)]))

# ---------------- regular file (THE TARGET: VREG, no v_object) -----------
content = b'hello-udf-file-content-12345\n'
put(PART_START + LB_FILEDATA, content)
put(PART_START + LB_REGFE, file_entry(LB_REGFE, 5, len(content),
    [(len(content), LB_FILEDATA)]))

out = sys.argv[1] if len(sys.argv) > 1 else 'mini_udf.img'
open(out, 'wb').write(img)
print("wrote %s (%d bytes)" % (out, len(img)))