kernel: Implement capability-table lookup
0d03396db090adcf83ec786e5d503343d2db22b558af92bb2cbf079d82f521fb
Assisted-by: Codex:gpt-6
1 parent
c1130035
kernel/kernel.rad
+1 -0
| 10 | 10 | export mod sync; |
|
| 11 | 11 | export mod platform; |
|
| 12 | 12 | @test export mod tests; |
|
| 13 | 13 | export mod trap; |
|
| 14 | 14 | export mod boot; |
|
| 15 | + | export mod capability; |
kernel/kernel/capability.rad
added
+110 -0
| 1 | + | //! Domain-relative capability tables. Callers serialize shared table mutations. |
|
| 2 | + | ||
| 3 | + | use super::abi; |
|
| 4 | + | use super::limits; |
|
| 5 | + | use super::slots; |
|
| 6 | + | ||
| 7 | + | /// Authority stored in one live capability slot. |
|
| 8 | + | export record Entry: Copy { |
|
| 9 | + | /// Authoritative kernel object kind. |
|
| 10 | + | kind: abi::Kind, |
|
| 11 | + | /// Generation-bearing reference in the table for this object kind. |
|
| 12 | + | object: abi::Ref, |
|
| 13 | + | /// Rights granted by this entry. |
|
| 14 | + | rights: abi::Rights, |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | /// Fixed capability storage for one domain. Object records own their lifetimes. |
|
| 18 | + | export record Table: Copy { |
|
| 19 | + | /// Domain whose calls select this table. |
|
| 20 | + | owner: abi::Ref, |
|
| 21 | + | /// Capability-slot generations and publication states. |
|
| 22 | + | slots: [slots::Slot; limits::HANDLES], |
|
| 23 | + | /// Payloads for live slots. Other payloads must not be read. |
|
| 24 | + | entries: [Entry; limits::HANDLES], |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Initialize a fresh table for a valid domain reference. |
|
| 28 | + | export fn initialize(table: &mut Table, owner: abi::Ref) { |
|
| 29 | + | assert owner.index < limits::DOMAINS and owner.generation <> 0; |
|
| 30 | + | set table.owner = owner; |
|
| 31 | + | slots::initialize(&mut table.slots[..]); |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Install authority after its object reference and lifetime have been acquired. |
|
| 35 | + | /// Invalid metadata or exhausted capacity leaves the table unchanged. |
|
| 36 | + | export fn install(table: &mut Table, entry: Entry) -> abi::Handle throws (abi::Error) { |
|
| 37 | + | if entry.kind == abi::Kind::Empty or entry.object.generation == 0 { |
|
| 38 | + | throw abi::Error::InvalidArg; |
|
| 39 | + | } |
|
| 40 | + | let rights = try abi::rights(*entry.rights as u64); |
|
| 41 | + | let reservation = try slots::reserve(&mut table.slots[..]); |
|
| 42 | + | let object = slots::reference(&reservation); |
|
| 43 | + | set table.entries[object.index] = entry; |
|
| 44 | + | let live = try! slots::commit(&mut table.slots[..], reservation); |
|
| 45 | + | return try! abi::handle(entry.kind, live); |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Read authoritative metadata after checking all encoded handle fields. |
|
| 49 | + | /// The caller validates the returned object reference against its object table. |
|
| 50 | + | export fn get(table: &Table, handle: abi::Handle) -> Entry throws (abi::Error) { |
|
| 51 | + | let decoded = try abi::decode(handle); |
|
| 52 | + | if not slots::matches(&table.slots[..], decoded.object, slots::State::Live) { |
|
| 53 | + | throw abi::Error::BadHandle; |
|
| 54 | + | } |
|
| 55 | + | let entry = table.entries[decoded.object.index]; |
|
| 56 | + | if entry.kind <> decoded.kind { |
|
| 57 | + | throw abi::Error::BadHandle; |
|
| 58 | + | } |
|
| 59 | + | return entry; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | /// Require a live capability of the requested kind with all requested rights. |
|
| 63 | + | export fn lookup(table: &Table, handle: abi::Handle, kind: abi::Kind, rights: abi::Rights) -> Entry throws (abi::Error) { |
|
| 64 | + | let entry = try get(table, handle); |
|
| 65 | + | if entry.kind <> kind { |
|
| 66 | + | throw abi::Error::BadHandle; |
|
| 67 | + | } |
|
| 68 | + | if not abi::permits(entry.rights, rights) { |
|
| 69 | + | throw abi::Error::Denied; |
|
| 70 | + | } |
|
| 71 | + | return entry; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Obtain the current typed handle at a caller-relative slot index. |
|
| 75 | + | export fn at(table: &Table, index: u32, kind: abi::Kind) -> abi::Handle throws (abi::Error) { |
|
| 76 | + | if index >= limits::HANDLES or table.slots[index].state <> slots::State::Live { |
|
| 77 | + | throw abi::Error::BadHandle; |
|
| 78 | + | } |
|
| 79 | + | if table.entries[index].kind <> kind { |
|
| 80 | + | throw abi::Error::BadHandle; |
|
| 81 | + | } |
|
| 82 | + | return try! abi::handle(kind, abi::Ref { index, generation: table.slots[index].generation }); |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// Invalidate a handle and return the payload for object-specific release. |
|
| 86 | + | /// The caller completes payload teardown in the same serialized transaction. |
|
| 87 | + | export fn invalidate(table: &mut Table, handle: abi::Handle) -> Entry throws (abi::Error) { |
|
| 88 | + | let entry = try get(table, handle); |
|
| 89 | + | let decoded = try! abi::decode(handle); |
|
| 90 | + | try slots::release(&mut table.slots[..], decoded.object); |
|
| 91 | + | return entry; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Resolve resource authority. Zero selects the first live self-Domain entry |
|
| 95 | + | /// with all requested rights. Nonzero handles use ordinary Domain lookup. |
|
| 96 | + | export fn authority(table: &Table, handle: abi::Handle, rights: abi::Rights) -> Entry throws (abi::Error) { |
|
| 97 | + | if *handle <> 0 { |
|
| 98 | + | return try lookup(table, handle, abi::Kind::Domain, rights); |
|
| 99 | + | } |
|
| 100 | + | for i in 0..limits::HANDLES { |
|
| 101 | + | if table.slots[i].state <> slots::State::Live { |
|
| 102 | + | continue; |
|
| 103 | + | } |
|
| 104 | + | let entry = table.entries[i]; |
|
| 105 | + | if entry.kind == abi::Kind::Domain and entry.object == table.owner and abi::permits(entry.rights, rights) { |
|
| 106 | + | return entry; |
|
| 107 | + | } |
|
| 108 | + | } |
|
| 109 | + | throw abi::Error::Denied; |
|
| 110 | + | } |
kernel/kernel/tests.rad
+1 -0
| 4 | 4 | export mod abi; |
|
| 5 | 5 | export mod slots; |
|
| 6 | 6 | export mod fdt; |
|
| 7 | 7 | export mod platform; |
|
| 8 | 8 | export mod trap; |
|
| 9 | + | export mod capability; |
kernel/kernel/tests/capability.rad
added
+176 -0
| 1 | + | //! Capability-table authority, generations, and domain-relative names. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::abi; |
|
| 5 | + | use kernel::slots; |
|
| 6 | + | use kernel::limits; |
|
| 7 | + | use kernel::capability; |
|
| 8 | + | ||
| 9 | + | /// Construct one live object's capability metadata. |
|
| 10 | + | fn entry(kind: abi::Kind, index: u32, rights: u16) -> capability::Entry { |
|
| 11 | + | return capability::Entry { kind, object: abi::Ref { index, generation: 1 }, rights: abi::Rights(rights) }; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | /// Check a rejected lookup's exact error without accepting successful lookup. |
|
| 15 | + | fn rejected(table: &capability::Table, value: abi::Handle, kind: abi::Kind, rights: u16, error: abi::Error) |
|
| 16 | + | throws (testing::TestError) |
|
| 17 | + | { |
|
| 18 | + | let mut failed = false; |
|
| 19 | + | try capability::lookup(table, value, kind, abi::Rights(rights)) catch err { |
|
| 20 | + | try testing::expect(err == error); set failed = true; |
|
| 21 | + | }; |
|
| 22 | + | try testing::expect(failed); |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Authoritative metadata controls kind, rights, and object identity. |
|
| 26 | + | @test unsafe fn lookup() throws (testing::TestError) { |
|
| 27 | + | let mut table: capability::Table = undefined; |
|
| 28 | + | capability::initialize(&mut table, abi::Ref { index: 0, generation: 1 }); |
|
| 29 | + | let page = entry(abi::Kind::Page, 9, abi::READ | abi::GRANT); |
|
| 30 | + | let value = try! capability::install(&mut table, page); |
|
| 31 | + | let found = try! capability::lookup(&table, value, abi::Kind::Page, abi::Rights(abi::READ)); |
|
| 32 | + | try testing::expect(found == page); |
|
| 33 | + | let decoded = try! abi::decode(value); |
|
| 34 | + | try testing::expect(try! capability::at(&table, decoded.object.index, abi::Kind::Page) == value); |
|
| 35 | + | try rejected(&table, value, abi::Kind::Device, 0, abi::Error::BadHandle); |
|
| 36 | + | try rejected(&table, value, abi::Kind::Page, abi::WRITE, abi::Error::Denied); |
|
| 37 | + | let forged = try! abi::handle(abi::Kind::Device, decoded.object); |
|
| 38 | + | try rejected(&table, forged, abi::Kind::Device, 0, abi::Error::BadHandle); |
|
| 39 | + | try rejected(&table, abi::Handle(*value | 0x01000000), abi::Kind::Page, 0, abi::Error::BadHandle); |
|
| 40 | + | try rejected(&table, abi::Handle(0), abi::Kind::Domain, 0, abi::Error::BadHandle); |
|
| 41 | + | let outside = try! abi::handle(abi::Kind::Page, abi::Ref { index: limits::HANDLES, generation: 1 }); |
|
| 42 | + | try rejected(&table, outside, abi::Kind::Page, 0, abi::Error::BadHandle); |
|
| 43 | + | let removed = try! capability::invalidate(&mut table, value); |
|
| 44 | + | try testing::expect(removed == page); |
|
| 45 | + | try rejected(&table, value, abi::Kind::Page, 0, abi::Error::BadHandle); |
|
| 46 | + | let next = try! capability::install(&mut table, page); |
|
| 47 | + | try testing::expect(next <> value); |
|
| 48 | + | try rejected(&table, value, abi::Kind::Page, 0, abi::Error::BadHandle); |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// A copied number selects only authority already present in the receiver's table. |
|
| 52 | + | @test unsafe fn domainScope() throws (testing::TestError) { |
|
| 53 | + | let mut left: capability::Table = undefined; |
|
| 54 | + | let mut right: capability::Table = undefined; |
|
| 55 | + | capability::initialize(&mut left, abi::Ref { index: 0, generation: 1 }); |
|
| 56 | + | capability::initialize(&mut right, abi::Ref { index: 1, generation: 1 }); |
|
| 57 | + | let value = try! capability::install(&mut left, entry(abi::Kind::Page, 7, abi::READ)); |
|
| 58 | + | try rejected(&right, value, abi::Kind::Page, 0, abi::Error::BadHandle); |
|
| 59 | + | let local = try! capability::install(&mut right, entry(abi::Kind::Page, 8, abi::WRITE)); |
|
| 60 | + | try testing::expect(value == local); |
|
| 61 | + | try rejected(&right, value, abi::Kind::Page, abi::READ, abi::Error::Denied); |
|
| 62 | + | let actual = try! capability::lookup(&right, value, abi::Kind::Page, abi::Rights(abi::WRITE)); |
|
| 63 | + | try testing::expect(actual.object.index == 8); |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Capacity failures preserve installed entries, and exhausted generations retire. |
|
| 67 | + | @test unsafe fn capacity() throws (testing::TestError) { |
|
| 68 | + | let mut table: capability::Table = undefined; |
|
| 69 | + | capability::initialize(&mut table, abi::Ref { index: 0, generation: 1 }); |
|
| 70 | + | let mut values: [abi::Handle; limits::HANDLES] = undefined; |
|
| 71 | + | for i in 0..limits::HANDLES { |
|
| 72 | + | set values[i] = try! capability::install(&mut table, entry(abi::Kind::Page, i, abi::READ)); |
|
| 73 | + | } |
|
| 74 | + | let mut failed = false; |
|
| 75 | + | try capability::install(&mut table, entry(abi::Kind::Page, 999, abi::READ)) catch err { |
|
| 76 | + | try testing::expect(err == abi::Error::Exhausted); set failed = true; |
|
| 77 | + | }; |
|
| 78 | + | try testing::expect(failed); |
|
| 79 | + | for i in 0..limits::HANDLES { |
|
| 80 | + | let found = try! capability::get(&table, values[i]); |
|
| 81 | + | try testing::expect(found.object.index == i); |
|
| 82 | + | } |
|
| 83 | + | let old = try! capability::invalidate(&mut table, values[0]); |
|
| 84 | + | set table.slots[0].generation = 0xffffffff; |
|
| 85 | + | let last = try! capability::install(&mut table, old); |
|
| 86 | + | let discarded = try! capability::invalidate(&mut table, last); |
|
| 87 | + | try testing::expect(table.slots[0].state == slots::State::Retired); |
|
| 88 | + | let mut retired = false; |
|
| 89 | + | try capability::install(&mut table, old) catch err { |
|
| 90 | + | try testing::expect(err == abi::Error::Exhausted); set retired = true; |
|
| 91 | + | }; |
|
| 92 | + | try testing::expect(retired); |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | /// Zero selects a live self capability; unrelated authority and stale slots do not count. |
|
| 96 | + | @test unsafe fn selfAuthority() throws (testing::TestError) { |
|
| 97 | + | let mut table: capability::Table = undefined; |
|
| 98 | + | capability::initialize(&mut table, abi::Ref { index: 4, generation: 1 }); |
|
| 99 | + | let other = try! capability::install(&mut table, entry(abi::Kind::Domain, 5, abi::CREATE | abi::ALLOCATE)); |
|
| 100 | + | let page = try! capability::install(&mut table, entry(abi::Kind::Page, 4, abi::ALLOCATE)); |
|
| 101 | + | let oldDomain = capability::Entry { kind: abi::Kind::Domain, object: abi::Ref { index: 4, generation: 2 }, |
|
| 102 | + | rights: abi::Rights(abi::ALLOCATE) }; |
|
| 103 | + | let staleOwner = try! capability::install(&mut table, oldDomain); |
|
| 104 | + | let mut failed = false; |
|
| 105 | + | try capability::authority(&table, abi::Handle(0), abi::Rights(abi::ALLOCATE)) catch err { |
|
| 106 | + | try testing::expect(err == abi::Error::Denied); set failed = true; |
|
| 107 | + | }; |
|
| 108 | + | try testing::expect(failed); |
|
| 109 | + | let explicit = try! capability::authority(&table, other, abi::Rights(abi::CREATE)); |
|
| 110 | + | try testing::expect(explicit.object.index == 5); |
|
| 111 | + | let own = try! capability::install(&mut table, entry(abi::Kind::Domain, 4, abi::CREATE | abi::ALLOCATE)); |
|
| 112 | + | let authorized = try! capability::authority(&table, abi::Handle(0), abi::Rights(abi::CREATE | abi::ALLOCATE)); |
|
| 113 | + | try testing::expect(authorized.object == table.owner); |
|
| 114 | + | let dropped = try! capability::invalidate(&mut table, own); |
|
| 115 | + | let mut lost = false; |
|
| 116 | + | try capability::authority(&table, abi::Handle(0), abi::Rights(abi::CREATE)) catch err { |
|
| 117 | + | try testing::expect(err == abi::Error::Denied); set lost = true; |
|
| 118 | + | }; |
|
| 119 | + | try testing::expect(lost); |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | /// Invalid installation metadata does not consume a slot or advance its generation. |
|
| 123 | + | @test unsafe fn invalidMetadata() throws (testing::TestError) { |
|
| 124 | + | let mut table: capability::Table = undefined; |
|
| 125 | + | capability::initialize(&mut table, abi::Ref { index: 0, generation: 1 }); |
|
| 126 | + | let mut bad = entry(abi::Kind::Empty, 0, 0); |
|
| 127 | + | let mut failures: u32 = 0; |
|
| 128 | + | for test in 0..3 { |
|
| 129 | + | if test == 1 { |
|
| 130 | + | set bad = entry(abi::Kind::Page, 0, 512); |
|
| 131 | + | } |
|
| 132 | + | if test == 2 { |
|
| 133 | + | set bad = entry(abi::Kind::Page, 0, abi::READ); |
|
| 134 | + | set bad.object.generation = 0; |
|
| 135 | + | } |
|
| 136 | + | try capability::install(&mut table, bad) catch err { |
|
| 137 | + | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
|
| 138 | + | }; |
|
| 139 | + | } |
|
| 140 | + | try testing::expect(failures == 3); |
|
| 141 | + | let value = try! capability::install(&mut table, entry(abi::Kind::Page, 0, abi::READ)); |
|
| 142 | + | let decoded = try! abi::decode(value); |
|
| 143 | + | try testing::expect(decoded.object.index == 0 and decoded.object.generation == 1); |
|
| 144 | + | let mut invalid = false; |
|
| 145 | + | try capability::at(&table, 0, abi::Kind::Device) catch err { |
|
| 146 | + | try testing::expect(err == abi::Error::BadHandle); set invalid = true; |
|
| 147 | + | }; |
|
| 148 | + | try testing::expect(invalid); |
|
| 149 | + | set table.slots[0].state = slots::State::Reserved; |
|
| 150 | + | try rejected(&table, value, abi::Kind::Page, 0, abi::Error::BadHandle); |
|
| 151 | + | let mut pending = false; |
|
| 152 | + | try capability::at(&table, 0, abi::Kind::Page) catch err { |
|
| 153 | + | try testing::expect(err == abi::Error::BadHandle); set pending = true; |
|
| 154 | + | }; |
|
| 155 | + | try testing::expect(pending); |
|
| 156 | + | } |
|
| 157 | + | ||
| 158 | + | /// Each resource-authority decision uses the complete rights of one live entry. |
|
| 159 | + | @test unsafe fn separateSelfRights() throws (testing::TestError) { |
|
| 160 | + | let mut table: capability::Table = undefined; |
|
| 161 | + | capability::initialize(&mut table, abi::Ref { index: 0, generation: 1 }); |
|
| 162 | + | let create = try! capability::install(&mut table, entry(abi::Kind::Domain, 0, abi::CREATE)); |
|
| 163 | + | let allocate = try! capability::install(&mut table, entry(abi::Kind::Domain, 0, abi::ALLOCATE)); |
|
| 164 | + | let selected = try! capability::authority(&table, abi::Handle(0), abi::Rights(abi::CREATE)); |
|
| 165 | + | try testing::expect(*selected.rights == abi::CREATE); |
|
| 166 | + | let mut denied = false; |
|
| 167 | + | try capability::authority(&table, abi::Handle(0), abi::Rights(abi::CREATE | abi::ALLOCATE)) catch err { |
|
| 168 | + | try testing::expect(err == abi::Error::Denied); set denied = true; |
|
| 169 | + | }; |
|
| 170 | + | try testing::expect(denied); |
|
| 171 | + | let mut missing = false; |
|
| 172 | + | try capability::authority(&table, allocate, abi::Rights(abi::CREATE)) catch err { |
|
| 173 | + | try testing::expect(err == abi::Error::Denied); set missing = true; |
|
| 174 | + | }; |
|
| 175 | + | try testing::expect(missing); |
|
| 176 | + | } |