code wiki / (root) / nx_printer_security.nx

nx_printer_security.nx source

↩ module page · 148 lines · 7112 B

1// nx_printer_security.nx -- honest printer security posture auditor (the #1 measurable exceed axis). 2// 3// WHY: the worst real-world printing failure isn't paper jams -- it's security. Sept 2024 cups-browsed 4// (CVE-2024-47176, CVSS 9.9): bind UDP *:631, trust any packet, fetch attacker IPP, install malicious PPD, 5// shell-out via foomatic -> unauthenticated RCE; 75k+ exposed, 42k unauthenticated. IPP also defaults to 6// CLEARTEXT on 631 (no TLS) and printers frequently ship with NO authentication -- so anyone on the LAN 7// can read your documents and submit jobs. NO vendor UI tells you this. This organ does. 8// 9// It reads the printer's own IPP-reported security companions to printer-uri-supported (RFC 8011 10// ยง5.4.2/5.4.3, verified in knowledge/fetched/ipp_rfc8011.raw): 11// uri-security-supported (1setOf keyword) values incl 'none','tls','ssl3' 12// uri-authentication-supported (1setOf keyword) values incl 'none','requesting-user-name','basic', 13// 'digest','certificate','negotiate' 14// and classifies the posture into a sealed verdict + a findings bitmask. 15// 16// NEVER-BRICK (#26): pure read-only model; no syscalls, no writes. We do NOT auto-install PPDs and NEVER 17// shell out -- so this whole stack is structurally immune to the cups-browsed RCE class by construction. 18// Sovereign: imports only nx_ipp_codec.nx; nx_cc -> nxasm. 19// genealogy_id: project-printer-management-ipp-sclass-2026-06-20 ; license_tier: ORIGINAL 20 21import "nx_ipp_codec.nx" 22 23// ---- sealed verdict ---- 24const NX_PSEC_SECURE: i64 = 1 // TLS available AND real auth AND no cleartext/no-auth offered 25const NX_PSEC_WEAK: i64 = 2 // partial: downgrade possible / ssl3 / auth-but-also-none, etc. 26const NX_PSEC_EXPOSED: i64 = 3 // cleartext + no auth, no TLS -> anyone reads & submits (the Brother) 27const NX_PSEC_UNKNOWN: i64 = 4 // printer reported no security attributes 28const NX_PSEC_VERDICT_N: i64 = 5 29 30// ---- findings bitmask ---- 31const PSEC_F_CLEARTEXT: i64 = 1 32const PSEC_F_NOAUTH: i64 = 2 33const PSEC_F_TLS: i64 = 4 34const PSEC_F_SSL3: i64 = 8 35const PSEC_F_AUTH: i64 = 16 36 37func nx_psec_verdict_is_valid(v: i64) -> i64 { 38 if v <= 0 { return 0 } 39 if v >= NX_PSEC_VERDICT_N { return 0 } 40 return 1 41} 42 43// 1 if the 1setOf keyword attribute `name` contains the value `target`. Walks the named attribute and all 44// its additional-values (name-length 0). Bounded + bounds-checked; malformed -> 0 (treated as "not advertised"). 45func nx_psec_set_contains(body: *u8, n: i64, name: *u8, target: *u8) -> i64 { 46 if n < 8 { return 0 } 47 let nm_len: i64 = nx_ipp_strlen(name) 48 let tlen: i64 = nx_ipp_strlen(target) 49 var off: i64 = 8 50 var in_set: i64 = 0 51 var found: i64 = 0 52 var keep: i64 = 1 53 while keep == 1 { 54 if off >= n { keep = 0 } 55 else { 56 let tag: i64 = body[off] as i64 57 if tag == NX_IPP_TAG_END { keep = 0 } 58 else { 59 if tag <= 0x0f { off = off + 1; in_set = 0 } 60 else { 61 if (off + 3) > n { keep = 0 } 62 else { 63 let nlen: i64 = nx_ipp_get_u16(body, off + 1) 64 let name_off: i64 = off + 3 65 if (name_off + nlen + 2) > n { keep = 0 } 66 else { 67 let vlen_off: i64 = name_off + nlen 68 let vlen: i64 = nx_ipp_get_u16(body, vlen_off) 69 let val_off: i64 = vlen_off + 2 70 if (val_off + vlen) > n { keep = 0 } 71 else { 72 if nlen > 0 { 73 var nm_match: i64 = 0 74 if nlen == nm_len { 75 if nx_ipp_name_eq(body, name_off, name, nlen) == 1 { nm_match = 1 } 76 } 77 in_set = nm_match 78 } 79 if in_set == 1 { 80 if vlen == tlen { 81 if nx_ipp_name_eq(body, val_off, target, tlen) == 1 { found = 1 } 82 } 83 } 84 off = val_off + vlen 85 } 86 } 87 } 88 } 89 } 90 } 91 } 92 return found 93} 94 95// 1 if the attribute `name` is present at all. 96func nx_psec_has(body: *u8, n: i64, name: *u8, scratch3: *i64) -> i64 { 97 let p_tag: *i64 = scratch3 98 let p_voff: *i64 = ((scratch3 as i64) + 8) as *i64 99 let p_vlen: *i64 = ((scratch3 as i64) + 16) as *i64 100 if nx_ipp_find(body, n, name, p_tag, p_voff, p_vlen) == NX_IPP_OK { return 1 } 101 return 0 102} 103 104// Classify the printer's security posture. Writes the findings bitmask via out_flags. scratch3 = i64[3]. 105func nx_printer_security(body: *u8, n: i64, scratch3: *i64, out_flags: *i64) -> i64 { 106 out_flags[0] = 0 107 let sec_present: i64 = nx_psec_has(body, n, "uri-security-supported", scratch3) 108 let auth_present: i64 = nx_psec_has(body, n, "uri-authentication-supported", scratch3) 109 if sec_present == 0 { 110 if auth_present == 0 { return NX_PSEC_UNKNOWN } 111 } 112 113 let has_tls: i64 = nx_psec_set_contains(body, n, "uri-security-supported", "tls") 114 let has_ssl3: i64 = nx_psec_set_contains(body, n, "uri-security-supported", "ssl3") 115 let has_clear: i64 = nx_psec_set_contains(body, n, "uri-security-supported", "none") 116 let has_noauth: i64 = nx_psec_set_contains(body, n, "uri-authentication-supported", "none") 117 118 var has_auth: i64 = 0 119 if nx_psec_set_contains(body, n, "uri-authentication-supported", "digest") == 1 { has_auth = 1 } 120 if nx_psec_set_contains(body, n, "uri-authentication-supported", "basic") == 1 { has_auth = 1 } 121 if nx_psec_set_contains(body, n, "uri-authentication-supported", "certificate") == 1 { has_auth = 1 } 122 if nx_psec_set_contains(body, n, "uri-authentication-supported", "negotiate") == 1 { has_auth = 1 } 123 if nx_psec_set_contains(body, n, "uri-authentication-supported", "requesting-user-name") == 1 { has_auth = 1 } 124 125 var flags: i64 = 0 126 if has_clear == 1 { flags = flags | PSEC_F_CLEARTEXT } 127 if has_noauth == 1 { flags = flags | PSEC_F_NOAUTH } 128 if has_tls == 1 { flags = flags | PSEC_F_TLS } 129 if has_ssl3 == 1 { flags = flags | PSEC_F_SSL3 } 130 if has_auth == 1 { flags = flags | PSEC_F_AUTH } 131 out_flags[0] = flags 132 133 // SECURE: real TLS + real auth, and the printer is NOT also offering an open downgrade path. 134 if has_tls == 1 { 135 if has_auth == 1 { 136 if has_clear == 0 { 137 if has_noauth == 0 { return NX_PSEC_SECURE } 138 } 139 } 140 } 141 // EXPOSED: no TLS at all, cleartext, and no authentication -> wide open. 142 if has_tls == 0 { 143 if has_clear == 1 { 144 if has_noauth == 1 { return NX_PSEC_EXPOSED } 145 } 146 } 147 return NX_PSEC_WEAK 148}