code wiki / (root) / nx_nishios_userspace.nx

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}