kernel: Implement capability transactions
2d5bac683bea186f4b08f44f33590aca790373291002df8faec96bda4188a86a
Assisted-by: Codex:gpt-6
1 parent
5a24e04e
kernel/kernel.rad
+1 -0
| 9 | 9 | export mod range; |
|
| 10 | 10 | export mod sync; |
|
| 11 | 11 | export mod platform; |
|
| 12 | 12 | export mod trap; |
|
| 13 | 13 | export mod capability; |
|
| 14 | + | export mod transactions; |
|
| 14 | 15 | export mod frames; |
|
| 15 | 16 | export mod backing; |
|
| 16 | 17 | export mod pages; |
|
| 17 | 18 | export mod boot; |
|
| 18 | 19 | @test export mod tests; |
kernel/kernel/pages.rad
+53 -0
| 4 | 4 | use super::limits; |
|
| 5 | 5 | use super::slots; |
|
| 6 | 6 | use super::frames; |
|
| 7 | 7 | use super::backing; |
|
| 8 | 8 | use super::capability; |
|
| 9 | + | use super::transactions; |
|
| 9 | 10 | use super::platform; |
|
| 10 | 11 | ||
| 11 | 12 | /// Page capability rights created by physical allocation. |
|
| 12 | 13 | export constant DEFAULT_RIGHTS: u16 = abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER; |
|
| 13 | 14 |
| 128 | 129 | set store.records[object.index].handles -= 1; |
|
| 129 | 130 | if page.handles > 1 { return; } |
|
| 130 | 131 | try! slots::release(&mut store.slots[..], object); |
|
| 131 | 132 | try! backing::releasePage(&mut store.backings, page.backing); |
|
| 132 | 133 | } |
|
| 134 | + | ||
| 135 | + | /// Acquire page lifetime and publish an attenuated capability into a resolved table. |
|
| 136 | + | unsafe fn receive(store: &mut Store, target: &mut capability::Table, owner: abi::Ref, |
|
| 137 | + | source: abi::Handle, entry: capability::Entry, rights: u64) -> abi::Handle throws (abi::Error) |
|
| 138 | + | { |
|
| 139 | + | let receiver = target.owner; |
|
| 140 | + | let pending = try transactions::reserve(target, owner, source, entry, rights, false); |
|
| 141 | + | try retain(store, entry.object, receiver) catch err { |
|
| 142 | + | transactions::cancel(target, pending); throw err; |
|
| 143 | + | }; |
|
| 144 | + | return transactions::grant(target, pending); |
|
| 145 | + | } |
|
| 146 | + | ||
| 147 | + | /// Grant page authority to a separately resolved domain table. |
|
| 148 | + | /// The caller serializes both tables and page metadata through publication. |
|
| 149 | + | export unsafe fn grant(store: &mut Store, source: &capability::Table, target: &mut capability::Table, |
|
| 150 | + | handle: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) |
|
| 151 | + | { |
|
| 152 | + | let entry = try capability::lookup(source, handle, abi::Kind::Page, abi::Rights(0)); |
|
| 153 | + | return try receive(store, target, source.owner, handle, entry, rights); |
|
| 154 | + | } |
|
| 155 | + | ||
| 156 | + | /// Create a narrower page capability in the calling domain's table. |
|
| 157 | + | export unsafe fn selfGrant(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, rights: u64) |
|
| 158 | + | -> abi::Handle throws (abi::Error) |
|
| 159 | + | { |
|
| 160 | + | let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0)); |
|
| 161 | + | let owner = table.owner; |
|
| 162 | + | return try receive(store, table, owner, handle, entry, rights); |
|
| 163 | + | } |
|
| 164 | + | ||
| 165 | + | /// Move page authority without changing its handle count or ending source exposure. |
|
| 166 | + | /// The caller serializes the resolved domain tables and page metadata. |
|
| 167 | + | export unsafe fn transfer(store: &mut Store, source: &mut capability::Table, target: &mut capability::Table, |
|
| 168 | + | handle: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) |
|
| 169 | + | { |
|
| 170 | + | let entry = try capability::lookup(source, handle, abi::Kind::Page, abi::Rights(0)); |
|
| 171 | + | let page = try get(store, entry.object); |
|
| 172 | + | let receiver = target.owner; |
|
| 173 | + | let pending = try transactions::reserve(target, source.owner, handle, entry, rights, true); |
|
| 174 | + | try backing::expose(&mut store.backings, page.backing, receiver) catch err { |
|
| 175 | + | transactions::cancel(target, pending); throw err; |
|
| 176 | + | }; |
|
| 177 | + | return transactions::transfer(source, target, pending); |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | /// Drop a page capability and release its object reference under serialization. |
|
| 181 | + | export fn drop(store: &mut Store, table: &mut capability::Table, handle: abi::Handle) throws (abi::Error) { |
|
| 182 | + | let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0)); |
|
| 183 | + | try release(store, entry.object); |
|
| 184 | + | let removed = try! capability::invalidate(table, handle); |
|
| 185 | + | } |
kernel/kernel/tests.rad
+1 -0
| 8 | 8 | export mod trap; |
|
| 9 | 9 | export mod capability; |
|
| 10 | 10 | export mod frames; |
|
| 11 | 11 | export mod backing; |
|
| 12 | 12 | export mod pages; |
|
| 13 | + | export mod transactions; |
kernel/kernel/tests/pages.rad
+88 -2
| 10 | 10 | ||
| 11 | 11 | /// Page and allocation metadata workspace. |
|
| 12 | 12 | unsafe static STORE: pages::Store = undefined; |
|
| 13 | 13 | /// Test domain's capability table. |
|
| 14 | 14 | unsafe static TABLE: capability::Table = undefined; |
|
| 15 | + | /// Receiver table for page capability transactions. |
|
| 16 | + | unsafe static TARGET: capability::Table = undefined; |
|
| 15 | 17 | /// Mapped bytes with room for page alignment and eight full pages. |
|
| 16 | 18 | static RAM: [u8; 36864] = [0; 36864]; |
|
| 17 | 19 | /// Byte offset to the first page-aligned test frame. |
|
| 18 | 20 | static OFFSET: u32 = 0; |
|
| 19 | 21 |
| 36 | 38 | }); |
|
| 37 | 39 | } |
|
| 38 | 40 | ||
| 39 | 41 | /// Drop the test's handle and its object reference in the same serialized operation. |
|
| 40 | 42 | unsafe fn drop(handle: abi::Handle) { |
|
| 41 | - | let entry = try! capability::invalidate(&mut TABLE, handle); |
|
| 42 | - | try! pages::release(&mut STORE, entry.object); |
|
| 43 | + | try! pages::drop(&mut STORE, &mut TABLE, handle); |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | /// Transfers keep both domains exposed without duplicating page handles. |
|
| 47 | + | @test unsafe fn transferExposure() throws (testing::TestError) { |
|
| 48 | + | initialize(); |
|
| 49 | + | let receiver = abi::Ref { index: 1, generation: 1 }; |
|
| 50 | + | capability::initialize(&mut TARGET, receiver); |
|
| 51 | + | try! backing::registerDomain(&mut STORE.backings, receiver); |
|
| 52 | + | let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8); |
|
| 53 | + | let entry = try! capability::get(&TABLE, original); |
|
| 54 | + | let moved = try! pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64); |
|
| 55 | + | let page = try! pages::get(&STORE, entry.object); |
|
| 56 | + | try testing::expect(page.handles == 1 and STORE.backings.records[page.backing.index].exposed == 2); |
|
| 57 | + | let actual = try! capability::get(&TARGET, moved); |
|
| 58 | + | try testing::expect(*actual.rights == abi::READ); |
|
| 59 | + | try! pages::drop(&mut STORE, &mut TARGET, moved); |
|
| 60 | + | try! backing::endDomain(&mut STORE.backings, receiver); |
|
| 61 | + | try testing::expect(not STORE.backings.pool.free[0]); |
|
| 62 | + | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 63 | + | try testing::expect(STORE.backings.pool.free[0]); |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Grants acquire references only after capacity and receiver checks succeed. |
|
| 67 | + | @test unsafe fn grantRollback() throws (testing::TestError) { |
|
| 68 | + | initialize(); |
|
| 69 | + | let receiver = abi::Ref { index: 1, generation: 1 }; |
|
| 70 | + | capability::initialize(&mut TARGET, receiver); |
|
| 71 | + | let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2); |
|
| 72 | + | let entry = try! capability::get(&TABLE, original); |
|
| 73 | + | let page = try! pages::get(&STORE, entry.object); |
|
| 74 | + | let mut dead = false; |
|
| 75 | + | try pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64) catch err { |
|
| 76 | + | try testing::expect(err == abi::Error::BadHandle); set dead = true; |
|
| 77 | + | }; |
|
| 78 | + | try testing::expect(dead and TARGET.slots[0].state == slots::State::Free); |
|
| 79 | + | let mut rejected = false; |
|
| 80 | + | try pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64) catch err { |
|
| 81 | + | try testing::expect(err == abi::Error::BadHandle); set rejected = true; |
|
| 82 | + | }; |
|
| 83 | + | try testing::expect(rejected and TARGET.slots[0].state == slots::State::Free); |
|
| 84 | + | try testing::expect(try! capability::get(&TABLE, original) == entry); |
|
| 85 | + | try! backing::registerDomain(&mut STORE.backings, receiver); |
|
| 86 | + | for i in 0..limits::HANDLES { |
|
| 87 | + | let filler = try! capability::install(&mut TARGET, capability::Entry { |
|
| 88 | + | kind: abi::Kind::Domain, object: receiver, rights: abi::Rights(0), |
|
| 89 | + | }); |
|
| 90 | + | } |
|
| 91 | + | let mut full: u32 = 0; |
|
| 92 | + | try pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64) catch err { |
|
| 93 | + | try testing::expect(err == abi::Error::InvalidArg); set full += 1; |
|
| 94 | + | }; |
|
| 95 | + | try pages::transfer(&mut STORE, &mut TABLE, &mut TARGET, original, abi::READ as u64) catch err { |
|
| 96 | + | try testing::expect(err == abi::Error::InvalidArg); set full += 1; |
|
| 97 | + | }; |
|
| 98 | + | try testing::expect(full == 2 and STORE.records[entry.object.index].handles == 1); |
|
| 99 | + | try testing::expect(STORE.backings.records[page.backing.index].exposed == 1); |
|
| 100 | + | try testing::expect(try! capability::get(&TABLE, original) == entry); |
|
| 101 | + | let filler = try! capability::at(&TARGET, 0, abi::Kind::Domain); |
|
| 102 | + | let removed = try! capability::invalidate(&mut TARGET, filler); |
|
| 103 | + | let granted = try! pages::grant(&mut STORE, &TABLE, &mut TARGET, original, abi::READ as u64); |
|
| 104 | + | try testing::expect(STORE.records[entry.object.index].handles == 2); |
|
| 105 | + | try testing::expect(STORE.backings.records[page.backing.index].exposed == 2); |
|
| 106 | + | drop(original); |
|
| 107 | + | try! pages::drop(&mut STORE, &mut TARGET, granted); |
|
| 108 | + | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 109 | + | try testing::expect(not STORE.backings.pool.free[0]); |
|
| 110 | + | try! backing::endDomain(&mut STORE.backings, receiver); |
|
| 111 | + | try testing::expect(STORE.backings.pool.free[0]); |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | /// Self-attenuation adds one reference, preserves authority, and prevents splitting. |
|
| 115 | + | @test unsafe fn selfAttenuation() throws (testing::TestError) { |
|
| 116 | + | initialize(); |
|
| 117 | + | let original = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2); |
|
| 118 | + | let copy = try! pages::selfGrant(&mut STORE, &mut TABLE, original, abi::READ as u64); |
|
| 119 | + | let entry = try! capability::get(&TABLE, copy); |
|
| 120 | + | try testing::expect(*entry.rights == abi::READ and STORE.records[entry.object.index].handles == 2); |
|
| 121 | + | let narrowed = try! pages::selfGrant(&mut STORE, &mut TABLE, copy, abi::WRITE as u64); |
|
| 122 | + | let none = try! capability::get(&TABLE, narrowed); |
|
| 123 | + | try testing::expect(*none.rights == 0); |
|
| 124 | + | drop(narrowed); drop(copy); |
|
| 125 | + | let right = try! pages::split(&mut STORE, &mut TABLE, original, 1); |
|
| 126 | + | drop(original); drop(right); |
|
| 127 | + | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 128 | + | try testing::expect(STORE.backings.pool.free[0] and STORE.backings.pool.free[1]); |
|
| 43 | 129 | } |
|
| 44 | 130 | ||
| 45 | 131 | /// New allocations are zeroed completely and carry the documented default rights. |
|
| 46 | 132 | @test unsafe fn allocation() throws (testing::TestError) { |
|
| 47 | 133 | initialize(); |
kernel/kernel/tests/transactions.rad
added
+166 -0
| 1 | + | //! Atomic capability publication and delegation rules. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::abi; |
|
| 5 | + | use kernel::limits; |
|
| 6 | + | use kernel::slots; |
|
| 7 | + | use kernel::capability; |
|
| 8 | + | use kernel::transactions; |
|
| 9 | + | ||
| 10 | + | /// Sender table used by serialized transactions. |
|
| 11 | + | unsafe static SOURCE: capability::Table = undefined; |
|
| 12 | + | /// Receiver table used by serialized transactions. |
|
| 13 | + | unsafe static TARGET: capability::Table = undefined; |
|
| 14 | + | ||
| 15 | + | /// Initialize two independent domain tables. |
|
| 16 | + | unsafe fn initialize() { |
|
| 17 | + | capability::initialize(&mut SOURCE, abi::Ref { index: 0, generation: 1 }); |
|
| 18 | + | capability::initialize(&mut TARGET, abi::Ref { index: 1, generation: 1 }); |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Install test authority whose object lifetime belongs to the caller. |
|
| 22 | + | unsafe fn install(kind: abi::Kind, rights: u16) -> abi::Handle { |
|
| 23 | + | return try! capability::install(&mut SOURCE, capability::Entry { |
|
| 24 | + | kind, object: abi::Ref { index: 7, generation: 1 }, rights: abi::Rights(rights), |
|
| 25 | + | }); |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Transfer failure preserves source authority; successful publication invalidates it. |
|
| 29 | + | @test unsafe fn atomicTransfer() throws (testing::TestError) { |
|
| 30 | + | initialize(); |
|
| 31 | + | let source = install(abi::Kind::Budget, abi::READ | abi::TRANSFER); |
|
| 32 | + | let entry = try! capability::get(&SOURCE, source); |
|
| 33 | + | for i in 0..limits::HANDLES { let filler = try! capability::install(&mut TARGET, entry); } |
|
| 34 | + | let mut full = false; |
|
| 35 | + | try move(source) catch err { try testing::expect(err == abi::Error::InvalidArg); set full = true; }; |
|
| 36 | + | try testing::expect(full and try! capability::get(&SOURCE, source) == entry); |
|
| 37 | + | let first = try! capability::at(&TARGET, 0, abi::Kind::Budget); |
|
| 38 | + | let removed = try! capability::invalidate(&mut TARGET, first); |
|
| 39 | + | let moved = try! move(source); |
|
| 40 | + | let actual = try! capability::get(&TARGET, moved); |
|
| 41 | + | try testing::expect(actual.object == entry.object and *actual.rights == abi::READ); |
|
| 42 | + | let mut stale = false; |
|
| 43 | + | try capability::get(&SOURCE, source) catch err { |
|
| 44 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 45 | + | }; |
|
| 46 | + | try testing::expect(stale); |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Complete a transfer after any required object-specific ownership update. |
|
| 50 | + | unsafe fn move(source: abi::Handle) -> abi::Handle throws (abi::Error) { |
|
| 51 | + | let entry = try capability::get(&SOURCE, source); |
|
| 52 | + | let owner = SOURCE.owner; |
|
| 53 | + | let pending = try transactions::reserve(&mut TARGET, owner, source, entry, abi::READ as u64, true); |
|
| 54 | + | return transactions::transfer(&mut SOURCE, &mut TARGET, pending); |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | /// Self-grants narrow rights without requiring Grant; budgets remain exclusive. |
|
| 58 | + | @test unsafe fn selfGrant() throws (testing::TestError) { |
|
| 59 | + | initialize(); |
|
| 60 | + | let source = install(abi::Kind::Events, abi::READ | abi::WRITE); |
|
| 61 | + | let entry = try! capability::get(&SOURCE, source); |
|
| 62 | + | let owner = SOURCE.owner; |
|
| 63 | + | let pending = try! transactions::reserve(&mut SOURCE, owner, source, entry, abi::READ as u64, false); |
|
| 64 | + | let copy = transactions::grant(&mut SOURCE, pending); |
|
| 65 | + | let narrowed = try! capability::get(&SOURCE, copy); |
|
| 66 | + | try testing::expect(*narrowed.rights == abi::READ); |
|
| 67 | + | try testing::expect(try! capability::get(&SOURCE, source) == entry); |
|
| 68 | + | let budget = install(abi::Kind::Budget, abi::GRANT | abi::TRANSFER); |
|
| 69 | + | let budgetEntry = try! capability::get(&SOURCE, budget); |
|
| 70 | + | let mut denied = false; |
|
| 71 | + | try probe(owner, budget, budgetEntry, false) catch err { |
|
| 72 | + | try testing::expect(err == abi::Error::Denied); set denied = true; |
|
| 73 | + | }; |
|
| 74 | + | try testing::expect(denied); |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Reserve and cancel a policy probe without leaving an outstanding token. |
|
| 78 | + | unsafe fn probe(owner: abi::Ref, handle: abi::Handle, entry: capability::Entry, moving: bool) throws (abi::Error) { |
|
| 79 | + | let pending = try transactions::reserve(&mut TARGET, owner, handle, entry, abi::RIGHTS_MASK as u64, moving); |
|
| 80 | + | transactions::cancel(&mut TARGET, pending); |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | /// Nondelegable kinds and missing rights fail before consuming target capacity. |
|
| 84 | + | @test unsafe fn delegation() throws (testing::TestError) { |
|
| 85 | + | initialize(); |
|
| 86 | + | let mut failures: u32 = 0; |
|
| 87 | + | for kind in &[abi::Kind::Events, abi::Kind::Interrupt, abi::Kind::Budget, abi::Kind::Page] { |
|
| 88 | + | let source = install(kind, 0); |
|
| 89 | + | let entry = try! capability::get(&SOURCE, source); |
|
| 90 | + | try probe(SOURCE.owner, source, entry, false) catch err { |
|
| 91 | + | try testing::expect(err == abi::Error::Denied); set failures += 1; |
|
| 92 | + | }; |
|
| 93 | + | } |
|
| 94 | + | let events = install(abi::Kind::Events, abi::TRANSFER); |
|
| 95 | + | let entry = try! capability::get(&SOURCE, events); |
|
| 96 | + | try probe(SOURCE.owner, events, entry, true) catch err { |
|
| 97 | + | try testing::expect(err == abi::Error::Denied); set failures += 1; |
|
| 98 | + | }; |
|
| 99 | + | try testing::expect(failures == 5 and TARGET.slots[0].generation == 1); |
|
| 100 | + | let interrupt = install(abi::Kind::Interrupt, abi::TRANSFER); |
|
| 101 | + | let moved = try! move(interrupt); |
|
| 102 | + | let actual = try! capability::get(&TARGET, moved); |
|
| 103 | + | try testing::expect(actual.kind == abi::Kind::Interrupt); |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Cancelled object work leaves no published capability and restores capacity. |
|
| 107 | + | @test unsafe fn cancellation() throws (testing::TestError) { |
|
| 108 | + | initialize(); |
|
| 109 | + | let source = install(abi::Kind::Page, abi::GRANT | abi::READ); |
|
| 110 | + | let entry = try! capability::get(&SOURCE, source); |
|
| 111 | + | let owner = SOURCE.owner; |
|
| 112 | + | let pending = try! transactions::reserve(&mut TARGET, owner, source, entry, abi::READ as u64, false); |
|
| 113 | + | try testing::expect(TARGET.slots[0].state == slots::State::Reserved); |
|
| 114 | + | transactions::cancel(&mut TARGET, pending); |
|
| 115 | + | try testing::expect(TARGET.slots[0].state == slots::State::Free); |
|
| 116 | + | try testing::expect(try! capability::get(&SOURCE, source) == entry); |
|
| 117 | + | } |
|
| 118 | + | ||
| 119 | + | /// Invalid masks, self-transfers, and budget self-grants preserve table state. |
|
| 120 | + | @test unsafe fn policyBoundaries() throws (testing::TestError) { |
|
| 121 | + | initialize(); |
|
| 122 | + | let source = install(abi::Kind::Budget, abi::GRANT | abi::TRANSFER); |
|
| 123 | + | let entry = try! capability::get(&SOURCE, source); |
|
| 124 | + | let mut failures: u32 = 0; |
|
| 125 | + | try selfProbe(source, entry, 0, true) catch err { |
|
| 126 | + | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
|
| 127 | + | }; |
|
| 128 | + | try selfProbe(source, entry, 0, false) catch err { |
|
| 129 | + | try testing::expect(err == abi::Error::Denied); set failures += 1; |
|
| 130 | + | }; |
|
| 131 | + | try selfProbe(source, entry, 0xffffffffffffffff, false) catch err { |
|
| 132 | + | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
|
| 133 | + | }; |
|
| 134 | + | try testing::expect(failures == 3 and SOURCE.slots[1].generation == 1); |
|
| 135 | + | try testing::expect(try! capability::get(&SOURCE, source) == entry); |
|
| 136 | + | } |
|
| 137 | + | ||
| 138 | + | /// Consume a successful self-directed policy probe without publishing it. |
|
| 139 | + | unsafe fn selfProbe(handle: abi::Handle, entry: capability::Entry, rights: u64, moving: bool) throws (abi::Error) { |
|
| 140 | + | let owner = SOURCE.owner; |
|
| 141 | + | let pending = try transactions::reserve(&mut SOURCE, owner, handle, entry, rights, moving); |
|
| 142 | + | transactions::cancel(&mut SOURCE, pending); |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Only the installed Events handle is protected from drop; attenuated copies can end. |
|
| 146 | + | @test unsafe fn installedEvents() throws (testing::TestError) { |
|
| 147 | + | initialize(); |
|
| 148 | + | let installed = install(abi::Kind::Events, abi::READ | abi::WRITE); |
|
| 149 | + | let entry = try! capability::get(&SOURCE, installed); |
|
| 150 | + | let owner = SOURCE.owner; |
|
| 151 | + | let pending = try! transactions::reserve(&mut SOURCE, owner, installed, entry, abi::READ as u64, false); |
|
| 152 | + | let copy = transactions::grant(&mut SOURCE, pending); |
|
| 153 | + | let mut denied = false; |
|
| 154 | + | try transactions::dropping(&SOURCE, installed, installed) catch err { |
|
| 155 | + | try testing::expect(err == abi::Error::Denied); set denied = true; |
|
| 156 | + | }; |
|
| 157 | + | try testing::expect(denied); |
|
| 158 | + | let allowed = try! transactions::dropping(&SOURCE, copy, installed); |
|
| 159 | + | let removed = try! capability::invalidate(&mut SOURCE, copy); |
|
| 160 | + | try testing::expect(removed == allowed and try! capability::get(&SOURCE, installed) == entry); |
|
| 161 | + | let mut stale = false; |
|
| 162 | + | try transactions::dropping(&SOURCE, copy, installed) catch err { |
|
| 163 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 164 | + | }; |
|
| 165 | + | try testing::expect(stale); |
|
| 166 | + | } |
kernel/kernel/transactions.rad
added
+111 -0
| 1 | + | //! Capability reservations held within one serialized object transaction. |
|
| 2 | + | ||
| 3 | + | use super::abi; |
|
| 4 | + | use super::slots; |
|
| 5 | + | use super::capability; |
|
| 6 | + | ||
| 7 | + | /// Metadata for unpublished authority and its source. |
|
| 8 | + | export record Pending: Copy { |
|
| 9 | + | /// Destination domain generation. |
|
| 10 | + | target: abi::Ref, |
|
| 11 | + | /// Reserved slot in the destination table. |
|
| 12 | + | slot: abi::Ref, |
|
| 13 | + | /// Source domain generation. |
|
| 14 | + | owner: abi::Ref, |
|
| 15 | + | /// Source capability to invalidate on transfer. |
|
| 16 | + | source: abi::Handle, |
|
| 17 | + | /// Validated source entry before attenuation. |
|
| 18 | + | original: capability::Entry, |
|
| 19 | + | /// Attenuated entry to publish after object work succeeds. |
|
| 20 | + | entry: capability::Entry, |
|
| 21 | + | /// True when publication also consumes the source capability. |
|
| 22 | + | moving: bool, |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Unpublished capacity that must be committed or cancelled in the same table. |
|
| 26 | + | export union Reservation: Once { |
|
| 27 | + | /// One destination slot and the authority it will receive. |
|
| 28 | + | Held(Pending), |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Reserve target capacity after delegation checks and rights validation. |
|
| 32 | + | /// The caller supplies a live source entry and keeps both tables serialized |
|
| 33 | + | /// through object validation, lifetime changes, and commit or cancellation. |
|
| 34 | + | export fn reserve(target: &mut capability::Table, owner: abi::Ref, source: abi::Handle, |
|
| 35 | + | original: capability::Entry, rights: u64, moving: bool) -> Reservation throws (abi::Error) |
|
| 36 | + | { |
|
| 37 | + | let requested = try abi::rights(rights); |
|
| 38 | + | let own = owner == target.owner; |
|
| 39 | + | if moving { |
|
| 40 | + | if own { throw abi::Error::InvalidArg; } |
|
| 41 | + | if original.kind == abi::Kind::Events or not abi::permits(original.rights, abi::Rights(abi::TRANSFER)) { |
|
| 42 | + | throw abi::Error::Denied; |
|
| 43 | + | } |
|
| 44 | + | } else { |
|
| 45 | + | if original.kind == abi::Kind::Interrupt or original.kind == abi::Kind::Budget { throw abi::Error::Denied; } |
|
| 46 | + | if not own and (original.kind == abi::Kind::Events or not abi::permits(original.rights, abi::Rights(abi::GRANT))) { |
|
| 47 | + | throw abi::Error::Denied; |
|
| 48 | + | } |
|
| 49 | + | } |
|
| 50 | + | let reservation = try slots::reserve(&mut target.slots[..]) catch err { throw abi::Error::InvalidArg; }; |
|
| 51 | + | match reservation { |
|
| 52 | + | case slots::Reservation::Held(slot) => return Reservation::Held(Pending { |
|
| 53 | + | target: target.owner, slot, owner, source, original, |
|
| 54 | + | entry: capability::Entry { kind: original.kind, object: original.object, |
|
| 55 | + | rights: abi::Rights(*original.rights & *requested) }, |
|
| 56 | + | moving, |
|
| 57 | + | }), |
|
| 58 | + | } |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Check the reserved destination before any publication or cancellation. |
|
| 62 | + | fn require(target: &capability::Table, pending: &Pending) { |
|
| 63 | + | assert target.owner == pending.target; |
|
| 64 | + | assert slots::matches(&target.slots[..], pending.slot, slots::State::Reserved); |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// Restore target capacity after object validation or lifetime acquisition fails. |
|
| 68 | + | export fn cancel(target: &mut capability::Table, reservation: Reservation) { |
|
| 69 | + | match reservation { |
|
| 70 | + | case Reservation::Held(pending) => { |
|
| 71 | + | require(target, &pending); |
|
| 72 | + | try! slots::cancel(&mut target.slots[..], slots::Reservation::Held(pending.slot)); |
|
| 73 | + | }, |
|
| 74 | + | } |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Publish a grant after acquiring its object reference and lifetime exposure. |
|
| 78 | + | export fn grant(target: &mut capability::Table, reservation: Reservation) -> abi::Handle { |
|
| 79 | + | match reservation { |
|
| 80 | + | case Reservation::Held(pending) => { |
|
| 81 | + | require(target, &pending); |
|
| 82 | + | assert not pending.moving; |
|
| 83 | + | return capability::publish(target, slots::Reservation::Held(pending.slot), pending.entry); |
|
| 84 | + | }, |
|
| 85 | + | } |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | /// Publish a transfer after object ownership and exposure updates succeed. |
|
| 89 | + | /// The caller holds serialization over both tables for the complete transaction. |
|
| 90 | + | export fn transfer(source: &mut capability::Table, target: &mut capability::Table, reservation: Reservation) -> abi::Handle { |
|
| 91 | + | match reservation { |
|
| 92 | + | case Reservation::Held(pending) => { |
|
| 93 | + | require(target, &pending); |
|
| 94 | + | assert pending.moving and source.owner == pending.owner; |
|
| 95 | + | assert try! capability::get(source, pending.source) == pending.original; |
|
| 96 | + | let result = capability::publish(target, slots::Reservation::Held(pending.slot), pending.entry); |
|
| 97 | + | let removed = try! capability::invalidate(source, pending.source); |
|
| 98 | + | return result; |
|
| 99 | + | }, |
|
| 100 | + | } |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | /// Validate drop authority before object-specific lifetime release. |
|
| 104 | + | /// installedEvents is the domain's original queue handle, or zero before installation. |
|
| 105 | + | export fn dropping(table: &capability::Table, handle: abi::Handle, installedEvents: abi::Handle) |
|
| 106 | + | -> capability::Entry throws (abi::Error) |
|
| 107 | + | { |
|
| 108 | + | let entry = try capability::get(table, handle); |
|
| 109 | + | if handle == installedEvents { throw abi::Error::Denied; } |
|
| 110 | + | return entry; |
|
| 111 | + | } |