Skip to main content

strat9_kernel/syscall/
ipc_port.rs

1//! IPC port syscall handlers.
2//!
3//! Provides capability-enforced wrappers around the low-level port IPC
4//! primitives: create, send, recv, try_recv, call, reply, bind, unbind.
5
6use super::error::SyscallError;
7use crate::{
8    capability::{get_capability_manager, CapId, CapPermissions, ResourceType},
9    ipc::{
10        message::IpcMessage,
11        port::{self, PortId},
12        reply,
13    },
14    memory::{UserSliceRead, UserSliceWrite},
15    process::current_task_clone,
16    silo,
17};
18
19/// SYS_IPC_CREATE_PORT: create an IPC port bound to the current task.
20pub fn sys_ipc_create_port(_flags: u64) -> Result<u64, SyscallError> {
21    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
22
23    // P1 fix: enforce per-process IPC quota.
24    task.process
25        .ipc_quota
26        .try_reserve(1)
27        .map_err(|_| SyscallError::OutOfMemory)?;
28
29    let port_id = port::create_port(task.id);
30    let cap = get_capability_manager().create_capability(
31        ResourceType::IpcPort,
32        port_id.as_u64() as usize,
33        // Owner may bind the port into the namespace (ipc_bind_port requires
34        // grant). No execute; no revoke. Other ops only need read/write.
35        CapPermissions {
36            read: true,
37            write: true,
38            execute: false,
39            grant: true,
40            revoke: false,
41        },
42    );
43    let cap_id = unsafe { (&mut *task.process.capabilities.get()).insert(cap) };
44    Ok(cap_id.as_u64())
45}
46
47/// SYS_IPC_SEND: send one IPC message to a port.
48pub fn sys_ipc_send(port_handle: u64, msg_ptr: u64) -> Result<u64, SyscallError> {
49    silo::enforce_cap_for_current_task(port_handle)?;
50    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
51    let caps = unsafe { &*task.process.capabilities.get() };
52    let required = CapPermissions {
53        read: false,
54        write: true,
55        execute: false,
56        grant: false,
57        revoke: false,
58    };
59    let cap = caps
60        .get_with_permissions(CapId::from_raw(port_handle), required)
61        .ok_or(SyscallError::PermissionDenied)?;
62    if cap.resource_type != ResourceType::IpcPort {
63        return Err(SyscallError::BadHandle);
64    }
65
66    const MSG_SIZE: usize = core::mem::size_of::<IpcMessage>();
67    let user = UserSliceRead::new(msg_ptr, MSG_SIZE)?;
68    let mut buf = [0u8; MSG_SIZE];
69    user.copy_to(&mut buf);
70    let mut msg = crate::ipc::message::ipc_message_from_raw(&buf);
71
72    // P1 fix: inject capability badge instead of raw task ID.
73    msg.sender = cap.badge;
74
75    if msg.flags == 0 {
76        if let Some((sid, _label, _mem_used, _mem_min, _mem_max)) =
77            silo::silo_info_for_task(task.id)
78        {
79            if let Some(snapshot) = silo::list_silos_snapshot()
80                .into_iter()
81                .find(|s| s.id == sid)
82            {
83                let structured_label = crate::ipc::message::IpcLabel {
84                    tier: snapshot.tier as u8,
85                    family: 5,
86                    compartment: sid as u16,
87                };
88                msg.flags = unsafe { core::mem::transmute(structured_label) };
89            }
90        }
91    }
92
93    let port_id = PortId::from_u64(cap.resource as u64);
94    let port_obj = port::get_port(port_id).ok_or(SyscallError::BadHandle)?;
95    port_obj.send(msg).map_err(SyscallError::from)?;
96    Ok(0)
97}
98
99/// SYS_IPC_RECV: block until a message is received from a port.
100pub fn sys_ipc_recv(port_handle: u64, msg_ptr: u64) -> Result<u64, SyscallError> {
101    silo::enforce_cap_for_current_task(port_handle)?;
102    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
103    let caps = unsafe { &*task.process.capabilities.get() };
104    let required = CapPermissions {
105        read: true,
106        write: false,
107        execute: false,
108        grant: false,
109        revoke: false,
110    };
111    let cap = caps
112        .get_with_permissions(CapId::from_raw(port_handle), required)
113        .ok_or(SyscallError::PermissionDenied)?;
114    if cap.resource_type != ResourceType::IpcPort {
115        return Err(SyscallError::BadHandle);
116    }
117
118    // P0 fix: validate user buffer BEFORE consuming the message.
119    const MSG_SIZE: usize = core::mem::size_of::<IpcMessage>();
120    let user = UserSliceWrite::new(msg_ptr, MSG_SIZE)?;
121
122    let port_id = PortId::from_u64(cap.resource as u64);
123    let port_obj = port::get_port(port_id).ok_or(SyscallError::BadHandle)?;
124    let mut msg = port_obj.recv().map_err(SyscallError::from)?;
125
126    // Handle transfer (optional): msg.flags contains a handle in the sender table.
127    if msg.flags != 0 {
128        let sender_id = crate::process::TaskId::from_u64(msg.sender);
129        let sender = crate::process::get_task_by_id(sender_id).ok_or(SyscallError::BadHandle)?;
130        let sender_caps = unsafe { &mut *sender.process.capabilities.get() };
131        let dup = sender_caps
132            .duplicate(CapId::from_raw(msg.flags as u64))
133            .ok_or(SyscallError::PermissionDenied)?;
134
135        let receiver = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
136        let receiver_caps = unsafe { &mut *receiver.process.capabilities.get() };
137        let new_id = super::dispatcher::insert_capability_with_retention(receiver_caps, dup)?;
138        if new_id.as_u64() > u32::MAX as u64 {
139            return Err(SyscallError::InvalidArgument);
140        }
141        msg.flags = new_id.as_u64() as u32;
142    }
143
144    let mut buf = [0u8; MSG_SIZE];
145    crate::ipc::message::ipc_message_to_raw(&msg, &mut buf);
146    user.copy_from(&buf);
147    Ok(0)
148}
149
150/// SYS_IPC_TRY_RECV: non-blocking receive from a port.
151pub fn sys_ipc_try_recv(port_handle: u64, msg_ptr: u64) -> Result<u64, SyscallError> {
152    silo::enforce_cap_for_current_task(port_handle)?;
153    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
154    let caps = unsafe { &*task.process.capabilities.get() };
155    let required = CapPermissions {
156        read: true,
157        write: false,
158        execute: false,
159        grant: false,
160        revoke: false,
161    };
162    let cap = caps
163        .get_with_permissions(CapId::from_raw(port_handle), required)
164        .ok_or(SyscallError::PermissionDenied)?;
165    if cap.resource_type != ResourceType::IpcPort {
166        return Err(SyscallError::BadHandle);
167    }
168
169    // P0 fix: validate user buffer BEFORE consuming the message.
170    const MSG_SIZE: usize = core::mem::size_of::<IpcMessage>();
171    let user = UserSliceWrite::new(msg_ptr, MSG_SIZE)?;
172
173    let port_id = PortId::from_u64(cap.resource as u64);
174    let port_obj = port::get_port(port_id).ok_or(SyscallError::BadHandle)?;
175    let msg_opt = port_obj.try_recv().map_err(SyscallError::from)?;
176
177    let mut msg = match msg_opt {
178        Some(m) => m,
179        None => return Err(SyscallError::Again),
180    };
181
182    // Handle transfer (optional).
183    if msg.flags != 0 {
184        let sender_id = crate::process::TaskId::from_u64(msg.sender);
185        let sender = crate::process::get_task_by_id(sender_id).ok_or(SyscallError::BadHandle)?;
186        let sender_caps = unsafe { &mut *sender.process.capabilities.get() };
187        let dup = sender_caps
188            .duplicate(CapId::from_raw(msg.flags as u64))
189            .ok_or(SyscallError::PermissionDenied)?;
190
191        let receiver = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
192        let receiver_caps = unsafe { &mut *receiver.process.capabilities.get() };
193        let new_id = super::dispatcher::insert_capability_with_retention(receiver_caps, dup)?;
194        if new_id.as_u64() > u32::MAX as u64 {
195            return Err(SyscallError::InvalidArgument);
196        }
197        msg.flags = new_id.as_u64() as u32;
198    }
199
200    let mut buf = [0u8; MSG_SIZE];
201    crate::ipc::message::ipc_message_to_raw(&msg, &mut buf);
202    user.copy_from(&buf);
203    Ok(0)
204}
205
206/// SYS_IPC_CONNECT: open a port by namespace path, returns a capability handle.
207pub fn sys_ipc_connect(path_ptr: u64, path_len: u64) -> Result<u64, SyscallError> {
208    if path_ptr == 0 || path_len == 0 {
209        return Err(SyscallError::Fault);
210    }
211    const MAX_PATH_LEN: usize = 4096;
212    if path_len as usize > MAX_PATH_LEN {
213        return Err(SyscallError::InvalidArgument);
214    }
215    let user = UserSliceRead::new(path_ptr, path_len as usize)?;
216    let bytes = user.read_to_vec();
217    let path = core::str::from_utf8(&bytes).map_err(SyscallError::from)?;
218
219    let (port_raw, _remaining) = crate::namespace::resolve(path).ok_or(SyscallError::NotFound)?;
220    let port_id = PortId::from_u64(port_raw);
221    if port::get_port(port_id).is_none() {
222        return Err(SyscallError::BadHandle);
223    }
224
225    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
226    let cap = get_capability_manager().create_capability(
227        ResourceType::IpcPort,
228        port_raw as usize,
229        CapPermissions {
230            read: true,
231            write: true,
232            execute: false,
233            grant: false,
234            revoke: false,
235        },
236    );
237    let cap_id = unsafe { (&mut *task.process.capabilities.get()).insert(cap) };
238    Ok(cap_id.as_u64())
239}
240
241/// SYS_IPC_CALL: synchronous RPC : send + block until reply arrives.
242pub fn sys_ipc_call(port_handle: u64, msg_ptr: u64) -> Result<u64, SyscallError> {
243    silo::enforce_cap_for_current_task(port_handle)?;
244    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
245    let caps = unsafe { &*task.process.capabilities.get() };
246    let required = CapPermissions {
247        read: false,
248        write: true,
249        execute: false,
250        grant: false,
251        revoke: false,
252    };
253    let cap = caps
254        .get_with_permissions(CapId::from_raw(port_handle), required)
255        .ok_or(SyscallError::PermissionDenied)?;
256    if cap.resource_type != ResourceType::IpcPort {
257        return Err(SyscallError::BadHandle);
258    }
259
260    const MSG_SIZE: usize = core::mem::size_of::<IpcMessage>();
261
262    // P0 fix: validate reply buffer BEFORE sending the request and blocking.
263    let reply_user = UserSliceWrite::new(msg_ptr, MSG_SIZE)?;
264
265    let user = UserSliceRead::new(msg_ptr, MSG_SIZE)?;
266    let mut buf = [0u8; MSG_SIZE];
267    user.copy_to(&mut buf);
268    let mut msg = crate::ipc::message::ipc_message_from_raw(&buf);
269    msg.sender = cap.badge;
270    if msg.flags != 0 {
271        let transfer_required = CapPermissions {
272            read: false,
273            write: false,
274            execute: false,
275            grant: true,
276            revoke: false,
277        };
278        if caps
279            .get_with_permissions(CapId::from_raw(msg.flags as u64), transfer_required)
280            .is_none()
281        {
282            return Err(SyscallError::PermissionDenied);
283        }
284    }
285
286    let port_id = PortId::from_u64(cap.resource as u64);
287    let port_obj = port::get_port(port_id).ok_or(SyscallError::BadHandle)?;
288    let port_owner = port_obj.owner;
289    port_obj.send(msg).map_err(SyscallError::from)?;
290
291    let reply_msg = reply::wait_for_reply(task.id, port_owner);
292    let mut out_buf = [0u8; MSG_SIZE];
293    crate::ipc::message::ipc_message_to_raw(&reply_msg, &mut out_buf);
294    reply_user.copy_from(&out_buf);
295    Ok(0)
296}
297
298/// SYS_IPC_REPLY: send a reply to a caller that used SYS_IPC_CALL.
299pub fn sys_ipc_reply(msg_ptr: u64) -> Result<u64, SyscallError> {
300    if msg_ptr == 0 {
301        return Err(SyscallError::Fault);
302    }
303    const MSG_SIZE: usize = core::mem::size_of::<IpcMessage>();
304    let user = UserSliceRead::new(msg_ptr, MSG_SIZE)?;
305    let mut buf = [0u8; MSG_SIZE];
306    user.copy_to(&mut buf);
307    let msg = crate::ipc::message::ipc_message_from_raw(&buf);
308
309    let target = crate::process::TaskId::from_u64(msg.sender);
310    let responder = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
311    let mut msg = msg;
312
313    // P1 fix: authorization BEFORE handle transfer.
314    // Check that the responder is actually the server the caller is waiting on.
315    match reply::check_authorization(responder.id, target) {
316        Ok(()) => {}
317        Err(reply::DeliverError::NoPendingCall) => return Err(SyscallError::BadHandle),
318        Err(reply::DeliverError::NotResponder) => return Err(SyscallError::PermissionDenied),
319    }
320
321    // Handle transfer (only after authorization succeeds).
322    if msg.flags != 0 {
323        let sender = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
324        let sender_caps = unsafe { &mut *sender.process.capabilities.get() };
325        let dup = sender_caps
326            .duplicate(CapId::from_raw(msg.flags as u64))
327            .ok_or(SyscallError::PermissionDenied)?;
328
329        let receiver = crate::process::get_task_by_id(target).ok_or(SyscallError::BadHandle)?;
330        let receiver_caps = unsafe { &mut *receiver.process.capabilities.get() };
331        let new_id = super::dispatcher::insert_capability_with_retention(receiver_caps, dup)?;
332        if new_id.as_u64() > u32::MAX as u64 {
333            return Err(SyscallError::InvalidArgument);
334        }
335        msg.flags = new_id.as_u64() as u32;
336    }
337
338    reply::deliver_reply(responder.id, target, msg).map_err(|e| match e {
339        reply::DeliverError::NoPendingCall => SyscallError::BadHandle,
340        reply::DeliverError::NotResponder => SyscallError::PermissionDenied,
341    })?;
342    Ok(0)
343}
344
345/// SYS_IPC_BIND_PORT: register a port under a namespace path.
346pub fn sys_ipc_bind_port(
347    port_handle: u64,
348    path_ptr: u64,
349    path_len: u64,
350) -> Result<u64, SyscallError> {
351    silo::enforce_registry_bind_for_current_task()?;
352    silo::enforce_cap_for_current_task(port_handle)?;
353    if path_ptr == 0 || path_len == 0 {
354        return Err(SyscallError::Fault);
355    }
356    const MAX_PATH_LEN: usize = 4096;
357    if path_len as usize > MAX_PATH_LEN {
358        return Err(SyscallError::InvalidArgument);
359    }
360    let user = UserSliceRead::new(path_ptr, path_len as usize)?;
361    let bytes = user.read_to_vec();
362    let path = core::str::from_utf8(&bytes).map_err(SyscallError::from)?;
363
364    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
365    let caps = unsafe { &*task.process.capabilities.get() };
366    let cap = caps
367        .get_with_permissions(
368            CapId::from_raw(port_handle),
369            CapPermissions {
370                read: true,
371                write: true,
372                execute: false,
373                grant: true,
374                revoke: false,
375            },
376        )
377        .ok_or(SyscallError::PermissionDenied)?;
378    if cap.resource_type != ResourceType::IpcPort {
379        return Err(SyscallError::BadHandle);
380    }
381
382    crate::vfs::mount(
383        path,
384        alloc::sync::Arc::new(crate::vfs::IpcScheme::new(PortId::from_u64(
385            cap.resource as u64,
386        ))),
387    )?;
388    let _ = crate::namespace::bind(path, cap.resource as u64);
389    let _ = silo::set_current_silo_label_from_path(path);
390
391    // Bootstrap convenience: if a privileged userspace server binds root `/`
392    // or a strate mountpoint, queue a bootstrap message.
393    let should_bootstrap = path == "/" || path.starts_with("/srv/strate-fs-");
394    if should_bootstrap {
395        let mut seeded_handle: u32 = 0;
396        if let Some(device) = crate::hardware::storage::virtio_block::get_device() {
397            let volume_resource = device as *const _ as usize;
398            let volume_perms = CapPermissions {
399                read: true,
400                write: true,
401                execute: false,
402                grant: true,
403                revoke: true,
404            };
405            let volume_cap = get_capability_manager().create_capability(
406                ResourceType::Volume,
407                volume_resource,
408                volume_perms,
409            );
410            let task_caps = unsafe { &mut *task.process.capabilities.get() };
411            let id = task_caps.insert(volume_cap);
412            let _ = silo::register_current_task_granted_resource(
413                ResourceType::Volume,
414                volume_resource,
415                volume_perms,
416            );
417            if id.as_u64() <= u32::MAX as u64 {
418                seeded_handle = id.as_u64() as u32;
419            }
420            log::info!(
421                "ipc_bind_port('/'): seeded volume capability handle={} for task {:?}",
422                id.as_u64(),
423                task.id
424            );
425        }
426
427        // Send a bootstrap message to the just-bound filesystem server.
428        const BOOTSTRAP_MSG_TYPE: u32 = 0x10;
429        let mut boot_msg = IpcMessage::new(BOOTSTRAP_MSG_TYPE);
430        boot_msg.sender = task.id.as_u64();
431        boot_msg.flags = seeded_handle;
432        let label_owned = silo::current_task_silo_label().unwrap_or_else(|| {
433            if path == "/" {
434                alloc::string::String::from("root")
435            } else {
436                alloc::string::String::from(
437                    path.rsplit('/')
438                        .find(|part| !part.is_empty())
439                        .unwrap_or("default"),
440                )
441            }
442        });
443        let label_bytes = label_owned.as_bytes();
444        let max_len = boot_msg.payload.len().saturating_sub(1);
445        let copy_len = core::cmp::min(label_bytes.len(), max_len);
446        boot_msg.payload[0] = copy_len as u8;
447        if copy_len > 0 {
448            boot_msg.payload[1..1 + copy_len].copy_from_slice(&label_bytes[..copy_len]);
449        }
450
451        let port_id = PortId::from_u64(cap.resource as u64);
452        if let Some(p) = port::get_port(port_id) {
453            if p.send(boot_msg).is_ok() {
454                log::info!(
455                    "ipc_bind_port('{}'): queued bootstrap message (handle={}, label={})",
456                    path,
457                    seeded_handle,
458                    label_owned
459                );
460            } else {
461                log::warn!(
462                    "ipc_bind_port('{}'): failed to queue bootstrap message",
463                    path
464                );
465            }
466        } else {
467            log::warn!(
468                "ipc_bind_port('{}'): bound port disappeared before bootstrap",
469                path
470            );
471        }
472    }
473    Ok(0)
474}
475
476/// SYS_IPC_UNBIND_PORT: remove a namespace binding.
477pub fn sys_ipc_unbind_port(path_ptr: u64, path_len: u64) -> Result<u64, SyscallError> {
478    silo::require_silo_admin()?;
479    if path_ptr == 0 || path_len == 0 {
480        return Err(SyscallError::Fault);
481    }
482    const MAX_PATH_LEN: usize = 4096;
483    if path_len as usize > MAX_PATH_LEN {
484        return Err(SyscallError::InvalidArgument);
485    }
486    let user = UserSliceRead::new(path_ptr, path_len as usize)?;
487    let bytes = user.read_to_vec();
488    let path = core::str::from_utf8(&bytes).map_err(SyscallError::from)?;
489    let _ = crate::namespace::unbind(path);
490    crate::vfs::unmount(path)?;
491    Ok(0)
492}