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}