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}