code wiki / (root) / nx_dns_adversarial_test.nx

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}