319 lines
8.8 KiB
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);
|
|
}
|