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}