nx_elf_inspect.nx source
↩ module page · 109 lines · 5389 B
1// nx_elf_inspect.nx -- SOVEREIGN ELF inspector: answers "is this binary truly sovereign?" = statically linked
2// with ZERO dynamic dependencies, WITHOUT the shell tools `file` + `readelf`. Parses the ELF64 program-header
3// table for a PT_INTERP (an interpreter => dynamically linked) and a PT_DYNAMIC segment, then walks the dynamic
4// array for DT_NEEDED (shared-library) entries. This is the crux capability for the bench/gate_nx_sovereign_deps.sh
5// gate (which uses `file | grep "statically linked"` + `readelf -d | grep NEEDED`) and, broadly, the binary-level
6// sovereignty check the ecosystem lacked (source-scan proves no lib REFERENCES; this proves the shipped ELF has
7// no lib DEPENDENCIES). Sovereign (syscalls only). license_tier: ORIGINAL expect_exit: 0 (on a static ELF)
8//
9// Usage: nx_elf_inspect <file> -- exit 0 = STATIC/sovereign (no interpreter, 0 NEEDED); exit 1 = DYNAMIC.
10import "nx_syscalls.nx"
11
12const EI_CAP: i64 = 16777216 // 16 MiB read cap (organ + system binaries are well under this)
13
14func ei_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
15func ei_putn(v: i64) -> i64 {
16 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
17 var m: i64 = v
18 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
19 let d: *u8 = sys_mmap(24); var k: i64 = 0
20 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
21 let o: *u8 = sys_mmap(24); var i: i64 = 0
22 while i < k { o[i] = d[k - 1 - i]; i = i + 1 }
23 sys_write(1, o, k); return 0
24}
25
26func ei_read_file(path: *u8, buf: *u8, cap: i64) -> i64 {
27 let fd: i64 = sys_openat_rd(path)
28 if fd < 0 { return 0 }
29 var total: i64 = 0
30 var go: i64 = 1
31 while go == 1 {
32 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total)
33 if r <= 0 { go = 0 } else { total = total + r; if total >= cap { go = 0 } }
34 }
35 sys_close(fd)
36 return total
37}
38
39// little-endian reads at byte offset o (offsets/values here are small, no sign concern)
40func ei_u16(buf: *u8, o: i64) -> i64 { return (buf[o] as i64) | ((buf[o + 1] as i64) << 8) }
41func ei_u32(buf: *u8, o: i64) -> i64 {
42 return (buf[o] as i64) | ((buf[o + 1] as i64) << 8) | ((buf[o + 2] as i64) << 16) | ((buf[o + 3] as i64) << 24)
43}
44func ei_u64(buf: *u8, o: i64) -> i64 {
45 var v: i64 = 0; var i: i64 = 0
46 while i < 8 { v = v | ((buf[o + i] as i64) << (8 * i)); i = i + 1 }
47 return v
48}
49
50func main(argc: i64, argv: *i64) -> i64 {
51 if argc < 2 { ei_puts("usage: nx_elf_inspect <file> (exit 0 = STATIC/sovereign, 1 = DYNAMIC)\n" as *u8); sys_exit(2); return 2 }
52 let path: *u8 = argv[1] as *u8
53 let buf: *u8 = sys_mmap(EI_CAP)
54 let n: i64 = ei_read_file(path, buf, EI_CAP)
55 if n < 64 { ei_puts("nx_elf_inspect: not an ELF (file < 64 bytes or unreadable): " as *u8); ei_puts(path); ei_puts("\n" as *u8); sys_exit(1); return 1 }
56 // magic 0x7f 'E' 'L' 'F' + class byte (2 = ELF64)
57 if (buf[0] & 0xff) != 127 { ei_puts("nx_elf_inspect: not an ELF (bad magic)\n" as *u8); sys_exit(1); return 1 }
58 if buf[1] != (69 as u8) { ei_puts("nx_elf_inspect: not an ELF (bad magic)\n" as *u8); sys_exit(1); return 1 } // 'E'
59 if buf[2] != (76 as u8) { ei_puts("nx_elf_inspect: not an ELF (bad magic)\n" as *u8); sys_exit(1); return 1 } // 'L'
60 if buf[3] != (70 as u8) { ei_puts("nx_elf_inspect: not an ELF (bad magic)\n" as *u8); sys_exit(1); return 1 } // 'F'
61 if buf[4] != (2 as u8) { ei_puts("nx_elf_inspect: not ELF64 (32-bit unsupported)\n" as *u8); sys_exit(1); return 1 }
62
63 let phoff: i64 = ei_u64(buf, 32) // e_phoff
64 let phentsize: i64 = ei_u16(buf, 54) // e_phentsize
65 let phnum: i64 = ei_u16(buf, 56) // e_phnum
66
67 var has_interp: i64 = 0
68 var has_dyn: i64 = 0
69 var dyn_off: i64 = 0
70 var i: i64 = 0
71 while i < phnum {
72 let ph: i64 = phoff + i * phentsize
73 if ph + 56 <= n {
74 let ptype: i64 = ei_u32(buf, ph) // p_type @+0
75 if ptype == 3 { has_interp = 1 } // PT_INTERP
76 if ptype == 2 { has_dyn = 1; dyn_off = ei_u64(buf, ph + 8) } // PT_DYNAMIC, p_offset @+8
77 }
78 i = i + 1
79 }
80
81 // walk the dynamic array (16-byte entries: d_tag @+0, d_val @+8) counting DT_NEEDED (tag 1) until DT_NULL (0)
82 var needed: i64 = 0
83 if has_dyn == 1 {
84 var d: i64 = dyn_off
85 var go: i64 = 1
86 while go == 1 {
87 if d + 16 > n { go = 0 } else {
88 let tag: i64 = ei_u64(buf, d)
89 if tag == 0 { go = 0 } else {
90 if tag == 1 { needed = needed + 1 }
91 d = d + 16
92 }
93 }
94 }
95 }
96
97 var static_ok: i64 = 1
98 if has_interp == 1 { static_ok = 0 }
99 if needed > 0 { static_ok = 0 }
100
101 ei_puts("=== nx_elf_inspect " as *u8); ei_puts(path); ei_puts(" ===\n interpreter=" as *u8); ei_putn(has_interp)
102 ei_puts(" PT_DYNAMIC=" as *u8); ei_putn(has_dyn); ei_puts(" DT_NEEDED(shared libs)=" as *u8); ei_putn(needed); ei_puts("\n" as *u8)
103 if static_ok == 1 {
104 ei_puts("NX-ELF-INSPECT STATIC: sovereign binary -- no interpreter, 0 dynamic dependencies (raw-syscall bits-up)\n" as *u8)
105 sys_exit(0); return 0
106 }
107 ei_puts("NX-ELF-INSPECT DYNAMIC: NOT sovereign -- has an interpreter or shared-library dependencies\n" as *u8)
108 sys_exit(1); return 1
109}