diff --git a/linux/build/scripts/build-inner.sh b/linux/build/scripts/build-inner.sh index ef82d15..1462193 100755 --- a/linux/build/scripts/build-inner.sh +++ b/linux/build/scripts/build-inner.sh @@ -292,6 +292,62 @@ post_process_for_reproducibility() { sudo --non-interactive mv -f "${new_iso}" "${iso_file}" sudo --non-interactive rm -f "${new_sqfs}" + # Post-write byte patch: xorriso's -update writes wall-clock + # mtime/atime into Rock Ridge TF directory entries at -commit time + # regardless of -alter_date / -alter_date_r / -find set_to_mtime / + # touching the source file's mtime (verified across iters 34-36). + # The leak is contained to the TF entry of the one node we + # replaced (/live/filesystem.squashfs) — every other timestamp in + # the ISO was already SDE before -update, and -update doesn't touch + # them. + # + # We have the SDE-derived 7-byte ISO9660 short-form date in the + # CREATION slot of that TF entry already (because we touched the + # source file's btime). We just copy those 7 bytes over the + # MODIFICATION (next 7 bytes) and ACCESS (7 after that) slots. + # + # The TF entry header is `54 46 1a 01 0e` ("TF" length=26 ver=1 + # flags=0x0e: CREATION+MODIFICATION+ACCESS, short form). The + # filename "filesystem.squashfs" follows immediately after the NM + # Rock Ridge entry that follows the TF dates. So a unique 5-byte + # marker plus the filename anchors the location. + echo "post-process: byte-patching Rock Ridge TF dates on /live/filesystem.squashfs" + sudo --non-interactive python3 - "${iso_file}" <<'PYEOF' +import sys, struct +path = sys.argv[1] +TF_HDR = b'TF\x1a\x01\x0e' # "TF" length=26 ver=1 flags=0x0e +NAME_TAG = b'filesystem.squashfs' # appears in the NM Rock Ridge entry +with open(path, 'r+b') as f: + data = f.read() +# Find TF entries near a "filesystem.squashfs" NM tag. We don't trust +# any single anchor; require both within a 96-byte window so we don't +# accidentally rewrite some other TF entry that happens to be near +# the NM tag of a different file. +patched = 0 +i = 0 +while True: + j = data.find(TF_HDR, i) + if j < 0: + break + # Look for the filename within ~96 bytes after this TF header. + window = data[j:j + 96] + if NAME_TAG in window: + # Date 1 starts at j + 5; copy its 7 bytes onto Date 2 (j+12) + # and Date 3 (j+19). + creation = data[j + 5:j + 12] + if creation[3:7] != b'\x00\x00\x00\x00': # sanity: not all zeroes + # only patch if MOD or ACCESS actually differs from CREATION + if data[j + 12:j + 19] != creation or data[j + 19:j + 26] != creation: + f.seek(j + 12) + f.write(creation) + f.seek(j + 19) + f.write(creation) + patched += 1 + print(f' patched TF at offset {j} (creation={creation.hex()})') + i = j + 1 +print(f'post-process: byte-patcher fixed {patched} TF entr{"y" if patched == 1 else "ies"}') +PYEOF + echo "post-process: ISO rebuilt with reproducible squashfs" sha256sum "${iso_file}" }