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