code wiki / (root) / nx_tls13_ch_adversarial_test.nx

nx_tls13_ch_adversarial_test.nx source

↩ module page · 145 lines · 5784 B

1// nx_tls13_ch_adversarial_test.nx -- Phase A2 of S-class hardening. 2// 3// Adversarial fuzzing of tls13_client_hello_parse, the parser exposed 4// to every public-internet client when the substrate serves TLS. 5// Top-priority hardening target for the nishifamily.com goal: a 6// single malformed ClientHello byte stream from the wild internet 7// must NOT crash, corrupt memory, or false-OK the server. 8// 9// What this smoke proves: parser returns structured verdict on every 10// malformed input class. No buffer overruns, no infinite loops, no 11// false-OK on a manifestly-broken ClientHello. 12// 13// Per the S-class honest audit 14// (docs/NISHI_3D_PRINT_SCLASS_HONEST_AUDIT_2026_05_20.md): 15// Phase A1 ✓ STL parser (commit prior) 16// Phase A2 -- this smoke 17// 18// expect_exit: 0 19// license_tier: ORIGINAL 20 21import "nx_syscalls.nx" 22import "nx_tls13.nx" 23import "nx_tls13_hello.nx" 24 25func ch_parse_call(buf: *u8, n: i64) -> i64 { 26 let pv_box: *i64 = sys_mmap(8) as *i64 27 let ro_box: *i64 = sys_mmap(8) as *i64 28 let sl_box: *i64 = sys_mmap(8) as *i64 29 let cso_box: *i64 = sys_mmap(8) as *i64 30 let csl_box: *i64 = sys_mmap(8) as *i64 31 let eo_box: *i64 = sys_mmap(8) as *i64 32 let el_box: *i64 = sys_mmap(8) as *i64 33 return tls13_client_hello_parse(buf, n, 34 pv_box, ro_box, sl_box, cso_box, csl_box, eo_box, el_box) 35} 36 37// Verdict is "valid range" if it falls in the sealed HELLO_VERDICT set. 38func verdict_in_range(v: i64) -> i64 { 39 if v < 0 { return 0 } 40 if v >= NX_TLS13_HELLO_VERDICT_N { return 0 } 41 return 1 42} 43 44func main() -> i64 { 45 // ===== (a) Empty buffer (0 bytes) -> TRUNCATED ===== 46 let empty: *u8 = sys_mmap(4) 47 let r_empty: i64 = ch_parse_call(empty, 0) 48 if r_empty != NX_TLS13_HELLO_VERDICT_TRUNCATED { return 10 } 49 50 // ===== (b) 2-byte buffer (header truncated) -> TRUNCATED ===== 51 let tiny: *u8 = sys_mmap(4) 52 let r_tiny: i64 = ch_parse_call(tiny, 2) 53 if r_tiny != NX_TLS13_HELLO_VERDICT_TRUNCATED { return 20 } 54 55 // ===== (c) msg_type != CLIENT_HELLO -> BAD_HEADER ===== 56 let bad_type: *u8 = sys_mmap(64) 57 bad_type[0] = 0x02 // ServerHello, not ClientHello 58 let r_bad_type: i64 = ch_parse_call(bad_type, 64) 59 if r_bad_type != NX_TLS13_HELLO_VERDICT_BAD_HEADER { return 30 } 60 61 // ===== (d) body_len claims more bytes than buffer -> TRUNCATED ===== 62 let lying_len: *u8 = sys_mmap(32) 63 lying_len[0] = HT_CLIENT_HELLO 64 // body_len = 0xFFFFFF (24-bit), buffer is only 32 bytes 65 lying_len[1] = 0xff 66 lying_len[2] = 0xff 67 lying_len[3] = 0xff 68 let r_lying: i64 = ch_parse_call(lying_len, 32) 69 if r_lying != NX_TLS13_HELLO_VERDICT_TRUNCATED { return 40 } 70 71 // ===== (e) legacy_version truncated (claim 100 bytes, only have 5) ----- 72 let trunc_ver: *u8 = sys_mmap(16) 73 trunc_ver[0] = HT_CLIENT_HELLO 74 // body_len = 4 (so 8 total bytes needed), buffer = 5 75 trunc_ver[3] = 4 76 let r_trunc_ver: i64 = ch_parse_call(trunc_ver, 5) 77 if verdict_in_range(r_trunc_ver) != 1 { return 50 } 78 if r_trunc_ver == NX_TLS13_HELLO_VERDICT_OK { return 51 } 79 80 // ===== (f) session_id_len > 32 -> BAD_HEADER ===== 81 // 82 // Layout: hdr(4) + version(2) + random(32) + sid_len(1). 83 // Put sid_len = 50 (illegal per RFC 8446). 84 let big_sid: *u8 = sys_mmap(128) 85 big_sid[0] = HT_CLIENT_HELLO 86 big_sid[3] = 100 // body_len = 100 (truthful within buf) 87 big_sid[4] = 0x03 // version high byte 88 big_sid[5] = 0x03 // version low byte = 0x0303 89 // random[32] -- leave zeros at offset 6..37 90 big_sid[38] = 50 // sid_len = 50 (invalid, > 32) 91 let r_big_sid: i64 = ch_parse_call(big_sid, 128) 92 if r_big_sid != NX_TLS13_HELLO_VERDICT_BAD_HEADER { return 60 } 93 94 // ===== (g) cipher_suites_len = 0 (invalid: must have >= 2) ----- 95 // Layout: hdr(4) + version(2) + random(32) + sid_len(1) + 96 // cipher_suites_len(2) 97 let zero_cs: *u8 = sys_mmap(128) 98 zero_cs[0] = HT_CLIENT_HELLO 99 zero_cs[3] = 100 100 zero_cs[4] = 0x03 101 zero_cs[5] = 0x03 102 // random 6..37 = 0 103 zero_cs[38] = 0 // sid_len = 0 104 // cipher_suites_len at offset 39..40 = 0 105 zero_cs[39] = 0 106 zero_cs[40] = 0 107 let r_zero_cs: i64 = ch_parse_call(zero_cs, 128) 108 if r_zero_cs != NX_TLS13_HELLO_VERDICT_BAD_HEADER { return 70 } 109 110 // ===== (h) cipher_suites_len walks past buffer -> TRUNCATED ===== 111 let cs_overflow: *u8 = sys_mmap(64) 112 cs_overflow[0] = HT_CLIENT_HELLO 113 cs_overflow[3] = 60 // body_len truthful within buf 114 cs_overflow[4] = 0x03 115 cs_overflow[5] = 0x03 116 cs_overflow[38] = 0 // sid_len = 0 117 // cipher_suites_len = 1000 bytes (way past buffer) 118 cs_overflow[39] = 0x03 119 cs_overflow[40] = 0xE8 120 let r_cs_over: i64 = ch_parse_call(cs_overflow, 64) 121 if r_cs_over != NX_TLS13_HELLO_VERDICT_TRUNCATED { return 80 } 122 123 // ===== (i) Extensions list_len walks past buffer -> TRUNCATED ===== 124 // 125 // Layout: hdr(4) + version(2) + random(32) + sid_len(1) + 126 // cipher_suites_len(2) + cipher_suites(2) + 127 // compress_len(1) + compress(1) + ext_list_len(2) 128 let ext_over: *u8 = sys_mmap(80) 129 ext_over[0] = HT_CLIENT_HELLO 130 ext_over[3] = 76 // body_len 131 ext_over[4] = 0x03; ext_over[5] = 0x03 132 // random zeros 133 ext_over[38] = 0 // sid_len = 0 134 ext_over[39] = 0; ext_over[40] = 2 // cipher_suites_len = 2 135 ext_over[41] = 0x13; ext_over[42] = 0x03 // 1 suite 136 ext_over[43] = 1 // compress_len = 1 137 ext_over[44] = 0 // compress = 0 138 // ext_list_len = 1000 (way past) 139 ext_over[45] = 0x03 140 ext_over[46] = 0xE8 141 let r_ext_over: i64 = ch_parse_call(ext_over, 80) 142 if r_ext_over != NX_TLS13_HELLO_VERDICT_TRUNCATED { return 90 } 143 144 return 0 145}