kernel: M1 shared-fate — Task.leader id, kill/signal re-keyed to the leader
Every task carries its process leader's id (main task: own id; threads: copied from the spawner; kernel tasks: 0, never followed). process_kill and process_signal resolve any member id to the leader and authorize against the leader's supervisor, making both capabilities per-process. ProcessDescriptor gains the leader field. No fan-out yet (docs/shared-fate-plan.md M1).
This commit is contained in:
@@ -715,17 +715,17 @@ fn systemThreadSpawn(state: *architecture.CpuState) void {
|
||||
null
|
||||
else
|
||||
ipc.resolveHandle(t, exit_handle) orelse return failErr(state, ipc.EBADF);
|
||||
const tid = spawnThreadSupervised(t.address_space, entry, stack_top, arg, t.priority, t.id, exit_endpoint) orelse return fail(state);
|
||||
const tid = spawnThreadSupervised(t.address_space, entry, stack_top, arg, t.priority, t.id, exit_endpoint, t.leader) orelse return fail(state);
|
||||
architecture.setSystemCallResult(state, tid);
|
||||
}
|
||||
|
||||
/// Spawn a thread sharing `address_space`, taking the exit-endpoint reference under the **same**
|
||||
/// lock as the spawn (as `spawnProcessSupervised` does), so the thread cannot die before
|
||||
/// its reference exists. Returns the new thread id, or null on resource exhaustion.
|
||||
fn spawnThreadSupervised(address_space: u64, entry: u64, stack_top: u64, arg: u64, priority: scheduler.Priority, supervisor: u32, exit_endpoint: ?*ipc.Endpoint) ?u32 {
|
||||
fn spawnThreadSupervised(address_space: u64, entry: u64, stack_top: u64, arg: u64, priority: scheduler.Priority, supervisor: u32, exit_endpoint: ?*ipc.Endpoint, leader: u32) ?u32 {
|
||||
const flags = sync.enter();
|
||||
defer sync.leave(flags);
|
||||
const tid = scheduler.spawnUserLocked(address_space, entry, stack_top, arg, priority, "thread", supervisor, if (exit_endpoint) |e| @ptrCast(e) else null) orelse return null;
|
||||
const tid = scheduler.spawnUserLocked(address_space, entry, stack_top, arg, priority, "thread", supervisor, if (exit_endpoint) |e| @ptrCast(e) else null, leader) orelse return null;
|
||||
if (exit_endpoint) |endpoint| endpoint.refcount += 1; // the thread holds it birth-to-death
|
||||
return tid;
|
||||
}
|
||||
@@ -972,7 +972,16 @@ pub fn killProcess(caller_id: u32, target_id: u32) i64 {
|
||||
defer sync.leave(flags);
|
||||
const target = scheduler.taskByIdLocked(target_id) orelse return -ipc.ESRCH;
|
||||
if (target.address_space == 0) return -ipc.ESRCH; // kernel tasks are not processes
|
||||
if (target.supervisor != caller_id) return -ipc.EPERM;
|
||||
// The kill authority is the supervision link of the process's LEADER, so the
|
||||
// capability is per-process, aimed at any member id — a thread's own
|
||||
// `supervisor` (the task that spawned it) grants nothing here
|
||||
// (docs/shared-fate-plan.md M1). Kernel tasks were -ESRCH'd above, so a
|
||||
// leader of 0 is unreachable.
|
||||
const leader = if (target.leader == target.id)
|
||||
target
|
||||
else
|
||||
scheduler.taskByIdLocked(target.leader) orelse return -ipc.ESRCH;
|
||||
if (leader.supervisor != caller_id) return -ipc.EPERM;
|
||||
target.exit_reason = .killed;
|
||||
if (target.state == .running) {
|
||||
target.kill_pending = true;
|
||||
@@ -1088,9 +1097,17 @@ fn systemProcessSignal(state: *architecture.CpuState) void {
|
||||
if (signal > 31) return failErr(state, ipc.EBADF); // not a Signal bit position
|
||||
const flags = sync.enter();
|
||||
defer sync.leave(flags);
|
||||
const target = scheduler.taskByIdLocked(@intCast(id)) orelse return failErr(state, ipc.ESRCH);
|
||||
if (target.address_space == 0) return failErr(state, ipc.ESRCH);
|
||||
if (target.supervisor != t.id and target.id != t.id) return failErr(state, ipc.EPERM);
|
||||
const member = scheduler.taskByIdLocked(@intCast(id)) orelse return failErr(state, ipc.ESRCH);
|
||||
if (member.address_space == 0) return failErr(state, ipc.ESRCH);
|
||||
// Signals address the process: any member id resolves to the LEADER, whose
|
||||
// endpoint the service harness binds. Authority mirrors process_kill (the
|
||||
// leader's supervisor), plus the group may signal itself
|
||||
// (docs/shared-fate-plan.md M1).
|
||||
const target = if (member.leader == member.id)
|
||||
member
|
||||
else
|
||||
scheduler.taskByIdLocked(member.leader) orelse return failErr(state, ipc.ESRCH);
|
||||
if (target.supervisor != t.id and target.id != t.leader) return failErr(state, ipc.EPERM);
|
||||
target.pending_signals |= @as(u32, 1) << @intCast(signal);
|
||||
if (target.signal_endpoint) |raw| {
|
||||
const endpoint: *ipc.Endpoint = @ptrCast(@alignCast(raw));
|
||||
@@ -1765,7 +1782,7 @@ pub fn spawnProcessSupervised(image: []const u8, priority: u3, argv: []const []c
|
||||
architecture.mapUserPageInto(address_space, page_virtual, stack_frame, true, false); // RW + NX
|
||||
}
|
||||
|
||||
const child = scheduler.spawnUserLocked(address_space, parsed.entry, user_sp, 0, priority, argv[0], supervisor, if (exit_endpoint) |endpoint| @ptrCast(endpoint) else null) orelse
|
||||
const child = scheduler.spawnUserLocked(address_space, parsed.entry, user_sp, 0, priority, argv[0], supervisor, if (exit_endpoint) |endpoint| @ptrCast(endpoint) else null, 0) orelse
|
||||
return error.OutOfMemory;
|
||||
// The child holds a reference to its exit endpoint from birth to death. Taken
|
||||
// only now, after nothing can fail; the lock is still held, so the child
|
||||
|
||||
Reference in New Issue
Block a user