nx_dns_adversarial_test.nx source
↩ module page · 118 lines · 4071 B
1// nx_dns_adversarial_test.nx -- Phase A4 of S-class hardening.
2//
3// Adversarial fuzzing of nx_dns_decode_name_ascii, the QNAME decoder
4// the authoritative DNS server runs on every incoming query.
5// Untrusted bytes from any public client must not crash the server
6// or read past the buffer.
7//
8// Per [[feedback-simulation-discipline-for-every-substrate]] +
9// the S-class honest audit: DNS server is on the public-internet
10// attack surface for ANY operator who deploys the authoritative
11// server (replacing third-party DNS for nishifamily.com).
12//
13// expect_exit: 0
14// license_tier: ORIGINAL
15
16import "nx_syscalls.nx"
17import "nx_dns_authoritative.nx"
18
19func decode_call(buf: *u8, n: i64, start: i64) -> i64 {
20 let out: *u8 = sys_mmap(256)
21 let after_box: *i64 = sys_mmap(8) as *i64
22 return nx_dns_decode_name_ascii(buf, n, start, out, 256, after_box)
23}
24
25func main() -> i64 {
26 // ===== (a) Null buf -> BAD_ARG =====
27 let exp_bad: i64 = 0 - NXDA_BAD_ARG
28 let r_null_buf: i64 = decode_call(0 as *u8, 4, 0)
29 if r_null_buf != exp_bad { return 10 }
30
31 // ===== (b) start out of range (>= n) -> BAD_ARG =====
32 let small: *u8 = sys_mmap(8)
33 let r_oob: i64 = decode_call(small, 4, 4)
34 if r_oob != exp_bad { return 20 }
35 let r_oob2: i64 = decode_call(small, 4, 100)
36 if r_oob2 != exp_bad { return 21 }
37
38 // ===== (c) Negative start -> BAD_ARG =====
39 let r_neg: i64 = decode_call(small, 4, -1)
40 if r_neg != exp_bad { return 30 }
41
42 // ===== (d) Pointer-compression label (high bits 0xc0) -> PARSE_ERR =====
43 // First byte 0xc0 = pointer compression, refused by authoritative.
44 let ptr_comp: *u8 = sys_mmap(16)
45 ptr_comp[0] = 0xc0
46 ptr_comp[1] = 0x05
47 let exp_parse: i64 = 0 - NXDA_PARSE_ERR
48 let r_ptr: i64 = decode_call(ptr_comp, 16, 0)
49 if r_ptr != exp_parse { return 40 }
50
51 // ===== (e) Label length > 63 -> PARSE_ERR =====
52 // Label of 64 bytes (exactly past the 63 byte spec maximum).
53 let big_label: *u8 = sys_mmap(80)
54 big_label[0] = 64 // length byte = 64
55 // Fill 64 bytes after with arbitrary ASCII
56 var i: i64 = 1
57 while i <= 64 {
58 big_label[i] = 0x61 // 'a'
59 i = i + 1
60 }
61 big_label[65] = 0 // null terminator
62 let r_big: i64 = decode_call(big_label, 80, 0)
63 if r_big != exp_parse { return 50 }
64
65 // ===== (f) Label walks past buffer end -> PARSE_ERR =====
66 // Label length = 10 but only 4 bytes left after length byte.
67 let walk_off: *u8 = sys_mmap(16)
68 walk_off[0] = 10
69 // bytes 1..4 = arbitrary
70 walk_off[1] = 0x61
71 walk_off[2] = 0x62
72 walk_off[3] = 0x63
73 walk_off[4] = 0x64
74 let r_walk: i64 = decode_call(walk_off, 5, 0)
75 if r_walk != exp_parse { return 60 }
76
77 // ===== (g) Just a NUL byte (root domain, empty name) -> OK with 0 bytes =====
78 let root: *u8 = sys_mmap(4)
79 root[0] = 0
80 let r_root: i64 = decode_call(root, 4, 0)
81 if r_root != 0 { return 70 } // 0 bytes written = root domain
82
83 // ===== (h) Valid "nishifamily.com" -> OK + 15 bytes =====
84 // Wire format: 12 'nishifamily' . 3 'com' . 0
85 let nf: *u8 = sys_mmap(32)
86 nf[0] = 11 // "nishifamily" len
87 nf[1] = 0x6e // n
88 nf[2] = 0x69 // i
89 nf[3] = 0x73 // s
90 nf[4] = 0x68 // h
91 nf[5] = 0x69 // i
92 nf[6] = 0x66 // f
93 nf[7] = 0x61 // a
94 nf[8] = 0x6d // m
95 nf[9] = 0x69 // i
96 nf[10] = 0x6c // l
97 nf[11] = 0x79 // y
98 nf[12] = 3 // "com" len
99 nf[13] = 0x63 // c
100 nf[14] = 0x6f // o
101 nf[15] = 0x6d // m
102 nf[16] = 0 // root
103 let r_good: i64 = decode_call(nf, 32, 0)
104 if r_good != 15 { return 80 } // 11 + 1 dot + 3 = 15 chars
105
106 // ===== (i) Unterminated (no NUL byte) -> PARSE_ERR =====
107 let no_term: *u8 = sys_mmap(16)
108 no_term[0] = 4
109 no_term[1] = 0x61
110 no_term[2] = 0x62
111 no_term[3] = 0x63
112 no_term[4] = 0x64
113 // no NUL terminator; buffer end at 5
114 let r_no_term: i64 = decode_call(no_term, 5, 0)
115 if r_no_term != exp_parse { return 90 }
116
117 return 0
118}