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}