init: a protocol you were not granted does not exist
The registry consults the open rows it has been parsing since P2, so reaching a contract now takes a grant as well as a binding. A caller without one is answered exactly as it would be for a name nobody ever bound: same status, same empty reply, same absent capability, byte for byte, and no log line on either path — klog_read is ungated, so a line on one and not the other would be the oracle the design set out to remove. Refusal and absence being one answer is what lets a supervisor later narrow, fake or park a child's namespace without the child learning what it was denied. The manifest gains a third permission for a shape the plan did not foresee: attestation is one hop, but the driver tree is three deep — the PS/2 keyboard and mouse are spawned by ps2-bus, which the device manager spawned — so no row could name them and PS/2 input would simply stop. A supervise grant lets a delegate vouch for what its children *reach*, never for what they claim; the bind path is untouched, and the laundering deputy is still refused. The review found the receive side of a rule this track had already written down. Every process holds a sendable handle to the registrar — resolve installs one for anyone who asks — and ipc_reply_wait never asked who owned the endpoint, so a stranger could dequeue there: take the provider endpoints riding bind requests, and answer other clients' opens in the registrar's name. Receiving is the owner's privilege, like binding a signal or a timer; sending remains anyone's. Suite 109/109.
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
# /system/configuration/protocol.csv — who may claim, and who may reach, a name
|
||||
# under /protocol (docs/os-development/protocol-namespace.md).
|
||||
#
|
||||
# init is the registrar: it serves /protocol, and every bind is checked against
|
||||
# this file. It is AUTHORITATIVE — a name no row grants cannot be bound, and a
|
||||
# missing file means nothing may be bound at all.
|
||||
# init is the registrar: it serves /protocol, and every bind AND every open is
|
||||
# checked against this file. It is AUTHORITATIVE — a name no row grants cannot be
|
||||
# bound or reached, and a missing file means nothing may be bound or reached at
|
||||
# all.
|
||||
#
|
||||
# A refused open is answered exactly as a name nobody bound is: -ENOENT, and no
|
||||
# capability. That is not politeness, it is the model — the namespace IS the
|
||||
# restriction, so what a process may not open simply does not exist for it, and
|
||||
# there is no "permission denied" for it to tell apart from "no such contract".
|
||||
# Which is why a missing row here shows up as a client retrying forever rather
|
||||
# than as an error: check this file first, and `readdir /protocol` second.
|
||||
#
|
||||
# '#' starts a comment (whole-line or trailing); blank lines are ignored.
|
||||
# Whitespace around a field is trimmed, so columns may be padded. Four
|
||||
@@ -26,13 +34,21 @@
|
||||
# confer; init's own path means this init; any other path means a
|
||||
# task init spawned itself or one the kernel spawned. Task ids are
|
||||
# monotonic and never reused, so an id cannot be borrowed.
|
||||
# permission bind (provide this contract) | open (speak to it)
|
||||
# permission bind (provide this contract) | open (speak to it) |
|
||||
# supervise (stand in someone else's chain — see below)
|
||||
# name the contract, relative to /protocol
|
||||
#
|
||||
# A trailing '*' on any field matches any tail — how a subtree is granted whole.
|
||||
#
|
||||
# NOTE: 'open' rows are parsed but not yet enforced; every open resolves today.
|
||||
# The milestone that turns them into refusals is P3 (docs/security-track-plan.md).
|
||||
# 'supervise' exists because attestation is one hop deep and the driver tree is
|
||||
# three: the device manager starts the PS/2 bus, and the bus starts the keyboard
|
||||
# and mouse drivers. Init never met the bus, so it cannot vouch for it by
|
||||
# acquaintance — and it must not vouch for it by name, or the laundering deputy
|
||||
# walks straight in. A 'supervise' row is the manifest saying it: a task running
|
||||
# this binary, under this supervisor, may be the supervising task an 'open' row
|
||||
# names, for this contract and no other. It grants the delegate nothing itself,
|
||||
# and it is deliberately open-only — a delegate may vouch for what its children
|
||||
# REACH, never for what they CLAIM, so every bind refusal is untouched by it.
|
||||
#
|
||||
# binary supervisor permission name
|
||||
|
||||
@@ -68,3 +84,72 @@
|
||||
# another fixture did.
|
||||
/test/*, kernel, bind, test/*
|
||||
/test/*, /test/*, bind, test/*
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# open — who may REACH each contract. One row per client per contract; a client
|
||||
# with no row here simply finds the name absent, forever.
|
||||
# ============================================================================
|
||||
|
||||
# --- init's own services ----------------------------------------------------
|
||||
# fat reaches the block device behind the volume it mounts; the compositor
|
||||
# reaches the scanout its driver announced, its own endpoint (the mouse-listener
|
||||
# thread opens /protocol/display like any other client — threads share no
|
||||
# handles), and the input stream that moves the cursor.
|
||||
/system/services/fat, /system/services/init, open, block
|
||||
/system/services/display, /system/services/init, open, scanout
|
||||
/system/services/display, /system/services/init, open, display
|
||||
/system/services/display, /system/services/init, open, input
|
||||
/system/services/display-demo, /system/services/init, open, display
|
||||
|
||||
# --- the same two when the kernel test harness starts them directly ---------
|
||||
/system/services/display, kernel, open, scanout
|
||||
/system/services/display, kernel, open, display
|
||||
/system/services/display, kernel, open, input
|
||||
/system/services/display-demo, kernel, open, display
|
||||
|
||||
# --- the drivers, and the discovery service ---------------------------------
|
||||
# Every driver says hello to the manager that started it — one row for the whole
|
||||
# subtree, because that handshake is what being a driver means. The rest are per
|
||||
# driver: the storage and HID class drivers talk to their controller, the HID
|
||||
# drivers publish into the input stream, and the GPU driver announces its scanout
|
||||
# to the compositor.
|
||||
/system/drivers/*, /system/services/device-manager, open, device-manager
|
||||
/system/services/discovery, /system/services/device-manager, open, device-manager
|
||||
/system/drivers/usb-storage, /system/services/device-manager, open, usb-transfer
|
||||
/system/drivers/usb-hid-keyboard, /system/services/device-manager, open, usb-transfer
|
||||
/system/drivers/usb-hid-keyboard, /system/services/device-manager, open, input
|
||||
/system/drivers/usb-hid-mouse, /system/services/device-manager, open, usb-transfer
|
||||
/system/drivers/usb-hid-mouse, /system/services/device-manager, open, input
|
||||
/system/drivers/virtio-gpu, /system/services/device-manager, open, display
|
||||
|
||||
# --- the PS/2 child drivers, one hop further down ---------------------------
|
||||
# The keyboard and mouse drivers are started by the BUS driver, not by the
|
||||
# device manager — the one three-deep chain in the tree. Init cannot vouch for
|
||||
# the bus by acquaintance (it never started it), so the manifest authorizes it
|
||||
# explicitly, and only for the two contracts its children need.
|
||||
/system/drivers/ps2-bus, /system/services/device-manager, supervise, ps2-bus
|
||||
/system/drivers/ps2-bus, /system/services/device-manager, supervise, input
|
||||
/system/drivers/ps2-keyboard, /system/drivers/ps2-bus, open, ps2-bus
|
||||
/system/drivers/ps2-keyboard, /system/drivers/ps2-bus, open, input
|
||||
/system/drivers/ps2-mouse, /system/drivers/ps2-bus, open, ps2-bus
|
||||
/system/drivers/ps2-mouse, /system/drivers/ps2-bus, open, input
|
||||
|
||||
# --- test fixtures ----------------------------------------------------------
|
||||
# The /protocol/test subtree is theirs whole, the way the bind rows give it to
|
||||
# them. Everything ABOVE that subtree is named one fixture at a time, so a
|
||||
# fixture reaches a system contract only where a scenario needs it — which is
|
||||
# what leaves the rest genuinely absent for the rest of them (the protocol-denied
|
||||
# case asks for one it was not given, and is told there is no such thing).
|
||||
/test/*, kernel, open, test/*
|
||||
/test/*, /test/*, open, test/*
|
||||
/test/*, kernel, open, device-manager
|
||||
/test/*, /system/services/device-manager, open, device-manager
|
||||
/test/system/services/input-source, kernel, open, input
|
||||
/test/system/services/input-test, kernel, open, input
|
||||
|
||||
# The laundering-deputy probe (test/system/services/protocol-registry-test) runs
|
||||
# a grandchild whose supervisor is a fixture nobody authorized — that is the
|
||||
# point of it, and its bind must stay refused. It still has to report the verdict
|
||||
# it got, so its reporting channel, and nothing else, is delegated.
|
||||
/test/*, /test/*, supervise, test/verdict
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 12 and column 15.
|
@@ -332,7 +332,14 @@ fn systemIpcCall(state: *architecture.CpuState) void {
|
||||
/// ipc_reply_wait(handle, reply_ptr, reply_len, receive_ptr, receive_cap) -> receive_len,
|
||||
/// with the sender's badge in the secondary result register (rdx).
|
||||
fn systemIpcReplyWait(state: *architecture.CpuState) void {
|
||||
const endpoint = ipc.resolveHandle(scheduler.current(), architecture.systemCallArg(state, 0)) orelse return failErr(state, ipc.EBADF);
|
||||
const t = scheduler.current();
|
||||
const endpoint = ipc.resolveHandle(t, architecture.systemCallArg(state, 0)) orelse return failErr(state, ipc.EBADF);
|
||||
// Receiving is the owner's privilege, the same rule the notification binders
|
||||
// enforce: a sendable handle means only "you may talk to this". Anything
|
||||
// else and a mount's backend endpoint — which `fs_resolve` installs in every
|
||||
// caller's table — would let a stranger dequeue the requests meant for the
|
||||
// server, taking the capabilities they carry and answering in its name.
|
||||
if (!ipc.ownedBy(endpoint, t)) return failErr(state, ipc.EPERM);
|
||||
var badge: u64 = 0;
|
||||
var received_cap: u64 = abi.no_cap;
|
||||
const r = ipc.replyWait(endpoint, architecture.systemCallArg(state, 1), architecture.systemCallArg(state, 2), architecture.systemCallArg(state, 3), architecture.systemCallArg(state, 4), architecture.systemCallArg(state, 5), &badge, &received_cap);
|
||||
|
||||
@@ -248,6 +248,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
deviceManagerTest(boot_information);
|
||||
} else if (eql(case, "protocol-registry")) {
|
||||
protocolRegistryTest(boot_information);
|
||||
} else if (eql(case, "protocol-denied")) {
|
||||
protocolDeniedTest(boot_information);
|
||||
} else if (eql(case, "reboot")) {
|
||||
rebootTest();
|
||||
} else {
|
||||
@@ -3843,6 +3845,59 @@ fn protocolRegistryTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// P3 — restriction stage one (docs/os-development/protocol-namespace.md). The
|
||||
/// registrar now checks `open` against `/system/configuration/protocol.csv`, and
|
||||
/// a caller with no grant is told exactly what a caller asking for a name nobody
|
||||
/// bound is told.
|
||||
///
|
||||
/// The scenario is the assertion's scaffolding: `/protocol` (init in its registry
|
||||
/// role), the **input service** — which binds a real contract the fixture is
|
||||
/// deliberately not granted — and the fixture. Without a live provider on the
|
||||
/// forbidden name, "refused" and "not bound yet" would be the same observation
|
||||
/// and the case would prove nothing; the fixture reads `/protocol`'s own listing
|
||||
/// to confirm the name is there before it asks for it.
|
||||
///
|
||||
/// The fixture's `protocol-denied: ok` is the marker; each step prints its own
|
||||
/// line, which the harness's ordered regex reads.
|
||||
fn protocolDeniedTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: protocol-denied\n", .{});
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
|
||||
process.setInitialRamdisk(image);
|
||||
check("registry (init) spawned", spawnRegistry(rd));
|
||||
// The provider of the contract the fixture may NOT reach. It needs no
|
||||
// hardware: it binds /protocol/input and waits for subscribers.
|
||||
check("input service spawned", spawnNamed(rd, "input"));
|
||||
check("protocol-denied-test spawned", spawnNamedWithArg(rd, "protocol-denied-test", "run"));
|
||||
|
||||
const pass_marker = "protocol-denied: ok";
|
||||
const fail_marker = "protocol-denied: FAIL";
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 20000;
|
||||
var saw_pass = false;
|
||||
var saw_fail = false;
|
||||
while (architecture.millis() < deadline and !saw_pass and !saw_fail) {
|
||||
if (bufferHas(pass_marker)) saw_pass = true;
|
||||
if (bufferHas(fail_marker)) saw_fail = true;
|
||||
scheduler.yield();
|
||||
}
|
||||
scheduler.setPriority(4);
|
||||
|
||||
check("no step of the restriction contract failed", !saw_fail);
|
||||
check("the fixture completed every restriction assertion", saw_pass);
|
||||
result();
|
||||
}
|
||||
|
||||
fn deviceManagerTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: device-manager\n", .{});
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
|
||||
+106
-13
@@ -35,6 +35,12 @@
|
||||
//! stranger's bytes; the only identity on it is the task id the kernel stamps.
|
||||
//! Content never authorizes (`onPowerEvent`), and neither does a name — the
|
||||
//! registrar attests a caller's supervision by task id (`supervisorSatisfies`).
|
||||
//! - **Absence is the enforcement.** P3: `open` consults the manifest with the
|
||||
//! same attested identity a `bind` does, and a caller with no grant is told
|
||||
//! exactly what a caller asking for a name nobody bound is told — `-ENOENT`,
|
||||
//! and no capability (`onOpen`). Restriction stage one of
|
||||
//! docs/os-development/protocol-namespace.md: what a process cannot open does
|
||||
//! not exist for it, so there is no "permission denied" to distinguish.
|
||||
//! - **A capability that arrives is closed unless it is claimed** (`Arrival`),
|
||||
//! because PID 1's thirty-two handle slots are a resource an unauthenticated
|
||||
//! caller would otherwise be able to spend.
|
||||
@@ -153,7 +159,7 @@ const maximum_restarts = 3;
|
||||
/// init holds.
|
||||
const maximum_name = 64;
|
||||
const maximum_bindings = 16;
|
||||
const maximum_grants = 48;
|
||||
const maximum_grants = 64;
|
||||
|
||||
/// One bound contract: the name, the provider's endpoint (a capability init
|
||||
/// holds and hands to whoever opens the name), and the provenance a diagnostic
|
||||
@@ -177,10 +183,26 @@ const Binding = struct {
|
||||
|
||||
var bindings: [maximum_bindings]Binding = .{Binding{}} ** maximum_bindings;
|
||||
|
||||
/// What a grant row permits: claiming a name, or reaching one. `open` rows are
|
||||
/// parsed and held but not yet enforced — every open resolves in P2, and P3 is
|
||||
/// the milestone that turns these into refusals (docs/security-track-plan.md).
|
||||
const Permission = enum { bind, open };
|
||||
/// What a grant row permits.
|
||||
///
|
||||
/// - `bind` — claim the name, i.e. provide the contract.
|
||||
/// - `open` — reach the name, i.e. speak the contract to whoever provides it.
|
||||
/// - `supervise` — stand in a third task's supervision chain: a task running this
|
||||
/// binary, under this supervisor, may be the supervising task named by an
|
||||
/// `open` row for this contract. It grants the *delegate* nothing itself.
|
||||
///
|
||||
/// `supervise` exists because attestation is deliberately one hop deep
|
||||
/// (`supervisorSatisfies`): init vouches only for tasks it or the kernel started.
|
||||
/// The driver tree is deeper than that — the device manager starts the PS/2 bus,
|
||||
/// and the bus starts the keyboard and mouse drivers — so without a way to say
|
||||
/// "this task is an authorized supervisor", a legitimate grandchild would be
|
||||
/// indistinguishable from a laundering deputy. Naming the delegate in the
|
||||
/// manifest is what tells them apart, and it is the same shape as every other
|
||||
/// row: a binary, the supervisor it must have, and the contract it concerns.
|
||||
/// Deliberately `open`-only — a delegate may vouch for what its children may
|
||||
/// *reach*, never for what they may *claim* — so the bind path's attestation is
|
||||
/// exactly what P2 shipped and every refusal it makes still holds.
|
||||
const Permission = enum { bind, open, supervise };
|
||||
|
||||
/// One row of `/system/configuration/protocol.csv`. Every field may end in `*`,
|
||||
/// which matches any tail — the subtree scoping the design doc describes, and
|
||||
@@ -193,8 +215,9 @@ const Grant = struct {
|
||||
};
|
||||
|
||||
/// Roomier than init.csv's: this manifest carries a row per provider per spawn
|
||||
/// path, its own format documentation, and grows again with the open grants.
|
||||
var protocol_csv: [8192]u8 = undefined;
|
||||
/// path, a row per client per contract it reaches, and its own format
|
||||
/// documentation — which is most of the bytes, and is the point of the file.
|
||||
var protocol_csv: [16384]u8 = undefined;
|
||||
var grants: [maximum_grants]Grant = .{Grant{}} ** maximum_grants;
|
||||
var grant_count: usize = 0;
|
||||
|
||||
@@ -225,6 +248,8 @@ fn loadGrants() void {
|
||||
.bind
|
||||
else if (std.mem.eql(u8, permission, "open"))
|
||||
.open
|
||||
else if (std.mem.eql(u8, permission, "supervise"))
|
||||
.supervise
|
||||
else
|
||||
continue; // an unreadable row grants nothing rather than something wrong
|
||||
grants[grant_count] = .{ .binary = binary, .supervisor = supervisor, .permission = kind, .name = name };
|
||||
@@ -393,6 +418,44 @@ fn granted(identity: Identity, permission: Permission, name: []const u8) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Whether `identity` may reach `name` — `granted(.open, …)`, plus the one hop
|
||||
/// `open` takes that `bind` does not (`Permission.supervise`).
|
||||
///
|
||||
/// The hop is needed because the driver tree is three deep and attestation is
|
||||
/// one: the PS/2 keyboard driver's supervising task is the PS/2 bus driver,
|
||||
/// which the device manager started, which init started. Init cannot vouch for
|
||||
/// the bus by acquaintance — it never met it — so the manifest says so instead,
|
||||
/// and says it per contract: `ps2-bus` may be the supervisor named in an `open`
|
||||
/// grant for `ps2-bus` and for `input`, and for nothing else.
|
||||
fn mayOpen(identity: Identity, name: []const u8) bool {
|
||||
if (granted(identity, .open, name)) return true;
|
||||
return delegatedOpen(identity, name);
|
||||
}
|
||||
|
||||
/// The delegated `open`: the row's supervisor column names the caller's actual
|
||||
/// supervising task by binary, that task is one init cannot vouch for directly,
|
||||
/// and a `supervise` row authorizes it for exactly this contract.
|
||||
///
|
||||
/// The delegate itself is attested the ordinary way (`granted` → strict
|
||||
/// `supervisorSatisfies`), so the chain is still anchored one hop above it in
|
||||
/// init or the kernel and the recursion stops there. Two hops of manifest, never
|
||||
/// an unbounded walk — a laundering deputy is refused at the first hop nobody
|
||||
/// wrote a row for.
|
||||
fn delegatedOpen(identity: Identity, name: []const u8) bool {
|
||||
if (identity.supervisor_task == 0) return false; // a kernel-spawned caller needs no delegate
|
||||
if (identity.supervisor_vouched) return false; // already answered by `granted` above
|
||||
const delegate = identify(identity.supervisor_task) orelse return false;
|
||||
if (!granted(delegate, .supervise, name)) return false;
|
||||
for (grants[0..grant_count]) |grant| {
|
||||
if (grant.permission != .open) continue;
|
||||
if (!matches(grant.binary, identity.binary)) continue;
|
||||
if (!matches(grant.supervisor, identity.supervisor_binary)) continue;
|
||||
if (!matches(grant.name, name)) continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn findBinding(name: []const u8) ?*Binding {
|
||||
for (&bindings) |*binding| {
|
||||
if (binding.used and std.mem.eql(u8, binding.nameSlice(), name)) return binding;
|
||||
@@ -501,7 +564,7 @@ fn serveRegistry(request_bytes: []const u8, reply: []u8, sender: u32, arrived: *
|
||||
// Only `bind` claims a capability; one attached to anything else is closed by
|
||||
// the turn's `defer` in the loop, along with the ones sent to a request that
|
||||
// was too short to name a verb at all.
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.open)) return onOpen(reply, payload);
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.open)) return onOpen(reply, sender, payload);
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.readdir)) return onReaddir(reply, cursor);
|
||||
// Everything else a filesystem answers is meaningless here: `/protocol` holds
|
||||
// contracts, not bytes.
|
||||
@@ -576,12 +639,42 @@ fn onBind(sender: u32, raw_name: []const u8, arrived: *Arrival) i32 {
|
||||
}
|
||||
|
||||
/// `open(name)` -> the provider's endpoint, delivered as the reply's capability.
|
||||
/// A name nothing has bound is `-ENOENT`; in P3 an ungranted one becomes the same
|
||||
/// answer, because absence and refusal are deliberately indistinguishable.
|
||||
fn onOpen(reply: []u8, raw_name: []const u8) usize {
|
||||
///
|
||||
/// **A refusal and an absence are the same answer, and that is the whole point.**
|
||||
/// The namespace is the restriction (docs/os-development/protocol-namespace.md):
|
||||
/// what a process may open is what exists for it, so "you may not have this" and
|
||||
/// "there is no such thing" collapse into one reply — `-ENOENT`, no payload, no
|
||||
/// capability. A caller therefore has no oracle: it cannot use `open` to learn
|
||||
/// that a contract it lacks is bound, and — the reason this matters beyond
|
||||
/// tidiness — stage two's supervisor can refuse, stall for a human, or substitute
|
||||
/// a fake without the child being able to tell which happened.
|
||||
///
|
||||
/// Indistinguishable is a claim about *work done*, not only about the bytes, so
|
||||
/// both questions are asked on every open whatever the first one answers: the
|
||||
/// process table is refreshed, the caller identified, the grants scanned and the
|
||||
/// bindings scanned, and only then is the single verdict formed. Nothing here
|
||||
/// logs, either — `klog_read` is ungated (system/kernel/process.zig), so a line
|
||||
/// written on one branch is a line the refused caller can read, and a serial line
|
||||
/// costs milliseconds it could time. The operator's diagnosis is the pair the
|
||||
/// namespace already publishes on purpose: `readdir` over `/protocol` says what is
|
||||
/// bound, `/system/configuration/protocol.csv` says who may reach it, and the
|
||||
/// client's own retry loop says which one it wanted.
|
||||
///
|
||||
/// (Not constant-time in the cryptographic sense, and not claimed to be: the two
|
||||
/// scans stop at the row they match, and the optimiser is free to sink a pure
|
||||
/// table walk past a branch that discards it. What is removed is the difference a
|
||||
/// caller could actually measure or read — a syscall on one branch and not the
|
||||
/// other, a line in a world-readable log ring, or a serial write costing
|
||||
/// milliseconds.)
|
||||
fn onOpen(reply: []u8, sender: u32, raw_name: []const u8) usize {
|
||||
const name = contractName(raw_name) orelse return answer(reply, -envelope.ENOENT, 0, 0);
|
||||
const binding = findBinding(name) orelse return answer(reply, -envelope.ENOENT, 0, 0);
|
||||
pending_capability = binding.endpoint;
|
||||
refreshProcessTable();
|
||||
const identity = identify(sender);
|
||||
const permitted = if (identity) |who| mayOpen(who, name) else false;
|
||||
const binding = findBinding(name);
|
||||
if (!permitted) return answer(reply, -envelope.ENOENT, 0, 0);
|
||||
const found = binding orelse return answer(reply, -envelope.ENOENT, 0, 0);
|
||||
pending_capability = found.endpoint;
|
||||
return answer(reply, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user