code wiki / _hdl_build / nx_pbkdf2_leak_probe.nx
nx_pbkdf2_leak_probe.nx source
↩ module page · 116 lines · 5846 B
1// nx_pbkdf2_leak_probe.nx -- BOUNDED VERIFICATION THAT THE PBKDF2 mmap LEAK IS ACTUALLY FIXED.
2//
3// WHY A PROBE AND NOT THE FULL RFC 6070 VECTOR: vector 4 is c = 16,777,216. Before the fix that leaked
4// ~16 KB/iteration (MEASURED: 11.3 GB resident, 3.18M pagefaults, 5.62s user vs 405.88s system, ~4%
5// complete in 10m39s => ~260 GB and ~4h to finish). It also destabilised the WSL VM twice. Re-running it
6// to prove a fix would take hours and risks the same collateral.
7//
8// So this probe runs a FIXED, SMALLER count and lets ARITHMETIC do the rest. The pre-fix rate is known and
9// measured, so the prediction is exact and falsifiable:
10// c = 1,000,000 -> PRE-FIX ~16 GB resident (1e6 x 16 KB)
11// POST-FIX a few MB, FLAT, independent of c
12// A 1000x separation cannot be explained by noise, so one run decides it.
13// ★★★★★A FIX IS NOT VERIFIED BY THE ABSENCE OF THE OLD SYMPTOM -- IT IS VERIFIED BY A NUMBER THAT MOVED IN
14// THE PREDICTED DIRECTION BY THE PREDICTED MAGNITUDE. "It didn't hang this time" would be worthless here;
15// maxresident is the instrument.
16//
17// ⚠THIS PROBE MAKES NO CORRECTNESS CLAIM. It says nothing about whether the derived key is right -- that is
18// nx_pbkdf2sha1_extvec_gate's job against RFC 6070 (vectors 1-3 PASS). ★A PERFORMANCE PROBE THAT ALSO
19// CLAIMED CORRECTNESS WOULD BE TWO INSTRUMENTS IN A TRENCHCOAT. Run under: time ./elf
20// license_tier: ORIGINAL expect_exit: 0
21import "nx_syscalls.nx"
22import "nx_pbkdf2_sha1.nx"
23
24func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
25
26func nn(v: i64) -> i64 {
27 var m: i64 = v
28 if m < 0 { w("-" as *u8); m = 0 - m }
29 let t: *u8 = sys_mmap(32)
30 var k: i64 = 0
31 if m == 0 { t[0] = 48 as u8; k = 1 }
32 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
33 let b: *u8 = sys_mmap(32)
34 var j: i64 = 0
35 while j < k { b[j] = t[k - 1 - j]; j = j + 1 }
36 sys_write(1, b, k)
37 return 0
38}
39
40// Read VmRSS-ish page count from /proc/self/statm field 2 (resident pages). Self-measured so the verdict
41// does not depend on the shell's rusage reporting being available.
42func rss_pages() -> i64 {
43 let lp: *i64 = sys_mmap(16) as *i64
44 lp[0] = 0
45 let b: *u8 = sys_read_file("/proc/self/statm\x00" as *u8, lp)
46 if lp[0] <= 0 { return 0 - 1 }
47 // /proc/self/statm is "size resident shared ..." -- field 2 is resident pages, so skip to the first
48 // space and parse the integer that follows.
49 var p: i64 = 0
50 var sp: i64 = 0 - 1
51 while p < lp[0] { if b[p] == (32 as u8) { sp = p; p = lp[0] } else { p = p + 1 } }
52 if sp < 0 { return 0 - 1 }
53 var q: i64 = sp + 1
54 var v: i64 = 0
55 var any: i64 = 0
56 var done: i64 = 0
57 while done == 0 {
58 if q >= lp[0] { done = 1 }
59 else {
60 let c: i64 = b[q] as i64
61 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; q = q + 1 } else { done = 1 } }
62 else { done = 1 }
63 }
64 }
65 if any == 0 { return 0 - 1 }
66 return v
67}
68
69func main() -> i64 {
70 let ITER: i64 = 200000
71 w("nx_pbkdf2_leak_probe -- PBKDF2-HMAC-SHA1, c=" as *u8); nn(ITER); w("\n" as *u8)
72 w(" PRE-FIX PREDICTION : ~3.2 GB resident (200k x 16 KB measured pre-fix)\n" as *u8)
73 w(" POST-FIX PREDICTION : a few MB, FLAT and independent of c\n" as *u8)
74
75 let r0: i64 = rss_pages()
76 w(" rss_before = " as *u8); nn(r0 * 4); w(" KB\n" as *u8)
77
78 let pw: *u8 = "password\x00" as *u8
79 let sa: *u8 = "salt\x00" as *u8
80 let out: *u8 = sys_mmap(64)
81 let t0: i64 = sys_now_ms()
82 pbkdf2_sha1(pw, 8, sa, 4, ITER, out, 20)
83 let t1: i64 = sys_now_ms()
84
85 let r1: i64 = rss_pages()
86 w(" rss_after = " as *u8); nn(r1 * 4); w(" KB\n" as *u8)
87 w(" growth = " as *u8); nn((r1 - r0) * 4); w(" KB over " as *u8); nn(ITER); w(" iterations\n" as *u8)
88 w(" elapsed_ms = " as *u8); nn(t1 - t0); w("\n" as *u8)
89
90 // 1e6 iterations at the pre-fix rate would be ~16,000,000 KB. Anything under 64 MB of growth is a
91 // categorical difference, not a marginal improvement. The threshold is deliberately generous: the claim
92 // is "the per-iteration leak is gone", not "growth is exactly zero".
93 // ⚠MY OWN INSTRUMENT FAILED SILENTLY THE FIRST TIME. sys_read_file stats the file for its length, and
94 // procfs reports size 0, so /proc/self/statm came back EMPTY and rss_pages returned a value that PRINTED
95 // AS 0 KB -- indistinguishable from "no memory used", which is the most flattering possible answer.
96 // ★★★★★AN INSTRUMENT THAT RETURNS 0 ON FAILURE INSTEAD OF ERRORING WILL CERTIFY THE THING YOU HOPED FOR.
97 // Now: if the reader did not work, the probe REFUSES to render a verdict and defers to the shell's
98 // rusage (maxresident), which is external, independent of this binary, and already proven on this host.
99 let growth_kb: i64 = (r1 - r0) * 4
100 if r0 <= 0 {
101 w("\n rss reader UNAVAILABLE (procfs is not stat-sizable) -- NO INTERNAL VERDICT.\n" as *u8)
102 w(" Read `maxresident` from `time` instead. PRE-FIX reference at c=16,777,216 was 11,294,208 KB\n" as *u8)
103 w(" after ~4% of the work; at c=200,000 the pre-fix expectation is ~3,200,000 KB.\n" as *u8)
104 w("nx_pbkdf2_leak_probe: verdict=UNMEASURED-INTERNALLY (defer to maxresident)\n" as *u8)
105 return 0
106 }
107 w("\n ref=RFC6070-vector4-regression probe=nx_pbkdf2_leak_probe\n" as *u8)
108 if growth_kb < 65536 {
109 w("nx_pbkdf2_leak_probe: growth_kb=" as *u8); nn(growth_kb)
110 w(" verdict=GREEN -- per-iteration allocation is GONE (pre-fix would be ~16,000,000 KB)\n" as *u8)
111 return 0
112 }
113 w("nx_pbkdf2_leak_probe: growth_kb=" as *u8); nn(growth_kb)
114 w(" verdict=RED -- still allocating per iteration\n" as *u8)
115 return 1
116}