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