.ai/RIL.TODO.md 4.8 KiB raw
1
# RIL Safety Verification
2
3
## Done
4
5
### Ptr type (Phase 1-2)
6
`il::Type::Ptr` distinct from `W64`. All memory ops use `Ptr` for base
7
registers. Loads/stores of pointer values use `Ptr` as the type.
8
Backend treats `Ptr` identically to `W64` for codegen.
9
10
### Explicit casts (Phase 3)
11
`MakePtr` (`ptr`) makes integer-to-pointer crossings explicit.
12
Pointer-to-integer is a safe demotion (no instruction needed).
13
14
### Provenance rules (Phase 4)
15
Closed set of pointer-producing instructions documented in `il.rad`:
16
Reserve, Copy(DataSym/FnAddr), Load with Ptr type, Elem, BinOp::Add
17
on Ptr, Call returning Ptr, block params, MakePtr. Pointer-to-integer
18
flows are safe and need no dedicated instruction.
19
20
### unsafe keyword (Phase 5)
21
`unsafe fn` and `unsafe { }` blocks. Integer-to-pointer casts
22
(`addr as *T`) only allowed in unsafe context. Resolver enforces.
23
24
### Elem instruction (Phase 6 partial)
25
Bounds-checked element pointer: `elem %dst %base %idx %len stride`.
26
Backend lowers to `bltu + ebreak + mul + add`. Verifier checks
27
locally without CFG analysis. Used for all user-facing array/slice
28
indexing, for-loop iteration, append, delete, fill, byte copy.
29
30
### Verifier (Phase 7)
31
SSA type checker in `il/verify.rad`. Tracks `Ptr(bound)` per register.
32
Checks:
33
- Memory op bases are Ptr
34
- Static offset access within allocation bounds
35
- Elem base is Ptr, result is Ptr(stride)
36
- MakePtr counted as provenance escape
37
38
## Remaining gaps
39
40
### Slice re-slicing uses unchecked `add ptr`
41
`&slice[start..end]` offsets the data pointer via `add ptr` with no
42
bounds check. This is safe in practice because the resulting pointer
43
is only used as a slice data pointer — all access goes through `Elem`.
44
But untrusted IL could forge a slice pointing anywhere by constructing
45
an `add ptr` with an arbitrary offset, then wrapping it in a slice
46
struct.
47
48
### Constant-index array access uses unchecked `add ptr`
49
When the resolver validates a constant array index at compile time,
50
the lowerer emits plain `add ptr`. The verifier checks this statically
51
against the allocation bound from `Reserve`. This is sound for known
52
allocations but `Ptr(0)` (unknown bound) skips the check.
53
54
### Pointers from memory/params/calls have unknown bounds
55
`load ptr`, function parameters, and call return values produce
56
`Ptr(0)`. The verifier can't check accesses through them. All
57
indexing goes through `Elem` which bounds-checks at runtime.
58
59
---
60
61
## Future: First-class slices in IL
62
63
The current model relies on runtime bounds checking (`Elem`) as the
64
last line of defense. Every element access is checked, so forging a
65
slice can't access arbitrary memory — the worst case is a bounds-check
66
trap. This is the WebAssembly model.
67
68
For static verification (proving at load time that no trap can occur),
69
slices need to be a first-class IL concept:
70
71
```
72
SliceNew  %slice %base %len %cap %stride   ;; construct from known alloc
73
SlicePtr  %ptr %slice                       ;; extract data pointer
74
SliceLen  %len %slice                       ;; extract length
75
SliceElem %ptr %slice %idx                  ;; bounds-checked element
76
```
77
78
### Why this helps
79
80
`SliceNew` is the only way to construct a slice. The verifier checks
81
that `%base` is `Ptr(B)` and `len * stride <= B` -- the allocation is
82
large enough for the claimed length. After construction, `SliceElem`
83
is provably safe because the length was validated against the
84
allocation.
85
86
An attacker can't forge a `(ptr, len)` pair by storing to memory --
87
the only path to a slice value is `SliceNew`, which the verifier
88
validates.
89
90
### What it costs
91
92
- Slices stop being plain `{ptr, len, cap}` memory layouts. They
93
  become opaque IL values.
94
- Every slice operation becomes a dedicated instruction.
95
- The lowerer must emit `SliceNew` instead of manual stores.
96
- Re-slicing becomes `SliceSlice %new %old %start %end` with the
97
  verifier checking `start <= end <= old.len`.
98
- More complex IL, simpler verifier.
99
100
### Trade-off
101
102
With `Elem` on every access, the current model is already safe against
103
memory corruption -- the worst outcome of a forged slice is a trap.
104
First-class slices would additionally prove no trap can occur, enabling
105
trap-free execution in verified domains.
106
107
Whether this is needed depends on the exokernel's requirements:
108
- If domains are allowed to trap (and the kernel handles it), the
109
  current model is sufficient.
110
- If domains must be proven trap-free, first-class slices are needed.
111
112
---
113
114
## Future: Global data bounds
115
116
`Copy(DataSym)` currently produces `Ptr(0)`. The verifier could look
117
up the `Data` entry in `Program.data` to get the size and produce
118
`Ptr(size)`. Straightforward extension, not yet implemented.
119
120
## Future: Exokernel region types
121
122
`PagePtr(handle, offset)`, `DevicePtr(handle, offset)`, etc. as
123
provenance classes checked against the domain's capability set.
124
Needs the exokernel's capability model to be defined first.