rumpk/hal/cspace.zig

319 lines
8.8 KiB
Zig

// SPEC-020: Capability Space (CSpace) Implementation
// Component: core/security/cspace
// Target: Ground Zero - Phase 1
const std = @import("std");
/// CapType: Closed enumeration of capability types (SPEC-020)
pub const CapType = enum(u8) {
Null = 0,
Entity = 1, // Control over Process/Fiber
Channel = 2, // Access to ION Ring
Memory = 3, // Access to Physical Frame
Interrupt = 4, // IRQ mask/unmask control
Time = 5, // Clock/Timer access
Entropy = 6, // HWRNG access
};
/// Permission flags (SPEC-020)
pub const CapPerms = packed struct(u8) {
read: bool = false,
write: bool = false,
execute: bool = false,
map: bool = false,
delegate: bool = false,
revoke: bool = false,
copy: bool = false,
spawn: bool = false,
};
/// Capability structure (32 bytes, cache-line aligned)
pub const Capability = packed struct {
cap_type: CapType, // 1 byte
perms: CapPerms, // 1 byte
_reserved: u16, // 2 bytes (alignment)
object_id: u64, // 8 bytes (SipHash of resource)
bounds_start: u64, // 8 bytes
bounds_end: u64, // 8 bytes
comptime {
if (@sizeOf(Capability) != 32) {
@compileError("Capability must be exactly 32 bytes");
}
}
/// Create a null capability
pub fn null_cap() Capability {
return .{
.cap_type = .Null,
.perms = .{},
._reserved = 0,
.object_id = 0,
.bounds_start = 0,
.bounds_end = 0,
};
}
/// Check if capability is null
pub fn is_null(self: *const Capability) bool {
return self.cap_type == .Null;
}
/// Validate bounds
pub fn check_bounds(self: *const Capability, addr: u64) bool {
if (self.is_null()) return false;
return addr >= self.bounds_start and addr < self.bounds_end;
}
/// Check permission
pub fn has_perm(self: *const Capability, perm: CapPerms) bool {
const self_bits = @as(u8, @bitCast(self.perms));
const perm_bits = @as(u8, @bitCast(perm));
return (self_bits & perm_bits) == perm_bits;
}
};
/// CSpace: Per-fiber capability table
pub const CSPACE_SIZE = 64; // Maximum capabilities per fiber
pub const CSpace = struct {
slots: [CSPACE_SIZE]Capability,
epoch: u32, // For revocation
fiber_id: u64, // Owner fiber
_padding: u32, // Alignment
/// Initialize empty CSpace
pub fn init(fiber_id: u64) CSpace {
var cs = CSpace{
.slots = undefined,
.epoch = 0,
.fiber_id = fiber_id,
._padding = 0,
};
// Initialize all slots to Null
for (&cs.slots) |*slot| {
slot.* = Capability.null_cap();
}
return cs;
}
/// Find first empty slot
pub fn find_empty_slot(self: *CSpace) ?usize {
for (&self.slots, 0..) |*cap, i| {
if (cap.is_null()) return i;
}
return null;
}
/// Grant capability (insert into CSpace)
pub fn grant(self: *CSpace, cap: Capability) !usize {
const slot = self.find_empty_slot() orelse return error.CSpaceFull;
self.slots[slot] = cap;
return slot;
}
/// Lookup capability by slot index
pub fn lookup(self: *const CSpace, slot: usize) ?*const Capability {
if (slot >= CSPACE_SIZE) return null;
const cap = &self.slots[slot];
if (cap.is_null()) return null;
return cap;
}
/// Revoke capability (set to Null)
pub fn revoke(self: *CSpace, slot: usize) void {
if (slot >= CSPACE_SIZE) return;
self.slots[slot] = Capability.null_cap();
}
/// Revoke all capabilities (epoch-based)
pub fn revoke_all(self: *CSpace) void {
for (&self.slots) |*cap| {
cap.* = Capability.null_cap();
}
self.epoch +%= 1;
}
/// Delegate capability (Move or Copy)
pub fn delegate(
self: *CSpace,
slot: usize,
target: *CSpace,
move: bool,
) !usize {
const cap = self.lookup(slot) orelse return error.InvalidCapability;
// Check DELEGATE permission
if (!cap.has_perm(.{ .delegate = true })) {
return error.NotDelegatable;
}
// Grant to target
const new_slot = try target.grant(cap.*);
// If move (not copy), revoke from source
if (move or !cap.has_perm(.{ .copy = true })) {
self.revoke(slot);
}
return new_slot;
}
};
/// Global CSpace table (one per fiber)
/// This will be integrated with Fiber Control Block in kernel.nim
pub const MAX_FIBERS = 16;
var global_cspaces: [MAX_FIBERS]CSpace = undefined;
var cspaces_initialized: bool = false;
/// Initialize global CSpace table
pub export fn cspace_init() void {
if (cspaces_initialized) return;
for (&global_cspaces, 0..) |*cs, i| {
cs.* = CSpace.init(i);
}
cspaces_initialized = true;
}
/// Get CSpace for fiber
pub export fn cspace_get(fiber_id: u64) ?*CSpace {
if (!cspaces_initialized) return null;
if (fiber_id >= MAX_FIBERS) return null;
return &global_cspaces[fiber_id];
}
/// Grant capability to fiber (C ABI)
pub export fn cspace_grant_cap(
fiber_id: u64,
cap_type: u8,
perms: u8,
object_id: u64,
bounds_start: u64,
bounds_end: u64,
) i32 {
const cs = cspace_get(fiber_id) orelse return -1;
const cap = Capability{
.cap_type = @enumFromInt(cap_type),
.perms = @bitCast(perms),
._reserved = 0,
.object_id = object_id,
.bounds_start = bounds_start,
.bounds_end = bounds_end,
};
const slot = cs.grant(cap) catch return -1;
return @intCast(slot);
}
/// Lookup capability (C ABI)
pub export fn cspace_lookup(fiber_id: u64, slot: usize) ?*const Capability {
const cs = cspace_get(fiber_id) orelse return null;
return cs.lookup(slot);
}
/// Revoke capability (C ABI)
pub export fn cspace_revoke(fiber_id: u64, slot: usize) void {
const cs = cspace_get(fiber_id) orelse return;
cs.revoke(slot);
}
/// Check capability permission (C ABI)
pub export fn cspace_check_perm(fiber_id: u64, slot: usize, perm_bits: u8) bool {
const cs = cspace_get(fiber_id) orelse return false;
const cap = cs.lookup(slot) orelse return false;
const perm: CapPerms = @bitCast(perm_bits);
return cap.has_perm(perm);
}
/// Check if fiber has Channel capability for given channel_id with required permission (C ABI)
/// Scans all CSpace slots for a matching Channel capability by object_id.
pub export fn cspace_check_channel(fiber_id: u64, channel_id: u64, perm_bits: u8) bool {
const cs = cspace_get(fiber_id) orelse return false;
const perm: CapPerms = @bitCast(perm_bits);
for (&cs.slots) |*cap| {
if (cap.cap_type == .Channel and cap.object_id == channel_id and cap.has_perm(perm)) {
return true;
}
}
return false;
}
// Unit tests
test "Capability creation and validation" {
const cap = Capability{
.cap_type = .Channel,
.perms = .{ .read = true, .write = true },
._reserved = 0,
.object_id = 0x1234,
.bounds_start = 0x1000,
.bounds_end = 0x2000,
};
try std.testing.expect(!cap.is_null());
try std.testing.expect(cap.check_bounds(0x1500));
try std.testing.expect(!cap.check_bounds(0x3000));
try std.testing.expect(cap.has_perm(.{ .read = true }));
try std.testing.expect(!cap.has_perm(.{ .execute = true }));
}
test "CSpace operations" {
var cs = CSpace.init(42);
const cap = Capability{
.cap_type = .Memory,
.perms = .{ .read = true, .write = true, .delegate = true },
._reserved = 0,
.object_id = 0xABCD,
.bounds_start = 0,
.bounds_end = 0x1000,
};
// Grant capability
const slot = try cs.grant(cap);
try std.testing.expect(slot == 0);
// Lookup capability
const retrieved = cs.lookup(slot).?;
try std.testing.expect(retrieved.object_id == 0xABCD);
// Revoke capability
cs.revoke(slot);
try std.testing.expect(cs.lookup(slot) == null);
}
test "Delegation" {
var cs1 = CSpace.init(1);
var cs2 = CSpace.init(2);
const cap = Capability{
.cap_type = .Channel,
.perms = .{ .read = true, .delegate = true, .copy = true },
._reserved = 0,
.object_id = 0x5678,
.bounds_start = 0,
.bounds_end = 0xFFFF,
};
const slot1 = try cs1.grant(cap);
// Copy delegation
const slot2 = try cs1.delegate(slot1, &cs2, false);
// Both should have the capability
try std.testing.expect(cs1.lookup(slot1) != null);
try std.testing.expect(cs2.lookup(slot2) != null);
// Move delegation
var cs3 = CSpace.init(3);
const slot3 = try cs2.delegate(slot2, &cs3, true);
// cs2 should no longer have it
try std.testing.expect(cs2.lookup(slot2) == null);
try std.testing.expect(cs3.lookup(slot3) != null);
}