.ai/
RIL.TODO.md
4.8 KiB
compiler/
lib/
scripts/
seed/
test/
vim/
.gitignore
353 B
.gitsigners
112 B
LICENSE
1.1 KiB
Makefile
3.0 KiB
README
2.5 KiB
std.lib
1.0 KiB
std.lib.test
252 B
.ai/RIL.TODO.md
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. |