nx_nishios_userspace.nx source
↩ module page · 126 lines · 10110 B
1// nx_nishios_userspace.nx -- NishiOS rung-9: BARE-METAL USERSPACE (the kernel<->user privilege boundary).
2// The essence of "userspace running ON the kernel" (not under Linux): a user program runs at ring 3 and
3// can only touch privileged resources THROUGH the kernel via syscalls. The emu gains a privilege level
4// (cpl), a syscall TRAP (user 0F05 -> save return, cpl=0, vector to the kernel handler via LSTAR), sysret
5// (0F07 -> cpl=3, back to user), and a WRITE-PRIVILEGE CHECK (a store to the kernel region at cpl!=0
6// FAULTS). The user program: (1) tries to write the kernel buffer DIRECTLY -> faults/blocked; (2) asks
7// the kernel to write it via syscall -> the kernel does it on the user's behalf.
8// KAT: the direct privileged write is BLOCKED (fault raised, value never lands); the syscall write
9// SUCCEEDS (kernel wrote it); only the kernel-mediated value is in the buffer; clean exit. => the
10// privilege boundary is real: userspace reaches the hardware only through the kernel.
11// HONEST SCOPE: a minimal ring0/ring3 + one syscall (kwrite); full process model (multiple tasks, memory
12// isolation per process, ELF loading) is the continuation. No hw writes (Rule 26). expect_exit: 0 tier: ORIGINAL
13import "nx_syscalls.nx"
14import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
15const K_MAGIC_2000000: i64 = 2000000
16const K_MAGIC_2048: i64 = 2048
17const K_MAGIC_2064: i64 = 2064
18const K_MAGIC_2056: i64 = 2056
19const K_MAGIC_4096: i64 = 4096
20const K_MAGIC_16384: i64 = 16384
21
22func us_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
23// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
24// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
25// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
26// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
27func us_num(v: i64) -> i64 { nxi_out(v); return 0 }
28func us_b(c: *u8, o: i64, b: i64) -> i64 { c[o]=(b & 0xff) as u8; return o+1 }
29func us_i32(c: *u8, o: i64, v: i64) -> i64 { c[o]=(v&0xff) as u8; c[o+1]=((v>>8)&0xff) as u8; c[o+2]=((v>>16)&0xff) as u8; c[o+3]=((v>>24)&0xff) as u8; return o+4 }
30func us_ld64(mem: *u8, a: i64) -> i64 { var v: i64=0; var i: i64=0; while i<8 { v=v|((mem[a+i] as i64)<<(i*8)); i=i+1 } return v }
31func us_st64(mem: *u8, a: i64, v: i64) -> i64 { var i: i64=0; while i<8 { mem[a+i]=((v>>(i*8))&0xff) as u8; i=i+1 } return 0 }
32func us_i32r(code: *u8, off: i64) -> i64 { var v: i64=(code[off] as i64)|((code[off+1] as i64)<<8)|((code[off+2] as i64)<<16)|((code[off+3] as i64)<<24); if (v & 0x80000000)!=0 { v=v-(1<<32) } return v }
33
34// memory map: SYSRET_SLOT=2048, FAULT=2056, LSTAR(handler addr)=2064, KERN_BASE=2048 (>= is kernel-only), KERN_BUF=4096
35// emu with privilege: cpl 0=kernel 3=user. Returns 0 clean hlt, neg on error.
36func emu_user(code: *u8, len: i64, mem: *u8) -> i64 {
37 let reg: *i64 = sys_mmap(8*8) as *i64
38 var pc: i64=0
39 var cpl: i64=0
40 var zf: i64=0
41 var guard: i64=0
42 while guard < K_MAGIC_2000000 {
43 guard=guard+1
44 let b: i64 = code[pc] as i64
45 var h: i64=0
46 if b==0x0F {
47 let b1: i64=code[pc+1] as i64
48 if b1==0x05 { us_st64(mem, K_MAGIC_2048, pc+2); cpl=0; pc=us_ld64(mem, K_MAGIC_2064); h=1 } // syscall TRAP -> kernel
49 if h==0 { if b1==0x07 { cpl=3; pc=reg[1]; h=1 } } // sysret -> user (pc=rcx)
50 if h==0 { return 0-1 }
51 }
52 if h==0 { if b==0xF4 { return 0 } } // hlt -> clean exit
53 if h==0 { if b==0x74 { var r: i64=code[pc+1] as i64; if r>127{r=r-256} if zf==1 {pc=pc+2+r} else {pc=pc+2} h=1 } } // je
54 if h==0 { if b==0x75 { var r: i64=code[pc+1] as i64; if r>127{r=r-256} if zf==0 {pc=pc+2+r} else {pc=pc+2} h=1 } } // jne
55 if h==0 { if b==0xEB { var r: i64=code[pc+1] as i64; if r>127{r=r-256} pc=pc+2+r; h=1 } }
56 if h==0 { if b==0x48 {
57 let op: i64=code[pc+1] as i64
58 if op==0xC7 { reg[(code[pc+2] as i64)&7]=us_i32r(code,pc+3); pc=pc+7; h=1 }
59 if op==0x89 { let m: i64=code[pc+2] as i64; let md: i64=(m>>6)&3; if md==3 { reg[m&7]=reg[(m>>3)&7] } else { let a: i64=reg[m&7]; if a>=K_MAGIC_2048 { if cpl!=0 { mem[K_MAGIC_2056]=((mem[K_MAGIC_2056] as i64)+1) as u8 } else { us_st64(mem,a,reg[(m>>3)&7]) } } else { us_st64(mem,a,reg[(m>>3)&7]) } } pc=pc+3; h=1 } // store w/ PRIV CHECK
60 if op==0x8B { let m: i64=code[pc+2] as i64; let md: i64=(m>>6)&3; if md==3 { reg[(m>>3)&7]=reg[m&7] } else { reg[(m>>3)&7]=us_ld64(mem, reg[m&7]) } pc=pc+3; h=1 }
61 if op==0x01 { let m: i64=code[pc+2] as i64; reg[m&7]=reg[m&7]+reg[(m>>3)&7]; pc=pc+3; h=1 }
62 if op==0x39 { let m: i64=code[pc+2] as i64; let t: i64=reg[m&7]-reg[(m>>3)&7]; if t==0 {zf=1} else {zf=0} pc=pc+3; h=1 }
63 } }
64 if h==0 { return 0-3 }
65 }
66 return 0-9
67}
68
69func main() -> i64 {
70 us_puts("NishiOS rung-9: BARE-METAL USERSPACE (ring0/ring3 + syscall boundary)\n" as *u8)
71 let c: *u8 = sys_mmap(512)
72 var o: i64=0
73 // KERNEL init (cpl=0): LSTAR = handler; drop to user
74 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC0); let hoff_imm: i64=o; o=us_i32(c,o,0) // mov rax, HANDLER
75 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC3); o=us_i32(c,o,K_MAGIC_2064) // mov rbx, K_MAGIC_2064 (LSTAR)
76 o=us_b(c,o,0x48); o=us_b(c,o,0x89); o=us_b(c,o,0x03) // mov [rbx], rax
77 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC1); let uoff_imm: i64=o; o=us_i32(c,o,0) // mov rcx, USER
78 o=us_b(c,o,0x0F); o=us_b(c,o,0x07) // sysret -> user
79 // USER (cpl=3)
80 let user_off: i64=o
81 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC0); o=us_i32(c,o,170) // mov rax, 170 (0xAA)
82 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC3); o=us_i32(c,o,K_MAGIC_4096) // mov rbx, K_MAGIC_4096 (KERN_BUF)
83 o=us_b(c,o,0x48); o=us_b(c,o,0x89); o=us_b(c,o,0x03) // mov [rbx], rax -> FAULTS at cpl=3
84 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC0); o=us_i32(c,o,1) // mov rax, 1 (syscall kwrite)
85 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC6); o=us_i32(c,o,187) // mov rsi, 187 (0xBB)
86 o=us_b(c,o,0x0F); o=us_b(c,o,0x05) // syscall -> kernel kwrite
87 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC0); o=us_i32(c,o,60) // mov rax, 60 (exit)
88 o=us_b(c,o,0x0F); o=us_b(c,o,0x05) // syscall -> kernel exit
89 // HANDLER (cpl=0)
90 let handler_off: i64=o
91 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC3); o=us_i32(c,o,60) // mov rbx, 60
92 o=us_b(c,o,0x48); o=us_b(c,o,0x39); o=us_b(c,o,0xD8) // cmp rax, rbx
93 o=us_b(c,o,0x74); let je_rel: i64=o; o=us_b(c,o,0) // je do_exit (patch)
94 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC3); o=us_i32(c,o,K_MAGIC_4096) // mov rbx, K_MAGIC_4096
95 o=us_b(c,o,0x48); o=us_b(c,o,0x89); o=us_b(c,o,0x33) // mov [rbx], rsi (kwrite, cpl=0 ok)
96 o=us_b(c,o,0x48); o=us_b(c,o,0xC7); o=us_b(c,o,0xC3); o=us_i32(c,o,K_MAGIC_2048) // mov rbx, K_MAGIC_2048 (SYSRET_SLOT)
97 o=us_b(c,o,0x48); o=us_b(c,o,0x8B); o=us_b(c,o,0x0B) // mov rcx, [rbx] (return pc)
98 o=us_b(c,o,0x0F); o=us_b(c,o,0x07) // sysret -> user
99 let do_exit_off: i64=o
100 o=us_b(c,o,0xF4) // hlt -> emu exits
101 // patches
102 c[hoff_imm]=(handler_off&0xff) as u8; c[hoff_imm+1]=((handler_off>>8)&0xff) as u8; c[hoff_imm+2]=((handler_off>>16)&0xff) as u8; c[hoff_imm+3]=((handler_off>>24)&0xff) as u8
103 c[uoff_imm]=(user_off&0xff) as u8; c[uoff_imm+1]=((user_off>>8)&0xff) as u8; c[uoff_imm+2]=((user_off>>16)&0xff) as u8; c[uoff_imm+3]=((user_off>>24)&0xff) as u8
104 c[je_rel]=((do_exit_off-(je_rel+1)) & 0xff) as u8
105 let codelen: i64=o
106
107 let mem: *u8 = sys_mmap(K_MAGIC_16384)
108 var z: i64=0
109 while z<K_MAGIC_16384 { mem[z]=0 as u8; z=z+1 }
110 let rc: i64 = emu_user(c, codelen, mem)
111 let kbuf: i64 = mem[K_MAGIC_4096] as i64
112 let faults: i64 = mem[K_MAGIC_2056] as i64
113
114 us_puts(" user ran at ring3; direct priv-write faults="); us_num(faults); us_puts("; kernel buffer after = "); us_num(kbuf); us_puts(" (187=kernel-mediated, 170=leaked-direct, 0=none)\n" as *u8)
115
116 var pass: i64=0
117 var ttl: i64=0
118 ttl=ttl+1; us_puts(" T1 direct privileged write from ring3 was BLOCKED (fault raised): " as *u8); if faults>=1 { pass=pass+1; us_puts("PASS\n" as *u8) } else { us_puts("FAIL\n" as *u8) }
119 ttl=ttl+1; us_puts(" T2 syscall kwrite SUCCEEDED (kernel wrote 187 on user's behalf): " as *u8); if kbuf==187 { pass=pass+1; us_puts("PASS\n" as *u8) } else { us_puts("FAIL\n" as *u8) }
120 ttl=ttl+1; us_puts(" T3 the user's direct value (170) NEVER leaked into the kernel buffer: " as *u8); if kbuf!=170 { pass=pass+1; us_puts("PASS\n" as *u8) } else { us_puts("FAIL\n" as *u8) }
121 ttl=ttl+1; us_puts(" T4 clean exit via the kernel (hlt reached): " as *u8); if rc==0 { pass=pass+1; us_puts("PASS\n" as *u8) } else { us_puts("FAIL\n" as *u8) }
122
123 us_puts("NISHIOS-USERSPACE-GATE passed "); us_num(pass); us_puts("/"); us_num(ttl)
124 if pass==ttl { us_puts(" verdict=GREEN (ring0/ring3 privilege boundary: userspace reaches the kernel only via syscalls)\n" as *u8); sys_exit(0); return 0 }
125 us_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
126}