aboutsummaryrefslogtreecommitdiff
path: root/PLAN.md
blob: 932fad69a559603dc69871b38c8269766812c65f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# Strix OS — Full Development Plan

> This file is the authoritative development roadmap. Update the **Status** column as phases complete.
> See `NOTES.md` for the running developer log (context recovery after resets).

## Filesystem Strategy

| Filesystem | Role | Phase |
|-----------|------|-------|
| **ext4** | Persistent root, Nix store | ext2 read-only (4) → ext4 xattrs (5) → write+journal (7) |
| **ramfs/tmpfs** | Initramfs root, /tmp | Phase 4 |
| **devfs** | /dev | Phase 4 |
| **procfs** | /proc | Phase 4 |
| **sysfs** | /sys skeleton | Phase 6 |
| **overlayfs** | Nix profile layering | Phase 6 |

## Branch & Commit Policy

- Work on `CLAUDE_TEST` or `CLAUDE_`-prefixed branches only
- Commit format: `[PhaseX.Y] description`
- Commit after each sub-task when all tests pass
- Update `NOTES.md` with every commit

---

## Progress Tracker

| Phase | Status | Description |
|-------|--------|-------------|
| Phase 1 | ✅ DONE | Memory management (GDT/IDT/PIC/heap) |
| Step 0 | ✅ DONE | Repo docs (PLAN.md, NOTES.md) |
| Phase 2.1 | 🔲 TODO | GDT user space segments |
| Phase 2.2 | 🔲 TODO | SYSCALL/SYSRET MSR setup |
| Phase 2.3 | 🔲 TODO | Syscall dispatcher |
| Phase 2.4 | 🔲 TODO | Process structure |
| Phase 2.5 | 🔲 TODO | Round-robin scheduler |
| Phase 2.6 | 🔲 TODO | Ring 3 jump (iretq) |
| Phase 3.1 | 🔲 TODO | ELF64 parser |
| Phase 3.2 | 🔲 TODO | Per-process page tables |
| Phase 3.3 | 🔲 TODO | User stack setup |
| Phase 3.4 | 🔲 TODO | execve syscall |
| Phase 3.5 | 🔲 TODO | Busybox embedded initramfs |
| Phase 4.1 | 🔲 TODO | VFS core abstractions |
| Phase 4.2 | 🔲 TODO | File descriptor table |
| Phase 4.3 | 🔲 TODO | ramfs/tmpfs |
| Phase 4.4 | 🔲 TODO | Initramfs unpacking |
| Phase 4.5 | 🔲 TODO | devfs |
| Phase 4.6 | 🔲 TODO | procfs |
| Phase 4.7 | 🔲 TODO | ATA driver + ext2 read-only |
| Phase 5.1 | 🔲 TODO | fork/clone |
| Phase 5.2 | 🔲 TODO | wait/waitpid |
| Phase 5.3 | 🔲 TODO | Signals |
| Phase 5.4 | 🔲 TODO | Pipes |
| Phase 5.5 | 🔲 TODO | Shell syscalls (stat/chdir/symlink/mmap/etc.) |
| Phase 6.1 | 🔲 TODO | TTY/PTY subsystem |
| Phase 6.2 | 🔲 TODO | mount/umount/pivot_root/chroot |
| Phase 6.3 | 🔲 TODO | Init launch (init= param, PID 1) |
| Phase 6.4 | 🔲 TODO | Mount namespaces (CLONE_NEWNS) |
| Phase 6.5 | 🔲 TODO | overlayfs |
| Phase 7.1 | 🔲 TODO | Dynamic linker (PT_INTERP) |
| Phase 7.2 | 🔲 TODO | File-backed mmap |
| Phase 7.3 | 🔲 TODO | ext4 write + journal |

---

## Phase 2 — User Space Foundation

**Goal**: Hello World from Ring 3 via SYSCALL.

### 2.1 GDT Extension
**File**: `StrixKernel/src/gdt.rs`
- GDT order (required for SYSCALL/SYSRET ABI):
  - `0x08`: kernel code (DPL=0) ← `STAR[47:32]`
  - `0x10`: kernel data (DPL=0) ← auto-selected by SYSCALL as CS+8
  - `0x18`: user data (DPL=3) ← `SYSRET SS = STAR[63:48]+8`
  - `0x20`: user code (DPL=3) ← `SYSRET CS = STAR[63:48]+16`
  - `0x28/0x30`: TSS (128-bit)
- Add `TSS.privilege_stack_table[0]` = 8 KiB static stack (RSP0 for Ring3→Ring0)
- Expose `Selectors` struct fields as `pub`
- Add `set_kernel_stack(VirtAddr)` for scheduler to update RSP0

### 2.2 SYSCALL/SYSRET MSR Setup
**File**: `StrixKernel/src/syscall/mod.rs` (new)
- `STAR[47:32]` = kernel CS (0x08), `STAR[63:48]` = 0x10 (→ user SS=0x18, user CS=0x20)
- `LSTAR` = address of `syscall_entry` stub
- `SFMASK` = clear IF (disable interrupts on SYSCALL entry)
- Entry stub: save rax/rcx/rdx/rsi/rdi/r8-r11 to kernel stack; call `syscall_handler`
- ABI: syscall# in rax, args in rdi/rsi/rdx/r10/r8/r9; return in rax; negative = errno
- Security: ALL user pointers validated to be in range `0..0x0000_8000_0000_0000`

### 2.3 Syscall Dispatcher
**File**: `StrixKernel/src/syscall/dispatch.rs` (new)
- `write` (1): fd=1/2 → VGA/serial (no VFS yet)
- `exit` (60) / `exit_group` (231): mark process exited, schedule next
- All unknown → return `-ENOSYS` (not halt/panic)

### 2.4 Process Structure
**File**: `StrixKernel/src/task/process.rs` (new)
- `ProcessState`: Running, Ready, Zombie
- `Process`: pid, state, page_table, kernel_stack_top, saved_regs (callee-save)
- `PROCESS_TABLE`: `spin::Mutex<[Option<Process>; 256]>`; PID 0 = idle, PID 1 = init

### 2.5 Scheduler
**File**: `StrixKernel/src/task/scheduler.rs` (new)
- Timer IRQ → `schedule()` → round-robin PROCESS_TABLE
- `switch_to(next)`: save current callee-saves to current Process, restore next's
- Before returning to user: `gdt::set_kernel_stack(next.kernel_stack_top)`
- Grow heap: `HEAP_SIZE` 100 KiB → 4 MiB

### 2.6 Ring 3 Jump
**File**: `StrixKernel/src/task/spawn.rs` (new)
- `spawn_user_task(entry: u64, user_stack: u64)` via `iretq`
- iretq frame: `[user_ss, user_rsp, rflags (IF=1), user_cs, entry_rip]`

### Phase 2 Integration Tests
- `tests/userspace_syscall.rs`: Ring 3 stub → `write` → serial "RING3" → `exit(0)`
- `tests/scheduler.rs`: 3 tasks, atomic counter, confirm all run in 1000 ticks
- `tests/syscall_invalid.rs`: unknown# → `-ENOSYS`; kernel pointer → `-EFAULT`

---

## Phase 3 — ELF Loading & execve

**Goal**: Execute static ELF binaries; embed busybox as rescue shell.

### 3.1 ELF64 Parser
**File**: `StrixKernel/src/loader/elf.rs` (new)
**Dep**: `goblin = { version = "0.7", default-features = false, features = ["elf64"] }`
- Validate magic, e_type; enumerate PT_LOAD; map R/W/X per flags
- Security: W^X enforced; segment offset/size bounds-checked

### 3.2 Per-Process Page Tables
**File**: `StrixKernel/src/memory/address_space.rs` (new)
- `AddressSpace::new()`: new L4, copy kernel high-half
- `map_range(vaddr, size, flags)`; `switch()` → CR3; frame tracking for cleanup

### 3.3 User Stack Setup
**File**: `StrixKernel/src/loader/stack.rs` (new)
- Stack at `0x7fff_f000_0000` (8 MiB)
- Writes: argc, argv[], envp[], AT_* aux vector (in correct order for musl/glibc)

### 3.4 execve (#59)
**File**: `StrixKernel/src/syscall/exec.rs` (new)
- Validate path + argv/envp in user range
- ELF parse → new address space → stack setup → enter Ring 3
- PT_INTERP handling: load interpreter binary, pass aux vector

### 3.5 Embedded Busybox
**Files**: `StrixKernel/build.rs` (new), `StrixKernel/initramfs/`
- `build.rs` packs `initramfs/` into cpio → `include_bytes!`
- Applets via symlinks: `sh`, `ls`, `cat`, `echo`, `mv`, `cp`, `rm`, `mkdir`, `pwd`, `mount`, `ln`, `stat`, `env`, `kill`

### Phase 3 Integration Tests
- `tests/elf_static.rs`: hand-crafted ELF → exit(42) → confirm
- `tests/elf_wxcheck.rs`: W+X segment → `Err(WxViolation)`
- `tests/elf_bounds.rs`: out-of-bounds segment → `Err(InvalidSegment)`
- `tests/address_space.rs`: two spaces, same vaddr → isolation confirmed

---

## Phase 4 — Virtual File System

### 4.1–4.7 (VFS, ramfs, initramfs, devfs, procfs, ext2)

See full plan in `/home/mun/.claude/plans/synthetic-drifting-globe.md`.

### Phase 4 Integration Tests
- `tests/vfs_ramfs.rs`, `tests/vfs_devnull.rs`, `tests/vfs_procfs.rs`
- `tests/initramfs_unpack.rs`, `tests/ext2_read.rs`, `tests/fd_table.rs`

---

## Phase 5 — POSIX Essentials

### 5.1–5.5 (fork, wait, signals, pipes, shell syscalls)

### Phase 5 Integration Tests
- `tests/fork_basic.rs`, `tests/fork_isolation.rs`
- `tests/signals_basic.rs`, `tests/pipe_basic.rs`, `tests/pipe_shell_pipeline.rs`
- `tests/symlink.rs`, `tests/ext4_xattr.rs`

---

## Phase 6 — Init System Agnosticism

### 6.1–6.5 (TTY, mount syscalls, init= param, namespaces, overlayfs)

**Init is configurable**: `init=` kernel param; default `/sbin/init`; fallback `/bin/sh`

### Phase 6 Integration Tests
- `tests/tty_canonical.rs`, `tests/mount_remount.rs`, `tests/pivot_root.rs`
- `tests/chroot_isolation.rs`, `tests/overlayfs_cow.rs`, `tests/mount_namespace.rs`

---

## Phase 7 — Dynamic Linking & Nix

### 7.1–7.3 (PT_INTERP, file mmap, ext4 write)

### Phase 7 Integration Tests
- `tests/mmap_file.rs`, `tests/mmap_wx_rejected.rs`
- `tests/dynamic_elf.rs`, `tests/ext4_write.rs`

---

## Security Baseline (always enforced)

| Rule | Location |
|------|----------|
| User pointers validated `0..0x0000_8000_0000_0000` | Every syscall with ptr arg |
| W^X: no WRITE+EXEC mapping | ELF loader, mmap |
| No kernel addresses in userspace | procfs output, aux vector |
| No `unwrap()` in kernel paths | Everywhere — use `?` or explicit match |
| Signal handler ptr validated | signal.rs |
| ELF segment bounds checked | elf.rs |
| ext4 on-disk values validated | ext4.rs |
| chroot path escape prevention | mount.rs path resolver |

---

## New Dependencies to Add

```toml
goblin = { version = "0.7", default-features = false, features = ["elf64"] }
bitflags = "2.4"
```