code wiki / (root) / nx_cgroup_v2_probe.nx

nx_cgroup_v2_probe.nx source

↩ module page · 164 lines · 6273 B

1// nx_cgroup_v2_probe.nx -- Linux cgroups v2 host detection. 2// 3// Stage A foundation per NISHI_ELDER_AI_OFF_DOCKER_2026_05_20.md. 4// To enforce nx_budget ceilings via the host kernel (memory.max, 5// pids.max, cpu.weight), the substrate must FIRST detect whether 6// cgroups v2 is available on the host. This primitive is the 7// inspection-only foundation; mutation primitives (mkdir cgroup, 8// write memory.max, attach pid) ship in a later session ON A 9// LINUX HOST where their smokes can actually run. 10// 11// Detection works by attempting to read /sys/fs/cgroup/cgroup.controllers: 12// - File exists + readable: cgroups v2 is mounted; returns 1 13// - File missing / unreadable: cgroups v2 unavailable; returns 0 14// 15// Host coverage: 16// Linux with cgroup v2 -> available()=1 17// Linux with cgroup v1 -> available()=0 (no v2 root) 18// Cygwin / Windows-mounted bash -> available()=0 (no /sys at all) 19// macOS -> available()=0 (no cgroups) 20// qemu-user-static -> passthrough to host kernel; if host 21// has cgroups v2, returns 1 22// 23// HONEST scope this V1: 24// - Read-only probe; no mutation 25// - Single canonical file check (cgroup.controllers) 26// - No delegated-controllers parsing (queued) 27// - No process-self-cgroup parsing (queued; reads /proc/self/cgroup) 28// - No mutation primitives (queued for Linux-host session) 29// 30// genealogy_id: linux_cgroup_v2_2018 + systemd_cgroup_delegation_2017 + 31// cardinal_2026-05-20_elder_ai_off_docker 32// lineage_id: substrate_cgroup_v2_probe_v1 33// 34// nx_capability_manifest: 35// variant_class: cgroup_v2_probe 36// variant_id: cgroup_v2_probe_v1_read_only 37// requires_isa: [rv64imac, x86_64, aarch64] 38// requires_syscalls: [openat, read, close, mmap] 39// requires_ram_min_b: 4096 40// tier_floor: NX_TIER_INF_MOBILE 41// tier_ceiling: NX_TIER_INF_HPC 42// cost_model: 43// flops_per_n: 0.0 44// bytes_per_n: 256.0 // controllers file < 256 bytes 45// syscalls_per_n: 3.0 // openat + read + close 46// adversary_class: THREAT_OPPORTUNISTIC 47// 48// nx_safety_envelope: 49// intended_use: "Host cgroups v2 detection; read-only probe; 50// foundation for nx_budget enforcement wiring" 51// sil_target: SIL2 52// evidence: [read_only, no_mutation, honest_skip_on_unavailable] 53// verdict: NOT_YET_EVALUATED 54 55import "nx_syscalls.nx" 56 57// ===== Verdicts ================================================== 58const NX_CG_AVAILABLE: i64 = 1 59const NX_CG_UNAVAILABLE: i64 = 0 60const NX_CG_PROBE_BAD_INPUT: i64 = -1 61 62// ===== Path constructor ========================================== 63// Builds "/sys/fs/cgroup/cgroup.controllers" as a NUL-terminated 64// byte string. Each byte written explicitly to keep the path 65// auditable and reproducible across builds (no string literal 66// optimizer surprises). 67func _cg_path_controllers(out: *u8) -> i64 { 68 // "/sys/fs/cgroup/cgroup.controllers" = 33 chars + NUL 69 out[0] = 47 as u8 // '/' 70 out[1] = 115 as u8 // 's' 71 out[2] = 121 as u8 // 'y' 72 out[3] = 115 as u8 // 's' 73 out[4] = 47 as u8 // '/' 74 out[5] = 102 as u8 // 'f' 75 out[6] = 115 as u8 // 's' 76 out[7] = 47 as u8 // '/' 77 out[8] = 99 as u8 // 'c' 78 out[9] = 103 as u8 // 'g' 79 out[10] = 114 as u8 // 'r' 80 out[11] = 111 as u8 // 'o' 81 out[12] = 117 as u8 // 'u' 82 out[13] = 112 as u8 // 'p' 83 out[14] = 47 as u8 // '/' 84 out[15] = 99 as u8 // 'c' 85 out[16] = 103 as u8 // 'g' 86 out[17] = 114 as u8 // 'r' 87 out[18] = 111 as u8 // 'o' 88 out[19] = 117 as u8 // 'u' 89 out[20] = 112 as u8 // 'p' 90 out[21] = 46 as u8 // '.' 91 out[22] = 99 as u8 // 'c' 92 out[23] = 111 as u8 // 'o' 93 out[24] = 110 as u8 // 'n' 94 out[25] = 116 as u8 // 't' 95 out[26] = 114 as u8 // 'r' 96 out[27] = 111 as u8 // 'o' 97 out[28] = 108 as u8 // 'l' 98 out[29] = 108 as u8 // 'l' 99 out[30] = 101 as u8 // 'e' 100 out[31] = 114 as u8 // 'r' 101 out[32] = 115 as u8 // 's' 102 out[33] = 0 as u8 // NUL 103 return 33 104} 105 106// ===== Available probe ========================================== 107// Returns 1 if /sys/fs/cgroup/cgroup.controllers can be opened 108// AND at least one byte read. Returns 0 on ENOENT, EACCES, or 109// any other failure mode. No mutation; safe to call on any host. 110 111func nx_cgroup_v2_available() -> i64 { 112 let path: *u8 = sys_mmap(64) 113 _cg_path_controllers(path) 114 let fd: i64 = sys_openat_rd(path) 115 if fd < 0 { return NX_CG_UNAVAILABLE } 116 // Try to read at least one byte. Cgroup v2 controllers file 117 // is never empty on a working v2 host; if read returns <= 0 118 // the host is not properly delegating v2. 119 let probe_buf: *u8 = sys_mmap(8) 120 let n: i64 = sys_read(fd, probe_buf, 1) 121 sys_close(fd) 122 if n <= 0 { return NX_CG_UNAVAILABLE } 123 return NX_CG_AVAILABLE 124} 125 126// ===== Read controllers file ==================================== 127// Reads /sys/fs/cgroup/cgroup.controllers into out_buf (up to 128// max_len bytes). Returns bytes read, or -1 on 129// unavailable / null-input. 130// 131// On a typical v2 host the contents are a space-separated list 132// like "cpuset cpu io memory pids" with a trailing newline, 133// typically under 100 bytes. 134 135func nx_cgroup_v2_controllers(out_buf: *u8, max_len: i64) -> i64 { 136 if (out_buf as i64) == 0 { return -1 } 137 if max_len <= 0 { return -1 } 138 let path: *u8 = sys_mmap(64) 139 _cg_path_controllers(path) 140 let fd: i64 = sys_openat_rd(path) 141 if fd < 0 { return -1 } 142 var total: i64 = 0 143 var go: i64 = 1 144 while go == 1 { 145 let remaining: i64 = max_len - total 146 if remaining <= 0 { go = 0 } 147 if go == 1 { 148 let tail: *u8 = ((out_buf as i64) + total) as *u8 149 let n: i64 = sys_read(fd, tail, remaining) 150 if n <= 0 { go = 0 } 151 if n > 0 { total = total + n } 152 } 153 } 154 sys_close(fd) 155 return total 156} 157 158// ===== Verdict accessor ========================================= 159// Returns 1 if 'v' is a valid NX_CG_* verdict. 160func nx_cg_verdict_is_valid(v: i64) -> i64 { 161 if v == NX_CG_AVAILABLE { return 1 } 162 if v == NX_CG_UNAVAILABLE { return 1 } 163 return 0 164}