code wiki / (root) / nx_tls13_transcript_snapkat_test.nx

nx_tls13_transcript_snapkat_test.nx source

↩ module page · 97 lines · 3849 B

1// nx_tls13_transcript_snapkat_test.nx -- DURABLE anti-regression KAT for 2// the TLS-FIN root fix (X-B1-FIN-001 / BL-015 / X-TLS-SNAPKAT-001). 3// 4// The bug it locks out: nx_tls13_transcript_snapshot once flat-byte-copied 5// the Sha256 ctx, duplicating bufptr (a POINTER) so the clone SHARED the 6// live transcript's 64-byte partial block. sha256_final(clone) then wrote 7// padding into that shared block -- and in the two-block path (idx > 56) 8// also zeroed bytes [0..55] -- CLOBBERING the live transcript. Every 9// subsequent update() then compressed corrupted bytes and the Finished MAC 10// diverged. It only triggered when a snapshot landed while idx was in the 11// lethal {57..63} range (a function of cumulative handshake byte-count), so 12// loopback + small cert chains passed while big real-server chains failed. 13// 14// The catch (the gate that WOULD have caught it): 15// For each fill-level S in {55, 56, 57, 63} -- straddling the lethal 16// idx>56 boundary -- snapshot the transcript at idx=S, THEN continue the 17// SAME state with >=64 more bytes and finalize. Assert: 18// (1) snapshot(S) == one-shot sha256(first S bytes) 19// (2) continue-then-final == one-shot sha256(full S+extra bytes) 20// Assertion (2) is precisely what the shallow-copy clobber violated: 21// if the snapshot at idx in {57..63} corrupts the live partial block, 22// the continuation hashes garbage and (2) fails (exit 32 / 42). 23// 24// expect_exit: 0 25// license_tier: ORIGINAL 26// genealogy_id: international-research-sources/ietf/rfc_8446 27// lineage_id: nishi_tls13_transcript_snapkat_q10 28 29import "nx_syscalls.nx" 30import "nx_sha256.nx" 31import "nx_tls13_transcript.nx" 32 33// Deterministic, content-bearing fill so a clobber actually changes bytes. 34func snapkat_fill(buf: *u8, n: i64) -> i64 { 35 var i: i64 = 0 36 while i < n { 37 buf[i] = (i * 7 + 3) & 0xff 38 i = i + 1 39 } 40 return 0 41} 42 43// One boundary case. Returns 0 on pass, or (base+1)=snapshot mismatch, 44// (base+2)=continue-after-snapshot mismatch (the regression signature). 45func snapkat_one(s: i64, extra: i64, base: i64) -> i64 { 46 let total: i64 = s + extra 47 let buf: *u8 = sys_mmap(total + 64) 48 snapkat_fill(buf, total) 49 50 // Feed exactly S bytes -> partial-block idx = S, then snapshot. 51 let st: *u8 = nx_tls13_transcript_new() 52 nx_tls13_transcript_update(st, buf, s) 53 let snap: *u8 = sys_mmap(64) 54 nx_tls13_transcript_snapshot(st, snap) 55 56 // (1) snapshot must equal one-shot sha256 of the first S bytes. 57 let ref_snap: *u8 = sys_mmap(64) 58 sha256_digest(buf, s, ref_snap) 59 var i: i64 = 0 60 while i < 32 { 61 if (snap[i] & 0xff) != (ref_snap[i] & 0xff) { return base + 1 } 62 i = i + 1 63 } 64 65 // Continue the SAME state across the >=64-byte boundary, then finalize. 66 let tail: *u8 = ((buf as i64) + s) as *u8 67 nx_tls13_transcript_update(st, tail, extra) 68 let cont: *u8 = sys_mmap(64) 69 nx_tls13_transcript_snapshot(st, cont) 70 71 // (2) continued-final must equal one-shot sha256 of the FULL stream. 72 // This is the assertion the shallow-copy clobber broke. 73 let ref_cont: *u8 = sys_mmap(64) 74 sha256_digest(buf, total, ref_cont) 75 i = 0 76 while i < 32 { 77 if (cont[i] & 0xff) != (ref_cont[i] & 0xff) { return base + 2 } 78 i = i + 1 79 } 80 return 0 81} 82 83func main() -> i64 { 84 let extra: i64 = 80 // >= 64: forces multi-block continuation 85 86 var r: i64 = 0 87 r = snapkat_one(55, extra, 10) // control: one-block path, must pass 88 if r != 0 { return r } 89 r = snapkat_one(56, extra, 20) // control: boundary, must pass 90 if r != 0 { return r } 91 r = snapkat_one(57, extra, 30) // LETHAL: two-block path, the catch 92 if r != 0 { return r } 93 r = snapkat_one(63, extra, 40) // LETHAL: top of the {57..63} range 94 if r != 0 { return r } 95 96 return 0 97}