block: per-sender range confinement — V2a mechanism
The block protocol gains define_range (appended, numbers hold): confine the process named by `badge` to blocks [base, base+count). usb-storage keeps a per-badge range table and, in read/write, translates volume-relative LBAs (base added) and refuses any transfer past the volume end. geometry returns the confined size, so a filesystem mounts against what it may actually touch. The security seam (decision 4, settled): the clamp lives at the PROVIDER, so a channel carries exactly the authority it grants — handing a filesystem the whole disk plus a base offset would let it reach the neighbouring partition. The gate: a confined caller may NOT call define_range, so a filesystem cannot widen its own range or confine anyone; only an unconfined party (the volume manager, whole-device) may. The volume manager defines a filesystem's range before handing it the channel, so the ordering holds by construction. Default (no range for a badge) is the whole device — behaviour-neutral for a single-volume boot and what the volume manager itself uses to probe partitions. The range table is declared through bounds.md as a runaway detector (ours, refuse at limit), not a real-partition cap. Neutral: fat-mount, usb-storage, iommu-usb-storage green. The discrimination fixture (a confined process reads past its range and is refused) follows next.
This commit is contained in:
@@ -37,6 +37,26 @@ pub const Transfer = extern struct {
|
||||
/// How many blocks a transfer actually moved.
|
||||
pub const Transferred = extern struct { count: u32 };
|
||||
|
||||
/// `define_range(badge, base_lba, block_count)`: confine the sender identified by
|
||||
/// `badge` to blocks `[base_lba, base_lba + block_count)`. The volume manager
|
||||
/// calls this for each filesystem process it hands a channel to — the badge is
|
||||
/// the filesystem's kernel-stamped task id, and the range is the partition it
|
||||
/// mounts. A confined sender's read/write LBAs are then volume-relative (the
|
||||
/// driver adds `base_lba`) and a transfer past `block_count` is refused. A
|
||||
/// sender with no range is unconfined (the whole device), the default until the
|
||||
/// volume manager defines one. The clamp lives at the provider because a channel
|
||||
/// must carry exactly the authority it grants (storage-architecture.md): handing
|
||||
/// a filesystem the whole disk plus a base offset would let it reach the
|
||||
/// neighbouring partition. **A confined caller may not call this** — a filesystem
|
||||
/// cannot redefine its own range and escape; only an unconfined party (the
|
||||
/// volume manager) confines others.
|
||||
pub const DefineRange = extern struct {
|
||||
badge: u32,
|
||||
_padding: u32 = 0,
|
||||
base_lba: u64,
|
||||
block_count: u64,
|
||||
};
|
||||
|
||||
pub const Protocol = envelope.Define(.{
|
||||
.name = "block",
|
||||
.version = 1,
|
||||
@@ -60,6 +80,10 @@ pub const Protocol = envelope.Define(.{
|
||||
// makes is revocable by the granter while alive; death remains the
|
||||
// mechanical backstop (storage-architecture.md, the lifecycle rule).
|
||||
.{ .name = "detach" },
|
||||
// define_range(): confine a sender to a block sub-range — the partition
|
||||
// it mounts. See `DefineRange`. Appended, so every verb above keeps its
|
||||
// number.
|
||||
.{ .name = "define_range", .request = DefineRange },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user