code wiki / _hdl_build / nx_tok_attr_probe.nx

nx_tok_attr_probe.nx source

↩ module page · 98 lines · 4329 B

1// nx_tok_attr_probe.nx -- ISOLATE the attribute-text leak (task #9, 2026-07-27). 2// Hypothesis: nx_html_next_token's attribute-skip loop does not track quote state, so a 3// '>' INSIDE a quoted attribute value ends the tag early and the tag's remainder leaks as 4// a TEXT token that then paints as visible content. Drives the tokenizer directly and 5// concatenates every TEXT token; a row FAILS if the text carries attribute bytes. 6// Control rows (C*) MUST pass, so a failing S* row is a real defect, not a broken probe. 7import "nx_syscalls.nx" 8import "nx_html_tokenizer.nx" 9const K_MAGIC_4096: i64 = 4096 10 11func tp_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 12 13// collect all TEXT-token bytes from `html` into `out`; return length. 14func tp_text(html: *u8, hlen: i64, out: *u8, cap: i64) -> i64 { 15 let c: *HtmlCursor = sys_mmap(24) as *HtmlCursor 16 nx_html_cursor_init(c, html, hlen) 17 let tok: *HtmlToken = sys_mmap(56) as *HtmlToken 18 var o: i64 = 0 19 var go: i64 = 1 20 while go == 1 { 21 nx_html_next_token(c, tok) 22 if tok.kind == NX_HTML_TOK_EOF { go = 0 } 23 else { 24 if tok.kind == NX_HTML_TOK_TEXT { 25 var i: i64 = 0 26 while i < tok.body_len { if o < cap - 1 { out[o] = html[tok.body_off + i]; o = o + 1 } i = i + 1 } 27 } 28 } 29 } 30 out[o] = 0 as u8 31 return o 32} 33 34// does out[0..n) contain the NUL-terminated needle? 35func tp_has(out: *u8, n: i64, needle: *u8) -> i64 { 36 var nl: i64 = 0 37 while needle[nl] != (0 as u8) { nl = nl + 1 } 38 if nl == 0 { return 1 } 39 var i: i64 = 0 40 while (i + nl) <= n { 41 var j: i64 = 0 42 var ok: i64 = 1 43 while j < nl { if (out[i+j] & 0xff) != (needle[j] & 0xff) { ok = 0; j = nl } else { j = j + 1 } } 44 if ok == 1 { return 1 } 45 i = i + 1 46 } 47 return 0 48} 49 50// A row: render `html`, require WANT present and BAD absent from the text stream. 51func tp_row(label: *u8, html: *u8, want: *u8, bad: *u8, pp: *i64, tp: *i64) -> i64 { 52 tp[0] = tp[0] + 1 53 var hlen: i64 = 0 54 while html[hlen] != (0 as u8) { hlen = hlen + 1 } 55 let out: *u8 = sys_mmap(K_MAGIC_4096) 56 let n: i64 = tp_text(html, hlen, out, K_MAGIC_4096) 57 let hw: i64 = tp_has(out, n, want) 58 let hb: i64 = tp_has(out, n, bad) 59 tp_p(label) 60 if hw == 1 { if hb == 0 { pp[0] = pp[0] + 1; tp_p(" ok text='\x00" as *u8) } else { tp_p(" FAIL(leak) text='\x00" as *u8) } } 61 else { tp_p(" FAIL(want-missing) text='\x00" as *u8) } 62 sys_write(1, out, n) 63 tp_p("'\x0a\x00" as *u8) 64 return 0 65} 66 67func main() -> i64 { 68 let pp: *i64 = sys_mmap(8) as *i64; pp[0] = 0 69 let tp: *i64 = sys_mmap(8) as *i64; tp[0] = 0 70 71 // C1 plain 72 tp_row("C1 plain tag \x00" as *u8, 73 "<div>HELLO</div>\x00" as *u8, "HELLO\x00" as *u8, "div\x00" as *u8, pp, tp) 74 // C2 simple attribute 75 tp_row("C2 simple attr \x00" as *u8, 76 "<div class=\"x\">HELLO</div>\x00" as *u8, "HELLO\x00" as *u8, "class\x00" as *u8, pp, tp) 77 // S1 '>' inside a double-quoted attr value. The leaked tail is ` b"` -- check for `b"`, 78 // the attribute-value bytes that must NOT reach the text stream. 79 tp_row("S1 gt in dq attr value \x00" as *u8, 80 "<div data-x=\"a > b\">HELLO</div>\x00" as *u8, "HELLO\x00" as *u8, "b\"\x00" as *u8, pp, tp) 81 // S2 '>' inside a single-quoted attr value. Leaked tail ` b'` -- check for `b'`. 82 tp_row("S2 gt in sq attr value \x00" as *u8, 83 "<div data-x='a > b'>HELLO</div>\x00" as *u8, "HELLO\x00" as *u8, "b'\x00" as *u8, pp, tp) 84 // S3 the stackoverflow shape: embedded markup in a data attr 85 tp_row("S3 markup in data attr \x00" as *u8, 86 "<div data-html=\"<button>x</button>\" data-s-popover-placement=\"bottom-start\">HELLO</div>\x00" as *u8, 87 "HELLO\x00" as *u8, "popover\x00" as *u8, pp, tp) 88 89 tp_p("TOK-ATTR-PROBE pass=\x00" as *u8) 90 // print pp/tp 91 let nb: *u8 = sys_mmap(8); nb[0] = (48 + pp[0]) as u8; sys_write(1, nb, 1) 92 tp_p("/\x00" as *u8) 93 let tb: *u8 = sys_mmap(8); tb[0] = (48 + tp[0]) as u8; sys_write(1, tb, 1) 94 tp_p("\x0a\x00" as *u8) 95 if pp[0] == tp[0] { tp_p("verdict=NO-LEAK\x0a\x00" as *u8); return 0 } 96 tp_p("verdict=LEAK-CONFIRMED\x0a\x00" as *u8) 97 return 0 98}