kernel: Implement capability-table lookup

79aa6df5b7828d881d208423ca83fac56bef2b3f3f840eb6ee00d5c7f4ffdaeb
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 17b40aa2
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 +92 -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 { throw abi::Error::InvalidArg; }
38 +
    let rights = try abi::rights(*entry.rights as u64);
39 +
    let reservation = try slots::reserve(&mut table.slots[..]);
40 +
    let object = slots::reference(&reservation);
41 +
    set table.entries[object.index] = entry;
42 +
    let live = try! slots::commit(&mut table.slots[..], reservation);
43 +
    return try! abi::handle(entry.kind, live);
44 +
}
45 +
46 +
/// Read authoritative metadata after checking all encoded handle fields.
47 +
/// The caller validates the returned object reference against its object table.
48 +
export fn get(table: &Table, handle: abi::Handle) -> Entry throws (abi::Error) {
49 +
    let decoded = try abi::decode(handle);
50 +
    if not slots::matches(&table.slots[..], decoded.object, slots::State::Live) { throw abi::Error::BadHandle; }
51 +
    let entry = table.entries[decoded.object.index];
52 +
    if entry.kind <> decoded.kind { throw abi::Error::BadHandle; }
53 +
    return entry;
54 +
}
55 +
56 +
/// Require a live capability of the requested kind with all requested rights.
57 +
export fn lookup(table: &Table, handle: abi::Handle, kind: abi::Kind, rights: abi::Rights) -> Entry throws (abi::Error) {
58 +
    let entry = try get(table, handle);
59 +
    if entry.kind <> kind { throw abi::Error::BadHandle; }
60 +
    if not abi::permits(entry.rights, rights) { throw abi::Error::Denied; }
61 +
    return entry;
62 +
}
63 +
64 +
/// Obtain the current typed handle at a caller-relative slot index.
65 +
export fn at(table: &Table, index: u32, kind: abi::Kind) -> abi::Handle throws (abi::Error) {
66 +
    if index >= limits::HANDLES or table.slots[index].state <> slots::State::Live { throw abi::Error::BadHandle; }
67 +
    if table.entries[index].kind <> kind { throw abi::Error::BadHandle; }
68 +
    return try! abi::handle(kind, abi::Ref { index, generation: table.slots[index].generation });
69 +
}
70 +
71 +
/// Invalidate a handle and return the payload for object-specific release.
72 +
/// The caller completes payload teardown in the same serialized transaction.
73 +
export fn invalidate(table: &mut Table, handle: abi::Handle) -> Entry throws (abi::Error) {
74 +
    let entry = try get(table, handle);
75 +
    let decoded = try! abi::decode(handle);
76 +
    try slots::release(&mut table.slots[..], decoded.object);
77 +
    return entry;
78 +
}
79 +
80 +
/// Resolve resource authority. Zero selects the first live self-Domain entry
81 +
/// with all requested rights. Nonzero handles use ordinary Domain lookup.
82 +
export fn authority(table: &Table, handle: abi::Handle, rights: abi::Rights) -> Entry throws (abi::Error) {
83 +
    if *handle <> 0 { return try lookup(table, handle, abi::Kind::Domain, rights); }
84 +
    for i in 0..limits::HANDLES {
85 +
        if table.slots[i].state <> slots::State::Live { continue; }
86 +
        let entry = table.entries[i];
87 +
        if entry.kind == abi::Kind::Domain and entry.object == table.owner and abi::permits(entry.rights, rights) {
88 +
            return entry;
89 +
        }
90 +
    }
91 +
    throw abi::Error::Denied;
92 +
}
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 +171 -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 { set bad = entry(abi::Kind::Page, 0, 512); }
130 +
        if test == 2 { set bad = entry(abi::Kind::Page, 0, abi::READ); set bad.object.generation = 0; }
131 +
        try capability::install(&mut table, bad) catch err {
132 +
            try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
133 +
        };
134 +
    }
135 +
    try testing::expect(failures == 3);
136 +
    let value = try! capability::install(&mut table, entry(abi::Kind::Page, 0, abi::READ));
137 +
    let decoded = try! abi::decode(value);
138 +
    try testing::expect(decoded.object.index == 0 and decoded.object.generation == 1);
139 +
    let mut invalid = false;
140 +
    try capability::at(&table, 0, abi::Kind::Device) catch err {
141 +
        try testing::expect(err == abi::Error::BadHandle); set invalid = true;
142 +
    };
143 +
    try testing::expect(invalid);
144 +
    set table.slots[0].state = slots::State::Reserved;
145 +
    try rejected(&table, value, abi::Kind::Page, 0, abi::Error::BadHandle);
146 +
    let mut pending = false;
147 +
    try capability::at(&table, 0, abi::Kind::Page) catch err {
148 +
        try testing::expect(err == abi::Error::BadHandle); set pending = true;
149 +
    };
150 +
    try testing::expect(pending);
151 +
}
152 +
153 +
/// Each resource-authority decision uses the complete rights of one live entry.
154 +
@test unsafe fn separateSelfRights() throws (testing::TestError) {
155 +
    let mut table: capability::Table = undefined;
156 +
    capability::initialize(&mut table, abi::Ref { index: 0, generation: 1 });
157 +
    let create = try! capability::install(&mut table, entry(abi::Kind::Domain, 0, abi::CREATE));
158 +
    let allocate = try! capability::install(&mut table, entry(abi::Kind::Domain, 0, abi::ALLOCATE));
159 +
    let selected = try! capability::authority(&table, abi::Handle(0), abi::Rights(abi::CREATE));
160 +
    try testing::expect(*selected.rights == abi::CREATE);
161 +
    let mut denied = false;
162 +
    try capability::authority(&table, abi::Handle(0), abi::Rights(abi::CREATE | abi::ALLOCATE)) catch err {
163 +
        try testing::expect(err == abi::Error::Denied); set denied = true;
164 +
    };
165 +
    try testing::expect(denied);
166 +
    let mut missing = false;
167 +
    try capability::authority(&table, allocate, abi::Rights(abi::CREATE)) catch err {
168 +
        try testing::expect(err == abi::Error::Denied); set missing = true;
169 +
    };
170 +
    try testing::expect(missing);
171 +
}