Skip to main content

strat9_kernel/syscall/
chan.rs

1//! Typed MPMC sync-channel syscall handlers.
2//!
3//! Provides bounded multi-producer, multi-consumer channels for
4//! inter-process message passing (IPC-02).
5
6use super::error::SyscallError;
7use crate::{
8    capability::{CapId, ResourceType},
9    ipc::{
10        channel::{self, ChanId},
11        message::IpcMessage,
12    },
13    memory::{UserSliceRead, UserSliceWrite},
14    process::current_task_clone,
15};
16
17const MSG_SIZE: usize = core::mem::size_of::<IpcMessage>();
18
19// ABI contract: userspace hardcodes MSG_SIZE = 256.  If IpcMessage's size
20// changes, this assertion forces a deliberate review of the userspace ABI.
21const _: () = assert!(
22    MSG_SIZE == 256,
23    "IpcMessage size changed : update userspace ABI"
24);
25
26/// SYS_CHAN_CREATE (220): create a bounded sync-channel.
27pub fn sys_chan_create(capacity: u64) -> Result<u64, SyscallError> {
28    let cap = capacity.clamp(1, 1024) as usize;
29
30    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
31
32    // Reserve quota before creating the channel.
33    task.process
34        .ipc_quota
35        .try_reserve(cap)
36        .map_err(|_| SyscallError::OutOfMemory)?;
37
38    let chan_id = match channel::create_channel(cap) {
39        id => id,
40    };
41
42    let caps = unsafe { &mut *task.process.capabilities.get() };
43    let cap_id = crate::capability::CapId::new();
44    let chan_cap = crate::capability::Capability {
45        id: cap_id,
46        permissions: crate::capability::CapPermissions {
47            read: true,
48            write: true,
49            execute: false,
50            grant: true,
51            revoke: false,
52        },
53        resource_type: ResourceType::Channel,
54        resource: chan_id.as_u64() as usize,
55        // Badge defaults to capability ID; receivers see this in msg.sender.
56        // When this capability is granted/delegated, the granter can supply
57        // a custom badge so the receiver can distinguish individual clients.
58        badge: cap_id.as_u64(),
59    };
60    let handle = caps.insert(chan_cap);
61
62    log::debug!(
63        "syscall: CHAN_CREATE(cap={}) => chan={} handle={}",
64        cap,
65        chan_id,
66        handle.as_u64()
67    );
68    Ok(handle.as_u64())
69}
70
71/// SYS_CHAN_SEND (221): send one `IpcMessage` to a channel, blocking if full.
72///
73/// **P1 fix**: The kernel now injects `cap.badge` into `msg.sender` instead
74/// of the raw task ID.  This follows the capability-endpoint model (seL4):
75/// the receiver sees only the badge of the delegation chain, never the
76/// sender's global identity.  A sender cannot forge the badge because the
77/// kernel overwrites `msg.sender` after reading the message from user-space.
78pub fn sys_chan_send(handle: u64, msg_ptr: u64) -> Result<u64, SyscallError> {
79    crate::silo::enforce_cap_for_current_task(handle)?;
80
81    let user_slice = UserSliceRead::new(msg_ptr, MSG_SIZE).map_err(SyscallError::from)?;
82    let mut msg = IpcMessage::new(0);
83    let n = user_slice.copy_to(unsafe {
84        core::slice::from_raw_parts_mut(&mut msg as *mut IpcMessage as *mut u8, MSG_SIZE)
85    });
86    if n != MSG_SIZE {
87        return Err(SyscallError::Fault);
88    }
89
90    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
91    let caps = unsafe { &*task.process.capabilities.get() };
92    let cap = caps
93        .get(CapId::from_raw(handle))
94        .ok_or(SyscallError::BadHandle)?;
95    if cap.resource_type != ResourceType::Channel || !cap.permissions.write {
96        return Err(SyscallError::PermissionDenied);
97    }
98    let chan_id = ChanId::from_u64(cap.resource as u64);
99
100    // Inject the capability badge : the receiver sees this in msg.sender,
101    // not the sender's global task ID.  The badge is set at capability
102    // creation time (defaults to cap_id) and can be overridden via grant.
103    msg.sender = cap.badge;
104
105    let chan = channel::get_channel(chan_id).ok_or(SyscallError::BadHandle)?;
106    chan.send(msg).map_err(SyscallError::from)?;
107
108    Ok(0)
109}
110
111/// SYS_CHAN_RECV (222): receive one `IpcMessage`, blocking if empty.
112
113pub fn sys_chan_recv(handle: u64, msg_ptr: u64) -> Result<u64, SyscallError> {
114    crate::silo::enforce_cap_for_current_task(handle)?;
115
116    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
117    let caps = unsafe { &*task.process.capabilities.get() };
118    let cap = caps
119        .get(CapId::from_raw(handle))
120        .ok_or(SyscallError::BadHandle)?;
121    if cap.resource_type != ResourceType::Channel || !cap.permissions.read {
122        return Err(SyscallError::PermissionDenied);
123    }
124    let chan_id = ChanId::from_u64(cap.resource as u64);
125
126    // Validate the destination buffer BEFORE consuming the message.
127    // If the pointer is invalid, we return EFAULT without touching the queue.
128    let user_slice = UserSliceWrite::new(msg_ptr, MSG_SIZE).map_err(SyscallError::from)?;
129
130    let chan = channel::get_channel(chan_id).ok_or(SyscallError::BadHandle)?;
131    let msg = chan.recv().map_err(SyscallError::from)?;
132
133    let n = user_slice.copy_from(unsafe {
134        core::slice::from_raw_parts(&msg as *const IpcMessage as *const u8, MSG_SIZE)
135    });
136    if n != MSG_SIZE {
137        // Defensive: should not happen after buffer validation, but the
138        // message has already been consumed
139        return Err(SyscallError::Fault);
140    }
141
142    Ok(0)
143}
144
145/// SYS_CHAN_TRY_RECV (223): non-blocking receive.
146///
147/// **P0 fix**: Same as `sys_chan_recv` : validate the user destination buffer
148/// before consuming the message from the queue.
149pub fn sys_chan_try_recv(handle: u64, msg_ptr: u64) -> Result<u64, SyscallError> {
150    crate::silo::enforce_cap_for_current_task(handle)?;
151
152    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
153    let caps = unsafe { &*task.process.capabilities.get() };
154    let cap = caps
155        .get(CapId::from_raw(handle))
156        .ok_or(SyscallError::BadHandle)?;
157    if cap.resource_type != ResourceType::Channel || !cap.permissions.read {
158        return Err(SyscallError::PermissionDenied);
159    }
160    let chan_id = ChanId::from_u64(cap.resource as u64);
161
162    let user_slice = UserSliceWrite::new(msg_ptr, MSG_SIZE).map_err(SyscallError::from)?;
163
164    let chan = channel::get_channel(chan_id).ok_or(SyscallError::BadHandle)?;
165    match chan.try_recv() {
166        Ok(msg) => {
167            let n = user_slice.copy_from(unsafe {
168                core::slice::from_raw_parts(&msg as *const IpcMessage as *const u8, MSG_SIZE)
169            });
170            if n != MSG_SIZE {
171                return Err(SyscallError::Fault);
172            }
173            Ok(0)
174        }
175        Err(e) => Err(SyscallError::from(e)),
176    }
177}
178
179/// SYS_CHAN_CLOSE (224): close a channel handle.
180
181pub fn sys_chan_close(handle: u64) -> Result<u64, SyscallError> {
182    crate::silo::enforce_cap_for_current_task(handle)?;
183
184    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
185    let caps = unsafe { &mut *task.process.capabilities.get() };
186    let cap = caps
187        .get(CapId::from_raw(handle))
188        .ok_or(SyscallError::BadHandle)?;
189    if cap.resource_type != ResourceType::Channel {
190        return Err(SyscallError::BadHandle);
191    }
192    let chan_id = ChanId::from_u64(cap.resource as u64);
193    let has_revoke = cap.permissions.revoke;
194
195    // Look up the channel to get its capacity for quota release.
196    let capacity = channel::get_channel(chan_id)
197        .map(|c| c.capacity())
198        .unwrap_or(0);
199
200    let cap = caps
201        .remove(CapId::from_raw(handle))
202        .ok_or(SyscallError::BadHandle)?;
203    debug_assert_eq!(cap.resource_type, ResourceType::Channel);
204
205    // Release per-process IPC quota for this handle.
206    task.process.ipc_quota.release(capacity);
207
208    if has_revoke {
209        // Full release: decrement global refcount and destroy channel if
210        // this was the last capability referencing it.
211        crate::capability::release_capability(&cap, Some(task.id));
212    } else {
213        // Local-only close: decrement the global refcount without triggering
214        // channel destruction.  The channel stays alive until all other
215        // capabilities (held by other processes) are also dropped.
216        crate::capability::get_capability_manager().revoke_capability(cap.id);
217    }
218
219    log::debug!("syscall: CHAN_CLOSE(handle={}) => chan={}", handle, chan_id);
220    Ok(0)
221}