Skip to main content

strat9_kernel/silo/
mod.rs

1//! Silo manager (kernel-side, minimal mechanisms only)
2//!
3//! This module provides the core kernel structures and syscalls
4//! to create and manage silos. Policy lives in userspace (silo admin).
5
6use crate::{
7    capability::{get_capability_manager, CapId, CapPermissions, Capability, ResourceType},
8    hardware::storage::{ahci, virtio_block},
9    ipc::port::{self, PortId},
10    memory::{UserSliceRead, UserSliceWrite},
11    process::{current_task_clone, task::Task, TaskId},
12    sync::{FixedQueue, SpinLock},
13    syscall::error::SyscallError,
14};
15use alloc::{
16    boxed::Box,
17    collections::BTreeMap,
18    string::{String, ToString},
19    sync::Arc,
20    vec::Vec,
21};
22use core::sync::atomic::{AtomicU64, Ordering};
23use heapless::Vec as HVec;
24
25// ============================================================================
26// Public ABI structs (repr(C) for syscall boundary)
27// ============================================================================
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
30#[repr(u8)]
31pub enum SiloTier {
32    Critical = 0,
33    System = 1,
34    User = 2,
35}
36
37#[repr(C)]
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
39pub struct SiloId {
40    pub sid: u32,
41    pub tier: SiloTier,
42}
43
44impl SiloId {
45    /// Creates a new instance.
46    pub const fn new(sid: u32) -> Self {
47        let tier = match sid {
48            1..=9 => SiloTier::Critical,
49            10..=999 => SiloTier::System,
50            _ => SiloTier::User,
51        };
52        Self { sid, tier }
53    }
54
55    /// Returns this as u64.
56    pub fn as_u64(&self) -> u64 {
57        self.sid as u64
58    }
59}
60
61#[cfg(feature = "selftest")]
62pub mod silo_test;
63
64use bitflags::bitflags;
65
66bitflags! {
67    #[repr(transparent)]
68    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
69    pub struct ControlMode: u8 {
70        const LIST  = 0b100;
71        const STOP  = 0b010;
72        const SPAWN = 0b001;
73    }
74}
75
76bitflags! {
77    #[repr(transparent)]
78    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
79    pub struct HardwareMode: u8 {
80        const INTERRUPT = 0b100;
81        const IO        = 0b010;
82        const DMA       = 0b001;
83    }
84}
85
86bitflags! {
87    #[repr(transparent)]
88    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
89    pub struct RegistryMode: u8 {
90        const LOOKUP = 0b100;
91        const BIND   = 0b010;
92        const PROXY  = 0b001;
93    }
94}
95
96#[repr(C)]
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct OctalMode {
99    pub control: ControlMode,
100    pub hardware: HardwareMode,
101    pub registry: RegistryMode,
102}
103
104impl OctalMode {
105    /// Builds this from octal.
106    pub const fn from_octal(val: u16) -> Self {
107        Self {
108            control: ControlMode::from_bits_truncate(((val >> 6) & 0o7) as u8),
109            hardware: HardwareMode::from_bits_truncate(((val >> 3) & 0o7) as u8),
110            registry: RegistryMode::from_bits_truncate((val & 0o7) as u8),
111        }
112    }
113
114    /// Returns whether subset of.
115    pub const fn is_subset_of(&self, other: &OctalMode) -> bool {
116        (self.control.bits() & !other.control.bits() == 0)
117            && (self.hardware.bits() & !other.hardware.bits() == 0)
118            && (self.registry.bits() & !other.registry.bits() == 0)
119    }
120
121    /// Performs the pledge operation.
122    pub fn pledge(&mut self, new_mode: OctalMode) -> Result<(), SyscallError> {
123        if !new_mode.is_subset_of(self) {
124            return Err(SyscallError::PermissionDenied); // Escalation attempt
125        }
126        *self = new_mode;
127        Ok(())
128    }
129}
130
131/// Performs the sys silo pledge operation.
132pub fn sys_silo_pledge(mode_val: u64) -> Result<u64, SyscallError> {
133    let new_mode = OctalMode::from_octal(mode_val as u16);
134    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
135
136    let mut mgr = SILO_MANAGER.lock();
137    if let Some(silo_id) = mgr.silo_for_task(task.id) {
138        if let Ok(silo) = mgr.get_mut(silo_id) {
139            silo.mode.pledge(new_mode)?;
140            // Keep the raw ABI view in sync (snapshots and sandbox checks
141            // read config.mode; kernel_pledge_silo does the same).
142            silo.config.mode = mode_val as u16;
143
144            mgr.push_event(SiloEvent {
145                silo_id: silo_id as u64,
146                kind: SiloEventKind::Started, // Re-using Started as "Updated" for now
147                data0: mode_val,
148                data1: 0,
149                tick: crate::process::scheduler::ticks(),
150            });
151            return Ok(0);
152        }
153    }
154    Err(SyscallError::BadHandle)
155}
156
157/// Performs the sys silo unveil operation.
158pub fn sys_silo_unveil(
159    path_ptr: u64,
160    path_len: u64,
161    rights_bits: u64,
162) -> Result<u64, SyscallError> {
163    const MAX_UNVEIL_PATH: usize = 1024;
164    const MAX_UNVEIL_RULES: usize = 128;
165
166    if path_ptr == 0 {
167        return Err(SyscallError::Fault);
168    }
169    let len = usize::try_from(path_len).map_err(|_| SyscallError::InvalidArgument)?;
170    if len == 0 || len > MAX_UNVEIL_PATH {
171        return Err(SyscallError::InvalidArgument);
172    }
173    let user = UserSliceRead::new(path_ptr, len)?;
174    let raw = user.read_to_vec();
175    let path = core::str::from_utf8(&raw).map_err(|_| SyscallError::InvalidArgument)?;
176    if path.is_empty() || !path.starts_with('/') || path.as_bytes().iter().any(|b| *b == 0) {
177        return Err(SyscallError::InvalidArgument);
178    }
179    let path = normalize_unveil_path(path)?;
180    let rights = UnveilRights::from_bits(rights_bits)?;
181
182    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
183    let mut mgr = SILO_MANAGER.lock();
184    let silo_id = mgr.silo_for_task(task.id).ok_or(SyscallError::BadHandle)?;
185    let silo = mgr.get_mut(silo_id)?;
186
187    if let Some(rule) = silo.unveil_rules.iter_mut().find(|r| r.path == path) {
188        rule.rights = rule.rights.intersect(rights);
189        return Ok(0);
190    }
191    if silo.unveil_rules.len() >= MAX_UNVEIL_RULES {
192        return Err(SyscallError::QueueFull);
193    }
194    // Pre-validated: len() < MAX_UNVEIL_RULES, push cannot fail.
195    let _ = silo.unveil_rules.push(UnveilRule { path, rights });
196    Ok(0)
197}
198
199/// Performs the sys silo enter sandbox operation.
200pub fn sys_silo_enter_sandbox() -> Result<u64, SyscallError> {
201    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
202    let mut mgr = SILO_MANAGER.lock();
203    let silo_id = mgr.silo_for_task(task.id).ok_or(SyscallError::BadHandle)?;
204    let silo = mgr.get_mut(silo_id)?;
205    if silo.sandboxed {
206        return Ok(0);
207    }
208    silo.sandboxed = true;
209    silo.mode.registry = RegistryMode::empty();
210    silo.config.mode =
211        ((silo.mode.control.bits() as u16) << 6) | ((silo.mode.hardware.bits() as u16) << 3);
212    Ok(0)
213}
214
215/// Sets the `strate_label` of a silo identified by handle.
216///
217/// Requires silo-admin capability. The label must be non-empty, at most 31
218/// bytes, and contain ONLY ASCII alphanumeric characters, `-`, `_`, or `.`.
219pub fn sys_silo_rename(handle: u64, label_ptr: u64, label_len: u64) -> Result<u64, SyscallError> {
220    require_silo_admin()?;
221    const MAX_LABEL: usize = 31;
222    let len = label_len as usize;
223    if len == 0 || len > MAX_LABEL {
224        return Err(SyscallError::InvalidArgument);
225    }
226    let user_slice = UserSliceRead::new(label_ptr, len)?;
227    let raw = user_slice.read_to_vec();
228    let label = core::str::from_utf8(&raw).map_err(|_| SyscallError::InvalidArgument)?;
229    if !is_valid_label(label) {
230        return Err(SyscallError::InvalidArgument);
231    }
232    let silo_id = resolve_silo_handle(handle, CapPermissions::read_write())?;
233    let mut mgr = SILO_MANAGER.lock();
234    // Reject if another silo already owns this label.
235    if mgr
236        .silos
237        .values()
238        .any(|s| s.id.sid != silo_id && s.strate_label.as_deref() == Some(label))
239    {
240        return Err(SyscallError::AlreadyExists);
241    }
242    let silo = mgr.get_mut(silo_id)?;
243    silo.strate_label = Some(String::from(label));
244    Ok(0)
245}
246
247/// Performs the enforce silo may grant operation.
248pub fn enforce_silo_may_grant() -> Result<(), SyscallError> {
249    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
250    if is_admin_task(&task) {
251        return Ok(());
252    }
253    let mgr = SILO_MANAGER.lock();
254    let Some(silo_id) = mgr.silo_for_task(task.id) else {
255        return Ok(());
256    };
257    let silo = mgr.get(silo_id)?;
258    if silo.sandboxed {
259        return Err(SyscallError::PermissionDenied);
260    }
261    Ok(())
262}
263
264/// Normalise an unveil path: collapse duplicate slashes, reject dot
265/// segments.
266///
267/// `.` and `..` are rejected outright rather than resolved: rule matching
268/// is lexical prefix matching, so a query like `/etc/../secret` would
269/// match an `/etc` rule while the backing filesystem resolves a different
270/// path - a policy bypass waiting for a resolver that handles dot
271/// segments.
272fn normalize_unveil_path(path: &str) -> Result<String, SyscallError> {
273    if !path.starts_with('/') {
274        return Err(SyscallError::InvalidArgument);
275    }
276    let mut out = String::new();
277    let mut prev_slash = false;
278    let mut cur_segment = String::new();
279    for ch in path.chars() {
280        if ch == '/' {
281            if !prev_slash {
282                if cur_segment == "." || cur_segment == ".." {
283                    return Err(SyscallError::InvalidArgument);
284                }
285                out.push('/');
286                cur_segment.clear();
287            }
288            prev_slash = true;
289            continue;
290        }
291        if ch == '\0' {
292            return Err(SyscallError::InvalidArgument);
293        }
294        prev_slash = false;
295        cur_segment.push(ch);
296        out.push(ch);
297    }
298    if cur_segment == "." || cur_segment == ".." {
299        return Err(SyscallError::InvalidArgument);
300    }
301    while out.len() > 1 && out.ends_with('/') {
302        out.pop();
303    }
304    if out.is_empty() {
305        out.push('/');
306    }
307    Ok(out)
308}
309
310/// Performs the path rule matches operation.
311fn path_rule_matches(rule: &str, path: &str) -> bool {
312    if rule == "/" {
313        return true;
314    }
315    if path == rule {
316        return true;
317    }
318    if !path.starts_with(rule) {
319        return false;
320    }
321    let bytes = path.as_bytes();
322    let idx = rule.len();
323    idx < bytes.len() && bytes[idx] == b'/'
324}
325
326/// Performs the enforce path for current task operation.
327pub fn enforce_path_for_current_task(
328    path: &str,
329    want_read: bool,
330    want_write: bool,
331    want_execute: bool,
332) -> Result<(), SyscallError> {
333    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
334    if is_admin_task(&task) {
335        return Ok(());
336    }
337    let path = normalize_unveil_path(path)?;
338    let mgr = SILO_MANAGER.lock();
339    let Some(silo_id) = mgr.silo_for_task(task.id) else {
340        return Ok(());
341    };
342    let silo = mgr.get(silo_id)?;
343    if silo.sandboxed {
344        return Err(SyscallError::PermissionDenied);
345    }
346    if silo.unveil_rules.is_empty() {
347        return Ok(());
348    }
349    for rule in &silo.unveil_rules {
350        if !path_rule_matches(&rule.path, &path) {
351            continue;
352        }
353        if (!want_read || rule.rights.read)
354            && (!want_write || rule.rights.write)
355            && (!want_execute || rule.rights.execute)
356        {
357            return Ok(());
358        }
359    }
360    Err(SyscallError::PermissionDenied)
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364#[repr(C)]
365struct UnveilRights {
366    read: bool,
367    write: bool,
368    execute: bool,
369}
370
371impl UnveilRights {
372    /// Builds this from bits.
373    fn from_bits(bits: u64) -> Result<Self, SyscallError> {
374        if bits & !0x7 != 0 {
375            return Err(SyscallError::InvalidArgument);
376        }
377        Ok(Self {
378            read: (bits & 0x1) != 0,
379            write: (bits & 0x2) != 0,
380            execute: (bits & 0x4) != 0,
381        })
382    }
383
384    /// Performs the intersect operation.
385    fn intersect(self, other: Self) -> Self {
386        Self {
387            read: self.read && other.read,
388            write: self.write && other.write,
389            execute: self.execute && other.execute,
390        }
391    }
392}
393
394#[derive(Debug, Clone)]
395struct UnveilRule {
396    path: String,
397    rights: UnveilRights,
398}
399
400#[repr(u8)]
401#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub enum StrateFamily {
403    SYS = 0,
404    DRV = 1,
405    FS = 2,
406    NET = 3,
407    WASM = 4,
408    USR = 5,
409}
410
411#[repr(u32)]
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub enum SiloState {
414    Created = 0,
415    Loading = 1,
416    Ready = 2,
417    Running = 3,
418    Paused = 4,
419    Stopping = 5,
420    Stopped = 6,
421    Crashed = 7,
422    Zombie = 8,
423    Destroyed = 9,
424}
425
426pub const SILO_FLAG_ADMIN: u64 = 1 << 0;
427pub const SILO_FLAG_GRAPHICS: u64 = 1 << 1;
428pub const SILO_FLAG_WEBRTC_NATIVE: u64 = 1 << 2;
429pub const SILO_FLAG_GRAPHICS_READ_ONLY: u64 = 1 << 3;
430pub const SILO_FLAG_WEBRTC_TURN_FORCE: u64 = 1 << 4;
431
432#[repr(C)]
433#[derive(Debug, Clone, Copy)]
434pub struct SiloConfig {
435    pub mem_min: u64,
436    pub mem_max: u64,
437    pub cpu_shares: u32,
438    pub cpu_quota_us: u64,
439    pub cpu_period_us: u64,
440    pub cpu_affinity_mask: u64,
441    pub max_tasks: u32,
442    pub io_bw_read: u64,
443    pub io_bw_write: u64,
444    pub caps_ptr: u64,
445    pub caps_len: u64,
446    pub flags: u64,
447    pub sid: u32,
448    pub mode: u16,
449    pub family: u8,
450    /// CPU features that this silo requires (bitflags from `CpuFeatures`).
451    pub cpu_features_required: u64,
452    /// CPU features that this silo is allowed to use.
453    pub cpu_features_allowed: u64,
454    /// Effective XCR0 mask (computed from allowed features & host capabilities).
455    pub xcr0_mask: u64,
456    /// Maximum concurrent graphics sessions for this silo (0 = disabled).
457    pub graphics_max_sessions: u16,
458    /// Graphics session time-to-live in seconds.
459    pub graphics_session_ttl_sec: u32,
460    /// Reserved for ABI expansion.
461    pub graphics_reserved: u16,
462}
463
464impl Default for SiloConfig {
465    fn default() -> Self {
466        SiloConfig {
467            mem_min: 0,
468            mem_max: 0,
469            cpu_shares: 0,
470            cpu_quota_us: 0,
471            cpu_period_us: 0,
472            cpu_affinity_mask: 0,
473            max_tasks: 0,
474            io_bw_read: 0,
475            io_bw_write: 0,
476            caps_ptr: 0,
477            caps_len: 0,
478            flags: 0,
479            sid: 42,
480            mode: 0,
481            family: StrateFamily::USR as u8,
482            cpu_features_required: 0,
483            cpu_features_allowed: u64::MAX,
484            xcr0_mask: 0,
485            graphics_max_sessions: 0,
486            graphics_session_ttl_sec: 0,
487            graphics_reserved: 0,
488        }
489    }
490}
491
492impl SiloConfig {
493    /// Performs the validate operation.
494    fn validate(&self) -> Result<(), SyscallError> {
495        if self.mem_min > self.mem_max && self.mem_max != 0 {
496            return Err(SyscallError::InvalidArgument);
497        }
498        if self.cpu_quota_us > 0 && self.cpu_period_us == 0 {
499            return Err(SyscallError::InvalidArgument);
500        }
501        if self.caps_len > MAX_SILO_CAPS as u64 {
502            return Err(SyscallError::InvalidArgument);
503        }
504        if self.caps_len > 0 && self.caps_ptr == 0 {
505            return Err(SyscallError::InvalidArgument);
506        }
507        if self.flags & SILO_FLAG_WEBRTC_NATIVE != 0 && self.flags & SILO_FLAG_GRAPHICS == 0 {
508            return Err(SyscallError::InvalidArgument);
509        }
510        if self.flags & SILO_FLAG_GRAPHICS == 0 {
511            if self.graphics_max_sessions != 0 || self.graphics_session_ttl_sec != 0 {
512                return Err(SyscallError::InvalidArgument);
513            }
514        } else {
515            if self.graphics_max_sessions == 0 {
516                return Err(SyscallError::InvalidArgument);
517            }
518            if self.graphics_session_ttl_sec == 0 {
519                return Err(SyscallError::InvalidArgument);
520            }
521        }
522        Ok(())
523    }
524}
525
526#[repr(C, packed)]
527#[derive(Clone, Copy)]
528pub struct Strat9ModuleHeader {
529    pub magic: [u8; 4], // "CMOD"
530    pub version: u16,
531    pub cpu_arch: u8, // 0 = x86_64
532    pub flags: u32,
533    pub code_offset: u64,
534    pub code_size: u64,
535    pub data_offset: u64,
536    pub data_size: u64,
537    pub bss_size: u64,
538    pub entry_point: u64,
539    pub export_table_offset: u64,
540    pub import_table_offset: u64,
541    pub relocation_table_offset: u64,
542    pub key_id: [u8; 8],
543    pub signature: [u8; 64],
544    /// CPU features required by this module (CpuFeatures bitflags). Header v2+.
545    pub cpu_features_required: u64,
546    pub reserved: [u8; 48],
547}
548
549impl core::fmt::Debug for Strat9ModuleHeader {
550    /// Performs the fmt operation.
551    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
552        // SAFETY: read fields via read_unaligned to avoid UB on packed struct.
553        let version = unsafe { core::ptr::addr_of!(self.version).read_unaligned() };
554        let flags = unsafe { core::ptr::addr_of!(self.flags).read_unaligned() };
555        let entry = unsafe { core::ptr::addr_of!(self.entry_point).read_unaligned() };
556        let code_sz = unsafe { core::ptr::addr_of!(self.code_size).read_unaligned() };
557        let data_sz = unsafe { core::ptr::addr_of!(self.data_size).read_unaligned() };
558        f.debug_struct("Strat9ModuleHeader")
559            .field("magic", &self.magic)
560            .field("version", &version)
561            .field("cpu_arch", &self.cpu_arch)
562            .field("flags", &flags)
563            .field("entry_point", &entry)
564            .field("code_size", &code_sz)
565            .field("data_size", &data_sz)
566            .finish_non_exhaustive()
567    }
568}
569
570#[repr(C)]
571#[derive(Debug, Clone, Copy)]
572pub struct ModuleInfo {
573    pub id: u64,
574    pub format: u32, // 0 = raw/ELF, 1 = CMOD
575    pub flags: u32,
576    pub version: u16,
577    pub cpu_arch: u8,
578    pub reserved: u8,
579    pub code_size: u64,
580    pub data_size: u64,
581    pub bss_size: u64,
582    pub entry_point: u64,
583    pub total_size: u64,
584}
585
586#[repr(u32)]
587#[derive(Debug, Clone, Copy, PartialEq, Eq)]
588pub enum SiloEventKind {
589    Started = 1,
590    Stopped = 2,
591    Killed = 3,
592    Crashed = 4,
593    Paused = 5,
594    Resumed = 6,
595}
596
597#[repr(u64)]
598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599pub enum SiloFaultReason {
600    PageFault = 1,
601    GeneralProtection = 2,
602    InvalidOpcode = 3,
603}
604
605#[repr(C)]
606#[derive(Debug, Clone, Copy)]
607pub struct SiloEvent {
608    pub silo_id: u64,
609    pub kind: SiloEventKind,
610    pub data0: u64,
611    pub data1: u64,
612    pub tick: u64,
613}
614
615// data0 encoding for Crashed:
616// - bits 0..15: fault reason (SiloFaultReason)
617// - bits 16..31: fault subcode (arch-specific)
618// - bits 32..63: reserved
619pub const FAULT_SUBCODE_SHIFT: u64 = 16;
620
621/// Performs the pack fault operation.
622pub fn pack_fault(reason: SiloFaultReason, subcode: u64) -> u64 {
623    (reason as u64) | (subcode << FAULT_SUBCODE_SHIFT)
624}
625
626// ============================================================================
627// Internal kernel structs
628// ============================================================================
629
630#[derive(Debug)]
631struct Silo {
632    id: SiloId,
633    name: String,
634    strate_label: Option<String>,
635    state: SiloState,
636    config: SiloConfig,
637    mode: OctalMode,
638    family: StrateFamily,
639    /// Current memory usage accounted to this silo (bytes).
640    /// This tracks user-space virtual regions reserved/mapped via AddressSpace APIs.
641    mem_usage_bytes: u64,
642    flags: u32,
643    module_id: Option<u64>,
644    tasks: HVec<TaskId, 64>,
645    granted_caps: HVec<u64, 64>,
646    granted_resources: HVec<GrantedResource, 32>,
647    unveil_rules: HVec<UnveilRule, 128>,
648    sandboxed: bool,
649    event_seq: u64,
650    /// Ring buffer capturing debug output for `silo attach`.
651    output_buf: Option<Box<SiloOutputBuf>>,
652}
653
654const SILO_OUTPUT_CAPACITY: usize = 4096;
655
656struct SiloOutputBuf {
657    buf: [u8; SILO_OUTPUT_CAPACITY],
658    head: usize,
659    count: usize,
660}
661
662impl core::fmt::Debug for SiloOutputBuf {
663    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
664        f.debug_struct("SiloOutputBuf")
665            .field("count", &self.count)
666            .finish()
667    }
668}
669
670impl SiloOutputBuf {
671    const fn new() -> Self {
672        Self {
673            buf: [0; SILO_OUTPUT_CAPACITY],
674            head: 0,
675            count: 0,
676        }
677    }
678
679    fn push(&mut self, data: &[u8]) {
680        for &b in data {
681            let tail = (self.head + self.count) % SILO_OUTPUT_CAPACITY;
682            self.buf[tail] = b;
683            if self.count < SILO_OUTPUT_CAPACITY {
684                self.count += 1;
685            } else {
686                self.head = (self.head + 1) % SILO_OUTPUT_CAPACITY;
687            }
688        }
689    }
690
691    fn drain(&mut self) -> Vec<u8> {
692        let mut out = Vec::with_capacity(self.count);
693        for i in 0..self.count {
694            out.push(self.buf[(self.head + i) % SILO_OUTPUT_CAPACITY]);
695        }
696        self.head = 0;
697        self.count = 0;
698        out
699    }
700}
701
702#[derive(Debug, Clone)]
703pub struct SiloSnapshot {
704    pub id: u32,
705    pub tier: SiloTier,
706    pub name: String,
707    pub strate_label: Option<String>,
708    pub state: SiloState,
709    pub task_count: usize,
710    pub mem_usage_bytes: u64,
711    pub mem_min_bytes: u64,
712    pub mem_max_bytes: u64,
713    pub mode: u16,
714    pub graphics_flags: u64,
715    pub graphics_max_sessions: u16,
716    pub graphics_session_ttl_sec: u32,
717}
718
719#[derive(Debug, Clone)]
720pub struct SiloDetailSnapshot {
721    pub base: SiloSnapshot,
722    pub family: StrateFamily,
723    pub sandboxed: bool,
724    pub cpu_shares: u32,
725    pub cpu_affinity_mask: u64,
726    pub max_tasks: u32,
727    pub task_ids: Vec<u64>,
728    pub unveil_rules: Vec<(String, u8)>,
729    pub granted_caps_count: usize,
730    pub cpu_features_required: u64,
731    pub cpu_features_allowed: u64,
732    pub xcr0_mask: u64,
733    pub graphics_flags: u64,
734    pub graphics_max_sessions: u16,
735    pub graphics_session_ttl_sec: u32,
736}
737
738#[derive(Debug, Clone)]
739pub struct SiloEventSnapshot {
740    pub silo_id: u64,
741    pub kind: SiloEventKind,
742    pub data0: u64,
743    pub data1: u64,
744    pub tick: u64,
745}
746
747struct SiloManager {
748    silos: BTreeMap<u32, Box<Silo>>,
749    events: FixedQueue<SiloEvent, SILO_EVENTS_CAPACITY>,
750    task_to_silo: BTreeMap<TaskId, u32>,
751}
752
753const SILO_EVENTS_CAPACITY: usize = 256;
754
755impl SiloManager {
756    /// Creates a new instance.
757    const fn new() -> Self {
758        SiloManager {
759            silos: BTreeMap::new(),
760            events: FixedQueue::new(),
761            task_to_silo: BTreeMap::new(),
762        }
763    }
764
765    /// Creates silo.
766    fn create_silo(&mut self, config: &SiloConfig) -> Result<SiloId, SyscallError> {
767        let id = SiloId::new(config.sid);
768        if self.silos.contains_key(&id.sid) {
769            return Err(SyscallError::AlreadyExists);
770        }
771
772        kernel_check_spawn_invariants(&id, &OctalMode::from_octal(config.mode))?;
773
774        let mut name = String::from("silo-");
775        name.push_str(&id.sid.to_string());
776
777        let family = decode_family(config.family)?;
778
779        let silo = Silo {
780            id,
781            name,
782            strate_label: None,
783            state: SiloState::Created,
784            config: *config,
785            mode: OctalMode::from_octal(config.mode),
786            family,
787            mem_usage_bytes: 0,
788            flags: config.flags as u32,
789            module_id: None,
790            tasks: HVec::new(),
791            granted_caps: HVec::new(),
792            granted_resources: HVec::new(),
793            unveil_rules: HVec::new(),
794            sandboxed: false,
795            event_seq: 0,
796            output_buf: None,
797        };
798
799        self.silos.insert(id.sid, Box::new(silo));
800        Ok(id)
801    }
802
803    /// Returns mut.
804    fn get_mut(&mut self, id: u32) -> Result<&mut Silo, SyscallError> {
805        self.silos
806            .get_mut(&id)
807            .map(Box::as_mut)
808            .ok_or(SyscallError::BadHandle)
809    }
810
811    /// Performs the get operation.
812    fn get(&self, id: u32) -> Result<&Silo, SyscallError> {
813        self.silos
814            .get(&id)
815            .map(Box::as_ref)
816            .ok_or(SyscallError::BadHandle)
817    }
818
819    /// Performs the push event operation.
820    fn push_event(&mut self, ev: SiloEvent) {
821        if self.events.is_full() {
822            let _ = self.events.pop_front();
823        }
824        self.events
825            .push_back(ev)
826            .expect("silo event queue push must succeed after dropping oldest entry");
827    }
828
829    /// Maps task.
830    fn map_task(&mut self, task_id: TaskId, silo_id: u32) {
831        self.task_to_silo.insert(task_id, silo_id);
832    }
833
834    /// Unmaps task.
835    fn unmap_task(&mut self, task_id: TaskId) {
836        self.task_to_silo.remove(&task_id);
837    }
838
839    /// Performs the silo for task operation.
840    fn silo_for_task(&self, task_id: TaskId) -> Option<u32> {
841        if let Some(silo_id) = self.task_to_silo.get(&task_id).copied() {
842            return Some(silo_id);
843        }
844
845        // Critical boot fallback: boot-time registration avoids BTreeMap inserts
846        // while holding SILO_MANAGER to eliminate allocator re-entrancy risk on
847        // the fragile early-init path.
848        self.silos
849            .iter()
850            .find_map(|(sid, silo)| silo.tasks.iter().any(|tid| *tid == task_id).then_some(*sid))
851    }
852}
853
854/// Performs the kernel check spawn invariants operation.
855pub fn kernel_check_spawn_invariants(id: &SiloId, mode: &OctalMode) -> Result<(), SyscallError> {
856    if id.tier == SiloTier::User && !mode.hardware.is_empty() {
857        return Err(SyscallError::PermissionDenied);
858    }
859    if id.tier == SiloTier::User && !mode.control.is_empty() {
860        return Err(SyscallError::PermissionDenied);
861    }
862    Ok(())
863}
864
865/// Performs the decode family operation.
866fn decode_family(raw: u8) -> Result<StrateFamily, SyscallError> {
867    match raw {
868        0 => Ok(StrateFamily::SYS),
869        1 => Ok(StrateFamily::DRV),
870        2 => Ok(StrateFamily::FS),
871        3 => Ok(StrateFamily::NET),
872        4 => Ok(StrateFamily::WASM),
873        5 => Ok(StrateFamily::USR),
874        _ => Err(SyscallError::InvalidArgument),
875    }
876}
877
878static SILO_MANAGER: SpinLock<SiloManager> = SpinLock::new(SiloManager::new());
879static BOOT_REG_IN_PROGRESS: core::sync::atomic::AtomicBool =
880    core::sync::atomic::AtomicBool::new(false);
881
882const SILO_ADMIN_RESOURCE: usize = 0;
883const MAX_SILO_CAPS: usize = 64;
884const MAX_MODULE_BLOB_LEN: usize = 64 * 1024 * 1024; // 64 MiB for large preloaded user modules such as strate-wasm
885const IPC_STREAM_DATA: u32 = 0xFFFF_FFFE;
886const IPC_STREAM_EOF: u32 = 0xFFFF_FFFF;
887const MODULE_FLAG_SIGNED: u32 = 1 << 0;
888const MODULE_FLAG_KERNEL: u32 = 1 << 1;
889
890/// Reads user config.
891fn read_user_config(ptr: u64) -> Result<SiloConfig, SyscallError> {
892    if ptr == 0 {
893        return Err(SyscallError::Fault);
894    }
895    const SIZE: usize = core::mem::size_of::<SiloConfig>();
896    let user = UserSliceRead::new(ptr, SIZE)?;
897    let mut buf = [0u8; SIZE];
898    user.copy_to(&mut buf);
899    // SAFETY: We copied the exact bytes for SiloConfig from userspace.
900    let config = unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const SiloConfig) };
901    Ok(config)
902}
903
904/// Reads caps list.
905fn read_caps_list(ptr: u64, len: u64) -> Result<Vec<u64>, SyscallError> {
906    if len == 0 {
907        return Ok(Vec::new());
908    }
909    if len > MAX_SILO_CAPS as u64 {
910        return Err(SyscallError::InvalidArgument);
911    }
912    let byte_len = len as usize * core::mem::size_of::<u64>();
913    let user = UserSliceRead::new(ptr, byte_len)?;
914    let bytes = user.read_to_vec();
915    let mut out = Vec::with_capacity(len as usize);
916    for chunk in bytes.chunks_exact(8) {
917        let mut arr = [0u8; 8];
918        arr.copy_from_slice(chunk);
919        out.push(u64::from_le_bytes(arr));
920    }
921    Ok(out)
922}
923
924/// Reads module stream from port.
925fn read_module_stream_from_port(
926    port: &alloc::sync::Arc<port::Port>,
927) -> Result<Vec<u8>, SyscallError> {
928    let mut out = Vec::new();
929    loop {
930        let msg = port.recv().map_err(|_| SyscallError::BadHandle)?;
931
932        if msg.msg_type == IPC_STREAM_EOF {
933            break;
934        }
935        if msg.msg_type != IPC_STREAM_DATA {
936            return Err(SyscallError::InvalidArgument);
937        }
938        if msg.flags != 0 {
939            return Err(SyscallError::InvalidArgument);
940        }
941
942        let chunk_len = u16::from_le_bytes([msg.payload[0], msg.payload[1]]) as usize;
943        if chunk_len == 0 {
944            break;
945        }
946        if chunk_len > msg.payload.len() - 2 {
947            return Err(SyscallError::InvalidArgument);
948        }
949        if out.len().saturating_add(chunk_len) > MAX_MODULE_BLOB_LEN {
950            return Err(SyscallError::InvalidArgument);
951        }
952
953        out.extend_from_slice(&msg.payload[2..2 + chunk_len]);
954    }
955    Ok(out)
956}
957
958/// Parses module header.
959fn parse_module_header(data: &[u8]) -> Result<Option<Strat9ModuleHeader>, SyscallError> {
960    const MAGIC: [u8; 4] = *b"CMOD";
961    let header_size = core::mem::size_of::<Strat9ModuleHeader>();
962
963    if data.len() < MAGIC.len() {
964        return Ok(None);
965    }
966    if data[0..4] != MAGIC {
967        return Ok(None);
968    }
969    if data.len() < header_size {
970        return Err(SyscallError::InvalidArgument);
971    }
972
973    // SAFETY: We checked length, and we read unaligned from a byte slice.
974    let header = unsafe { core::ptr::read_unaligned(data.as_ptr() as *const Strat9ModuleHeader) };
975
976    if header.version != 1 && header.version != 2 {
977        return Err(SyscallError::InvalidArgument);
978    }
979    if header.cpu_arch != 0 {
980        return Err(SyscallError::InvalidArgument);
981    }
982
983    let version = unsafe { core::ptr::addr_of!(header.version).read_unaligned() };
984    let req = if version >= 2 {
985        unsafe { core::ptr::addr_of!(header.cpu_features_required).read_unaligned() }
986    } else {
987        0
988    };
989    if req != 0 {
990        let host = crate::arch::cpuid::host();
991        let required = crate::arch::cpuid::CpuFeatures::from_bits_truncate(req);
992        if !host.features.contains(required) {
993            log::warn!(
994                "[cmod] module requires CPU features {:#x} but host has {:#x}",
995                req,
996                host.features.bits()
997            );
998            return Err(SyscallError::InvalidArgument);
999        }
1000    }
1001
1002    let data_len = data.len() as u64;
1003    let code_end = header
1004        .code_offset
1005        .checked_add(header.code_size)
1006        .ok_or(SyscallError::InvalidArgument)?;
1007    let data_end = header
1008        .data_offset
1009        .checked_add(header.data_size)
1010        .ok_or(SyscallError::InvalidArgument)?;
1011    if code_end > data_len || data_end > data_len {
1012        return Err(SyscallError::InvalidArgument);
1013    }
1014    if header.entry_point >= header.code_size && header.code_size != 0 {
1015        return Err(SyscallError::InvalidArgument);
1016    }
1017    if header.export_table_offset > data_len
1018        || header.import_table_offset > data_len
1019        || header.relocation_table_offset > data_len
1020    {
1021        return Err(SyscallError::InvalidArgument);
1022    }
1023
1024    // Segmentation rules: code/data must not overlap and must be page-aligned.
1025    const PAGE_SIZE: u64 = 4096;
1026    if header.code_size > 0 {
1027        if header.code_offset % PAGE_SIZE != 0 || header.code_size % PAGE_SIZE != 0 {
1028            return Err(SyscallError::InvalidArgument);
1029        }
1030    }
1031    if header.data_size > 0 {
1032        if header.data_offset % PAGE_SIZE != 0 || header.data_size % PAGE_SIZE != 0 {
1033            return Err(SyscallError::InvalidArgument);
1034        }
1035    }
1036    let code_range = header.code_offset..code_end;
1037    let data_range = header.data_offset..data_end;
1038    if code_range.start < data_range.end && data_range.start < code_range.end {
1039        return Err(SyscallError::InvalidArgument);
1040    }
1041
1042    // Flags/signature checks (Ed25519 cryptographic verification).
1043    //
1044    // Security: the signed payload is code+data ONLY (after the header).
1045    // The header (containing key_id and signature) is NOT signed. This
1046    // prevents an attacker from embedding a known key_id to trick the
1047    // kernel into looking up a legitimate key while verifying a bad sig.
1048    if header.flags & MODULE_FLAG_SIGNED != 0 {
1049        // Compute offsets from the packed struct layout : never hardcode.
1050        let key_id_offset = core::mem::offset_of!(Strat9ModuleHeader, key_id);
1051        let sig_offset = core::mem::offset_of!(Strat9ModuleHeader, signature);
1052        let header_size = core::mem::size_of::<Strat9ModuleHeader>();
1053
1054        // Payload = everything after the header (code + data sections).
1055        // This is what was actually signed.
1056        let payload = if data.len() > header_size {
1057            &data[header_size..]
1058        } else {
1059            return Err(SyscallError::InvalidArgument);
1060        };
1061
1062        let result = crate::crypto::verify_cmod_signature(data, key_id_offset, sig_offset, payload);
1063
1064        match result {
1065            crate::crypto::VerifyResult::Valid => {
1066                log::info!("[cmod] Ed25519 signature verified");
1067            }
1068            crate::crypto::VerifyResult::Unsigned => {
1069                log::warn!("[cmod] MODULE_FLAG_SIGNED set but signature is zero");
1070                return Err(SyscallError::PermissionDenied);
1071            }
1072            crate::crypto::VerifyResult::KeyNotFound => {
1073                log::warn!("[cmod] no trusted key for id {:02x?}", header.key_id);
1074                return Err(SyscallError::PermissionDenied);
1075            }
1076            crate::crypto::VerifyResult::InvalidSignature => {
1077                log::warn!("[cmod] Ed25519 verification FAILED");
1078                return Err(SyscallError::PermissionDenied);
1079            }
1080        }
1081    }
1082    if header.flags & MODULE_FLAG_KERNEL != 0 {
1083        // Kernel modules are allowed only when loaded by admin (already enforced).
1084    }
1085
1086    Ok(Some(header))
1087}
1088
1089/// Reads u32 le.
1090fn read_u32_le(data: &[u8], offset: usize) -> Result<u32, SyscallError> {
1091    if offset + 4 > data.len() {
1092        return Err(SyscallError::InvalidArgument);
1093    }
1094    let mut buf = [0u8; 4];
1095    buf.copy_from_slice(&data[offset..offset + 4]);
1096    Ok(u32::from_le_bytes(buf))
1097}
1098
1099/// Reads u64 le.
1100fn read_u64_le(data: &[u8], offset: usize) -> Result<u64, SyscallError> {
1101    if offset + 8 > data.len() {
1102        return Err(SyscallError::InvalidArgument);
1103    }
1104    let mut buf = [0u8; 8];
1105    buf.copy_from_slice(&data[offset..offset + 8]);
1106    Ok(u64::from_le_bytes(buf))
1107}
1108
1109/// Performs the resolve export offset operation.
1110fn resolve_export_offset(module: &ModuleImage, ordinal: u64) -> Result<u64, SyscallError> {
1111    let header = module.header.ok_or(SyscallError::InvalidArgument)?;
1112    if header.export_table_offset == 0 {
1113        return Err(SyscallError::NotFound);
1114    }
1115    let table_off = header.export_table_offset as usize;
1116    let count = read_u32_le(module.data.as_slice(), table_off)? as u64;
1117    if ordinal >= count {
1118        return Err(SyscallError::InvalidArgument);
1119    }
1120    // Layout: u32 count + u32 reserved, then u64 entries.
1121    let entries_off = table_off + 8;
1122    let entry_off = entries_off + (ordinal as usize * 8);
1123    let rva = read_u64_le(module.data.as_slice(), entry_off)?;
1124    Ok(rva)
1125}
1126
1127/// Performs the require silo admin operation.
1128pub fn require_silo_admin() -> Result<(), SyscallError> {
1129    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1130    // SAFETY: Current task owns its capability table during syscall execution.
1131    let caps = unsafe { &*task.process.capabilities.get() };
1132    let required = CapPermissions {
1133        read: false,
1134        write: false,
1135        execute: false,
1136        grant: true,
1137        revoke: false,
1138    };
1139
1140    if caps.has_resource_with_permissions(ResourceType::Silo, SILO_ADMIN_RESOURCE, required) {
1141        Ok(())
1142    } else {
1143        Err(SyscallError::PermissionDenied)
1144    }
1145}
1146
1147/// Performs the resolve silo handle operation.
1148fn resolve_silo_handle(handle: u64, required: CapPermissions) -> Result<u32, SyscallError> {
1149    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1150    let caps = unsafe { &*task.process.capabilities.get() };
1151    let cap_id = CapId::from_raw(handle);
1152    let cap = caps.get(cap_id).ok_or(SyscallError::BadHandle)?;
1153
1154    // Ensure this is a Silo capability and permissions are sufficient.
1155    if cap.resource_type != ResourceType::Silo {
1156        return Err(SyscallError::BadHandle);
1157    }
1158
1159    if (!required.read || cap.permissions.read)
1160        && (!required.write || cap.permissions.write)
1161        && (!required.execute || cap.permissions.execute)
1162        && (!required.grant || cap.permissions.grant)
1163        && (!required.revoke || cap.permissions.revoke)
1164    {
1165        Ok(cap.resource as u32)
1166    } else {
1167        Err(SyscallError::PermissionDenied)
1168    }
1169}
1170
1171// ============================================================================
1172// Module registry (temporary blob store for .cmod/ELF)
1173// ============================================================================
1174
1175#[derive(Debug, Clone)]
1176enum ModuleData {
1177    Owned(Arc<[u8]>),
1178    Static(&'static [u8]),
1179}
1180
1181impl ModuleData {
1182    fn as_slice(&self) -> &[u8] {
1183        match self {
1184            ModuleData::Owned(data) => data,
1185            ModuleData::Static(data) => data,
1186        }
1187    }
1188
1189    fn len(&self) -> usize {
1190        self.as_slice().len()
1191    }
1192}
1193
1194#[derive(Debug)]
1195struct ModuleImage {
1196    id: u64,
1197    data: ModuleData,
1198    header: Option<Strat9ModuleHeader>,
1199}
1200
1201struct ModuleRegistry {
1202    modules: BTreeMap<u64, ModuleImage>,
1203    next_id: u64,
1204}
1205
1206impl ModuleRegistry {
1207    /// Creates a new instance.
1208    const fn new() -> Self {
1209        ModuleRegistry {
1210            modules: BTreeMap::new(),
1211            next_id: 1,
1212        }
1213    }
1214
1215    // Both storage paths share the same namespace. Never recycle IDs: stale
1216    // module capabilities must not resolve to a later registration.
1217    fn allocate_id(&mut self) -> Result<u64, SyscallError> {
1218        let id = self.next_id;
1219        self.next_id = id.checked_add(1).ok_or(SyscallError::OutOfMemory)?;
1220        Ok(id)
1221    }
1222
1223    /// Performs the register operation.
1224    fn register(&mut self, data: Vec<u8>) -> Result<u64, SyscallError> {
1225        let header = parse_module_header(&data)?;
1226        let id = self.allocate_id()?;
1227        self.modules.insert(
1228            id,
1229            ModuleImage {
1230                id,
1231                data: ModuleData::Owned(Arc::from(data.into_boxed_slice())),
1232                header,
1233            },
1234        );
1235        Ok(id)
1236    }
1237
1238    fn register_static(&mut self, data: &'static [u8]) -> Result<u64, SyscallError> {
1239        let header = parse_module_header(data)?;
1240        let id = self.allocate_id()?;
1241        self.modules.insert(
1242            id,
1243            ModuleImage {
1244                id,
1245                data: ModuleData::Static(data),
1246                header,
1247            },
1248        );
1249        Ok(id)
1250    }
1251
1252    /// Performs the get operation.
1253    fn get(&self, id: u64) -> Option<&ModuleImage> {
1254        self.modules.get(&id)
1255    }
1256
1257    /// Performs the remove operation.
1258    fn remove(&mut self, id: u64) -> Option<ModuleImage> {
1259        self.modules.remove(&id)
1260    }
1261}
1262
1263static MODULE_REGISTRY: SpinLock<ModuleRegistry> = SpinLock::new(ModuleRegistry::new());
1264
1265/// Charge memory usage against a task's silo quota (if any).
1266///
1267/// Uses `try_lock`: callers sit on memory-mapping paths that can execute
1268/// while `SILO_MANAGER` is held elsewhere on the same CPU (e.g. a snapshot
1269/// helper allocating under the lock). A blocking acquisition there would
1270/// self-deadlock the kernel; skipping accounting under contention is the
1271/// documented best-effort trade-off (same policy as
1272/// [`try_silo_id_for_task`]).
1273fn charge_task_silo_memory(task_id: TaskId, bytes: u64) -> Result<(), SyscallError> {
1274    if bytes == 0 {
1275        return Ok(());
1276    }
1277    let Some(mut mgr) = SILO_MANAGER.try_lock() else {
1278        return Ok(());
1279    };
1280    let Some(silo_id) = mgr.silo_for_task(task_id) else {
1281        return Ok(());
1282    };
1283    let silo = mgr.get_mut(silo_id)?;
1284    let next = silo
1285        .mem_usage_bytes
1286        .checked_add(bytes)
1287        .ok_or(SyscallError::OutOfMemory)?;
1288    if silo.config.mem_max != 0 && next > silo.config.mem_max {
1289        return Err(SyscallError::OutOfMemory);
1290    }
1291    silo.mem_usage_bytes = next;
1292    Ok(())
1293}
1294
1295/// Release memory usage from a task's silo quota (if any).
1296///
1297/// Best-effort `try_lock`, mirroring [`charge_task_silo_memory`].
1298fn release_task_silo_memory(task_id: TaskId, bytes: u64) {
1299    if bytes == 0 {
1300        return;
1301    }
1302    let Some(mut mgr) = SILO_MANAGER.try_lock() else {
1303        return;
1304    };
1305    let Some(silo_id) = mgr.silo_for_task(task_id) else {
1306        return;
1307    };
1308    if let Ok(silo) = mgr.get_mut(silo_id) {
1309        silo.mem_usage_bytes = silo.mem_usage_bytes.saturating_sub(bytes);
1310    }
1311}
1312
1313/// Charge memory usage against the current task's silo quota (if any).
1314///
1315/// Returns `OutOfMemory` when charging would exceed `SiloConfig.mem_max`.
1316/// Tasks that are not part of a silo are ignored.
1317pub fn charge_current_task_memory(bytes: u64) -> Result<(), SyscallError> {
1318    let Some(task) = crate::process::scheduler::current_task_clone_try() else {
1319        // Boot-time/kernel contexts may have no current task.
1320        // Also avoid deadlock when scheduler lock is already held in cleanup paths.
1321        return Ok(());
1322    };
1323    charge_task_silo_memory(task.id, bytes)
1324}
1325
1326/// Release memory usage from the current task's silo quota (if any).
1327///
1328/// Tasks that are not part of a silo are ignored.
1329pub fn release_current_task_memory(bytes: u64) {
1330    if let Some(task) = crate::process::scheduler::current_task_clone_try() {
1331        release_task_silo_memory(task.id, bytes);
1332    }
1333}
1334
1335/// Performs the extract strate label operation.
1336fn extract_strate_label(path: &str) -> Option<String> {
1337    let prefix = "/srv/strate-fs-";
1338    let rest = path.strip_prefix(prefix)?;
1339    let mut parts = rest.split('/').filter(|p| !p.is_empty());
1340    let _strate_type = parts.next()?;
1341    let label = parts.next()?;
1342    if label.is_empty() || parts.next().is_some() {
1343        return None;
1344    }
1345    Some(String::from(label))
1346}
1347
1348/// Performs the sanitize label operation.
1349fn sanitize_label(raw: &str) -> String {
1350    let mut out = String::new();
1351    for b in raw.bytes().take(31) {
1352        let ok = (b as char).is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.';
1353        out.push(if ok { b as char } else { '_' });
1354    }
1355    if out.is_empty() {
1356        String::from("default")
1357    } else {
1358        out
1359    }
1360}
1361
1362/// Returns whether valid label.
1363fn is_valid_label(raw: &str) -> bool {
1364    if raw.is_empty() || raw.len() > 31 {
1365        return false;
1366    }
1367    raw.bytes()
1368        .all(|b| (b as char).is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.')
1369}
1370
1371/// Sets current silo label from path.
1372pub fn set_current_silo_label_from_path(path: &str) -> Result<(), SyscallError> {
1373    let Some(label) = extract_strate_label(path) else {
1374        return Ok(());
1375    };
1376    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1377    let mut mgr = SILO_MANAGER.lock();
1378    let Some(silo_id) = mgr.silo_for_task(task.id) else {
1379        return Ok(());
1380    };
1381    let silo = mgr.get_mut(silo_id)?;
1382    // Do not overwrite a label that was already set (e.g. by kernel_spawn_strate).
1383    // The spawner's requested label takes precedence over the default path-derived one.
1384    if silo.strate_label.is_none() {
1385        silo.strate_label = Some(label);
1386    }
1387    Ok(())
1388}
1389
1390/// Performs the current task silo label operation.
1391pub fn current_task_silo_label() -> Option<String> {
1392    let task = current_task_clone()?;
1393    let mgr = SILO_MANAGER.lock();
1394    let silo_id = mgr.silo_for_task(task.id)?;
1395    let silo = mgr.get(silo_id).ok()?;
1396    silo.strate_label.clone()
1397}
1398
1399/// Performs the list silos snapshot operation.
1400pub fn list_silos_snapshot() -> Vec<SiloSnapshot> {
1401    let mgr = SILO_MANAGER.lock();
1402    mgr.silos
1403        .values()
1404        .map(|s| SiloSnapshot {
1405            id: s.id.sid,
1406            tier: s.id.tier,
1407            name: s.name.clone(),
1408            strate_label: s.strate_label.clone(),
1409            state: s.state,
1410            task_count: s.tasks.len(),
1411            mem_usage_bytes: s.mem_usage_bytes,
1412            mem_min_bytes: s.config.mem_min,
1413            mem_max_bytes: s.config.mem_max,
1414            mode: s.config.mode,
1415            graphics_flags: s.config.flags
1416                & (SILO_FLAG_GRAPHICS
1417                    | SILO_FLAG_WEBRTC_NATIVE
1418                    | SILO_FLAG_GRAPHICS_READ_ONLY
1419                    | SILO_FLAG_WEBRTC_TURN_FORCE),
1420            graphics_max_sessions: s.config.graphics_max_sessions,
1421            graphics_session_ttl_sec: s.config.graphics_session_ttl_sec,
1422        })
1423        .collect()
1424}
1425
1426/// Return silo identity + memory accounting for a task, if the task belongs to a silo.
1427///
1428/// Tuple layout:
1429/// - silo id (u32)
1430/// - optional label
1431/// - current usage bytes
1432/// - configured minimum bytes
1433/// - configured maximum bytes (0 = unlimited)
1434/// Best-effort, non-blocking silo lookup for allocator-internal accounting.
1435///
1436/// Uses `try_lock` so that callers running under IRQs-disabled conditions
1437/// (e.g. inside vmalloc) do not deadlock when SILO_MANAGER is already held
1438/// by an outer call on the same CPU.  Returns `None` if the lock is contended
1439/// or if the task is not registered in any silo.
1440pub fn try_silo_id_for_task(task_id: TaskId) -> Option<u32> {
1441    SILO_MANAGER.try_lock()?.silo_for_task(task_id)
1442}
1443
1444pub fn silo_info_for_task(task_id: TaskId) -> Option<(u32, Option<String>, u64, u64, u64)> {
1445    let mgr = SILO_MANAGER.lock();
1446    let silo_id = mgr.silo_for_task(task_id)?;
1447    let silo = mgr.get(silo_id).ok()?;
1448    Some((
1449        silo.id.sid,
1450        silo.strate_label.clone(),
1451        silo.mem_usage_bytes,
1452        silo.config.mem_min,
1453        silo.config.mem_max,
1454    ))
1455}
1456
1457/// Performs the resolve volume resource from dev path operation.
1458fn resolve_volume_resource_from_dev_path(dev_path: &str) -> Result<usize, SyscallError> {
1459    match dev_path {
1460        "/dev/sda" => ahci::get_device()
1461            .map(|d| d as *const _ as usize)
1462            .ok_or(SyscallError::NotFound),
1463        "/dev/vda" => virtio_block::get_device()
1464            .map(|d| d as *const _ as usize)
1465            .ok_or(SyscallError::NotFound),
1466        _ => Err(SyscallError::NotFound),
1467    }
1468}
1469
1470/// Compute the effective XCR0 mask for a silo from its allowed CPU features.
1471fn compute_silo_xcr0(config: &SiloConfig) -> u64 {
1472    use crate::arch::cpuid::{xcr0_for_features, CpuFeatures};
1473    let allowed = CpuFeatures::from_bits_truncate(config.cpu_features_allowed);
1474    xcr0_for_features(allowed)
1475}
1476
1477/// Performs the kernel spawn strate operation.
1478pub fn kernel_spawn_strate(
1479    elf_data: &[u8],
1480    label: Option<&str>,
1481    dev_path: Option<&str>,
1482) -> Result<u32, SyscallError> {
1483    let module_id = {
1484        let mut registry = MODULE_REGISTRY.lock();
1485        registry.register(elf_data.to_vec())?
1486    };
1487
1488    let silo_id = {
1489        let mut mgr = SILO_MANAGER.lock();
1490        // For kernel_spawn_strate (manual command), we auto-assign SID > 1000.
1491        // In a production system, this would follow the "42" rule from Init.
1492        let mut sid = 1000u32;
1493        while mgr.silos.contains_key(&sid) {
1494            sid = sid.checked_add(1).ok_or(SyscallError::OutOfMemory)?;
1495        }
1496
1497        let id = SiloId::new(sid);
1498        let requested_label = label
1499            .map(sanitize_label)
1500            .unwrap_or_else(|| alloc::format!("inst-{}", id.sid));
1501
1502        if mgr
1503            .silos
1504            .values()
1505            .any(|s| s.strate_label.as_deref() == Some(requested_label.as_str()))
1506        {
1507            return Err(SyscallError::AlreadyExists);
1508        }
1509
1510        let mut cfg = SiloConfig {
1511            sid: id.sid,
1512            mode: 0o000,
1513            family: StrateFamily::USR as u8,
1514            ..SiloConfig::default()
1515        };
1516        cfg.xcr0_mask = compute_silo_xcr0(&cfg);
1517
1518        let silo = Silo {
1519            id,
1520            name: alloc::format!("silo-{}", id.sid),
1521            strate_label: Some(requested_label),
1522            state: SiloState::Ready,
1523            config: cfg,
1524            mode: OctalMode::from_octal(0),
1525            family: StrateFamily::USR,
1526            mem_usage_bytes: 0,
1527            flags: 0,
1528            module_id: Some(module_id),
1529            tasks: HVec::new(),
1530            granted_caps: HVec::new(),
1531            granted_resources: HVec::new(),
1532            unveil_rules: HVec::new(),
1533            sandboxed: false,
1534            event_seq: 0,
1535            output_buf: None,
1536        };
1537
1538        mgr.silos.insert(id.sid, Box::new(silo));
1539        id.sid
1540    };
1541
1542    let module_data = {
1543        let registry = MODULE_REGISTRY.lock();
1544        let module = registry.get(module_id).ok_or(SyscallError::BadHandle)?;
1545        module.data.clone()
1546    };
1547
1548    let mut seed_caps = Vec::new();
1549    seed_caps.push(create_silo_admin_capability());
1550    if let Some(path) = dev_path {
1551        let resource = resolve_volume_resource_from_dev_path(path)?;
1552        let cap = get_capability_manager().create_capability(
1553            ResourceType::Volume,
1554            resource,
1555            CapPermissions {
1556                read: true,
1557                write: true,
1558                execute: false,
1559                grant: true,
1560                revoke: true,
1561            },
1562        );
1563        seed_caps.push(cap);
1564    }
1565
1566    let display = {
1567        let mgr = SILO_MANAGER.lock();
1568        let silo = mgr.get(silo_id)?;
1569        silo.strate_label
1570            .clone()
1571            .unwrap_or_else(|| alloc::format!("silo-{}", silo.id.sid))
1572    };
1573    let task_name: &'static str =
1574        Box::leak(alloc::format!("silo-{}/strate-admin-{}", silo_id, display).into_boxed_str());
1575    let task =
1576        crate::process::elf::load_elf_task_with_caps(module_data.as_slice(), task_name, &seed_caps)
1577            .map_err(|_| SyscallError::InvalidArgument)?;
1578    let task_id = task.id;
1579
1580    let mut mgr = SILO_MANAGER.lock();
1581    {
1582        let silo = mgr.get_mut(silo_id)?;
1583        if let Err(task) = silo.tasks.push(task_id) {
1584            log::error!(
1585                "[silo] tasks full (capacity=64), task {} not registered",
1586                task.0
1587            );
1588        }
1589        silo.state = SiloState::Running;
1590        let fpu_xcr0 = unsafe { (*task.fpu_state.get()).xcr0_mask };
1591        let effective_xcr0 = (silo.config.xcr0_mask & fpu_xcr0).max(0x3);
1592        task.xcr0_mask
1593            .store(effective_xcr0, core::sync::atomic::Ordering::Relaxed);
1594        // Propagate silo CPU affinity to task for scheduler placement.
1595        let affinity = silo.config.cpu_affinity_mask;
1596        if affinity != 0 {
1597            task.affinity_mask
1598                .store(affinity, core::sync::atomic::Ordering::Relaxed);
1599        }
1600    }
1601    mgr.map_task(task_id, silo_id);
1602    mgr.push_event(SiloEvent {
1603        silo_id: silo_id.into(),
1604        kind: SiloEventKind::Started,
1605        data0: 0,
1606        data1: 0,
1607        tick: crate::process::scheduler::ticks(),
1608    });
1609    drop(mgr);
1610    crate::process::add_task(task);
1611    Ok(silo_id)
1612}
1613
1614/// Performs the resolve selector to silo id operation.
1615fn resolve_selector_to_silo_id(selector: &str, mgr: &SiloManager) -> Result<u32, SyscallError> {
1616    if let Ok(id) = selector.parse::<u32>() {
1617        if mgr.silos.contains_key(&id) {
1618            return Ok(id);
1619        }
1620        return Err(SyscallError::NotFound);
1621    }
1622    let mut found: Option<u32> = None;
1623    for s in mgr.silos.values() {
1624        if s.strate_label.as_deref() == Some(selector) {
1625            if found.is_some() {
1626                return Err(SyscallError::InvalidArgument);
1627            }
1628            found = Some(s.id.sid);
1629        }
1630    }
1631    found.ok_or(SyscallError::NotFound)
1632}
1633
1634/// Performs the kernel stop silo operation.
1635pub fn kernel_stop_silo(selector: &str, force_kill: bool) -> Result<u32, SyscallError> {
1636    let (silo_id, tasks) = {
1637        let mut mgr = SILO_MANAGER.lock();
1638        let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
1639        let mut tasks = HVec::<TaskId, 64>::new();
1640        {
1641            let silo = mgr.get_mut(silo_id)?;
1642            match silo.state {
1643                SiloState::Running | SiloState::Paused => {
1644                    tasks = silo.tasks.clone();
1645                    silo.tasks.clear();
1646                    silo.state = if force_kill {
1647                        SiloState::Stopped
1648                    } else {
1649                        SiloState::Stopping
1650                    };
1651                }
1652                SiloState::Stopping => {
1653                    if force_kill {
1654                        silo.state = SiloState::Stopped;
1655                    }
1656                }
1657                SiloState::Stopped | SiloState::Created | SiloState::Ready => {}
1658                _ => return Err(SyscallError::InvalidArgument),
1659            }
1660        }
1661        for tid in &tasks {
1662            mgr.unmap_task(*tid);
1663        }
1664        mgr.push_event(SiloEvent {
1665            silo_id: silo_id as u64,
1666            kind: if force_kill {
1667                SiloEventKind::Killed
1668            } else {
1669                SiloEventKind::Stopped
1670            },
1671            data0: 0,
1672            data1: 0,
1673            tick: crate::process::scheduler::ticks(),
1674        });
1675        (silo_id, tasks)
1676    };
1677    for tid in tasks {
1678        crate::process::kill_task(tid);
1679    }
1680    Ok(silo_id)
1681}
1682
1683/// Performs the kernel start silo operation.
1684pub fn kernel_start_silo(selector: &str) -> Result<u32, SyscallError> {
1685    let silo_id = {
1686        let mgr = SILO_MANAGER.lock();
1687        resolve_selector_to_silo_id(selector, &mgr)?
1688    };
1689    let _ = start_silo_by_id(silo_id)?;
1690    Ok(silo_id)
1691}
1692
1693/// Performs the kernel destroy silo operation.
1694pub fn kernel_destroy_silo(selector: &str) -> Result<u32, SyscallError> {
1695    let (silo_id, module_id) = {
1696        let mut mgr = SILO_MANAGER.lock();
1697        let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
1698        let module_id = {
1699            let silo = mgr.get(silo_id)?;
1700            if !silo.tasks.is_empty() {
1701                return Err(SyscallError::InvalidArgument);
1702            }
1703            match silo.state {
1704                SiloState::Stopped | SiloState::Created | SiloState::Ready | SiloState::Crashed => {
1705                }
1706                _ => return Err(SyscallError::InvalidArgument),
1707            }
1708            silo.module_id
1709        };
1710        let _ = mgr.silos.remove(&silo_id);
1711        (silo_id, module_id)
1712    };
1713    if let Some(mid) = module_id {
1714        let mut reg = MODULE_REGISTRY.lock();
1715        let _ = reg.remove(mid);
1716    }
1717    Ok(silo_id)
1718}
1719
1720/// Performs the kernel rename silo label operation.
1721pub fn kernel_rename_silo_label(selector: &str, new_label: &str) -> Result<u32, SyscallError> {
1722    if !is_valid_label(new_label) {
1723        return Err(SyscallError::InvalidArgument);
1724    }
1725    let mut mgr = SILO_MANAGER.lock();
1726    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
1727    if mgr
1728        .silos
1729        .values()
1730        .any(|s| s.id.sid != silo_id && s.strate_label.as_deref() == Some(new_label))
1731    {
1732        return Err(SyscallError::AlreadyExists);
1733    }
1734    let silo = mgr.get_mut(silo_id)?;
1735    match silo.state {
1736        SiloState::Stopped | SiloState::Created | SiloState::Ready | SiloState::Crashed => {
1737            silo.strate_label = Some(String::from(new_label));
1738            Ok(silo_id)
1739        }
1740        _ => Err(SyscallError::InvalidArgument),
1741    }
1742}
1743
1744/// Performs the register boot strate task operation.
1745pub fn register_boot_strate_task(task_id: TaskId, label: &str) -> Result<u32, SyscallError> {
1746    BOOT_REG_IN_PROGRESS.store(true, Ordering::Relaxed);
1747    let result = (|| -> Result<u32, SyscallError> {
1748        let sanitized = sanitize_label(label);
1749        let mgr = SILO_MANAGER.lock();
1750        let mut sid = 1u32;
1751        while mgr.silos.contains_key(&sid) {
1752            sid = sid.checked_add(1).ok_or(SyscallError::OutOfMemory)?;
1753        }
1754        if mgr
1755            .silos
1756            .values()
1757            .any(|s| s.strate_label.as_deref() == Some(sanitized.as_str()))
1758        {
1759            return Err(SyscallError::AlreadyExists);
1760        }
1761        drop(mgr);
1762
1763        let id = SiloId::new(sid);
1764        let silo = Silo {
1765            id,
1766            name: alloc::format!("silo-{}", id.sid),
1767            strate_label: Some(sanitized),
1768            state: SiloState::Running,
1769            config: SiloConfig {
1770                sid: id.sid,
1771                mode: 0o777,
1772                family: StrateFamily::SYS as u8,
1773                ..SiloConfig::default()
1774            },
1775            mode: OctalMode::from_octal(0o777),
1776            family: StrateFamily::SYS,
1777            mem_usage_bytes: 0,
1778            flags: 0,
1779            module_id: None,
1780            tasks: {
1781                let mut v = HVec::new();
1782                let _ = v.push(task_id);
1783                v
1784            },
1785            granted_caps: HVec::new(),
1786            granted_resources: HVec::new(),
1787            unveil_rules: HVec::new(),
1788            sandboxed: false,
1789            event_seq: 0,
1790            output_buf: None,
1791        };
1792
1793        let mut mgr = SILO_MANAGER.lock();
1794        if mgr.silos.contains_key(&id.sid) {
1795            return Err(SyscallError::Again);
1796        }
1797        if mgr
1798            .silos
1799            .values()
1800            .any(|s| s.strate_label.as_deref() == silo.strate_label.as_deref())
1801        {
1802            return Err(SyscallError::AlreadyExists);
1803        }
1804        mgr.silos.insert(id.sid, Box::new(silo));
1805        drop(mgr);
1806        Ok(id.sid)
1807    })();
1808    BOOT_REG_IN_PROGRESS.store(false, Ordering::Relaxed);
1809    result
1810}
1811
1812/// Returns true while boot-time silo registration critical path is executing.
1813pub fn debug_boot_reg_active() -> bool {
1814    BOOT_REG_IN_PROGRESS.load(Ordering::Relaxed)
1815}
1816
1817/// Performs the resolve module handle operation.
1818fn resolve_module_handle(handle: u64, required: CapPermissions) -> Result<u64, SyscallError> {
1819    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1820    let caps = unsafe { &*task.process.capabilities.get() };
1821    let cap_id = CapId::from_raw(handle);
1822    let cap = caps.get(cap_id).ok_or(SyscallError::BadHandle)?;
1823
1824    if cap.resource_type != ResourceType::Module {
1825        return Err(SyscallError::BadHandle);
1826    }
1827
1828    if (!required.read || cap.permissions.read)
1829        && (!required.write || cap.permissions.write)
1830        && (!required.execute || cap.permissions.execute)
1831        && (!required.grant || cap.permissions.grant)
1832        && (!required.revoke || cap.permissions.revoke)
1833    {
1834        Ok(cap.resource as u64)
1835    } else {
1836        Err(SyscallError::PermissionDenied)
1837    }
1838}
1839
1840/// Grant the Silo Admin capability to a task (bootstrapping).
1841///
1842/// This should be called only for the initial admin task (e.g. "init").
1843pub fn create_silo_admin_capability() -> Capability {
1844    get_capability_manager().create_capability(
1845        ResourceType::Silo,
1846        SILO_ADMIN_RESOURCE,
1847        CapPermissions::all(),
1848    )
1849}
1850
1851pub fn grant_silo_admin_to_task(task: &alloc::sync::Arc<Task>) -> CapId {
1852    let cap = create_silo_admin_capability();
1853    // SAFETY: Bootstrapping. Caller must ensure exclusive access.
1854    unsafe { (&mut *task.process.capabilities.get()).insert(cap) }
1855}
1856
1857// ============================================================================
1858// Module syscalls (temporary blob loader)
1859// ============================================================================
1860
1861/// Performs the sys module load operation.
1862pub fn sys_module_load(fd_or_ptr: u64, len: u64) -> Result<u64, SyscallError> {
1863    // Module loading is currently restricted to admin.
1864    require_silo_admin()?;
1865
1866    // Transitional path: if len != 0, treat arg1 as a userspace blob pointer.
1867    if len != 0 {
1868        let len = len as usize;
1869        if len == 0 || len > MAX_MODULE_BLOB_LEN {
1870            return Err(SyscallError::InvalidArgument);
1871        }
1872
1873        if len <= 4096 {
1874            let user = UserSliceRead::new(fd_or_ptr, len)?;
1875            if matches!(user.read_u8(0), Ok(b'/')) {
1876                let path_buf = user.read_to_vec();
1877                let path =
1878                    core::str::from_utf8(&path_buf).map_err(|_| SyscallError::InvalidArgument)?;
1879                let data = crate::vfs::get_initfs_file_bytes(path).ok_or_else(|| {
1880                    log::warn!("module_load: initfs path not found: '{}'", path);
1881                    SyscallError::NotFound
1882                })?;
1883                let mut registry = MODULE_REGISTRY.lock();
1884                let id = registry.register_static(data)?;
1885                drop(registry);
1886
1887                let cap = get_capability_manager().create_capability(
1888                    ResourceType::Module,
1889                    id as usize,
1890                    CapPermissions::all(),
1891                );
1892
1893                let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1894                let cap_id = unsafe { (&mut *task.process.capabilities.get()).insert(cap) };
1895                return Ok(cap_id.as_u64());
1896            }
1897        }
1898
1899        let user = UserSliceRead::new(fd_or_ptr, len)?;
1900        let data = user.read_to_vec();
1901        if data.len() >= 4 {
1902            log::debug!(
1903                "module_load: len={} magic={:02x}{:02x}{:02x}{:02x}",
1904                data.len(),
1905                data[0],
1906                data[1],
1907                data[2],
1908                data[3]
1909            );
1910        } else {
1911            log::debug!("module_load: len={} (too small)", data.len());
1912        }
1913
1914        let mut registry = MODULE_REGISTRY.lock();
1915        let id = registry.register(data)?;
1916        drop(registry);
1917
1918        let cap = get_capability_manager().create_capability(
1919            ResourceType::Module,
1920            id as usize,
1921            CapPermissions::all(),
1922        );
1923
1924        let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1925        let cap_id = unsafe { (&mut *task.process.capabilities.get()).insert(cap) };
1926
1927        return Ok(cap_id.as_u64());
1928    }
1929
1930    // TODO: Load from a file handle (fd) via VFS once the path exists.
1931    // For now, interpret `fd_or_ptr` as either:
1932    // - a File handle (read all), or
1933    // - an IPC port handle that streams the module bytes.
1934    //
1935    // Stream protocol:
1936    // - msg_type = IPC_STREAM_DATA, flags = payload length (0..48)
1937    // - msg_type = IPC_STREAM_EOF (or DATA with flags=0) ends the stream
1938    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1939    let caps = unsafe { &*task.process.capabilities.get() };
1940    let required = CapPermissions {
1941        read: true,
1942        write: false,
1943        execute: false,
1944        grant: false,
1945        revoke: false,
1946    };
1947    let cap = caps
1948        .get_with_permissions(CapId::from_raw(fd_or_ptr), required)
1949        .ok_or(SyscallError::PermissionDenied)?;
1950    let data = match cap.resource_type {
1951        ResourceType::File => {
1952            let fd = u32::try_from(cap.resource).map_err(|_| SyscallError::BadHandle)?;
1953            crate::vfs::read_all(fd)?
1954        }
1955        ResourceType::IpcPort => {
1956            let port_id = PortId::from_u64(cap.resource as u64);
1957            let port = port::get_port(port_id).ok_or(SyscallError::BadHandle)?;
1958            read_module_stream_from_port(&port)?
1959        }
1960        _ => return Err(SyscallError::BadHandle),
1961    };
1962    if data.len() > MAX_MODULE_BLOB_LEN {
1963        return Err(SyscallError::InvalidArgument);
1964    }
1965
1966    let mut registry = MODULE_REGISTRY.lock();
1967    let id = registry.register(data)?;
1968    drop(registry);
1969
1970    let cap = get_capability_manager().create_capability(
1971        ResourceType::Module,
1972        id as usize,
1973        CapPermissions::all(),
1974    );
1975
1976    let cap_id = unsafe { (&mut *task.process.capabilities.get()).insert(cap) };
1977
1978    Ok(cap_id.as_u64())
1979}
1980
1981/// Performs the sys module unload operation.
1982pub fn sys_module_unload(handle: u64) -> Result<u64, SyscallError> {
1983    require_silo_admin()?;
1984    let required = CapPermissions {
1985        read: false,
1986        write: false,
1987        execute: false,
1988        grant: false,
1989        revoke: true,
1990    };
1991    let module_id = resolve_module_handle(handle, required)?;
1992    let mut registry = MODULE_REGISTRY.lock();
1993    registry.remove(module_id);
1994    Ok(0)
1995}
1996
1997/// Performs the sys module get symbol operation.
1998pub fn sys_module_get_symbol(handle: u64, _ordinal: u64) -> Result<u64, SyscallError> {
1999    let required = CapPermissions {
2000        read: true,
2001        write: false,
2002        execute: false,
2003        grant: false,
2004        revoke: false,
2005    };
2006    let module_id = resolve_module_handle(handle, required)?;
2007    let registry = MODULE_REGISTRY.lock();
2008    let module = registry.get(module_id).ok_or(SyscallError::BadHandle)?;
2009
2010    // The export table format is a simple array of u64 RVAs indexed by ordinal.
2011    let rva = resolve_export_offset(module, _ordinal)?;
2012    let header = module.header.ok_or(SyscallError::InvalidArgument)?;
2013    Ok(header.code_offset.saturating_add(rva))
2014}
2015
2016/// Performs the sys module query operation.
2017pub fn sys_module_query(handle: u64, out_ptr: u64) -> Result<u64, SyscallError> {
2018    let required = CapPermissions {
2019        read: true,
2020        write: false,
2021        execute: false,
2022        grant: false,
2023        revoke: false,
2024    };
2025    let module_id = resolve_module_handle(handle, required)?;
2026    if out_ptr == 0 {
2027        return Err(SyscallError::Fault);
2028    }
2029
2030    let registry = MODULE_REGISTRY.lock();
2031    let module = registry.get(module_id).ok_or(SyscallError::BadHandle)?;
2032
2033    let (format, flags, version, cpu_arch, code_size, data_size, bss_size, entry_point) =
2034        if let Some(header) = module.header {
2035            (
2036                1u32,
2037                header.flags,
2038                header.version,
2039                header.cpu_arch,
2040                header.code_size,
2041                header.data_size,
2042                header.bss_size,
2043                header.entry_point,
2044            )
2045        } else {
2046            (0u32, 0u32, 0u16, 0u8, 0u64, 0u64, 0u64, 0u64)
2047        };
2048
2049    let info = ModuleInfo {
2050        id: module.id,
2051        format,
2052        flags,
2053        version,
2054        cpu_arch,
2055        reserved: 0,
2056        code_size,
2057        data_size,
2058        bss_size,
2059        entry_point,
2060        total_size: module.data.len() as u64,
2061    };
2062
2063    const INFO_SIZE: usize = core::mem::size_of::<ModuleInfo>();
2064    let user = UserSliceWrite::new(out_ptr, INFO_SIZE)?;
2065    let src =
2066        unsafe { core::slice::from_raw_parts(&info as *const ModuleInfo as *const u8, INFO_SIZE) };
2067    user.copy_from(src);
2068    Ok(0)
2069}
2070
2071// ============================================================================
2072// Syscall handlers (kernel entry points)
2073// ============================================================================
2074
2075/// Performs the sys silo create operation.
2076pub fn sys_silo_create(config_ptr: u64) -> Result<u64, SyscallError> {
2077    require_silo_admin()?;
2078    let config = read_user_config(config_ptr)?;
2079    config.validate()?;
2080
2081    let mut mgr = SILO_MANAGER.lock();
2082    let id = mgr.create_silo(&config)?;
2083    drop(mgr);
2084
2085    let cap = get_capability_manager().create_capability(
2086        ResourceType::Silo,
2087        id.sid as usize,
2088        CapPermissions::all(),
2089    );
2090
2091    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
2092    let cap_id = unsafe { (&mut *task.process.capabilities.get()).insert(cap) };
2093
2094    Ok(cap_id.as_u64())
2095}
2096
2097/// Performs the sys silo config operation.
2098pub fn sys_silo_config(handle: u64, res_ptr: u64) -> Result<u64, SyscallError> {
2099    require_silo_admin()?;
2100    let config = read_user_config(res_ptr)?;
2101    config.validate()?;
2102    let family = decode_family(config.family)?;
2103
2104    let mut granted_caps = HVec::<u64, 64>::new();
2105    let mut granted_resources = HVec::<GrantedResource, 32>::new();
2106    if config.caps_len > 0 {
2107        let caps_list = read_caps_list(config.caps_ptr, config.caps_len)?;
2108        let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
2109        let caps = unsafe { &*task.process.capabilities.get() };
2110
2111        for cap_handle in caps_list {
2112            let cap = caps
2113                .get(CapId::from_raw(cap_handle))
2114                .ok_or(SyscallError::BadHandle)?;
2115            if !cap.permissions.grant {
2116                return Err(SyscallError::PermissionDenied);
2117            }
2118            if !is_delegated_resource(cap.resource_type) {
2119                return Err(SyscallError::InvalidArgument);
2120            }
2121            if !granted_caps.contains(&cap_handle) {
2122                if let Err(cap) = granted_caps.push(cap_handle) {
2123                    log::warn!(
2124                        "[silo] granted_caps full (capacity=64), cap {} dropped",
2125                        cap
2126                    );
2127                }
2128            }
2129            add_or_merge_granted_resource(
2130                &mut granted_resources,
2131                GrantedResource {
2132                    resource_type: cap.resource_type,
2133                    resource: cap.resource,
2134                    permissions: cap.permissions,
2135                },
2136            );
2137        }
2138    }
2139
2140    let sid = resolve_silo_handle(handle, CapPermissions::read_write())?;
2141    let mut mgr = SILO_MANAGER.lock();
2142    let silo = mgr.get_mut(sid as u32)?;
2143
2144    let requested_mode = OctalMode::from_octal(config.mode);
2145    kernel_check_spawn_invariants(&silo.id, &requested_mode)?;
2146    if silo.sandboxed && !requested_mode.is_subset_of(&silo.mode) {
2147        return Err(SyscallError::PermissionDenied);
2148    }
2149    if silo.sandboxed && !requested_mode.registry.is_empty() {
2150        return Err(SyscallError::PermissionDenied);
2151    }
2152
2153    // XCR0 is a kernel-computed derived value: never trust the
2154    // user-supplied field (kernel_spawn_strate already derives it).
2155    let mut config = config;
2156    config.xcr0_mask = compute_silo_xcr0(&config);
2157
2158    silo.config = config;
2159    silo.mode = requested_mode;
2160    silo.family = family;
2161    silo.flags = config.flags as u32;
2162    silo.granted_caps = granted_caps;
2163    silo.granted_resources = granted_resources;
2164    Ok(0)
2165}
2166
2167/// Performs the sys silo attach module operation.
2168pub fn sys_silo_attach_module(handle: u64, module_handle: u64) -> Result<u64, SyscallError> {
2169    require_silo_admin()?;
2170    let silo_id = resolve_silo_handle(handle, CapPermissions::read_write())?;
2171
2172    let required = CapPermissions {
2173        read: true,
2174        write: false,
2175        execute: false,
2176        grant: false,
2177        revoke: false,
2178    };
2179    let module_id = resolve_module_handle(module_handle, required)?;
2180
2181    let mut mgr = SILO_MANAGER.lock();
2182    let silo = mgr.get_mut(silo_id)?;
2183
2184    match silo.state {
2185        SiloState::Created | SiloState::Stopped | SiloState::Ready => {
2186            silo.module_id = Some(module_id);
2187            silo.state = SiloState::Ready;
2188            Ok(0)
2189        }
2190        SiloState::Running | SiloState::Paused => {
2191            silo.module_id = Some(module_id);
2192            Ok(0)
2193        }
2194        _ => Err(SyscallError::InvalidArgument),
2195    }
2196}
2197
2198/// Starts silo by id.
2199fn start_silo_by_id(silo_id: u32) -> Result<u64, SyscallError> {
2200    let (
2201        module_id,
2202        granted_caps,
2203        granted_resources,
2204        silo_flags,
2205        previous_state,
2206        can_start,
2207        within_task_limit,
2208        silo_name,
2209        silo_label,
2210    ) = {
2211        let mut mgr = SILO_MANAGER.lock();
2212        let silo = mgr.get_mut(silo_id)?;
2213        let previous_state = silo.state;
2214        let can_start = matches!(
2215            previous_state,
2216            SiloState::Ready | SiloState::Stopped | SiloState::Running
2217        );
2218        let within_task_limit = match silo.config.max_tasks {
2219            0 => true, // 0 = unlimited
2220            max => silo.tasks.len() < max as usize,
2221        };
2222        let module_id = silo.module_id;
2223        let granted_caps = silo.granted_caps.clone();
2224        let granted_resources = silo.granted_resources.clone();
2225        let silo_flags = silo.config.flags;
2226        let silo_name = silo.name.clone();
2227        let silo_label = silo.strate_label.clone();
2228        if can_start && within_task_limit {
2229            silo.state = SiloState::Loading;
2230        }
2231        (
2232            module_id,
2233            granted_caps,
2234            granted_resources,
2235            silo_flags,
2236            previous_state,
2237            can_start,
2238            within_task_limit,
2239            silo_name,
2240            silo_label,
2241        )
2242    };
2243
2244    if !can_start {
2245        return Err(SyscallError::InvalidArgument);
2246    }
2247    if !within_task_limit {
2248        return Err(SyscallError::QueueFull);
2249    }
2250
2251    let rollback_loading = |state: SiloState| {
2252        let mut mgr = SILO_MANAGER.lock();
2253        if let Ok(silo) = mgr.get_mut(silo_id) {
2254            if matches!(silo.state, SiloState::Loading) {
2255                silo.state = state;
2256            }
2257        }
2258    };
2259
2260    let module_id = match module_id {
2261        Some(id) => id,
2262        None => {
2263            rollback_loading(previous_state);
2264            return Err(SyscallError::InvalidArgument);
2265        }
2266    };
2267
2268    let mut seed_caps = {
2269        let task = match current_task_clone() {
2270            Some(t) => t,
2271            None => {
2272                rollback_loading(previous_state);
2273                return Err(SyscallError::PermissionDenied);
2274            }
2275        };
2276        let caps = unsafe { &mut *task.process.capabilities.get() };
2277        let mut out = Vec::with_capacity(granted_caps.len());
2278        for handle in granted_caps {
2279            // Enforce: caller must currently hold the capability.
2280            if !silo_has_capability(&task, handle) {
2281                rollback_loading(previous_state);
2282                return Err(SyscallError::PermissionDenied);
2283            }
2284            let Some(dup) = caps.duplicate(CapId::from_raw(handle)) else {
2285                rollback_loading(previous_state);
2286                return Err(SyscallError::PermissionDenied);
2287            };
2288            // Capability-confusion guard: granted_caps stores raw handle
2289            // slots recorded when the silo was configured (possibly by a
2290            // different process). Slot numbers are per-process: the starter
2291            // could hold an unrelated capability at the same slot. Verify
2292            // the duplicated capability actually matches a resource that
2293            // was explicitly granted to this silo before seeding it.
2294            let authorized = granted_resources
2295                .iter()
2296                .any(|g| g.resource_type == dup.resource_type && g.resource == dup.resource);
2297            if !authorized {
2298                log::warn!(
2299                    "[silo] start sid={}: cap handle {} does not match any \
2300                     granted resource (type={:?}, resource={:#x}) - denied",
2301                    silo_id,
2302                    handle,
2303                    dup.resource_type,
2304                    dup.resource
2305                );
2306                rollback_loading(previous_state);
2307                return Err(SyscallError::PermissionDenied);
2308            }
2309            out.push(dup);
2310        }
2311        out
2312    };
2313    if silo_flags & SILO_FLAG_ADMIN != 0 {
2314        seed_caps.push(create_silo_admin_capability());
2315    }
2316
2317    let display = silo_label.unwrap_or(silo_name);
2318    let task_name_owned = if silo_flags & SILO_FLAG_ADMIN != 0 {
2319        alloc::format!("silo-{}/strate-admin-{}", silo_id, display)
2320    } else {
2321        alloc::format!("silo-{}/strate-{}", silo_id, display)
2322    };
2323    // Intentional leak: task names are expected to live for the task lifetime.
2324    // This avoids generic "silo" labels in process viewers.
2325    let task_name: &'static str = Box::leak(task_name_owned.into_boxed_str());
2326
2327    let module_data = {
2328        let registry = MODULE_REGISTRY.lock();
2329        match registry.get(module_id) {
2330            Some(module) => module.data.clone(),
2331            None => {
2332                rollback_loading(previous_state);
2333                return Err(SyscallError::BadHandle);
2334            }
2335        }
2336    };
2337
2338    let load_result =
2339        crate::process::elf::load_elf_task_with_caps(module_data.as_slice(), task_name, &seed_caps)
2340            .map_err(|err| {
2341                log::warn!(
2342                    "silo_start: sid={} module={} task='{}' load failed: {}",
2343                    silo_id,
2344                    module_id,
2345                    task_name,
2346                    err
2347                );
2348                map_elf_start_error(err)
2349            });
2350
2351    let task = match load_result {
2352        Ok(task) => task,
2353        Err(e) => {
2354            rollback_loading(previous_state);
2355            return Err(e);
2356        }
2357    };
2358    let task_id = task.id;
2359    let task_pid = task.pid;
2360
2361    // Give the silo an EOF stdin so that any read(0, …) returns 0 immediately
2362    // instead of EBADF (which can cause busy-loops) or blocking on the
2363    // keyboard (which would steal input from the foreground shell).
2364    let bg_stdin = crate::vfs::create_background_stdin();
2365    let fd_table = unsafe { &mut *task.process.fd_table.get() };
2366    fd_table.insert_at(crate::vfs::STDIN, bg_stdin);
2367
2368    let mut mgr = SILO_MANAGER.lock();
2369    {
2370        let silo = match mgr.get_mut(silo_id) {
2371            Ok(silo) => silo,
2372            Err(e) => {
2373                return Err(e);
2374            }
2375        };
2376        if let Err(task) = silo.tasks.push(task_id) {
2377            log::error!(
2378                "[silo] tasks full (capacity=64), task {} not registered",
2379                task.0
2380            );
2381        }
2382        silo.state = SiloState::Running;
2383        let fpu_xcr0 = unsafe { (*task.fpu_state.get()).xcr0_mask };
2384        let effective_xcr0 = (silo.config.xcr0_mask & fpu_xcr0).max(0x3);
2385        task.xcr0_mask
2386            .store(effective_xcr0, core::sync::atomic::Ordering::Relaxed);
2387        // Propagate silo CPU affinity to task for scheduler placement.
2388        let affinity = silo.config.cpu_affinity_mask;
2389        if affinity != 0 {
2390            task.affinity_mask
2391                .store(affinity, core::sync::atomic::Ordering::Relaxed);
2392        }
2393    }
2394    mgr.map_task(task_id, silo_id);
2395    mgr.push_event(SiloEvent {
2396        silo_id: silo_id.into(),
2397        kind: SiloEventKind::Started,
2398        data0: 0,
2399        data1: 0,
2400        tick: crate::process::scheduler::ticks(),
2401    });
2402    drop(mgr);
2403    crate::process::add_task(task);
2404    Ok(task_pid as u64)
2405}
2406
2407/// Performs the sys silo start operation.
2408pub fn sys_silo_start(handle: u64) -> Result<u64, SyscallError> {
2409    require_silo_admin()?;
2410    let required = CapPermissions {
2411        read: false,
2412        write: false,
2413        execute: true,
2414        grant: false,
2415        revoke: false,
2416    };
2417    let silo_id = resolve_silo_handle(handle, required)?;
2418    start_silo_by_id(silo_id)
2419}
2420
2421/// Best-effort cleanup hook called by the scheduler when a task terminates.
2422///
2423/// Ensures `task_to_silo` mappings are removed even for normal exits and
2424/// transitions a running/paused silo to `Stopped` when its last task is gone.
2425pub fn on_task_terminated(task_id: TaskId) {
2426    let mut mgr = SILO_MANAGER.lock();
2427    let silo_id = match mgr.silo_for_task(task_id) {
2428        Some(id) => id,
2429        None => return,
2430    };
2431    mgr.unmap_task(task_id);
2432
2433    let mut emit_stopped = false;
2434    if let Ok(silo) = mgr.get_mut(silo_id) {
2435        if let Some(pos) = silo.tasks.iter().position(|tid| *tid == task_id) {
2436            silo.tasks.swap_remove(pos);
2437        }
2438        if silo.tasks.is_empty() {
2439            match silo.state {
2440                SiloState::Running | SiloState::Paused | SiloState::Stopping => {
2441                    silo.state = SiloState::Stopped;
2442                    silo.event_seq = silo.event_seq.wrapping_add(1);
2443                    emit_stopped = true;
2444                }
2445                _ => {}
2446            }
2447        }
2448    }
2449
2450    if emit_stopped {
2451        mgr.push_event(SiloEvent {
2452            silo_id: silo_id.into(),
2453            kind: SiloEventKind::Stopped,
2454            data0: 0,
2455            data1: 0,
2456            tick: crate::process::scheduler::ticks(),
2457        });
2458    }
2459}
2460
2461/// Stops or kill silo by id.
2462fn stop_or_kill_silo_by_id(
2463    silo_id: u32,
2464    force_kill: bool,
2465    require_running: bool,
2466) -> Result<HVec<TaskId, 64>, SyscallError> {
2467    let mut mgr = SILO_MANAGER.lock();
2468    let tasks = {
2469        let silo = mgr.get_mut(silo_id)?;
2470        if force_kill {
2471            silo.state = SiloState::Stopped;
2472            let tasks = silo.tasks.clone();
2473            silo.tasks.clear();
2474            tasks
2475        } else {
2476            match silo.state {
2477                SiloState::Running | SiloState::Paused => {
2478                    silo.state = SiloState::Stopping;
2479                    let tasks = silo.tasks.clone();
2480                    silo.tasks.clear();
2481                    tasks
2482                }
2483                _ if require_running => return Err(SyscallError::InvalidArgument),
2484                _ => HVec::new(),
2485            }
2486        }
2487    };
2488
2489    for tid in &tasks {
2490        mgr.unmap_task(*tid);
2491    }
2492    if !force_kill {
2493        if let Ok(silo) = mgr.get_mut(silo_id) {
2494            silo.state = SiloState::Stopped;
2495        }
2496    }
2497    mgr.push_event(SiloEvent {
2498        silo_id: silo_id.into(),
2499        kind: if force_kill {
2500            SiloEventKind::Killed
2501        } else {
2502            SiloEventKind::Stopped
2503        },
2504        data0: 0,
2505        data1: 0,
2506        tick: crate::process::scheduler::ticks(),
2507    });
2508
2509    Ok(tasks)
2510}
2511
2512/// Performs the sys silo stop operation.
2513pub fn sys_silo_stop(handle: u64) -> Result<u64, SyscallError> {
2514    require_silo_admin()?;
2515    let required = CapPermissions {
2516        read: false,
2517        write: false,
2518        execute: true,
2519        grant: false,
2520        revoke: false,
2521    };
2522    let silo_id = resolve_silo_handle(handle, required)?;
2523    let tasks = stop_or_kill_silo_by_id(silo_id, false, true)?;
2524
2525    for tid in tasks {
2526        crate::process::kill_task(tid);
2527    }
2528    Ok(0)
2529}
2530
2531/// Performs the sys silo kill operation.
2532pub fn sys_silo_kill(handle: u64) -> Result<u64, SyscallError> {
2533    require_silo_admin()?;
2534    let required = CapPermissions {
2535        read: false,
2536        write: false,
2537        execute: true,
2538        grant: false,
2539        revoke: false,
2540    };
2541    let silo_id = resolve_silo_handle(handle, required)?;
2542    let tasks = stop_or_kill_silo_by_id(silo_id, true, false)?;
2543
2544    for tid in tasks {
2545        crate::process::kill_task(tid);
2546    }
2547    Ok(0)
2548}
2549
2550/// Performs the silo has capability operation.
2551fn silo_has_capability(task: &Task, cap_id: u64) -> bool {
2552    let caps = unsafe { &*task.process.capabilities.get() };
2553    caps.get(CapId::from_raw(cap_id)).is_some()
2554}
2555
2556/// Returns whether delegated resource.
2557fn is_delegated_resource(rt: ResourceType) -> bool {
2558    matches!(
2559        rt,
2560        ResourceType::Nic
2561            | ResourceType::FileSystem
2562            | ResourceType::Console
2563            | ResourceType::Keyboard
2564            | ResourceType::Volume
2565            | ResourceType::Namespace
2566            | ResourceType::Device
2567            | ResourceType::File
2568            | ResourceType::IoPortRange
2569            | ResourceType::InterruptLine
2570    )
2571}
2572
2573/// Returns whether admin task.
2574fn is_admin_task(task: &Task) -> bool {
2575    let caps = unsafe { &*task.process.capabilities.get() };
2576    let required = CapPermissions {
2577        read: false,
2578        write: false,
2579        execute: false,
2580        grant: true,
2581        revoke: false,
2582    };
2583    caps.has_resource_with_permissions(ResourceType::Silo, SILO_ADMIN_RESOURCE, required)
2584}
2585
2586/// Maps elf start error.
2587fn map_elf_start_error(err: &'static str) -> SyscallError {
2588    if err.contains("allocate")
2589        || err.contains("Out of memory")
2590        || err.contains("No virtual range")
2591        || err.contains("Failed to map page")
2592    {
2593        return SyscallError::OutOfMemory;
2594    }
2595    if err.contains("ELF")
2596        || err.contains("PT_")
2597        || err.contains("entry")
2598        || err.contains("relocation")
2599        || err.contains("Program header")
2600        || err.contains("x86_64")
2601        || err.contains("Unsupported")
2602    {
2603        return SyscallError::ExecFormatError;
2604    }
2605    SyscallError::InvalidArgument
2606}
2607
2608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2609struct GrantedResource {
2610    resource_type: ResourceType,
2611    resource: usize,
2612    permissions: CapPermissions,
2613}
2614
2615/// Performs the merge permissions operation.
2616fn merge_permissions(a: CapPermissions, b: CapPermissions) -> CapPermissions {
2617    CapPermissions {
2618        read: a.read || b.read,
2619        write: a.write || b.write,
2620        execute: a.execute || b.execute,
2621        grant: a.grant || b.grant,
2622        revoke: a.revoke || b.revoke,
2623    }
2624}
2625
2626/// Performs the permissions subset operation.
2627fn permissions_subset(requested: CapPermissions, allowed: CapPermissions) -> bool {
2628    (!requested.read || allowed.read)
2629        && (!requested.write || allowed.write)
2630        && (!requested.execute || allowed.execute)
2631        && (!requested.grant || allowed.grant)
2632        && (!requested.revoke || allowed.revoke)
2633}
2634
2635/// Performs the add or merge granted resource operation.
2636fn add_or_merge_granted_resource(list: &mut HVec<GrantedResource, 32>, grant: GrantedResource) {
2637    for existing in list.iter_mut() {
2638        if existing.resource_type == grant.resource_type && existing.resource == grant.resource {
2639            existing.permissions = merge_permissions(existing.permissions, grant.permissions);
2640            return;
2641        }
2642    }
2643    if list.push(grant).is_err() {
2644        log::warn!("[silo] granted_resources full (capacity=32), resource dropped");
2645    }
2646}
2647
2648/// Performs the register current task granted resource operation.
2649pub fn register_current_task_granted_resource(
2650    resource_type: ResourceType,
2651    resource: usize,
2652    permissions: CapPermissions,
2653) -> Result<(), SyscallError> {
2654    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
2655    let mut mgr = SILO_MANAGER.lock();
2656    let silo_id = mgr
2657        .silo_for_task(task.id)
2658        .ok_or(SyscallError::PermissionDenied)?;
2659    let silo = mgr.get_mut(silo_id)?;
2660    add_or_merge_granted_resource(
2661        &mut silo.granted_resources,
2662        GrantedResource {
2663            resource_type,
2664            resource,
2665            permissions,
2666        },
2667    );
2668    Ok(())
2669}
2670
2671/// Enforce that the current task may use a delegated capability.
2672pub fn enforce_cap_for_current_task(handle: u64) -> Result<(), SyscallError> {
2673    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
2674
2675    // Admin tasks bypass delegated-cap enforcement.
2676    if is_admin_task(&task) {
2677        return Ok(());
2678    }
2679
2680    let caps = unsafe { &*task.process.capabilities.get() };
2681    let cap = caps
2682        .get(CapId::from_raw(handle))
2683        .ok_or(SyscallError::BadHandle)?;
2684
2685    if !is_delegated_resource(cap.resource_type) {
2686        return Ok(());
2687    }
2688
2689    let mgr = SILO_MANAGER.lock();
2690    if let Some(silo_id) = mgr.silo_for_task(task.id) {
2691        if let Ok(silo) = mgr.get(silo_id) {
2692            for grant in &silo.granted_resources {
2693                if grant.resource_type == cap.resource_type && grant.resource == cap.resource {
2694                    if permissions_subset(cap.permissions, grant.permissions) {
2695                        return Ok(());
2696                    }
2697                    return Err(SyscallError::PermissionDenied);
2698                }
2699            }
2700        }
2701    }
2702
2703    Err(SyscallError::PermissionDenied)
2704}
2705
2706/// Performs the enforce registry bind for current task operation.
2707pub fn enforce_registry_bind_for_current_task() -> Result<(), SyscallError> {
2708    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
2709    if is_admin_task(&task) {
2710        return Ok(());
2711    }
2712    let mgr = SILO_MANAGER.lock();
2713    let silo_id = mgr
2714        .silo_for_task(task.id)
2715        .ok_or(SyscallError::PermissionDenied)?;
2716    let silo = mgr.get(silo_id)?;
2717    if silo.sandboxed {
2718        return Err(SyscallError::PermissionDenied);
2719    }
2720    if silo.mode.registry.contains(RegistryMode::BIND) {
2721        Ok(())
2722    } else {
2723        Err(SyscallError::PermissionDenied)
2724    }
2725}
2726
2727/// Enforce console access for the current task.
2728///
2729/// Only admin tasks or tasks holding a Console capability with write permission
2730/// can access the kernel console (SYS_WRITE fd=1/2).
2731pub fn enforce_console_access() -> Result<(), SyscallError> {
2732    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
2733    if is_admin_task(&task) {
2734        return Ok(());
2735    }
2736    let mgr = SILO_MANAGER.lock();
2737    if let Some(silo_id) = mgr.silo_for_task(task.id) {
2738        if let Ok(silo) = mgr.get(silo_id) {
2739            if matches!(silo.family, StrateFamily::SYS | StrateFamily::NET) {
2740                return Ok(());
2741            }
2742        }
2743    }
2744    drop(mgr);
2745    let caps = unsafe { &*task.process.capabilities.get() };
2746    let required = CapPermissions {
2747        read: false,
2748        write: true,
2749        execute: false,
2750        grant: false,
2751        revoke: false,
2752    };
2753    if caps.has_resource_type_with_permissions(ResourceType::Console, required) {
2754        Ok(())
2755    } else {
2756        Err(SyscallError::PermissionDenied)
2757    }
2758}
2759
2760/// Performs the sys silo event next operation.
2761pub fn sys_silo_event_next(_event_ptr: u64) -> Result<u64, SyscallError> {
2762    require_silo_admin()?;
2763    if _event_ptr == 0 {
2764        return Err(SyscallError::Fault);
2765    }
2766
2767    let event = {
2768        let mut mgr = SILO_MANAGER.lock();
2769        mgr.events.pop_front()
2770    };
2771
2772    let event = match event {
2773        Some(e) => e,
2774        None => return Err(SyscallError::Again),
2775    };
2776
2777    const EVT_SIZE: usize = core::mem::size_of::<SiloEvent>();
2778    let user = UserSliceWrite::new(_event_ptr, EVT_SIZE)?;
2779    let src =
2780        unsafe { core::slice::from_raw_parts(&event as *const SiloEvent as *const u8, EVT_SIZE) };
2781    user.copy_from(src);
2782    Ok(0)
2783}
2784
2785/// Performs the sys silo suspend operation.
2786pub fn sys_silo_suspend(handle: u64) -> Result<u64, SyscallError> {
2787    require_silo_admin()?;
2788    let required = CapPermissions {
2789        read: false,
2790        write: false,
2791        execute: true,
2792        grant: false,
2793        revoke: false,
2794    };
2795    let silo_id = resolve_silo_handle(handle, required)?;
2796
2797    // Lock is released before suspend_task (which takes the scheduler lock)
2798    // to avoid lock-ordering deadlock. Tasks added between the two locks
2799    // won't be suspended : acceptable best-effort trade-off.
2800    let tasks = {
2801        let mut mgr = SILO_MANAGER.lock();
2802        let silo = mgr.get_mut(silo_id)?;
2803        match silo.state {
2804            SiloState::Running => {
2805                silo.state = SiloState::Paused;
2806                silo.tasks.clone()
2807            }
2808            _ => return Err(SyscallError::InvalidArgument),
2809        }
2810    };
2811
2812    for tid in &tasks {
2813        crate::process::suspend_task(*tid);
2814    }
2815
2816    let mut mgr = SILO_MANAGER.lock();
2817    mgr.push_event(SiloEvent {
2818        silo_id: silo_id.into(),
2819        kind: SiloEventKind::Paused,
2820        data0: 0,
2821        data1: 0,
2822        tick: crate::process::scheduler::ticks(),
2823    });
2824
2825    Ok(0)
2826}
2827
2828/// Performs the sys silo resume operation.
2829pub fn sys_silo_resume(handle: u64) -> Result<u64, SyscallError> {
2830    require_silo_admin()?;
2831    let required = CapPermissions {
2832        read: false,
2833        write: false,
2834        execute: true,
2835        grant: false,
2836        revoke: false,
2837    };
2838    let silo_id = resolve_silo_handle(handle, required)?;
2839
2840    let tasks = {
2841        let mut mgr = SILO_MANAGER.lock();
2842        let silo = mgr.get_mut(silo_id)?;
2843        match silo.state {
2844            SiloState::Paused => {
2845                silo.state = SiloState::Running;
2846                silo.tasks.clone()
2847            }
2848            _ => return Err(SyscallError::InvalidArgument),
2849        }
2850    };
2851
2852    // Same lock-ordering pattern as sys_silo_suspend (see note there).
2853    for tid in &tasks {
2854        crate::process::resume_task(*tid);
2855    }
2856
2857    let mut mgr = SILO_MANAGER.lock();
2858    mgr.push_event(SiloEvent {
2859        silo_id: silo_id.into(),
2860        kind: SiloEventKind::Resumed,
2861        data0: 0,
2862        data1: 0,
2863        tick: crate::process::scheduler::ticks(),
2864    });
2865
2866    Ok(0)
2867}
2868
2869// ============================================================================
2870// Kernel-side CLI helpers (no capability gate : shell runs in Ring 0)
2871// ============================================================================
2872
2873pub fn kernel_suspend_silo(selector: &str) -> Result<u32, SyscallError> {
2874    let (silo_id, tasks) = {
2875        let mut mgr = SILO_MANAGER.lock();
2876        let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
2877        let silo = mgr.get_mut(silo_id)?;
2878        match silo.state {
2879            SiloState::Running => {
2880                silo.state = SiloState::Paused;
2881                let t = silo.tasks.clone();
2882                (silo_id, t)
2883            }
2884            _ => return Err(SyscallError::InvalidArgument),
2885        }
2886    };
2887    for tid in &tasks {
2888        crate::process::suspend_task(*tid);
2889    }
2890    let mut mgr = SILO_MANAGER.lock();
2891    mgr.push_event(SiloEvent {
2892        silo_id: silo_id.into(),
2893        kind: SiloEventKind::Paused,
2894        data0: 0,
2895        data1: 0,
2896        tick: crate::process::scheduler::ticks(),
2897    });
2898    Ok(silo_id)
2899}
2900
2901pub fn kernel_resume_silo(selector: &str) -> Result<u32, SyscallError> {
2902    let (silo_id, tasks) = {
2903        let mut mgr = SILO_MANAGER.lock();
2904        let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
2905        let silo = mgr.get_mut(silo_id)?;
2906        match silo.state {
2907            SiloState::Paused => {
2908                silo.state = SiloState::Running;
2909                let t = silo.tasks.clone();
2910                (silo_id, t)
2911            }
2912            _ => return Err(SyscallError::InvalidArgument),
2913        }
2914    };
2915    for tid in &tasks {
2916        crate::process::resume_task(*tid);
2917    }
2918    let mut mgr = SILO_MANAGER.lock();
2919    mgr.push_event(SiloEvent {
2920        silo_id: silo_id.into(),
2921        kind: SiloEventKind::Resumed,
2922        data0: 0,
2923        data1: 0,
2924        tick: crate::process::scheduler::ticks(),
2925    });
2926    Ok(silo_id)
2927}
2928
2929pub fn silo_detail_snapshot(selector: &str) -> Result<SiloDetailSnapshot, SyscallError> {
2930    let mgr = SILO_MANAGER.lock();
2931    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
2932    let s = mgr.get(silo_id)?;
2933    Ok(SiloDetailSnapshot {
2934        base: SiloSnapshot {
2935            id: s.id.sid,
2936            tier: s.id.tier,
2937            name: s.name.clone(),
2938            strate_label: s.strate_label.clone(),
2939            state: s.state,
2940            task_count: s.tasks.len(),
2941            mem_usage_bytes: s.mem_usage_bytes,
2942            mem_min_bytes: s.config.mem_min,
2943            mem_max_bytes: s.config.mem_max,
2944            mode: s.config.mode,
2945            graphics_flags: s.config.flags
2946                & (SILO_FLAG_GRAPHICS
2947                    | SILO_FLAG_WEBRTC_NATIVE
2948                    | SILO_FLAG_GRAPHICS_READ_ONLY
2949                    | SILO_FLAG_WEBRTC_TURN_FORCE),
2950            graphics_max_sessions: s.config.graphics_max_sessions,
2951            graphics_session_ttl_sec: s.config.graphics_session_ttl_sec,
2952        },
2953        family: s.family,
2954        sandboxed: s.sandboxed,
2955        cpu_shares: s.config.cpu_shares,
2956        cpu_affinity_mask: s.config.cpu_affinity_mask,
2957        max_tasks: s.config.max_tasks,
2958        task_ids: s.tasks.iter().map(|t| t.as_u64()).collect(),
2959        unveil_rules: s
2960            .unveil_rules
2961            .iter()
2962            .map(|r| {
2963                let bits = (if r.rights.read { 4 } else { 0 })
2964                    | (if r.rights.write { 2 } else { 0 })
2965                    | (if r.rights.execute { 1 } else { 0 });
2966                (r.path.clone(), bits)
2967            })
2968            .collect(),
2969        granted_caps_count: s.granted_caps.len(),
2970        cpu_features_required: s.config.cpu_features_required,
2971        cpu_features_allowed: s.config.cpu_features_allowed,
2972        xcr0_mask: s.config.xcr0_mask,
2973        graphics_flags: s.config.flags
2974            & (SILO_FLAG_GRAPHICS
2975                | SILO_FLAG_WEBRTC_NATIVE
2976                | SILO_FLAG_GRAPHICS_READ_ONLY
2977                | SILO_FLAG_WEBRTC_TURN_FORCE),
2978        graphics_max_sessions: s.config.graphics_max_sessions,
2979        graphics_session_ttl_sec: s.config.graphics_session_ttl_sec,
2980    })
2981}
2982
2983pub fn list_events_snapshot() -> Vec<SiloEventSnapshot> {
2984    let mgr = SILO_MANAGER.lock();
2985    mgr.events
2986        .iter()
2987        .map(|e| SiloEventSnapshot {
2988            silo_id: e.silo_id,
2989            kind: e.kind,
2990            data0: e.data0,
2991            data1: e.data1,
2992            tick: e.tick,
2993        })
2994        .collect()
2995}
2996
2997pub fn list_events_for_silo(selector: &str) -> Result<Vec<SiloEventSnapshot>, SyscallError> {
2998    let mgr = SILO_MANAGER.lock();
2999    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
3000    let sid64 = silo_id as u64;
3001    Ok(mgr
3002        .events
3003        .iter()
3004        .filter(|e| e.silo_id == sid64)
3005        .map(|e| SiloEventSnapshot {
3006            silo_id: e.silo_id,
3007            kind: e.kind,
3008            data0: e.data0,
3009            data1: e.data1,
3010            tick: e.tick,
3011        })
3012        .collect())
3013}
3014
3015pub fn kernel_pledge_silo(selector: &str, mode_val: u16) -> Result<(u16, u16), SyscallError> {
3016    let new_mode = OctalMode::from_octal(mode_val);
3017    let mut mgr = SILO_MANAGER.lock();
3018    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
3019    let silo = mgr.get_mut(silo_id)?;
3020    let old_raw = silo.config.mode;
3021    silo.mode.pledge(new_mode)?;
3022    silo.config.mode = mode_val;
3023    Ok((old_raw, mode_val))
3024}
3025
3026pub fn kernel_unveil_silo(
3027    selector: &str,
3028    path: &str,
3029    rights_str: &str,
3030) -> Result<u32, SyscallError> {
3031    let rights = UnveilRights {
3032        read: rights_str.contains('r'),
3033        write: rights_str.contains('w'),
3034        execute: rights_str.contains('x'),
3035    };
3036    let mut mgr = SILO_MANAGER.lock();
3037    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
3038    let silo = mgr.get_mut(silo_id)?;
3039    if let Some(rule) = silo.unveil_rules.iter_mut().find(|r| r.path == path) {
3040        rule.rights = rights;
3041    } else {
3042        if silo
3043            .unveil_rules
3044            .push(UnveilRule {
3045                path: String::from(path),
3046                rights,
3047            })
3048            .is_err()
3049        {
3050            log::warn!("[silo] unveil_rules full (capacity=128), rule dropped");
3051        }
3052    }
3053    Ok(silo_id)
3054}
3055
3056pub fn kernel_sandbox_silo(selector: &str) -> Result<u32, SyscallError> {
3057    let mut mgr = SILO_MANAGER.lock();
3058    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
3059    let silo = mgr.get_mut(silo_id)?;
3060    silo.sandboxed = true;
3061    crate::audit::log(
3062        crate::audit::AuditCategory::Security,
3063        0,
3064        silo_id,
3065        alloc::format!("silo sandboxed"),
3066    );
3067    Ok(silo_id)
3068}
3069
3070/// Get the silo ID for a given task, if any.
3071pub fn task_silo_id(task_id: TaskId) -> Option<u32> {
3072    SILO_MANAGER.lock().silo_for_task(task_id)
3073}
3074
3075/// Append data to a silo's output ring buffer (called from `sys_debug_log`).
3076pub fn silo_output_write(silo_id: u32, data: &[u8]) {
3077    let mut mgr = SILO_MANAGER.lock();
3078    if let Ok(silo) = mgr.get_mut(silo_id) {
3079        let buf = silo
3080            .output_buf
3081            .get_or_insert_with(|| Box::new(SiloOutputBuf::new()));
3082        buf.push(data);
3083    }
3084}
3085
3086/// Drain the output buffer for a silo, returning accumulated bytes.
3087pub fn silo_output_drain(selector: &str) -> Result<Vec<u8>, SyscallError> {
3088    let mut mgr = SILO_MANAGER.lock();
3089    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
3090    let silo = mgr.get_mut(silo_id)?;
3091    let mut buf = match silo.output_buf.take() {
3092        Some(buf) => buf,
3093        None => return Ok(Vec::new()),
3094    };
3095    let out = buf.drain();
3096    silo.output_buf = Some(buf);
3097    Ok(out)
3098}
3099
3100/// Dynamically adjust resource quotas for a silo.
3101///
3102/// `key` can be: `mem_max`, `mem_min`, `max_tasks`, `cpu_shares`.
3103/// Values are parsed as u64 (bytes for memory, count otherwise).
3104pub fn kernel_limit_silo(selector: &str, key: &str, value: u64) -> Result<u32, SyscallError> {
3105    let mut mgr = SILO_MANAGER.lock();
3106    let silo_id = resolve_selector_to_silo_id(selector, &mgr)?;
3107    let silo = mgr.get_mut(silo_id)?;
3108    let mut next_mem_min = silo.config.mem_min;
3109    let mut next_mem_max = silo.config.mem_max;
3110    let mut next_max_tasks = silo.config.max_tasks;
3111    let mut next_cpu_shares = silo.config.cpu_shares;
3112    match key {
3113        "mem_max" => next_mem_max = value,
3114        "mem_min" => next_mem_min = value,
3115        "max_tasks" => {
3116            if value > u32::MAX as u64 {
3117                return Err(SyscallError::InvalidArgument);
3118            }
3119            next_max_tasks = value as u32;
3120        }
3121        "cpu_shares" => {
3122            if value > u32::MAX as u64 {
3123                return Err(SyscallError::InvalidArgument);
3124            }
3125            next_cpu_shares = value as u32;
3126        }
3127        _ => return Err(SyscallError::InvalidArgument),
3128    }
3129    if next_mem_max != 0 && next_mem_min > next_mem_max {
3130        return Err(SyscallError::InvalidArgument);
3131    }
3132    silo.config.mem_min = next_mem_min;
3133    silo.config.mem_max = next_mem_max;
3134    silo.config.max_tasks = next_max_tasks;
3135    silo.config.cpu_shares = next_cpu_shares;
3136    crate::audit::log(
3137        crate::audit::AuditCategory::Security,
3138        0,
3139        silo_id,
3140        alloc::format!("silo limit: {}={}", key, value),
3141    );
3142    Ok(silo_id)
3143}
3144
3145// ============================================================================
3146// Fault handling (called from exception handlers)
3147// ============================================================================
3148
3149/// Performs the dump user fault operation.
3150fn dump_user_fault(task_id: TaskId, reason: SiloFaultReason, extra: u64, subcode: u64, rip: u64) {
3151    let task_meta = crate::process::get_task_by_id(task_id).map(|task| {
3152        let state = task.get_state();
3153        let as_ref = task.process.address_space_arc();
3154        (
3155            task.pid,
3156            task.tid,
3157            task.name,
3158            state,
3159            as_ref.cr3().as_u64(),
3160            as_ref.is_kernel(),
3161        )
3162    });
3163
3164    if let Some((pid, tid, name, state, as_cr3, as_is_kernel)) = task_meta {
3165        crate::serial_force_println!(
3166            "\x1b[31m[handle_user_fault]\x1b[0m task={} \x1b[36mpid={}\x1b[0m tid={} name='{}' state={:?} reason={:?} \x1b[35mrip={:#x}\x1b[0m \x1b[35mextra={:#x}\x1b[0m subcode={:#x} as_cr3={:#x} as_kernel={}",
3167            task_id.as_u64(),
3168            pid,
3169            tid,
3170            name,
3171            state,
3172            reason,
3173            rip,
3174            extra,
3175            subcode,
3176            as_cr3,
3177            as_is_kernel
3178        );
3179    } else {
3180        crate::serial_force_println!(
3181            "\x1b[31m[handle_user_fault]\x1b[0m task={} reason={:?} \x1b[35mrip={:#x}\x1b[0m \x1b[35mextra={:#x}\x1b[0m subcode={:#x} (task metadata unavailable)",
3182            task_id.as_u64(),
3183            reason,
3184            rip,
3185            extra,
3186            subcode
3187        );
3188    }
3189
3190    if reason == SiloFaultReason::PageFault {
3191        let present = (subcode & 0x1) != 0;
3192        let write = (subcode & 0x2) != 0;
3193        let user = (subcode & 0x4) != 0;
3194        let reserved = (subcode & 0x8) != 0;
3195        let instr_fetch = (subcode & 0x10) != 0;
3196        let pkey = (subcode & 0x20) != 0;
3197        let shadow_stack = (subcode & 0x40) != 0;
3198        let sgx = (subcode & 0x8000) != 0;
3199        crate::serial_force_println!(
3200            "\x1b[31m[handle_user_fault]\x1b[0m \x1b[31mpagefault\x1b[0m \x1b[35maddr={:#x}\x1b[0m \x1b[35mrip={:#x}\x1b[0m ec={:#x} present={} write={} user={} reserved={} ifetch={} pkey={} shadow_stack={} sgx={}",
3201            extra,
3202            rip,
3203            subcode,
3204            present,
3205            write,
3206            user,
3207            reserved,
3208            instr_fetch,
3209            pkey,
3210            shadow_stack,
3211            sgx
3212        );
3213        if user && extra < 0x1000 {
3214            crate::serial_force_println!(
3215                "\x1b[31m[handle_user_fault]\x1b[0m \x1b[33mhint: low user address fault ({:#x}) -> probable NULL/near-NULL dereference\x1b[0m",
3216                extra
3217            );
3218        }
3219    } else {
3220        crate::serial_force_println!(
3221            "\x1b[31m[handle_user_fault]\x1b[0m \x1b[31mfault detail\x1b[0m \x1b[35mrip={:#x}\x1b[0m code={:#x}",
3222            rip,
3223            subcode
3224        );
3225    }
3226}
3227
3228/// Handles user fault.
3229pub fn handle_user_fault(
3230    task_id: TaskId,
3231    reason: SiloFaultReason,
3232    extra: u64,
3233    subcode: u64,
3234    rip: u64,
3235) {
3236    // FORCE OUTPUT for user fault - bypasses normal logging mutexes
3237    crate::serial_force_println!(
3238        "\x1b[31;1m[handle_user_fault] CRITICAL FAULT\x1b[0m: tid={} reason={:?} rip={:#x} addr={:#x} err={:#x}",
3239        task_id.as_u64(),
3240        reason,
3241        rip,
3242        extra,
3243        subcode
3244    );
3245
3246    dump_user_fault(task_id, reason, extra, subcode, rip);
3247
3248    // Best-effort: map task to silo, mark crashed, emit event, kill tasks.
3249    let tasks = {
3250        let mut mgr = SILO_MANAGER.lock();
3251        let silo_id = match mgr.silo_for_task(task_id) {
3252            Some(id) => id,
3253            None => {
3254                crate::serial_force_println!(
3255                    "[handle_user_fault] Non-silo task {} crashed (reason={:?})! Killing it.",
3256                    task_id.as_u64(),
3257                    reason
3258                );
3259                drop(mgr);
3260                crate::process::kill_task(task_id);
3261                return;
3262            }
3263        };
3264        let mut tasks = HVec::<TaskId, 64>::new();
3265        {
3266            if let Ok(silo) = mgr.get_mut(silo_id) {
3267                silo.state = SiloState::Crashed;
3268                tasks = silo.tasks.clone();
3269                silo.tasks.clear();
3270                silo.event_seq = silo.event_seq.wrapping_add(1);
3271            }
3272        }
3273        for tid in &tasks {
3274            mgr.unmap_task(*tid);
3275        }
3276        mgr.push_event(SiloEvent {
3277            silo_id: silo_id.into(),
3278            kind: SiloEventKind::Crashed,
3279            data0: pack_fault(reason, subcode),
3280            data1: extra,
3281            tick: crate::process::scheduler::ticks(),
3282        });
3283        tasks
3284    };
3285
3286    for tid in &tasks {
3287        crate::process::kill_task(*tid);
3288    }
3289    if !tasks.contains(&task_id) {
3290        crate::process::kill_task(task_id);
3291    }
3292}