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