code wiki / (root) / nx_http_request_adversarial_test.nx

nx_http_request_adversarial_test.nx source

↩ module page · 129 lines · 4963 B

1// nx_http_request_adversarial_test.nx -- Phase A3 of S-class hardening. 2// 3// Adversarial fuzzing of nx_http_parse_request, the parser the HTTP 4// server runs on every incoming connection. An untrusted public 5// client's bytes must NOT crash the server. 6// 7// Per [[feedback-simulation-discipline-for-every-substrate]] + 8// the S-class honest audit: HTTP server is the second-most-exposed 9// attack surface after TLS handshake. This fuzz proves graceful 10// rejection on 8 malformed input classes. 11// 12// expect_exit: 0 13// license_tier: ORIGINAL 14 15import "nx_syscalls.nx" 16import "nx_http_io.nx" 17 18func parse_call(buf: *u8, n: i64) -> i64 { 19 let mk_box: *i64 = sys_mmap(8) as *i64 20 let po_box: *i64 = sys_mmap(8) as *i64 21 let pl_box: *i64 = sys_mmap(8) as *i64 22 let cl_box: *i64 = sys_mmap(8) as *i64 23 let bo_box: *i64 = sys_mmap(8) as *i64 24 return nx_http_parse_request(buf, n, mk_box, po_box, pl_box, cl_box, bo_box) 25} 26 27func main() -> i64 { 28 // ===== (a) Empty buffer -> rejected ===== 29 let empty: *u8 = sys_mmap(4) 30 let r_empty: i64 = parse_call(empty, 0) 31 if r_empty >= 0 { return 10 } // must NOT be OK 32 33 // ===== (b) Tiny buffer (< 14 bytes) -> rejected ===== 34 let tiny: *u8 = sys_mmap(16) 35 tiny[0] = 0x47 // 'G' 36 let r_tiny: i64 = parse_call(tiny, 10) 37 if r_tiny >= 0 { return 20 } 38 39 // ===== (c) 64 bytes of NUL -> rejected (no method/path/spaces) ===== 40 let nul_buf: *u8 = sys_mmap(64) 41 let r_nul: i64 = parse_call(nul_buf, 64) 42 if r_nul >= 0 { return 30 } 43 44 // ===== (d) 64 bytes of 0xFF -> rejected ===== 45 let ff_buf: *u8 = sys_mmap(64) 46 var fi: i64 = 0 47 while fi < 64 { 48 ff_buf[fi] = 0xff 49 fi = fi + 1 50 } 51 let r_ff: i64 = parse_call(ff_buf, 64) 52 if r_ff >= 0 { return 40 } 53 54 // ===== (e) Method without path or CRLF -> rejected ===== 55 // "GET " (only spaces after method) 56 let bad_method: *u8 = sys_mmap(64) 57 bad_method[0] = 0x47 // G 58 bad_method[1] = 0x45 // E 59 bad_method[2] = 0x54 // T 60 bad_method[3] = 0x20 // ' ' 61 var bi: i64 = 4 62 while bi < 60 { 63 bad_method[bi] = 0x20 64 bi = bi + 1 65 } 66 let r_bad_method: i64 = parse_call(bad_method, 60) 67 if r_bad_method >= 0 { return 50 } 68 69 // ===== (f) Request line missing CRLF terminator -> rejected ===== 70 // "GET / HTTP/1.1" no \r\n 71 let no_crlf: *u8 = sys_mmap(64) 72 no_crlf[0] = 0x47; no_crlf[1] = 0x45; no_crlf[2] = 0x54 // GET 73 no_crlf[3] = 0x20 // ' ' 74 no_crlf[4] = 0x2f // / 75 no_crlf[5] = 0x20 // ' ' 76 no_crlf[6] = 0x48; no_crlf[7] = 0x54; no_crlf[8] = 0x54 // HTT 77 no_crlf[9] = 0x50 // P 78 no_crlf[10] = 0x2f; no_crlf[11] = 0x31; no_crlf[12] = 0x2e // /1. 79 no_crlf[13] = 0x31 // 1 80 let r_no_crlf: i64 = parse_call(no_crlf, 14) 81 if r_no_crlf >= 0 { return 60 } 82 83 // ===== (g) Valid GET request -> OK + method_kind 1 ===== 84 // "GET / HTTP/1.1\r\nHost: x\r\n\r\n" 85 let good: *u8 = sys_mmap(64) 86 let s: *u8 = "GET / HTTP/1.1\r\nHost: x\r\n\r\n" 87 var n: i64 = 0 88 while s[n] != 0 { n = n + 1 } 89 var k: i64 = 0 90 while k < n { good[k] = s[k]; k = k + 1 } 91 let mk: *i64 = sys_mmap(8) as *i64 92 let po: *i64 = sys_mmap(8) as *i64 93 let pl: *i64 = sys_mmap(8) as *i64 94 let cl: *i64 = sys_mmap(8) as *i64 95 let bo: *i64 = sys_mmap(8) as *i64 96 let r_good: i64 = nx_http_parse_request(good, n, mk, po, pl, cl, bo) 97 if r_good != 0 { return 70 } 98 if *mk != 1 { return 71 } // GET = 1 99 if *pl != 1 { return 72 } // path "/" length 1 100 101 // ===== (h) GET with binary garbage in path - rejected or graceful ===== 102 // Even with junk bytes, must not crash 103 let garbage_path: *u8 = sys_mmap(80) 104 garbage_path[0] = 0x47; garbage_path[1] = 0x45; garbage_path[2] = 0x54; garbage_path[3] = 0x20 105 var gi: i64 = 4 106 while gi < 50 { 107 garbage_path[gi] = 0x80 // high-bit byte (not ASCII) 108 gi = gi + 1 109 } 110 garbage_path[50] = 0x20 111 garbage_path[51] = 0x48 // 'H' 112 garbage_path[52] = 0x54 // 'T' 113 garbage_path[53] = 0x54 // 'T' 114 garbage_path[54] = 0x50 // 'P' 115 garbage_path[55] = 0x0D // \r 116 garbage_path[56] = 0x0A // \n 117 garbage_path[57] = 0x0D 118 garbage_path[58] = 0x0A 119 let r_garbage: i64 = parse_call(garbage_path, 59) 120 // Parser may accept with non-OK method (method != GET because 121 // method bytes are 'GET ' = 0x47 0x45 0x54 0x20, which IS GET); 122 // path will be the garbage. Whether it returns OK or non-OK is 123 // a contract decision; the hardening claim is NO CRASH. We 124 // accept either outcome here. 125 if r_garbage > 100 { return 80 } // sanity: not garbage return 126 if r_garbage < -100 { return 81 } 127 128 return 0 129}