code wiki / (root) / nx_stl_adversarial_test.nx

nx_stl_adversarial_test.nx source

↩ module page · 144 lines · 5502 B

1// nx_stl_adversarial_test.nx -- Phase A1 of S-class hardening per 2// docs/NISHI_3D_PRINT_SCLASS_HONEST_AUDIT_2026_05_20.md. 3// 4// Operator question: "are we really at s class or still theoretical 5// bullshit, aka will it work like an ak47 in the worst of the worst" 6// 7// Honest answer: not S-class yet. This smoke is the FIRST STONE of 8// adversarial hardening -- proves the binary STL parser gracefully 9// rejects 8 classes of malformed input without crashing or returning 10// false-OK verdicts. 11// 12// What this proves: parser DOES NOT crash on adversarial input + DOES 13// return non-OK verdict on every malformed case + DOES detect class 14// of corruption correctly (TOO_SHORT vs TRUNCATED vs BAD_FLOAT vs 15// ASCII_REFUSED). 16// 17// What this does NOT prove: the parser is bug-free for ALL malformed 18// inputs. Future work: fuzz with longer / randomized byte streams. 19// 20// expect_exit: 0 21// license_tier: ORIGINAL 22 23import "nx_syscalls.nx" 24import "nx_stl.nx" 25 26func main() -> i64 { 27 // ===== (a) Empty buffer -> TOO_SHORT ===== 28 let empty_buf: *u8 = sys_mmap(4) 29 let r_empty: *NxStlResult = nx_stl_load_binary(empty_buf, 0) 30 if r_empty.verdict != NX_STL_ERR_TOO_SHORT { return 10 } 31 32 // ===== (b) Buffer of 10 bytes -> TOO_SHORT (below 84-byte header) ===== 33 let tiny_buf: *u8 = sys_mmap(16) 34 let r_tiny: *NxStlResult = nx_stl_load_binary(tiny_buf, 10) 35 if r_tiny.verdict != NX_STL_ERR_TOO_SHORT { return 20 } 36 37 // ===== (c) All-zero 84-byte buffer (legitimately-empty STL) ===== 38 // 39 // Four-pillar fix (2026-05-20): parser now distinguishes 40 // legitimately-empty STL (n_tris=0) from real OOM via the new 41 // NX_STL_ERR_EMPTY verdict. This MONITOR-pillar assertion 42 // ensures the fix doesn't regress. 43 let zero_buf: *u8 = sys_mmap(84) 44 let r_zero: *NxStlResult = nx_stl_load_binary(zero_buf, 84) 45 if r_zero.verdict != NX_STL_ERR_EMPTY { return 30 } // distinct verdict 46 if r_zero.n_tris_header != 0 { return 33 } // header parsed 47 48 // ===== (d) All-0xFF 84-byte buffer (n_tris claims 4 billion tris) ===== 49 // Parser should detect TRUNCATED because the buffer is only 84 50 // bytes but 4B tris would need ~200GB. 51 let ff_buf: *u8 = sys_mmap(84) 52 var ff_i: i64 = 0 53 while ff_i < 84 { 54 ff_buf[ff_i] = 0xff 55 ff_i = ff_i + 1 56 } 57 let r_ff: *NxStlResult = nx_stl_load_binary(ff_buf, 84) 58 if r_ff.verdict == NX_STL_OK { return 40 } // must NOT be OK 59 60 // ===== (e) Bit-flipped n_tris claims 2^31 triangles ===== 61 // n_tris field at offset 80, little-endian u32. Set high byte 62 // to 0x80 → n_tris = 0x80000000 = 2.1B triangles. Parser must 63 // detect TRUNCATED (or refuse via some honest verdict). 64 let flip_buf: *u8 = sys_mmap(100) 65 var fi: i64 = 0 66 while fi < 100 { 67 flip_buf[fi] = 0 68 fi = fi + 1 69 } 70 flip_buf[83] = 0x80 // n_tris[3] = 0x80 (little-endian u32 sign) 71 let r_flip: *NxStlResult = nx_stl_load_binary(flip_buf, 100) 72 if r_flip.verdict == NX_STL_OK { return 50 } // must NOT be OK 73 74 // ===== (f) Truncated mid-triangle record ===== 75 // Header says n_tris=2 (100 bytes of records needed) but buffer 76 // is only 84 + 25 = 109 bytes (half a record). 77 let trunc_buf: *u8 = sys_mmap(120) 78 var ti: i64 = 0 79 while ti < 120 { 80 trunc_buf[ti] = 0 81 ti = ti + 1 82 } 83 trunc_buf[80] = 2 // n_tris = 2 84 let r_trunc: *NxStlResult = nx_stl_load_binary(trunc_buf, 109) 85 if r_trunc.verdict != NX_STL_ERR_TRUNCATED { return 60 } 86 87 // ===== (g) NaN float in triangle coords -> BAD_FLOAT ===== 88 // 89 // IEEE 754 NaN bit pattern: any exponent=0xFF with non-zero 90 // mantissa. Use 0x7FC00000 (canonical quiet NaN). 91 let nan_buf: *u8 = sys_mmap(160) 92 var ni: i64 = 0 93 while ni < 160 { 94 nan_buf[ni] = 0 95 ni = ni + 1 96 } 97 nan_buf[80] = 1 // n_tris = 1 98 // First float at offset 84+12 = 96 (vertex 0 X coord). 99 // NaN bits 0x7FC00000 -> little-endian: 0x00, 0x00, 0xC0, 0x7F 100 nan_buf[96] = 0x00 101 nan_buf[97] = 0x00 102 nan_buf[98] = 0xC0 103 nan_buf[99] = 0x7F 104 let r_nan: *NxStlResult = nx_stl_load_binary(nan_buf, 134) 105 if r_nan.verdict != NX_STL_ERR_BAD_FLOAT { return 70 } 106 107 // ===== (h) +Infinity float bits ===== 108 // IEEE 754 +Inf = 0x7F800000 → 0x00, 0x00, 0x80, 0x7F LE. 109 let inf_buf: *u8 = sys_mmap(160) 110 var ii: i64 = 0 111 while ii < 160 { 112 inf_buf[ii] = 0 113 ii = ii + 1 114 } 115 inf_buf[80] = 1 116 inf_buf[96] = 0x00 117 inf_buf[97] = 0x00 118 inf_buf[98] = 0x80 119 inf_buf[99] = 0x7F 120 let r_inf: *NxStlResult = nx_stl_load_binary(inf_buf, 134) 121 if r_inf.verdict != NX_STL_ERR_BAD_FLOAT { return 80 } 122 123 // ===== (i) ASCII STL content -> ASCII_REFUSED ===== 124 // ASCII format starts with "solid " (lowercase). 125 let ascii_buf: *u8 = sys_mmap(128) 126 ascii_buf[0] = 0x73 // 's' 127 ascii_buf[1] = 0x6F // 'o' 128 ascii_buf[2] = 0x6C // 'l' 129 ascii_buf[3] = 0x69 // 'i' 130 ascii_buf[4] = 0x64 // 'd' 131 ascii_buf[5] = 0x20 // ' ' 132 // pad rest with 'a' so the buffer is non-binary-like 133 var ai: i64 = 6 134 while ai < 128 { 135 ascii_buf[ai] = 0x61 // 'a' 136 ai = ai + 1 137 } 138 let r_ascii: *NxStlResult = nx_stl_load_binary(ascii_buf, 128) 139 if r_ascii.verdict != NX_STL_ERR_ASCII_REFUSED { return 90 } 140 141 // All 9 adversarial classes were detected. Parser doesn't crash; 142 // each malformed input gets a structured verdict. 143 return 0 144}