From 8a617af78204e6a0f96848b5f9683db63c6d93f6 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Thu, 9 Apr 2026 01:29:27 -0400 Subject: [PATCH 01/17] observability(core): add append-only audit log infrastructure Introduce a minimal append-only audit ring buffer for recording fault, quarantine, deny, and recovery events from the core runtime. Export the audit module from the core crate so later hardening changes can report abnormal conditions without relying on panic output alone. --- src/core/audit.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++++ src/core/lib.rs | 3 ++ 2 files changed, 102 insertions(+) create mode 100644 src/core/audit.rs diff --git a/src/core/audit.rs b/src/core/audit.rs new file mode 100644 index 0000000..0dd08c0 --- /dev/null +++ b/src/core/audit.rs @@ -0,0 +1,99 @@ +//! 監査ログ +//! +//! panic の代わりに、拒否・隔離・破損検出・復旧不能な局所失敗を +//! append-only な簡易リングバッファへ記録する。 + +use core::fmt; +use core::sync::atomic::{AtomicUsize, Ordering}; + +use crate::interrupt::spinlock::SpinLock; + +const AUDIT_CAPACITY: usize = 256; +const AUDIT_MSG_LEN: usize = 160; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AuditEventKind { + Deny, + Fault, + Revoke, + Quarantine, + Restart, + Policy, + Usercopy, + Device, + Exec, + Ipc, + Memory, +} + +#[derive(Clone, Copy)] +pub struct AuditRecord { + seq: u64, + kind: AuditEventKind, + len: usize, + msg: [u8; AUDIT_MSG_LEN], +} + +impl AuditRecord { + const fn empty() -> Self { + Self { + seq: 0, + kind: AuditEventKind::Fault, + len: 0, + msg: [0; AUDIT_MSG_LEN], + } + } + + fn write_message(&mut self, message: &str) { + let bytes = message.as_bytes(); + let len = bytes.len().min(AUDIT_MSG_LEN); + self.msg[..len].copy_from_slice(&bytes[..len]); + if len < AUDIT_MSG_LEN { + self.msg[len..].fill(0); + } + self.len = len; + } + + pub fn message(&self) -> &str { + core::str::from_utf8(&self.msg[..self.len]).unwrap_or("") + } + + pub fn seq(&self) -> u64 { + self.seq + } + + pub fn kind(&self) -> AuditEventKind { + self.kind + } +} + +impl fmt::Debug for AuditRecord { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AuditRecord") + .field("seq", &self.seq) + .field("kind", &self.kind) + .field("message", &self.message()) + .finish() + } +} + +static AUDIT_LOG: SpinLock<[AuditRecord; AUDIT_CAPACITY]> = + SpinLock::new([AuditRecord::empty(); AUDIT_CAPACITY]); +static AUDIT_SEQ: AtomicUsize = AtomicUsize::new(1); + +pub fn log(kind: AuditEventKind, message: &str) { + let seq = AUDIT_SEQ.fetch_add(1, Ordering::Relaxed) as u64; + let idx = (seq as usize) % AUDIT_CAPACITY; + { + let mut log = AUDIT_LOG.lock(); + let slot = &mut log[idx]; + slot.seq = seq; + slot.kind = kind; + slot.write_message(message); + } + crate::warn!("[AUDIT {:?} #{seq}] {}", kind, message); +} + +pub fn snapshot() -> [AuditRecord; AUDIT_CAPACITY] { + *AUDIT_LOG.lock() +} diff --git a/src/core/lib.rs b/src/core/lib.rs index 63532d1..cc0fc68 100644 --- a/src/core/lib.rs +++ b/src/core/lib.rs @@ -11,6 +11,9 @@ extern crate alloc; /// エラー型定義 pub mod result; +/// 監査ログ +pub mod audit; + /// 割込み管理 pub mod interrupt; From 0b551bbac7a90e38d2e92bd08025073b69aa6159 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Thu, 9 Apr 2026 01:30:37 -0400 Subject: [PATCH 02/17] security(core): enable runtime CPU hardening controls Enable supervisor write-protect, UMIP, SMEP/SMAP reinforcement, and supported speculation-control state during core runtime initialization. Reassert hardening state on timer entry and replace the boot CPU APIC table assertion with an audited fallback path. --- src/core/cpu.rs | 346 +++++++++++++++++++++++++++++++++++- src/core/interrupt/timer.rs | 1 + src/core/percpu.rs | 17 +- 3 files changed, 353 insertions(+), 11 deletions(-) diff --git a/src/core/cpu.rs b/src/core/cpu.rs index 7a28fd2..171a5f4 100644 --- a/src/core/cpu.rs +++ b/src/core/cpu.rs @@ -9,18 +9,30 @@ use spin::Mutex; static FSGSBASE_SUPPORTED: AtomicBool = AtomicBool::new(false); static SMAP_ENABLED: AtomicBool = AtomicBool::new(false); +static IBPB_SUPPORTED: AtomicBool = AtomicBool::new(false); +static SPEC_CTRL_MASK: AtomicU64 = AtomicU64::new(0); static BOOT_ENTROPY: AtomicU64 = AtomicU64::new(0); static CMOS_LOCK: Mutex<()> = Mutex::new(()); -/// CPUの初期化(SSE/FPU/NXE/SMEP/SMAP有効化) +#[derive(Clone, Copy, PartialEq, Eq)] +enum CpuVendor { + Intel, + Amd, + Other, +} + +/// CPUの初期化(SSE/FPU/NXE/WP/UMIP/SMEP/SMAP/SpecCtrl有効化) pub fn init() { crate::info!("Initializing CPU features..."); unsafe { enable_nxe(); + enable_write_protect(); enable_fpu(); enable_sse(); + enable_umip(); enable_smep_smap(); + enable_speculation_controls(); } } @@ -65,6 +77,17 @@ unsafe fn enable_fpu() { asm!("mov cr0, {}", in(reg) cr0, options(nomem, nostack)); } +/// CR0.WP を有効化して supervisor write-protect を強制する +unsafe fn enable_write_protect() { + let mut cr0 = read_cr0(); + const CR0_WP_BIT: u64 = 1 << 16; + if (cr0 & CR0_WP_BIT) == 0 { + cr0 |= CR0_WP_BIT; + write_cr0(cr0); + crate::info!("CR0.WP enabled"); + } +} + /// SSEを有効化 unsafe fn enable_sse() { // CR4レジスタを読み取り @@ -89,10 +112,22 @@ unsafe fn enable_sse() { asm!("mov cr4, {}", in(reg) cr4, options(nomem, nostack)); } +/// UMIP を有効化してユーザーモードからの SGDT/SIDT 等による情報漏えいを抑止する +unsafe fn enable_umip() { + if !cpu_has_umip() { + crate::warn!("UMIP not supported; skipping"); + return; + } + + let mut cr4 = read_cr4(); + cr4 |= 1 << 11; + write_cr4(cr4); + crate::info!("UMIP enabled"); +} + /// SMEP/SMAPを有効化 unsafe fn enable_smep_smap() { - let mut cr4: u64; - asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack)); + let mut cr4 = read_cr4(); if cpu_has_smep() { // ビット20 (SMEP) をセット - カーネルモードでのユーザーページ実行禁止 (L-1修正) @@ -113,7 +148,50 @@ unsafe fn enable_smep_smap() { crate::warn!("SMAP not supported; skipping"); } - asm!("mov cr4, {}", in(reg) cr4, options(nomem, nostack)); + write_cr4(cr4); +} + +unsafe fn enable_speculation_controls() { + const IA32_SPEC_CTRL: u32 = 0x48; + const IA32_PRED_CMD: u32 = 0x49; + const IA32_EFER: u32 = 0xC000_0080; + const SPEC_CTRL_IBRS: u64 = 1 << 0; + const SPEC_CTRL_STIBP: u64 = 1 << 1; + const SPEC_CTRL_BHI_DIS_S: u64 = 1 << 10; + const EFER_AUTOIBRS: u64 = 1 << 21; + + let vendor = cpu_vendor(); + + let mut spec_ctrl_mask = 0u64; + if cpu_has_ibrs_ibpb() && !cpu_has_amd_autoibrs() { + spec_ctrl_mask |= SPEC_CTRL_IBRS; + } + if cpu_has_stibp() { + spec_ctrl_mask |= SPEC_CTRL_STIBP; + } + if vendor == CpuVendor::Intel && cpu_has_bhi_ctrl() { + spec_ctrl_mask |= SPEC_CTRL_BHI_DIS_S; + } + if spec_ctrl_mask != 0 { + let current = read_msr(IA32_SPEC_CTRL); + write_msr(IA32_SPEC_CTRL, current | spec_ctrl_mask); + SPEC_CTRL_MASK.store(spec_ctrl_mask, Ordering::Release); + crate::info!("IA32_SPEC_CTRL hardened with mask {:#x}", spec_ctrl_mask); + } + + if cpu_has_amd_autoibrs() { + let efer = read_msr(IA32_EFER); + if (efer & EFER_AUTOIBRS) == 0 { + write_msr(IA32_EFER, efer | EFER_AUTOIBRS); + crate::info!("AMD AutoIBRS enabled"); + } + } + + if cpu_has_ibrs_ibpb() { + let _ = IA32_PRED_CMD; + IBPB_SUPPORTED.store(true, Ordering::Release); + crate::info!("IBPB supported"); + } } fn cpuid_leaf7_ebx() -> u32 { @@ -134,6 +212,147 @@ fn cpuid_leaf7_ebx() -> u32 { ebx as u32 } +fn cpuid_leaf7_ecx() -> u32 { + let tmp: u64; + let ecx: u32; + unsafe { + asm!( + "xchg {rbx_tmp}, rbx", + "cpuid", + "xchg {rbx_tmp}, rbx", + inout("eax") 7u32 => _, + inout("ecx") 0u32 => ecx, + rbx_tmp = inout(reg) 0u64 => tmp, + out("edx") _, + options(nomem, nostack) + ); + } + let _ = tmp; + ecx +} + +fn cpuid_leaf7_edx() -> u32 { + let tmp: u64; + let edx: u32; + unsafe { + asm!( + "xchg {rbx_tmp}, rbx", + "cpuid", + "xchg {rbx_tmp}, rbx", + inout("eax") 7u32 => _, + inout("ecx") 0u32 => _, + rbx_tmp = inout(reg) 0u64 => tmp, + out("edx") edx, + options(nomem, nostack) + ); + } + let _ = tmp; + edx +} + +fn cpuid_leaf7_subleaf2_edx() -> u32 { + let tmp: u64; + let edx: u32; + unsafe { + asm!( + "xchg {rbx_tmp}, rbx", + "cpuid", + "xchg {rbx_tmp}, rbx", + inout("eax") 7u32 => _, + inout("ecx") 2u32 => _, + rbx_tmp = inout(reg) 0u64 => tmp, + out("edx") edx, + options(nomem, nostack) + ); + } + let _ = tmp; + edx +} + +fn cpuid_max_extended_leaf() -> u32 { + let eax: u32; + let tmp: u64; + unsafe { + asm!( + "xchg {rbx_tmp}, rbx", + "cpuid", + "xchg {rbx_tmp}, rbx", + inout("eax") 0x8000_0000u32 => eax, + in("ecx") 0u32, + rbx_tmp = inout(reg) 0u64 => tmp, + out("edx") _, + options(nomem, nostack) + ); + } + let _ = tmp; + eax +} + +fn cpuid_ext_8000_0008_ebx() -> u32 { + if cpuid_max_extended_leaf() < 0x8000_0008 { + return 0; + } + let ebx: u64; + unsafe { + asm!( + "xchg {tmp}, rbx", + "cpuid", + "xchg {tmp}, rbx", + inout("eax") 0x8000_0008u32 => _, + in("ecx") 0u32, + tmp = inout(reg) 0u64 => ebx, + out("edx") _, + options(nomem, nostack) + ); + } + ebx as u32 +} + +fn cpuid_ext_8000_0021_eax() -> u32 { + if cpuid_max_extended_leaf() < 0x8000_0021 { + return 0; + } + let eax: u32; + let tmp: u64; + unsafe { + asm!( + "xchg {rbx_tmp}, rbx", + "cpuid", + "xchg {rbx_tmp}, rbx", + inout("eax") 0x8000_0021u32 => eax, + in("ecx") 0u32, + rbx_tmp = inout(reg) 0u64 => tmp, + out("edx") _, + options(nomem, nostack) + ); + } + let _ = tmp; + eax +} + +fn cpu_vendor() -> CpuVendor { + let ebx: u64; + let ecx: u32; + let edx: u32; + unsafe { + asm!( + "xchg {rbx_tmp}, rbx", + "cpuid", + "xchg {rbx_tmp}, rbx", + inout("eax") 0u32 => _, + inout("ecx") 0u32 => ecx, + rbx_tmp = inout(reg) 0u64 => ebx, + out("edx") edx, + options(nomem, nostack) + ); + } + match (ebx as u32, edx, ecx) { + (0x756e_6547, 0x4965_6e69, 0x6c65_746e) => CpuVendor::Intel, + (0x6874_7541, 0x6974_6e65, 0x444d_4163) => CpuVendor::Amd, + _ => CpuVendor::Other, + } +} + fn cpuid_leaf1_ecx() -> u32 { // rbx は LLVM が予約するため xchg で保存/復元する let tmp: u64; @@ -154,6 +373,11 @@ fn cpuid_leaf1_ecx() -> u32 { ecx } +/// CPUID で UMIP サポートを確認 (leaf 7, ECX bit 2) +fn cpu_has_umip() -> bool { + (cpuid_leaf7_ecx() & (1 << 2)) != 0 +} + /// CPUID で FSGSBASE サポートを確認 (leaf 7, EBX bit 0) fn cpu_has_fsgsbase() -> bool { (cpuid_leaf7_ebx() & (1 << 0)) != 0 @@ -169,6 +393,26 @@ fn cpu_has_smap() -> bool { (cpuid_leaf7_ebx() & (1 << 20)) != 0 } +/// CPUID で IBRS/IBPB サポートを確認 (leaf 7, EDX bit 26) +fn cpu_has_ibrs_ibpb() -> bool { + (cpuid_leaf7_edx() & (1 << 26)) != 0 +} + +/// CPUID で STIBP サポートを確認 +fn cpu_has_stibp() -> bool { + ((cpuid_leaf7_edx() & (1 << 27)) != 0) || ((cpuid_ext_8000_0008_ebx() & (1 << 15)) != 0) +} + +/// Intel BHI control bit の有無を確認 (leaf 7, subleaf 2, EDX bit 4) +fn cpu_has_bhi_ctrl() -> bool { + (cpuid_leaf7_subleaf2_edx() & (1 << 4)) != 0 +} + +/// AMD AutoIBRS サポートを確認 (Fn8000_0021:EAX bit 8) +fn cpu_has_amd_autoibrs() -> bool { + cpu_vendor() == CpuVendor::Amd && (cpuid_ext_8000_0021_eax() & (1 << 8)) != 0 +} + /// CPUID で RDRAND サポートを確認 (leaf 1, ECX bit 30) fn cpu_has_rdrand() -> bool { (cpuid_leaf1_ecx() & (1 << 30)) != 0 @@ -234,6 +478,51 @@ pub fn is_smap_enabled() -> bool { SMAP_ENABLED.load(Ordering::Acquire) } +#[inline] +pub fn speculation_barrier() { + #[cfg(target_arch = "x86_64")] + unsafe { + asm!("lfence", options(nomem, nostack, preserves_flags)); + } +} + +pub fn branch_predictor_barrier() { + const IA32_PRED_CMD: u32 = 0x49; + if IBPB_SUPPORTED.load(Ordering::Acquire) { + unsafe { + write_msr(IA32_PRED_CMD, 1); + } + } + speculation_barrier(); +} + +pub fn reassert_runtime_hardening() { + unsafe { + let mut cr0 = read_cr0(); + let mut cr4 = read_cr4(); + cr0 |= 1 << 16; + if cpu_has_umip() { + cr4 |= 1 << 11; + } + if cpu_has_smep() { + cr4 |= 1 << 20; + } + if cpu_has_smap() { + cr4 |= 1 << 21; + } + write_cr0(cr0); + write_cr4(cr4); + let spec_ctrl_mask = SPEC_CTRL_MASK.load(Ordering::Acquire); + if spec_ctrl_mask != 0 { + const IA32_SPEC_CTRL: u32 = 0x48; + let current = read_msr(IA32_SPEC_CTRL); + if (current & spec_ctrl_mask) != spec_ctrl_mask { + write_msr(IA32_SPEC_CTRL, current | spec_ctrl_mask); + } + } + } +} + #[inline] fn rdtsc() -> u64 { let lo: u32; @@ -320,3 +609,52 @@ pub fn boot_entropy_u64() -> u64 { Err(v) => v, } } + +unsafe fn read_msr(msr: u32) -> u64 { + let lo: u32; + let hi: u32; + asm!( + "rdmsr", + in("ecx") msr, + out("eax") lo, + out("edx") hi, + options(nomem, nostack) + ); + ((hi as u64) << 32) | (lo as u64) +} + +unsafe fn write_msr(msr: u32, val: u64) { + let lo = val as u32; + let hi = (val >> 32) as u32; + asm!( + "wrmsr", + in("ecx") msr, + in("eax") lo, + in("edx") hi, + options(nomem, nostack) + ); +} + +#[inline] +unsafe fn read_cr0() -> u64 { + let cr0: u64; + asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack)); + cr0 +} + +#[inline] +unsafe fn write_cr0(val: u64) { + asm!("mov cr0, {}", in(reg) val, options(nomem, nostack)); +} + +#[inline] +unsafe fn read_cr4() -> u64 { + let cr4: u64; + asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack)); + cr4 +} + +#[inline] +unsafe fn write_cr4(val: u64) { + asm!("mov cr4, {}", in(reg) val, options(nomem, nostack)); +} diff --git a/src/core/interrupt/timer.rs b/src/core/interrupt/timer.rs index 4a61887..dd115ca 100644 --- a/src/core/interrupt/timer.rs +++ b/src/core/interrupt/timer.rs @@ -16,6 +16,7 @@ static TIMER_TICKS: AtomicU64 = AtomicU64::new(0); pub extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { // KPTI: ユーザーCR3で割り込みに入った場合、先にカーネルCR3へ切り替える let entered_from_user = crate::syscall::syscall_entry::switch_to_kernel_page_table() != 0; + crate::cpu::reassert_runtime_hardening(); // タイマーカウンタを増加 let ticks = TIMER_TICKS diff --git a/src/core/percpu.rs b/src/core/percpu.rs index 64393f5..80c8d09 100644 --- a/src/core/percpu.rs +++ b/src/core/percpu.rs @@ -89,15 +89,18 @@ fn local_apic_id() -> u32 { pub fn init_boot_cpu(syscall_kernel_rsp: u64) { let apic_id = local_apic_id() as usize; - assert!( - apic_id < MAX_CPUS, - "Boot CPU APIC ID {} exceeds MAX_CPUS {}", - apic_id, - MAX_CPUS - ); + let slot = if apic_id < MAX_CPUS { + apic_id + } else { + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "boot CPU APIC ID exceeded per-cpu table; falling back to CPU0 slot", + ); + 0 + }; let (cr3, _) = Cr3::read(); - let state = &CPU_STATES[apic_id]; + let state = &CPU_STATES[slot]; state .kernel_cr3 .store(cr3.start_address().as_u64(), Ordering::SeqCst); From f05c489f1be87f2b09fccd691f19fe75395c8fce Mon Sep 17 00:00:00 2001 From: minto-dane Date: Thu, 9 Apr 2026 01:31:14 -0400 Subject: [PATCH 03/17] security(core): move user memory access to page-table-walk primitives Convert memory initialization, syscall entry, and ELF loading to use page-table-walk usercopy helpers instead of temporary user address-space switching. Keep kernel CR3 active during kernel-side copies, tighten user mapping permissions, and return explicit initialization errors from paging setup. --- src/core/init/mod.rs | 2 +- src/core/mem/mod.rs | 29 ++++---- src/core/mem/paging.rs | 143 +++++++++++++++++++++++++++++++++++----- src/core/mem/user.rs | 53 +++++++++++---- src/core/syscall/mod.rs | 138 +++++++++++++++++++------------------- src/core/task/elf.rs | 123 ++++++++++++++++++++++------------ 6 files changed, 334 insertions(+), 154 deletions(-) diff --git a/src/core/init/mod.rs b/src/core/init/mod.rs index 34b35bc..bdfa0f0 100644 --- a/src/core/init/mod.rs +++ b/src/core/init/mod.rs @@ -37,7 +37,7 @@ pub fn kinit(boot_info: &'static BootInfo) -> Result<&'static [MemoryRegion]> { mem::init_frame_allocator(memory_map)?; // メモリ管理の初期化 - mem::init(boot_info); + mem::init(boot_info)?; fs::init(); diff --git a/src/core/mem/mod.rs b/src/core/mem/mod.rs index fbc2ade..ad946f8 100644 --- a/src/core/mem/mod.rs +++ b/src/core/mem/mod.rs @@ -15,7 +15,7 @@ pub(crate) mod user; /// /// ## Arguments /// - `boot_info`: ブートローダから渡される情報構造体 -pub fn init(boot_info: &'static crate::BootInfo) { +pub fn init(boot_info: &'static crate::BootInfo) -> Result<()> { info!("Initializing memory..."); x86_64::instructions::interrupts::disable(); @@ -23,16 +23,18 @@ pub fn init(boot_info: &'static crate::BootInfo) { gdt::init(); interrupt::init_idt(); - paging::init(boot_info); + paging::init(boot_info)?; let mut page_table_lock = paging::PAGE_TABLE.lock(); let page_table = match page_table_lock.as_mut() { Some(p) => p, None => { crate::warn!("PAGE_TABLE not initialized"); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "memory init missing page table", + ); + return Err(crate::Kernel::Memory(crate::result::Memory::NotMapped)); } }; let mut frame_alloc_lock = frame::FRAME_ALLOCATOR.lock(); @@ -40,9 +42,11 @@ pub fn init(boot_info: &'static crate::BootInfo) { Some(fa) => fa, None => { crate::warn!("FRAME_ALLOCATOR not initialized"); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "memory init missing frame allocator", + ); + return Err(crate::Kernel::Memory(crate::result::Memory::OutOfMemory)); } }; if let Err(e) = allocator::init_heap( @@ -51,9 +55,11 @@ pub fn init(boot_info: &'static crate::BootInfo) { boot_info.kernel_heap_addr, ) { crate::warn!("Heap initialization failed: {:?}", e); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "memory init heap initialization failed", + ); + return Err(crate::Kernel::Memory(crate::result::Memory::InvalidAddress)); } // PITを停止してからPICを初期化 @@ -61,6 +67,7 @@ pub fn init(boot_info: &'static crate::BootInfo) { interrupt::init_pic(); debug!("Memory initialized"); + Ok(()) } /// メモリマップを設定してフレームアロケータを初期化 diff --git a/src/core/mem/paging.rs b/src/core/mem/paging.rs index 3ff997e..4ee0cab 100644 --- a/src/core/mem/paging.rs +++ b/src/core/mem/paging.rs @@ -88,7 +88,7 @@ fn protect_kernel_text_pages(page_table: &mut OffsetPageTable<'static>) { /// /// ## Arguments /// - `boot_info`: ブートローダーから提供される情報(メモリマップ、物理メモリオフセットなど) -pub fn init(boot_info: &'static crate::BootInfo) { +pub fn init(boot_info: &'static crate::BootInfo) -> Result<()> { info!("Initializing paging..."); let physical_memory_offset = boot_info.physical_memory_offset; @@ -98,10 +98,11 @@ pub fn init(boot_info: &'static crate::BootInfo) { Ok(f) => f, Err(e) => { crate::warn!("Failed to allocate frame for new page table: {:?}", e); - x86_64::instructions::interrupts::disable(); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "paging init failed to allocate L4 frame", + ); + return Err(Kernel::Memory(Memory::OutOfMemory)); } }; let l4_table_addr = l4_frame.start_address().as_u64(); @@ -120,9 +121,11 @@ pub fn init(boot_info: &'static crate::BootInfo) { Some(a) => a, None => { crate::warn!("Frame allocator not initialized"); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "paging init missing frame allocator", + ); + return Err(Kernel::Memory(Memory::InvalidAddress)); } }; @@ -257,6 +260,7 @@ pub fn init(boot_info: &'static crate::BootInfo) { "Paging initialized. New table active. Mapped {} pages.", mapped_pages ); + Ok(()) } /// アクティブなレベル4ページテーブルへの参照を取得 @@ -482,6 +486,114 @@ fn page_is_user_mapped_in_table(table_phys: u64, page_addr: u64) -> bool { }) } +fn translate_user_addr_in_table( + table_phys: u64, + addr: u64, + require_writable: bool, +) -> Option<(u64, usize)> { + if addr > USER_SPACE_END { + return None; + } + let page_base = addr & !0xfffu64; + let page_off = (addr & 0xfff) as usize; + let flags = user_page_flags_in_table(table_phys, page_base)?; + if !flags.contains(PageTableFlags::PRESENT) || !flags.contains(PageTableFlags::USER_ACCESSIBLE) + { + return None; + } + if require_writable && !flags.contains(PageTableFlags::WRITABLE) { + return None; + } + let (phys, _) = translate_addr_in_table(table_phys, VirtAddr::new(addr))?; + Some((phys.as_u64(), page_off)) +} + +#[inline] +fn nospec_usercopy_barrier() { + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("lfence", options(nomem, nostack, preserves_flags)); + } +} + +pub fn copy_from_user_in_table(table_phys: u64, src_ptr: u64, dst: &mut [u8]) -> Result<()> { + use core::sync::atomic::{compiler_fence, Ordering}; + + if dst.is_empty() { + return Ok(()); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + let len = dst.len() as u64; + let end = src_ptr + .checked_add(len.saturating_sub(1)) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if src_ptr == 0 || src_ptr > USER_SPACE_END || end > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + compiler_fence(Ordering::SeqCst); + nospec_usercopy_barrier(); + + let mut copied = 0usize; + while copied < dst.len() { + let cur = src_ptr + .checked_add(copied as u64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let (phys, page_off) = translate_user_addr_in_table(table_phys, cur, false) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + nospec_usercopy_barrier(); + let chunk = core::cmp::min(4096usize.saturating_sub(page_off), dst.len() - copied); + let src = phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::InvalidAddress))? as *const u8; + unsafe { + core::ptr::copy_nonoverlapping(src, dst[copied..].as_mut_ptr(), chunk); + } + copied += chunk; + } + + Ok(()) +} + +pub fn copy_to_user_in_table(table_phys: u64, dst_ptr: u64, src: &[u8]) -> Result<()> { + use core::sync::atomic::{compiler_fence, Ordering}; + + if src.is_empty() { + return Ok(()); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + let len = src.len() as u64; + let end = dst_ptr + .checked_add(len.saturating_sub(1)) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if dst_ptr == 0 || dst_ptr > USER_SPACE_END || end > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + compiler_fence(Ordering::SeqCst); + nospec_usercopy_barrier(); + + let mut copied = 0usize; + while copied < src.len() { + let cur = dst_ptr + .checked_add(copied as u64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let (phys, page_off) = translate_user_addr_in_table(table_phys, cur, true) + .ok_or(Kernel::Memory(Memory::PermissionDenied))?; + nospec_usercopy_barrier(); + let chunk = core::cmp::min(4096usize.saturating_sub(page_off), src.len() - copied); + let dst = phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::InvalidAddress))? as *mut u8; + unsafe { + core::ptr::copy_nonoverlapping(src[copied..].as_ptr(), dst, chunk); + } + copied += chunk; + } + + Ok(()) +} + /// 指定したページテーブルでユーザー範囲がすべて有効にマップされているか確認する pub fn is_user_range_mapped_in_table(table_phys: u64, addr: u64, len: u64) -> bool { if addr > USER_SPACE_END { @@ -807,16 +919,6 @@ pub fn create_user_page_table() -> Result { new_l4[0].set_addr(PhysAddr::new(new_l3_phys), kernel_l4[0].flags()); } - // カーネルヒープ (0x4444_4444_0000, L4[136]) をユーザーページテーブルと共有する。 - // with_user_memory_access がユーザーCR3に切り替えた際に - // カーネルヒープ上のデータ(FileHandle, Box<[u8]> など)へアクセスできるようにする。 - // ヒープは init_memory 時に全ページがマップ済みのため、 - // L4エントリ(L3テーブルへのポインタ)を共有するだけで十分。 - const KERNEL_HEAP_L4_IDX: usize = 136; // 0x4444_4444_0000 >> 39 & 0x1ff - if !kernel_l4[KERNEL_HEAP_L4_IDX].is_unused() { - new_l4[KERNEL_HEAP_L4_IDX] = kernel_l4[KERNEL_HEAP_L4_IDX].clone(); - } - Ok(new_l4_phys) } @@ -972,6 +1074,10 @@ pub fn map_and_copy_segment_to( use crate::result::{Kernel, Memory}; use x86_64::structures::paging::PageTableFlags as Flags; + if writable && executable { + return Err(Kernel::Memory(Memory::PermissionDenied)); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; if memsz == 0 { return if filesz == 0 { @@ -1520,6 +1626,7 @@ pub fn map_page_in_table( } if user_accessible { flags |= PageTableFlags::USER_ACCESSIBLE; + flags |= PageTableFlags::NO_EXECUTE; } l1e.set_addr(PhysAddr::new(phys_addr), flags); diff --git a/src/core/mem/user.rs b/src/core/mem/user.rs index e387386..c0318bc 100644 --- a/src/core/mem/user.rs +++ b/src/core/mem/user.rs @@ -19,8 +19,22 @@ pub struct UserStack { pub top: u64, } -/// 任意のユーザ空間レンジをマップ -pub fn map_user_range(start: u64, size: u64, flags: PageTableFlags) -> Result<()> { +fn current_process_user_page_table() -> Result { + let pid = crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |thread| thread.process_id())) + .ok_or(Kernel::Memory(Memory::NotMapped))?; + crate::task::with_process(pid, |proc| proc.page_table()) + .flatten() + .ok_or(Kernel::Memory(Memory::NotMapped)) +} + +/// 指定したユーザーページテーブル上に任意のユーザ空間レンジをマップ +pub fn map_user_range_in_table( + table_phys: u64, + start: u64, + size: u64, + flags: PageTableFlags, +) -> Result<()> { if size == 0 { return Ok(()); } @@ -33,20 +47,36 @@ pub fn map_user_range(start: u64, size: u64, flags: PageTableFlags) -> Result<() if start == 0 || start > USER_SPACE_END || end > USER_SPACE_END { return Err(Kernel::Memory(Memory::InvalidAddress)); } - - let start_page = Page::::containing_address(VirtAddr::new(start)); - let end_page = Page::::containing_address(VirtAddr::new(end)); - - for page in Page::range_inclusive(start_page, end_page) { - let frame = frame::allocate_frame()?; - paging::map_page(page, frame, flags)?; + if !flags.contains(PageTableFlags::USER_ACCESSIBLE) || !flags.contains(PageTableFlags::PRESENT) + { + return Err(Kernel::Memory(Memory::PermissionDenied)); } - Ok(()) + paging::map_and_copy_segment_to( + table_phys, + start, + 0, + size, + &[], + flags.contains(PageTableFlags::WRITABLE), + !flags.contains(PageTableFlags::NO_EXECUTE), + ) +} + +/// 任意のユーザ空間レンジをマップ +pub fn map_user_range(start: u64, size: u64, flags: PageTableFlags) -> Result<()> { + let table_phys = current_process_user_page_table()?; + map_user_range_in_table(table_phys, start, size, flags) } /// ユーザスタックを確保 pub fn alloc_user_stack(pages: u64) -> Result { + let table_phys = current_process_user_page_table()?; + alloc_user_stack_in_table(table_phys, pages) +} + +/// 指定したユーザーページテーブル上にユーザスタックを確保 +pub fn alloc_user_stack_in_table(table_phys: u64, pages: u64) -> Result { if pages == 0 { return Err(Kernel::Memory(Memory::InvalidAddress)); } @@ -63,13 +93,12 @@ pub fn alloc_user_stack(pages: u64) -> Result { let stack_bottom = new_top + USER_STACK_GUARD_PAGES * PAGE_SIZE; let stack_top = new_top + total; - // NO_EXECUTE フラグを設定してスタックの実行を禁止する (MED-03) let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::NO_EXECUTE; - map_user_range(stack_bottom, stack_size, flags)?; + map_user_range_in_table(table_phys, stack_bottom, stack_size, flags)?; *top = new_top; diff --git a/src/core/syscall/mod.rs b/src/core/syscall/mod.rs index a229243..fbedaf2 100644 --- a/src/core/syscall/mod.rs +++ b/src/core/syscall/mod.rs @@ -64,27 +64,25 @@ pub fn validate_user_ptr(ptr: u64, len: u64) -> bool { crate::mem::paging::is_user_range_mapped_in_table(user_pt, ptr, len) } +fn current_user_page_table() -> Option { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.page_table())) + .flatten() +} + /// ユーザー空間の null 終端文字列を最大長付きで読み取り、カーネル所有の `String` を返す。 pub fn read_user_cstring(ptr: u64, max_len: usize) -> Result { if ptr == 0 || max_len == 0 { return Err(EINVAL); } - if !validate_user_ptr(ptr, 1) { - return Err(EFAULT); - } let mut bytes = Vec::with_capacity(max_len); - let mut checked_page = u64::MAX; for i in 0..max_len { let addr = ptr.checked_add(i as u64).ok_or(EFAULT)?; - let page_base = addr & !0xfffu64; - if page_base != checked_page { - if !validate_user_ptr(addr, 1) { - return Err(EFAULT); - } - checked_page = page_base; - } - let b = with_user_memory_access(|| unsafe { core::ptr::read(addr as *const u8) }); + let mut one = [0u8; 1]; + copy_from_user(addr, &mut one)?; + let b = one[0]; if b == 0 { return String::from_utf8(bytes).map_err(|_| EINVAL); } @@ -98,19 +96,25 @@ pub fn copy_from_user(src_ptr: u64, dst: &mut [u8]) -> Result<(), u64> { if dst.is_empty() { return Ok(()); } + let user_pt = match current_user_page_table() { + Some(pt) => pt, + None => return Err(EFAULT), + }; if src_ptr == 0 { return Err(EFAULT); } - if !validate_user_ptr(src_ptr, dst.len() as u64) { - return Err(EFAULT); - } - - let dst_ptr = dst.as_mut_ptr(); - let len = dst.len(); - with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(src_ptr as *const u8, dst_ptr, len); - }); - Ok(()) + crate::mem::paging::copy_from_user_in_table(user_pt, src_ptr, dst).map_err(|err| { + crate::audit::log( + crate::audit::AuditEventKind::Usercopy, + "copy_from_user rejected unmapped or unreadable range", + ); + match err { + crate::Kernel::Memory(crate::result::Memory::OutOfMemory) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::PermissionDenied) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::InvalidAddress) => EFAULT, + _ => EFAULT, + } + }) } /// バイト列をユーザー空間へコピーする(コピー元はカーネル空間)。 @@ -118,70 +122,61 @@ pub fn copy_to_user(dst_ptr: u64, src: &[u8]) -> Result<(), u64> { if src.is_empty() { return Ok(()); } + let user_pt = match current_user_page_table() { + Some(pt) => pt, + None => return Err(EFAULT), + }; if dst_ptr == 0 { return Err(EFAULT); } - if !validate_user_ptr(dst_ptr, src.len() as u64) { - return Err(EFAULT); - } + crate::mem::paging::copy_to_user_in_table(user_pt, dst_ptr, src).map_err(|err| { + crate::audit::log( + crate::audit::AuditEventKind::Usercopy, + "copy_to_user rejected unmapped or unwritable range", + ); + match err { + crate::Kernel::Memory(crate::result::Memory::OutOfMemory) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::PermissionDenied) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::InvalidAddress) => EFAULT, + _ => EFAULT, + } + }) +} - let src_ptr = src.as_ptr(); - let len = src.len(); - with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(src_ptr, dst_ptr as *mut u8, len); - }); - Ok(()) +pub fn read_user_u64(ptr: u64) -> Result { + let mut buf = [0u8; 8]; + copy_from_user(ptr, &mut buf)?; + Ok(u64::from_ne_bytes(buf)) } -/// ユーザーポインタを実際に参照する短い区間を、必要に応じてユーザーCR3で実行する。 -/// -/// KPTI有効時、syscall本体はkernel CR3で実行されるため、ユーザー仮想アドレスを -/// 直接参照する区間だけ一時的にuser CR3へ切り替える。 -pub fn with_user_memory_access(f: impl FnOnce() -> R) -> R { - use x86_64::registers::control::Cr3; - x86_64::instructions::interrupts::without_interrupts(|| { - let kernel_cr3 = crate::percpu::kernel_cr3(); - if kernel_cr3 == 0 { - return f(); - } +pub fn read_user_u32(ptr: u64) -> Result { + let mut buf = [0u8; 4]; + copy_from_user(ptr, &mut buf)?; + Ok(u32::from_ne_bytes(buf)) +} - let (cur, _) = Cr3::read(); - let current_cr3 = cur.start_address().as_u64(); - if current_cr3 != kernel_cr3 { - return f(); - } +pub fn read_user_i64(ptr: u64) -> Result { + let mut buf = [0u8; 8]; + copy_from_user(ptr, &mut buf)?; + Ok(i64::from_ne_bytes(buf)) +} - let user_pt = crate::task::current_thread_id() - .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) - .and_then(|pid| crate::task::with_process(pid, |p| p.page_table())) - .flatten() - .unwrap_or(0); - if user_pt == 0 { - return f(); - } +pub fn write_user_u64(ptr: u64, value: u64) -> Result<(), u64> { + copy_to_user(ptr, &value.to_ne_bytes()) +} - if crate::cpu::is_smap_enabled() { - unsafe { - asm!("stac", options(nostack, preserves_flags)); - } - } - crate::mem::paging::switch_page_table(user_pt); - let out = f(); - crate::mem::paging::switch_page_table(kernel_cr3); - if crate::cpu::is_smap_enabled() { - unsafe { - asm!("clac", options(nostack, preserves_flags)); - } - } - out - }) +pub fn write_user_u32(ptr: u64, value: u32) -> Result<(), u64> { + copy_to_user(ptr, &value.to_ne_bytes()) +} + +pub fn write_user_i32(ptr: u64, value: i32) -> Result<(), u64> { + copy_to_user(ptr, &value.to_ne_bytes()) } pub use types::{ SyscallNumber, EAGAIN, EBADF, EFAULT, EINVAL, ENODATA, ENOENT, ENOSYS, EPERM, ESRCH, SUCCESS, }; -use core::arch::asm; use x86_64::structures::idt::InterruptStackFrame; /// システムコールのディスパッチ @@ -460,6 +455,7 @@ extern "C" fn syscall_handler_rust( crate::percpu::install_current_cpu_gs_base(); let current_tid = crate::task::current_thread_id(); let prev_cr3 = syscall_entry::switch_to_kernel_page_table(); + crate::cpu::reassert_runtime_hardening(); if let Some(tid) = current_tid { crate::task::with_thread_mut(tid, |t| t.set_in_syscall(true)); } diff --git a/src/core/task/elf.rs b/src/core/task/elf.rs index 76eb13c..2ba396b 100644 --- a/src/core/task/elf.rs +++ b/src/core/task/elf.rs @@ -1,9 +1,11 @@ //! ELFローダ use crate::init; -use crate::mem::{frame, user}; +use crate::mem::{frame, paging, user}; use crate::result::{Kernel, Memory, Process, Result}; -use crate::task::{add_process, add_thread, PrivilegeLevel, Process as TaskProcess, Thread}; +use crate::task::{ + add_process, add_thread, remove_process, PrivilegeLevel, Process as TaskProcess, Thread, +}; use core::sync::atomic::{AtomicU64, Ordering}; use x86_64::structures::paging::Page; use x86_64::structures::paging::PageTableFlags; @@ -95,7 +97,29 @@ fn next_pie_load_bias() -> u64 { PIE_LOAD_BIAS + offset_pages * 4096 } +fn current_user_page_table() -> Result { + let pid = crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |thread| thread.process_id())) + .ok_or(Kernel::Memory(Memory::NotMapped))?; + crate::task::with_process(pid, |proc| proc.page_table()) + .flatten() + .ok_or(Kernel::Memory(Memory::NotMapped)) +} + +fn write_user_bytes_in_table(table_phys: u64, user_addr: u64, bytes: &[u8]) -> Result<()> { + paging::copy_to_user_in_table(table_phys, user_addr, bytes) +} + +fn write_user_u64_in_table(table_phys: u64, user_addr: u64, value: u64) -> Result<()> { + write_user_bytes_in_table(table_phys, user_addr, &value.to_ne_bytes()) +} + pub fn load_elf(data: &[u8]) -> Result { + let table_phys = current_user_page_table()?; + load_elf_into(table_phys, data) +} + +pub fn load_elf_into(table_phys: u64, data: &[u8]) -> Result { let header = parse_header(data)?; validate_header(header)?; @@ -136,33 +160,29 @@ pub fn load_elf(data: &[u8]) -> Result { return Err(Kernel::Memory(Memory::InvalidAddress)); } - let mut flags = PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE; - // ロード時は常に書き込み可能にする(初期データコピーのため) - // TODO: 実行後にPF_Wに応じて保護属性を調整する - flags |= PageTableFlags::WRITABLE; - if phdr.p_flags & PF_X == 0 { - flags |= PageTableFlags::NO_EXECUTE; - } - let vaddr = phdr.p_vaddr.wrapping_add(load_bias); - user::map_user_range(vaddr, phdr.p_memsz, flags)?; - - unsafe { - let dst = vaddr as *mut u8; - let src = data.as_ptr().add(phdr.p_offset as usize); - core::ptr::copy_nonoverlapping(src, dst, filesz); - - if memsz > filesz { - core::ptr::write_bytes(dst.add(filesz), 0, memsz - filesz); - } + let seg_src = &data[phdr.p_offset as usize..file_end]; + let writable = (phdr.p_flags & PF_W) != 0; + let executable = (phdr.p_flags & PF_X) != 0; + if writable && executable { + return Err(Kernel::Memory(Memory::PermissionDenied)); } + paging::map_and_copy_segment_to( + table_phys, + vaddr, + filesz as u64, + phdr.p_memsz, + seg_src, + writable, + executable, + )?; } if load_bias != 0 { - apply_relocations(data, header, load_bias)?; + apply_relocations_to(table_phys, data, header, load_bias)?; } - let stack = user::alloc_user_stack(8)?; + let stack = user::alloc_user_stack_in_table(table_phys, 8)?; Ok(LoadedElf { entry: header.e_entry.wrapping_add(load_bias), @@ -174,16 +194,26 @@ pub fn load_elf(data: &[u8]) -> Result { pub fn spawn_service(path: &str, name: &'static str) -> Result<()> { let data = init::fs::read(path).ok_or(Kernel::InvalidParam)?; - let loaded = load_elf(&*data)?; + let new_pt_phys = paging::create_user_page_table()?; - // Services run in Ring3 (Service), not Core - let process = TaskProcess::new(name, PrivilegeLevel::Service, None, 1); + let mut process = TaskProcess::new(name, PrivilegeLevel::Service, None, 1); + process.set_page_table(new_pt_phys); let pid = process.id(); if add_process(process).is_none() { + let _ = paging::destroy_user_page_table(new_pt_phys); return Err(Kernel::Process(Process::MaxProcessesReached)); } + let loaded = match load_elf_into(new_pt_phys, &data) { + Ok(loaded) => loaded, + Err(err) => { + let _ = remove_process(pid); + let _ = paging::destroy_user_page_table(new_pt_phys); + return Err(err); + } + }; + // Allocate a kernel stack (pages) for the service thread and map frames let stack_size = (loaded.stack_top - loaded.stack_bottom) as usize; let page_size: usize = 4096; @@ -208,9 +238,14 @@ pub fn spawn_service(path: &str, name: &'static str) -> Result<()> { crate::mem::paging::map_page(page, f, flags)?; } - let entry_fn: fn() -> ! = unsafe { core::mem::transmute(loaded.entry) }; - // Create thread with kernel stack, then set its context.rsp to the user stack - let mut thread = Thread::new(pid, name, entry_fn, kernel_stack_addr, pages * page_size); + let mut thread = Thread::new_usermode( + pid, + name, + loaded.entry, + loaded.stack_top, + kernel_stack_addr, + pages * page_size, + ); // Build initial user stack: argc/argv/envp/auxv and strings // We'll place strings at lower addresses and pointers/auxv above them. @@ -220,10 +255,15 @@ pub fn spawn_service(path: &str, name: &'static str) -> Result<()> { let argv0 = path.as_bytes(); // store argv0 string sp = sp.saturating_sub((argv0.len() + 1) as u64); - unsafe { - let dst = sp as *mut u8; - core::ptr::copy_nonoverlapping(argv0.as_ptr(), dst, argv0.len()); - *dst.add(argv0.len()) = 0; + if let Err(err) = write_user_bytes_in_table(new_pt_phys, sp, argv0) { + let _ = remove_process(pid); + let _ = paging::destroy_user_page_table(new_pt_phys); + return Err(err); + } + if let Err(err) = write_user_bytes_in_table(new_pt_phys, sp + argv0.len() as u64, &[0]) { + let _ = remove_process(pid); + let _ = paging::destroy_user_page_table(new_pt_phys); + return Err(err); } let argv0_addr = sp; @@ -254,10 +294,7 @@ pub fn spawn_service(path: &str, name: &'static str) -> Result<()> { return Err(Kernel::Memory(Memory::InvalidAddress)); } sp = new_sp; - unsafe { - *(sp as *mut u64) = val; - } - Ok(()) + write_user_u64_in_table(new_pt_phys, sp, val) }; // AT_NULL @@ -301,6 +338,8 @@ pub fn spawn_service(path: &str, name: &'static str) -> Result<()> { thread.context_mut().rbp = 0; if add_thread(thread).is_none() { + let _ = remove_process(pid); + let _ = paging::destroy_user_page_table(new_pt_phys); return Err(Kernel::Process(Process::MaxProcessesReached)); } @@ -346,7 +385,12 @@ struct Elf64Rela { r_addend: i64, } -fn apply_relocations(data: &[u8], header: Elf64Header, load_bias: u64) -> Result<()> { +fn apply_relocations_to( + table_phys: u64, + data: &[u8], + header: Elf64Header, + load_bias: u64, +) -> Result<()> { let mut rela_addr = None; let mut rela_size = None; let mut rela_ent = None; @@ -418,11 +462,8 @@ fn apply_relocations(data: &[u8], header: Elf64Header, load_bias: u64) -> Result { return Err(Kernel::InvalidParam); } - let reloc_addr = reloc_vaddr as *mut u64; let value = load_bias.wrapping_add(rela.r_addend as u64); - unsafe { - reloc_addr.write(value); - } + write_user_u64_in_table(table_phys, reloc_vaddr, value)?; } } From e0c57c1c75946cafbbef4a8bd872afbbe917a932 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Thu, 9 Apr 2026 01:31:49 -0400 Subject: [PATCH 04/17] security(core): harden fs, exec, and pipe user-buffer handling Route filesystem, exec, generic I/O, and pipe descriptor paths through the hardened usercopy helpers. Replace direct user-pointer reads and writes in these runtime paths while preserving the existing fd table model used by the service stack. --- src/core/syscall/exec.rs | 88 +++++++++++++++++----------- src/core/syscall/fs.rs | 118 ++++++++++++++++---------------------- src/core/syscall/io.rs | 20 +++---- src/core/syscall/pipe.rs | 35 ++++------- src/core/task/fd_table.rs | 26 +++++++++ 5 files changed, 151 insertions(+), 136 deletions(-) diff --git a/src/core/syscall/exec.rs b/src/core/syscall/exec.rs index 7863d10..acff29b 100644 --- a/src/core/syscall/exec.rs +++ b/src/core/syscall/exec.rs @@ -149,17 +149,19 @@ fn read_nul_args_from_user( } let mut storage: Vec = Vec::new(); - crate::syscall::with_user_memory_access(|| unsafe { - let ptr = args_ptr as *const u8; - for i in 0..max_total_bytes { - let b = ptr.add(i).read_volatile(); - storage.push(b); - let len = storage.len(); - if len >= 2 && storage[len - 1] == 0 && storage[len - 2] == 0 { - break; - } + for i in 0..max_total_bytes { + let addr = match args_ptr.checked_add(i as u64) { + Some(v) => v, + None => return Err(EFAULT), + }; + let mut one = [0u8; 1]; + crate::syscall::copy_from_user(addr, &mut one)?; + storage.push(one[0]); + let len = storage.len(); + if len >= 2 && storage[len - 1] == 0 && storage[len - 2] == 0 { + break; } - }); + } let mut out = Vec::new(); for s in storage.split(|&b| b == 0) { @@ -487,7 +489,33 @@ pub fn exec_from_fs_stream(path_ptr: u64, args_ptr: u64) -> u64 { delegated_parent_pid(), ); } - let eh = eh_opt.unwrap(); + let eh = match eh_opt { + Some(eh) => eh, + None => { + crate::warn!("exec: ELF header disappeared during zero-copy path"); + let mut image: Vec = vec![0u8; total]; + copy_frames_to_buf(&frames, phys_off, &mut image); + let _ = crate::mem::paging::unmap_range_in_table( + crate::task::with_process(fs_pid, |p| p.page_table()) + .flatten() + .unwrap_or(0), + map_start, + (frames.len() as u64) * 4096, + ); + for p in frames.iter() { + let fr = PhysAddr::new(*p); + let framef = x86_64::structures::paging::PhysFrame::containing_address(fr); + let _ = frame::deallocate_frame(framef); + } + return exec_with_data( + &image, + path.as_str(), + &path, + &extra_args, + delegated_parent_pid(), + ); + } + }; let phoff = eh.e_phoff as usize; let phentsz = eh.e_phentsize as usize; let phnum = eh.e_phnum as usize; @@ -1606,9 +1634,10 @@ fn read_user_ptr_array(array_ptr: u64, max_entries: usize) -> Vec { if !crate::syscall::validate_user_ptr(ptr_addr, 8) { break; } - let entry_ptr = crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::read_unaligned(ptr_addr as *const u64) - }); + let entry_ptr = match crate::syscall::read_user_u64(ptr_addr) { + Ok(v) => v, + Err(_) => break, + }; if entry_ptr == 0 { break; } @@ -1910,14 +1939,10 @@ pub fn exec_from_buffer_syscall(buf_ptr: u64, buf_len: u64) -> u64 { return EFAULT; } - // KPTI 環境ではカーネルは kernel CR3 で動作しており、ユーザー空間の - // 仮想アドレスに直接アクセスできない。 - // with_user_memory_access でユーザー CR3 に一時切替してバルクコピーする。 let mut owned = alloc::vec![0u8; buf_len as usize]; - let dst_ptr = owned.as_mut_ptr(); - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(buf_ptr as *const u8, dst_ptr, buf_len as usize); - }); + if crate::syscall::copy_from_user(buf_ptr, &mut owned).is_err() { + return EFAULT; + } exec_with_data( &owned, @@ -1954,10 +1979,9 @@ pub fn exec_from_buffer_named_syscall(buf_ptr: u64, buf_len: u64, path_ptr: u64) let process_name = path.rsplit('/').next().unwrap_or(path.as_str()); let mut owned = alloc::vec![0u8; buf_len as usize]; - let dst_ptr = owned.as_mut_ptr(); - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(buf_ptr as *const u8, dst_ptr, buf_len as usize); - }); + if crate::syscall::copy_from_user(buf_ptr, &mut owned).is_err() { + return EFAULT; + } exec_with_data( &owned, @@ -2006,10 +2030,9 @@ pub fn exec_from_buffer_named_args_syscall( let args_refs: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect(); let mut owned = alloc::vec![0u8; buf_len as usize]; - let dst_ptr = owned.as_mut_ptr(); - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(buf_ptr as *const u8, dst_ptr, buf_len as usize); - }); + if crate::syscall::copy_from_user(buf_ptr, &mut owned).is_err() { + return EFAULT; + } exec_with_data( &owned, @@ -2053,10 +2076,9 @@ pub fn exec_from_buffer_named_args_with_requester_syscall( let args_refs: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect(); let mut owned = alloc::vec![0u8; buf_len as usize]; - let dst_ptr = owned.as_mut_ptr(); - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(buf_ptr as *const u8, dst_ptr, buf_len as usize); - }); + if crate::syscall::copy_from_user(buf_ptr, &mut owned).is_err() { + return EFAULT; + } let parent_override = if requester_tid != 0 { let requester = crate::task::ThreadId::from_u64(requester_tid); diff --git a/src/core/syscall/fs.rs b/src/core/syscall/fs.rs index 339e00f..341ca57 100644 --- a/src/core/syscall/fs.rs +++ b/src/core/syscall/fs.rs @@ -603,24 +603,15 @@ pub fn seek(fd: u64, offset: i64, whence: u64) -> u64 { fn write_stat_buf(stat_ptr: u64, mode: u32, size: u64) { const STAT_SIZE: usize = 144; let blocks = size.div_ceil(512); - crate::syscall::with_user_memory_access(|| unsafe { - let buf = core::slice::from_raw_parts_mut(stat_ptr as *mut u8, STAT_SIZE); - buf.fill(0); - // st_dev = 1 (仮のデバイス番号) - buf[0..8].copy_from_slice(&1u64.to_ne_bytes()); - // st_ino = 1 (inode 番号は省略) - buf[8..16].copy_from_slice(&1u64.to_ne_bytes()); - // st_nlink = 1 - buf[16..24].copy_from_slice(&1u64.to_ne_bytes()); - // st_mode - buf[24..28].copy_from_slice(&mode.to_ne_bytes()); - // st_size - buf[48..56].copy_from_slice(&size.to_ne_bytes()); - // st_blksize = 4096 - buf[56..64].copy_from_slice(&4096u64.to_ne_bytes()); - // st_blocks - buf[64..72].copy_from_slice(&blocks.to_ne_bytes()); - }); + let mut buf = [0u8; STAT_SIZE]; + buf[0..8].copy_from_slice(&1u64.to_ne_bytes()); + buf[8..16].copy_from_slice(&1u64.to_ne_bytes()); + buf[16..24].copy_from_slice(&1u64.to_ne_bytes()); + buf[24..28].copy_from_slice(&mode.to_ne_bytes()); + buf[48..56].copy_from_slice(&size.to_ne_bytes()); + buf[56..64].copy_from_slice(&4096u64.to_ne_bytes()); + buf[64..72].copy_from_slice(&blocks.to_ne_bytes()); + let _ = crate::syscall::copy_to_user(stat_ptr, &buf); } /// Fstatシステムコール @@ -770,10 +761,9 @@ pub fn readdir(fd: u64, buf_ptr: u64, buf_len: u64) -> u64 { if n == 0 { break; } - crate::syscall::with_user_memory_access(|| unsafe { - let dst = core::slice::from_raw_parts_mut((buf_ptr + copied as u64) as *mut u8, n); - dst.copy_from_slice(&chunk[..n]); - }); + if crate::syscall::copy_to_user(buf_ptr + copied as u64, &chunk[..n]).is_err() { + return EFAULT; + } copied += n; offset = next_index; if n < want { @@ -795,10 +785,9 @@ pub fn readdir(fd: u64, buf_ptr: u64, buf_len: u64) -> u64 { let joined = names.join("\n"); let bytes = joined.as_bytes(); let to_copy = core::cmp::min(bytes.len(), buf_len as usize); - crate::syscall::with_user_memory_access(|| unsafe { - let dst = core::slice::from_raw_parts_mut(buf_ptr as *mut u8, to_copy); - dst.copy_from_slice(&bytes[..to_copy]); - }); + if crate::syscall::copy_to_user(buf_ptr, &bytes[..to_copy]).is_err() { + return EFAULT; + } to_copy as u64 } @@ -859,10 +848,12 @@ pub fn getcwd(buf_ptr: u64, size: u64) -> u64 { if (size as usize) < needed { return EINVAL; } - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(tmp.as_ptr(), buf_ptr as *mut u8, cwd_len); - *(buf_ptr as *mut u8).add(cwd_len) = 0; - }); + if crate::syscall::copy_to_user(buf_ptr, &tmp[..cwd_len]).is_err() { + return EFAULT; + } + if crate::syscall::copy_to_user(buf_ptr + cwd_len as u64, &[0]).is_err() { + return EFAULT; + } buf_ptr } @@ -902,10 +893,9 @@ pub fn read(fd: u64, buf_ptr: u64, len: u64) -> u64 { Err(e) => return e, }; if n > 0 { - crate::syscall::with_user_memory_access(|| unsafe { - let dst = core::slice::from_raw_parts_mut(buf_ptr as *mut u8, n); - dst.copy_from_slice(&tmp[..n]); - }); + if crate::syscall::copy_to_user(buf_ptr, &tmp[..n]).is_err() { + return EFAULT; + } } return n as u64; } @@ -930,10 +920,9 @@ pub fn read(fd: u64, buf_ptr: u64, len: u64) -> u64 { return 0; } - crate::syscall::with_user_memory_access(|| unsafe { - let dst = core::slice::from_raw_parts_mut(buf_ptr as *mut u8, local.len()); - dst.copy_from_slice(&local); - }); + if crate::syscall::copy_to_user(buf_ptr, &local).is_err() { + return EFAULT; + } local.len() as u64 } @@ -1345,37 +1334,30 @@ pub fn getdents64(fd: u64, buf_ptr: u64, buf_len: u64) -> u64 { v }; - crate::syscall::with_user_memory_access(|| { - for (i, (name, dtype)) in all_entries.iter().enumerate().skip(start_pos) { - let name_bytes = name.as_bytes(); - let name_len = name_bytes.len() + 1; // null 終端含む - // d_ino(8) + d_off(8) + d_reclen(2) + d_type(1) + d_name - let raw_size = 8 + 8 + 2 + 1 + name_len; - let reclen = (raw_size + 7) & !7usize; // 8 バイトアライン - if written + reclen > buf_len as usize { - break; - } - let entry_ptr = (buf_ptr + written as u64) as *mut u8; - unsafe { - let buf = core::slice::from_raw_parts_mut(entry_ptr, reclen); - buf.fill(0); - // d_ino = i+1 - buf[0..8].copy_from_slice(&((i as u64 + 1).to_ne_bytes())); - // d_off = next position - let next_off = (i + 1) as u64; - buf[8..16].copy_from_slice(&next_off.to_ne_bytes()); - // d_reclen - buf[16..18].copy_from_slice(&(reclen as u16).to_ne_bytes()); - // d_type - buf[18] = *dtype; - // d_name (null terminated) - buf[19..19 + name_bytes.len()].copy_from_slice(name_bytes); - buf[19 + name_bytes.len()] = 0; - } - written += reclen; - new_pos = i + 1; + let mut out = alloc::vec![0u8; buf_len as usize]; + for (i, (name, dtype)) in all_entries.iter().enumerate().skip(start_pos) { + let name_bytes = name.as_bytes(); + let name_len = name_bytes.len() + 1; + let raw_size = 8 + 8 + 2 + 1 + name_len; + let reclen = (raw_size + 7) & !7usize; + if written + reclen > buf_len as usize { + break; } - }); + let buf = &mut out[written..written + reclen]; + buf.fill(0); + buf[0..8].copy_from_slice(&((i as u64 + 1).to_ne_bytes())); + let next_off = (i + 1) as u64; + buf[8..16].copy_from_slice(&next_off.to_ne_bytes()); + buf[16..18].copy_from_slice(&(reclen as u16).to_ne_bytes()); + buf[18] = *dtype; + buf[19..19 + name_bytes.len()].copy_from_slice(name_bytes); + buf[19 + name_bytes.len()] = 0; + written += reclen; + new_pos = i + 1; + } + if written > 0 && crate::syscall::copy_to_user(buf_ptr, &out[..written]).is_err() { + return EFAULT; + } // FD の pos を更新する with_fd_table_mut(pid, |t| { diff --git a/src/core/syscall/io.rs b/src/core/syscall/io.rs index 628ee8e..63236a7 100644 --- a/src/core/syscall/io.rs +++ b/src/core/syscall/io.rs @@ -200,7 +200,7 @@ fn write_fd(fd: u64, buf_ptr: u64, len: u64) -> u64 { Err(e) => e, } } - Some((None, _)) | Some((Some(_), false)) => { + Some((None, _)) | Some((Some(_), _)) => { // 通常ファイル or 読み込み端への write: EBADF(書き込みサポートなし) EBADF } @@ -228,18 +228,18 @@ pub fn read(fd: u64, buf_ptr: u64, len: u64) -> u64 { // 少なくとも1バイト届くまでブロックする let first = crate::syscall::keyboard::read_char_blocking(); - crate::syscall::with_user_memory_access(|| unsafe { - (buf_ptr as *mut u8).write(first); - }); + if crate::syscall::copy_to_user(buf_ptr, &[first]).is_err() { + return EFAULT; + } // バッファに残っているスキャンコードを len-1 バイトまで追加で読む(ノンブロッキング) let mut read_count: u64 = 1; while read_count < len { match crate::util::ps2kbd::pop_scancode() { Some(sc) => { - crate::syscall::with_user_memory_access(|| unsafe { - (buf_ptr as *mut u8).add(read_count as usize).write(sc); - }); + if crate::syscall::copy_to_user(buf_ptr + read_count, &[sc]).is_err() { + return EFAULT; + } read_count += 1; } None => break, @@ -281,9 +281,9 @@ fn read_fd(fd: u64, buf_ptr: u64, len: u64) -> u64 { let mut buf = alloc::vec![0u8; len as usize]; let n = crate::syscall::pipe::pipe_read_blocking(pipe_id, &mut buf); if n > 0 { - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::copy_nonoverlapping(buf.as_ptr(), buf_ptr as *mut u8, n); - }); + if crate::syscall::copy_to_user(buf_ptr, &buf[..n]).is_err() { + return EFAULT; + } } n as u64 } diff --git a/src/core/syscall/pipe.rs b/src/core/syscall/pipe.rs index dd286a2..c604839 100644 --- a/src/core/syscall/pipe.rs +++ b/src/core/syscall/pipe.rs @@ -4,6 +4,7 @@ //! 読み込み端がブロックする場合は KEYBOARD_WAITER と同様に wake_thread を使う。 use crate::interrupt::spinlock::SpinLock; +use crate::task::fd_table::{FileHandle, O_CLOEXEC}; use core::sync::atomic::{AtomicU64, Ordering}; /// パイプバッファのサイズ(64 KiB) @@ -209,7 +210,6 @@ pub fn pipe_syscall(pipefd_ptr: u64) -> u64 { /// pipe2(2) システムコール(flags: O_CLOEXEC / O_NONBLOCK に部分対応) pub fn pipe2_syscall(pipefd_ptr: u64, flags: u64) -> u64 { use super::types::EFAULT; - use crate::task::fd_table::{FileHandle, O_CLOEXEC}; const EMFILE_VAL: u64 = (-24i64) as u64; @@ -234,26 +234,8 @@ pub fn pipe2_syscall(pipefd_ptr: u64, flags: u64) -> u64 { } }; - let read_handle = alloc::boxed::Box::new(FileHandle { - data: alloc::boxed::Box::new([]), - pos: 0, - dir_path: None, - is_remote: false, - fd_remote: 0, - remote_refs: None, - pipe_id: Some(pipe_id), - pipe_write: false, - }); - let write_handle = alloc::boxed::Box::new(FileHandle { - data: alloc::boxed::Box::new([]), - pos: 0, - dir_path: None, - is_remote: false, - fd_remote: 0, - remote_refs: None, - pipe_id: Some(pipe_id), - pipe_write: true, - }); + let read_handle = alloc::boxed::Box::new(FileHandle::new_pipe_read(pipe_id)); + let write_handle = alloc::boxed::Box::new(FileHandle::new_pipe_write(pipe_id)); let pid_id = crate::task::ids::ProcessId::from_u64(pid); let read_fd = @@ -265,10 +247,13 @@ pub fn pipe2_syscall(pipefd_ptr: u64, flags: u64) -> u64 { match (read_fd, write_fd) { (Some(rfd), Some(wfd)) => { - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::write_unaligned(pipefd_ptr as *mut u32, rfd as u32); - core::ptr::write_unaligned((pipefd_ptr + 4) as *mut u32, wfd as u32); - }); + if crate::syscall::write_user_u32(pipefd_ptr, rfd as u32).is_err() + || crate::syscall::write_user_u32(pipefd_ptr + 4, wfd as u32).is_err() + { + close_read_end(pipe_id); + close_write_end(pipe_id); + return super::types::EFAULT; + } super::types::SUCCESS } _ => { diff --git a/src/core/task/fd_table.rs b/src/core/task/fd_table.rs index 8619dc1..314bebd 100644 --- a/src/core/task/fd_table.rs +++ b/src/core/task/fd_table.rs @@ -38,6 +38,32 @@ pub struct FileHandle { } impl FileHandle { + pub fn new_pipe_read(pipe_id: usize) -> Self { + Self { + data: Box::new([]), + pos: 0, + dir_path: None, + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: Some(pipe_id), + pipe_write: false, + } + } + + pub fn new_pipe_write(pipe_id: usize) -> Self { + Self { + data: Box::new([]), + pos: 0, + dir_path: None, + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: Some(pipe_id), + pipe_write: true, + } + } + #[inline] pub fn clone_remote_refs(&self) -> Option> { if !self.is_remote { From 1d4f13082ec22716bd50953df244fa13fa4c31b8 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Thu, 9 Apr 2026 01:32:21 -0400 Subject: [PATCH 05/17] security(core): update process and signal syscalls for hardened usercopy Replace remaining direct user-buffer accesses in process control, signal delivery, timekeeping, framebuffer, and mprotect-related syscall paths with the hardened copy helpers. Add bounded heap-base randomization and explicit handling for futex wake-queue overflow instead of relying on debug assertions. --- src/core/syscall/pgroup.rs | 76 ++++++++++++++++--------------- src/core/syscall/process.rs | 54 ++++++++++++++++------ src/core/syscall/signal.rs | 91 +++++++++++++++++++++++-------------- src/core/syscall/time.rs | 12 +++-- src/core/syscall/vga.rs | 18 ++++---- 5 files changed, 150 insertions(+), 101 deletions(-) diff --git a/src/core/syscall/pgroup.rs b/src/core/syscall/pgroup.rs index 49b1fdf..b092885 100644 --- a/src/core/syscall/pgroup.rs +++ b/src/core/syscall/pgroup.rs @@ -1,6 +1,6 @@ //! プロセスグループ・セッション関連のシステムコール -use super::types::{EINVAL, EPERM, ESRCH, SUCCESS}; +use super::types::{EFAULT, EINVAL, EPERM, ESRCH, SUCCESS}; #[inline] fn current_pid() -> Option { @@ -133,9 +133,9 @@ pub fn ioctl(fd: u64, request: u64, arg: u64) -> u64 { Some(pid) => crate::task::with_process(pid, |p| p.pgid()).unwrap_or(1), None => return EINVAL, }; - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::write_unaligned(arg as *mut u32, pgid as u32); - }); + if crate::syscall::write_user_u32(arg, pgid as u32).is_err() { + return EFAULT; + } SUCCESS } TIOCSPGRP => SUCCESS, @@ -144,12 +144,12 @@ pub fn ioctl(fd: u64, request: u64, arg: u64) -> u64 { if arg == 0 || !crate::syscall::validate_user_ptr(arg, 8) { return EINVAL; } - crate::syscall::with_user_memory_access(|| unsafe { - let buf = core::slice::from_raw_parts_mut(arg as *mut u8, 8); - buf.fill(0); - buf[0..2].copy_from_slice(&24u16.to_ne_bytes()); // ws_row - buf[2..4].copy_from_slice(&80u16.to_ne_bytes()); // ws_col - }); + let mut buf = [0u8; 8]; + buf[0..2].copy_from_slice(&24u16.to_ne_bytes()); + buf[2..4].copy_from_slice(&80u16.to_ne_bytes()); + if crate::syscall::copy_to_user(arg, &buf).is_err() { + return EFAULT; + } SUCCESS } TIOCSWINSZ => SUCCESS, @@ -160,15 +160,13 @@ pub fn ioctl(fd: u64, request: u64, arg: u64) -> u64 { if arg == 0 || !crate::syscall::validate_user_ptr(arg, TERMIOS_SIZE) { return EINVAL; } - crate::syscall::with_user_memory_access(|| unsafe { - let buf = core::slice::from_raw_parts_mut(arg as *mut u8, TERMIOS_SIZE as usize); - buf.fill(0); - // c_cflag: CS8(0x30) | CREAD(0x80) | CLOCAL(0x800) - let cflag: u32 = 0x30 | 0x80 | 0x800; - buf[8..12].copy_from_slice(&cflag.to_ne_bytes()); - // c_cc[VMIN]=1, c_cc[VTIME]=0 (offset 17 for VMIN) - buf[17] = 1; - }); + let mut buf = [0u8; TERMIOS_SIZE as usize]; + let cflag: u32 = 0x30 | 0x80 | 0x800; + buf[8..12].copy_from_slice(&cflag.to_ne_bytes()); + buf[17] = 1; + if crate::syscall::copy_to_user(arg, &buf).is_err() { + return EFAULT; + } SUCCESS } TCSETS | TCSETSW | TCSETSF => SUCCESS, // termios 設定は無視して成功 @@ -227,15 +225,15 @@ pub fn uname(buf_ptr: u64) -> u64 { b"x86_64", // machine b"", // domainname ]; - crate::syscall::with_user_memory_access(|| unsafe { - let buf = core::slice::from_raw_parts_mut(buf_ptr as *mut u8, UTSNAME_SIZE as usize); - buf.fill(0); - for (i, f) in fields.iter().enumerate() { - let off = i * FIELD_LEN; - let n = f.len().min(FIELD_LEN - 1); - buf[off..off + n].copy_from_slice(&f[..n]); - } - }); + let mut buf = [0u8; UTSNAME_SIZE as usize]; + for (i, f) in fields.iter().enumerate() { + let off = i * FIELD_LEN; + let n = f.len().min(FIELD_LEN - 1); + buf[off..off + n].copy_from_slice(&f[..n]); + } + if crate::syscall::copy_to_user(buf_ptr, &buf).is_err() { + return EFAULT; + } SUCCESS } @@ -246,11 +244,14 @@ pub fn nanosleep(req_ptr: u64, _rem_ptr: u64) -> u64 { if req_ptr == 0 || !crate::syscall::validate_user_ptr(req_ptr, 16) { return EINVAL; } - let (secs, nsecs) = crate::syscall::with_user_memory_access(|| unsafe { - let secs = core::ptr::read_unaligned(req_ptr as *const i64); - let nsecs = core::ptr::read_unaligned((req_ptr + 8) as *const i64); - (secs, nsecs) - }); + let secs = match crate::syscall::read_user_i64(req_ptr) { + Ok(v) => v, + Err(e) => return e, + }; + let nsecs = match crate::syscall::read_user_i64(req_ptr + 8) { + Ok(v) => v, + Err(e) => return e, + }; if secs < 0 || nsecs < 0 || nsecs >= 1_000_000_000 { return EINVAL; } @@ -278,10 +279,11 @@ pub fn getrlimit(_resource: u64, rlim_ptr: u64) -> u64 { return EINVAL; } // struct rlimit { rlim_cur: u64, rlim_max: u64 } - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::write_unaligned(rlim_ptr as *mut u64, u64::MAX); - core::ptr::write_unaligned((rlim_ptr + 8) as *mut u64, u64::MAX); - }); + if crate::syscall::write_user_u64(rlim_ptr, u64::MAX).is_err() + || crate::syscall::write_user_u64(rlim_ptr + 8, u64::MAX).is_err() + { + return EFAULT; + } SUCCESS } diff --git a/src/core/syscall/process.rs b/src/core/syscall/process.rs index 4df9a92..39d85ec 100644 --- a/src/core/syscall/process.rs +++ b/src/core/syscall/process.rs @@ -26,6 +26,23 @@ const NO_TIMEOUT_WAKE_TICK: u64 = u64::MAX; static FUTEX_WAIT_QUEUE: SpinLock<[Option; MAX_FUTEX_WAITERS]> = SpinLock::new([None; MAX_FUTEX_WAITERS]); +#[inline] +fn aslr_mix64(mut x: u64) -> u64 { + x ^= x >> 30; + x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9); + x ^= x >> 27; + x = x.wrapping_mul(0x94d0_49bb_1331_11eb); + x ^ (x >> 31) +} + +fn randomized_heap_base(pid: crate::task::ProcessId, floor: u64, max_pages: u64) -> u64 { + let seed = crate::cpu::boot_entropy_u64() + ^ crate::interrupt::timer::get_ticks().rotate_left(17) + ^ pid.as_u64().rotate_left(9) + ^ floor.rotate_left(3); + floor.saturating_add((aslr_mix64(seed) % max_pages) * 4096) +} + #[inline] fn page_align_up(addr: u64) -> Option { addr.checked_add(4095).map(|v| v & !4095) @@ -101,9 +118,15 @@ pub fn wake_due_futex_waiters(now_tick: u64) { if let Some(entry) = *slot { if entry.wake_tick != NO_TIMEOUT_WAKE_TICK && now_tick >= entry.wake_tick { *slot = None; - debug_assert!(wake_count < wake_list.len()); - wake_list[wake_count] = Some(entry.tid); - wake_count += 1; + if wake_count < wake_list.len() { + wake_list[wake_count] = Some(entry.tid); + wake_count += 1; + } else { + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "futex wake list overflow; dropping excess wake event", + ); + } } } } @@ -185,7 +208,7 @@ pub fn brk(addr: u64) -> u64 { process.heap_end() ); if process.heap_start() == 0 { - let default_heap_base = 0x4000_0000; + let default_heap_base = randomized_heap_base(pid, 0x4000_0000, 0x8000); process.set_heap_start(default_heap_base); process.set_heap_end(default_heap_base); } @@ -430,9 +453,9 @@ pub fn wait(_pid: u64, status_ptr: u64, options: u64) -> u64 { { if status_ptr != 0 { let status = ((exit_code & 0xff) << 8) as i32; - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::write_unaligned(status_ptr as *mut i32, status); - }); + if crate::syscall::write_user_i32(status_ptr, status).is_err() { + return EFAULT; + } } return reaped_pid.as_u64(); } @@ -504,7 +527,7 @@ pub fn mmap(addr: u64, length: u64, _prot: u64, flags: u64, _fd: u64) -> u64 { // mmap用のヒープ領域を現在のbrk以降に割り当てる // (簡易実装: brkと同じ領域を使う) if process.heap_start() == 0 { - let default_heap_base = 0x5000_0000u64; + let default_heap_base = randomized_heap_base(pid, 0x5000_0000, 0x10000); process.set_heap_start(default_heap_base); process.set_heap_end(default_heap_base); } @@ -655,9 +678,10 @@ pub fn futex(uaddr: u64, op: u32, val: u64, timeout: u64) -> u64 { // yield_now() 内部の switch_to_thread も CLI を実行するため、 // without_interrupts をネストしても安全に動作する。 let queued = x86_64::instructions::interrupts::without_interrupts(|| { - let current_val = crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::read_volatile(uaddr as *const u32) - }); + let current_val = match crate::syscall::read_user_u32(uaddr) { + Ok(v) => v, + Err(_) => return Err(EFAULT), + }; if current_val != val as u32 { return Err(EAGAIN); } @@ -797,10 +821,10 @@ pub fn arch_prctl(code: u64, addr: u64) -> u64 { if !super::validate_user_ptr(addr, 8) { return EFAULT; } - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::write_unaligned(addr as *mut u64, val) - }); - SUCCESS + match crate::syscall::write_user_u64(addr, val) { + Ok(()) => SUCCESS, + Err(e) => e, + } } _ => EINVAL, } diff --git a/src/core/syscall/signal.rs b/src/core/syscall/signal.rs index 4a97899..9538baa 100644 --- a/src/core/syscall/signal.rs +++ b/src/core/syscall/signal.rs @@ -53,13 +53,14 @@ pub fn rt_sigaction(signum: u64, new_act_ptr: u64, old_act_ptr: u64) -> u64 { } let old = with_process(pid, |p| p.signal_state().action(sig)) .unwrap_or(SigAction::default_action()); - crate::syscall::with_user_memory_access(|| unsafe { - let p = old_act_ptr as *mut u64; - p.add(0).write(old.handler); - p.add(1).write(old.flags); - p.add(2).write(old.restorer); - p.add(3).write(old.mask); - }); + let mut buf = [0u8; SIGACTION_SIZE as usize]; + buf[0..8].copy_from_slice(&old.handler.to_ne_bytes()); + buf[8..16].copy_from_slice(&old.flags.to_ne_bytes()); + buf[16..24].copy_from_slice(&old.restorer.to_ne_bytes()); + buf[24..32].copy_from_slice(&old.mask.to_ne_bytes()); + if crate::syscall::copy_to_user(old_act_ptr, &buf).is_err() { + return super::types::EFAULT; + } } // 新アクションをカーネルに保存 @@ -67,15 +68,22 @@ pub fn rt_sigaction(signum: u64, new_act_ptr: u64, old_act_ptr: u64) -> u64 { if !crate::syscall::validate_user_ptr(new_act_ptr, SIGACTION_SIZE) { return super::types::EFAULT; } - let (handler, flags, restorer, mask) = crate::syscall::with_user_memory_access(|| unsafe { - let p = new_act_ptr as *const u64; - ( - p.add(0).read(), - p.add(1).read(), - p.add(2).read(), - p.add(3).read(), - ) - }); + let handler = match crate::syscall::read_user_u64(new_act_ptr) { + Ok(v) => v, + Err(e) => return e, + }; + let flags = match crate::syscall::read_user_u64(new_act_ptr + 8) { + Ok(v) => v, + Err(e) => return e, + }; + let restorer = match crate::syscall::read_user_u64(new_act_ptr + 16) { + Ok(v) => v, + Err(e) => return e, + }; + let mask = match crate::syscall::read_user_u64(new_act_ptr + 24) { + Ok(v) => v, + Err(e) => return e, + }; // mask の uncatchable ビットは強制クリア let mask = mask & !UNCATCHABLE_MASK; let action = SigAction { @@ -108,9 +116,9 @@ pub fn rt_sigprocmask(how: u64, set_ptr: u64, oldset_ptr: u64) -> u64 { return super::types::EFAULT; } let old_mask = with_process(pid, |p| p.signal_state().mask).unwrap_or(0); - crate::syscall::with_user_memory_access(|| unsafe { - (oldset_ptr as *mut u64).write(old_mask); - }); + if crate::syscall::write_user_u64(oldset_ptr, old_mask).is_err() { + return super::types::EFAULT; + } } if set_ptr == 0 { @@ -119,9 +127,10 @@ pub fn rt_sigprocmask(how: u64, set_ptr: u64, oldset_ptr: u64) -> u64 { if !crate::syscall::validate_user_ptr(set_ptr, 8) { return super::types::EFAULT; } - let new_set = - crate::syscall::with_user_memory_access(|| unsafe { (set_ptr as *const u64).read() }) - & !UNCATCHABLE_MASK; // SIGKILL/SIGSTOP は常にアンブロック + let new_set = match crate::syscall::read_user_u64(set_ptr) { + Ok(v) => v & !UNCATCHABLE_MASK, + Err(e) => return e, + }; with_process_mut(pid, |p| { let mask = &mut p.signal_state_mut().mask; @@ -254,7 +263,7 @@ pub fn deliver_sigchld_to_parent(child_pid: ProcessId) { /// 最終的な syscall 戻り値(シグナル送達時は変更される場合がある) /// /// # kstack レイアウト(push 順の逆、低アドレスが先頭) -/// ``` +/// ```text /// [0] r15, [1] r14, [2] r13, [3] r12, /// [4] r11, [5] r10, [6] r9, [7] r8, /// [8] rdi (arg0), [9] rsi, [10] rbp, [11] rbx, @@ -365,14 +374,12 @@ fn write_signal_frame( if !crate::syscall::validate_user_ptr(new_rsp, 32) { return false; } - crate::syscall::with_user_memory_access(|| unsafe { - let p = new_rsp as *mut u64; - p.add(0).write(restorer); // return address - p.add(1).write(saved_rip); - p.add(2).write(saved_rsp); - p.add(3).write(saved_rflags); - }); - true + let mut frame = [0u8; 32]; + frame[0..8].copy_from_slice(&restorer.to_ne_bytes()); + frame[8..16].copy_from_slice(&saved_rip.to_ne_bytes()); + frame[16..24].copy_from_slice(&saved_rsp.to_ne_bytes()); + frame[24..32].copy_from_slice(&saved_rflags.to_ne_bytes()); + crate::syscall::copy_to_user(new_rsp, &frame).is_ok() } /// rt_sigreturn システムコール @@ -392,10 +399,24 @@ pub fn rt_sigreturn(kstack: *mut u64) { crate::task::exit_current_task(11); // SIGSEGV } - let (saved_rip, saved_rsp, saved_rflags) = crate::syscall::with_user_memory_access(|| unsafe { - let p = user_rsp as *const u64; - (p.add(0).read(), p.add(1).read(), p.add(2).read()) - }); + let saved_rip = match crate::syscall::read_user_u64(user_rsp) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + let saved_rsp = match crate::syscall::read_user_u64(user_rsp + 8) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + let saved_rflags = match crate::syscall::read_user_u64(user_rsp + 16) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; unsafe { kstack.add(15).write(saved_rip); diff --git a/src/core/syscall/time.rs b/src/core/syscall/time.rs index 48af744..644ced1 100644 --- a/src/core/syscall/time.rs +++ b/src/core/syscall/time.rs @@ -98,11 +98,13 @@ pub fn clock_gettime(clk_id: u64, ts_ptr: u64) -> u64 { match clk_id { CLOCK_REALTIME | CLOCK_MONOTONIC | CLOCK_PROCESS_CPUTIME_ID | CLOCK_THREAD_CPUTIME_ID => { // timespec { tv_sec: i64, tv_nsec: i64 } - crate::syscall::with_user_memory_access(|| unsafe { - core::ptr::write_unaligned(ts_ptr as *mut i64, sec as i64); - core::ptr::write_unaligned((ts_ptr + 8) as *mut i64, nsec as i64); - }); - SUCCESS + let mut buf = [0u8; 16]; + buf[0..8].copy_from_slice(&(sec as i64).to_ne_bytes()); + buf[8..16].copy_from_slice(&(nsec as i64).to_ne_bytes()); + match crate::syscall::copy_to_user(ts_ptr, &buf) { + Ok(()) => SUCCESS, + Err(e) => e, + } } _ => EINVAL, } diff --git a/src/core/syscall/vga.rs b/src/core/syscall/vga.rs index b39b89c..7f0e2ee 100644 --- a/src/core/syscall/vga.rs +++ b/src/core/syscall/vga.rs @@ -33,15 +33,15 @@ pub fn get_framebuffer_info(info_ptr: u64) -> u64 { None => return EINVAL, }; - crate::syscall::with_user_memory_access(|| unsafe { - let ptr = info_ptr as *mut u32; - ptr.add(0).write_volatile(fb_info.width as u32); - ptr.add(1).write_volatile(fb_info.height as u32); - ptr.add(2).write_volatile(fb_info.stride as u32); - ptr.add(3).write_volatile(0u32); - }); - - SUCCESS + let mut buf = [0u8; FB_INFO_SIZE as usize]; + buf[0..4].copy_from_slice(&(fb_info.width as u32).to_ne_bytes()); + buf[4..8].copy_from_slice(&(fb_info.height as u32).to_ne_bytes()); + buf[8..12].copy_from_slice(&(fb_info.stride as u32).to_ne_bytes()); + buf[12..16].copy_from_slice(&0u32.to_ne_bytes()); + match crate::syscall::copy_to_user(info_ptr, &buf) { + Ok(()) => SUCCESS, + Err(e) => e, + } } /// フレームバッファ物理メモリを呼び出し元プロセスのアドレス空間にマップする From 2b12efeca3edca08fb7e36a113041103ba202812 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Thu, 9 Apr 2026 01:32:56 -0400 Subject: [PATCH 06/17] reliability(core): add audit-backed containment for low-level runtime faults Replace panic-prone mailbox bookkeeping and unexpected thread or context return paths with explicit audit logging and containment behavior. Record GDT lookup failures, IPC mailbox corruption, kernel stack guard faults, and scheduler dead-end conditions without relying on silent corruption or unchecked panic paths. --- src/core/mem/gdt.rs | 41 +++++++++------------- src/core/syscall/ipc.rs | 65 ++++++++++++++++++++--------------- src/core/task/context.rs | 70 +++++++++++++++++++++++++++++--------- src/core/task/scheduler.rs | 24 ++++++++++--- src/core/task/thread.rs | 42 ++++++++++++++--------- 5 files changed, 154 insertions(+), 88 deletions(-) diff --git a/src/core/mem/gdt.rs b/src/core/mem/gdt.rs index 1bb9f32..5d707ef 100644 --- a/src/core/mem/gdt.rs +++ b/src/core/mem/gdt.rs @@ -14,6 +14,15 @@ pub const DOUBLE_FAULT_IST_INDEX: u16 = tss::DOUBLE_FAULT_IST_INDEX; static GDT: Once<(GlobalDescriptorTable, Selectors)> = Once::new(); +fn halt_on_missing_gdt(which: &'static str) -> ! { + crate::audit::log(crate::audit::AuditEventKind::Fault, which); + crate::warn!("GDT not initialized"); + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } +} + /// GDTセレクタ #[allow(unused)] struct Selectors { @@ -98,44 +107,28 @@ pub fn init() { pub fn user_code_selector() -> u16 { GDT.get() .map(|g| g.1.user_code_selector.0) - .unwrap_or_else(|| { - crate::warn!("GDT not initialized"); - loop { - x86_64::instructions::hlt(); - } - }) + .unwrap_or_else(|| halt_on_missing_gdt("gdt user_code_selector unavailable")) } /// ユーザーモード用データセレクタ(RPL=3)を返す pub fn user_data_selector() -> u16 { GDT.get() .map(|g| g.1.user_data_selector.0) - .unwrap_or_else(|| { - crate::warn!("GDT not initialized"); - loop { - x86_64::instructions::hlt(); - } - }) + .unwrap_or_else(|| halt_on_missing_gdt("gdt user_data_selector unavailable")) } /// カーネル用コードセレクタを返す pub fn kernel_code_selector() -> u16 { - GDT.get().map(|g| g.1.code_selector.0).unwrap_or_else(|| { - crate::warn!("GDT not initialized"); - loop { - x86_64::instructions::hlt(); - } - }) + GDT.get() + .map(|g| g.1.code_selector.0) + .unwrap_or_else(|| halt_on_missing_gdt("gdt kernel_code_selector unavailable")) } /// カーネル用データセレクタを返す pub fn kernel_data_selector() -> u16 { - GDT.get().map(|g| g.1.data_selector.0).unwrap_or_else(|| { - crate::warn!("GDT not initialized"); - loop { - x86_64::instructions::hlt(); - } - }) + GDT.get() + .map(|g| g.1.data_selector.0) + .unwrap_or_else(|| halt_on_missing_gdt("gdt kernel_data_selector unavailable")) } #[allow(unused)] diff --git a/src/core/syscall/ipc.rs b/src/core/syscall/ipc.rs index 4854475..1c289fb 100644 --- a/src/core/syscall/ipc.rs +++ b/src/core/syscall/ipc.rs @@ -6,7 +6,7 @@ use super::{EAGAIN, EFAULT, EINVAL}; const MAX_THREADS: usize = crate::task::ThreadQueue::MAX_THREADS; const MAILBOX_CAP: usize = 64; -const MAX_MSG_SIZE: usize = 4128; // FsResponse(4112) / DiskBulkResponse(2064) を収容 +const MAX_MSG_SIZE: usize = 4128; // large control payload upper bound #[derive(Debug, Clone, Copy)] pub struct Message { @@ -77,23 +77,29 @@ impl Mailbox { Some(self.free[self.free_count] as usize) } - fn free_slot(&mut self, idx: usize) { + fn quarantine(&mut self, reason: &str) { + crate::audit::log(crate::audit::AuditEventKind::Quarantine, reason); + *self = Mailbox::new(); + } + + fn free_slot(&mut self, idx: usize) -> bool { if idx >= MAILBOX_CAP { - panic!("ipc mailbox free_slot: idx out of range: {}", idx); + self.quarantine("ipc mailbox slot index out of range"); + return false; } if self.free_count >= MAILBOX_CAP { - panic!("ipc mailbox free_slot: free_count overflow"); + self.quarantine("ipc mailbox free_count overflow"); + return false; } for i in 0..self.free_count { if self.free[i] as usize == idx { - panic!( - "ipc mailbox free_slot: double free detected for slot {}", - idx - ); + self.quarantine("ipc mailbox double free detected"); + return false; } } self.free[self.free_count] = idx as u8; self.free_count += 1; + true } fn enqueue_slot(&mut self, slot_idx: usize) -> Result<(), ()> { @@ -142,7 +148,7 @@ impl Mailbox { msg.data[..data.len()].copy_from_slice(data); } if self.enqueue_slot(slot_idx).is_err() { - self.free_slot(slot_idx); + let _ = self.free_slot(slot_idx); return Err(()); } Ok(()) @@ -166,7 +172,9 @@ impl Mailbox { let from = msg.from; let ext_pages_count = msg.ext_pages_count; let ext_pages = msg.ext_pages; - self.free_slot(slot_idx); + if !self.free_slot(slot_idx) { + return None; + } return Some((from, 0usize, ext_pages_count, ext_pages)); } if copy_len > 0 { @@ -175,11 +183,15 @@ impl Mailbox { let from = msg.from; let ext_pages_count = msg.ext_pages_count; let ext_pages = msg.ext_pages; - self.free_slot(slot_idx); + if !self.free_slot(slot_idx) { + return None; + } return Some((from, copy_len, ext_pages_count, ext_pages)); } // 古い宛先のメッセージは破棄 - self.free_slot(slot_idx); + if !self.free_slot(slot_idx) { + return None; + } } None } @@ -207,7 +219,7 @@ impl Mailbox { || msg.to_generation != receiver_generation { if self.enqueue_slot(slot_idx).is_err() { - self.free_slot(slot_idx); + let _ = self.free_slot(slot_idx); return None; } continue; @@ -218,7 +230,9 @@ impl Mailbox { out[..copy_len].copy_from_slice(&msg.data[..copy_len]); } let from = msg.from; - self.free_slot(slot_idx); + if !self.free_slot(slot_idx) { + return None; + } return Some((from, copy_len)); } @@ -304,7 +318,7 @@ pub fn send_pages_from_kernel( // serialize map_start, total, then pages into data let mut off = 0usize; if (16 + pages.len() * 8) > MAX_MSG_SIZE { - mb.free_slot(slot_idx); + let _ = mb.free_slot(slot_idx); return false; } msg.data[off..off + 8].copy_from_slice(&map_start.to_ne_bytes()); @@ -322,7 +336,7 @@ pub fn send_pages_from_kernel( } // enqueue if mb.enqueue_slot(slot_idx).is_err() { - mb.free_slot(slot_idx); + let _ = mb.free_slot(slot_idx); return false; } let waiter = mb.take_waiter(); @@ -360,7 +374,7 @@ pub fn send_map_header_from_kernel(dest_thread_id: u64, map_start: u64, total: u // serialize map_start, total into data let mut off = 0usize; if 16 > MAX_MSG_SIZE { - mb.free_slot(slot_idx); + let _ = mb.free_slot(slot_idx); return false; } msg.data[off..off + 8].copy_from_slice(&map_start.to_ne_bytes()); @@ -371,7 +385,7 @@ pub fn send_map_header_from_kernel(dest_thread_id: u64, map_start: u64, total: u msg.ext_pages_count = 0; // enqueue if mb.enqueue_slot(slot_idx).is_err() { - mb.free_slot(slot_idx); + let _ = mb.free_slot(slot_idx); return false; } let waiter = mb.take_waiter(); @@ -492,10 +506,9 @@ pub fn recv(buf_ptr: u64, max_len: u64) -> u64 { if !crate::syscall::validate_user_ptr(buf_ptr, copy_len as u64) { return EFAULT; } - crate::syscall::with_user_memory_access(|| unsafe { - let dest_slice = core::slice::from_raw_parts_mut(buf_ptr as *mut u8, copy_len); - dest_slice.copy_from_slice(&recv_buf[..copy_len]); - }); + if crate::syscall::copy_to_user(buf_ptr, &recv_buf[..copy_len]).is_err() { + return EFAULT; + } } // 上位32bitに送信元ID、下位32bitに長さ @@ -550,11 +563,9 @@ pub fn recv_blocking(buf_ptr: u64, max_len: u64) -> u64 { if !crate::syscall::validate_user_ptr(buf_ptr, copy_len as u64) { return EFAULT; } - crate::syscall::with_user_memory_access(|| unsafe { - let dest_slice = - core::slice::from_raw_parts_mut(buf_ptr as *mut u8, copy_len); - dest_slice.copy_from_slice(&recv_buf[..copy_len]); - }); + if crate::syscall::copy_to_user(buf_ptr, &recv_buf[..copy_len]).is_err() { + return EFAULT; + } } return (from << 32) | (copy_len as u64); } diff --git a/src/core/task/context.rs b/src/core/task/context.rs index 02d4b57..3fc748a 100644 --- a/src/core/task/context.rs +++ b/src/core/task/context.rs @@ -126,18 +126,24 @@ pub unsafe fn switch_to_thread(current_id: Option, next_id: ThreadId) let mut queue = THREAD_QUEUE.lock(); - let old_ctx_ptr = if let Some(id) = current_id { + let (old_ctx_ptr, current_process_id, current_priv) = if let Some(id) = current_id { if let Some(thread) = queue.get_mut(id) { if !thread.is_kernel_stack_guard_intact() { + let bottom = thread.kernel_stack_bottom(); + let top = thread.kernel_stack_top(); + drop(queue); crate::error!( "Kernel stack guard corrupted: tid={:?}, kstack=[{:#x}..{:#x})", id, - thread.kernel_stack_bottom(), - thread.kernel_stack_top() + bottom, + top ); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Quarantine, + "context switch detected kernel stack corruption", + ); + crate::task::terminate_thread(id); + return; } let ptr = thread.context_mut() as *mut Context; crate::debug!( @@ -146,14 +152,21 @@ pub unsafe fn switch_to_thread(current_id: Option, next_id: ThreadId) thread.context().rsp, thread.context().rip ); - ptr + let pid = thread.process_id(); + let priv_level = + with_process(pid, |p| p.privilege()).unwrap_or(crate::task::PrivilegeLevel::Core); + (ptr, Some(pid), priv_level) } else { return; // 現在のスレッドが見つからない } } else { // 現在のスレッドがない場合(初回スイッチ)はダミーに書き込む(値は捨てられる) crate::debug!(" No current thread (initial switch)"); - unsafe { core::ptr::addr_of_mut!(INITIAL_DUMMY_CONTEXT) } + ( + unsafe { core::ptr::addr_of_mut!(INITIAL_DUMMY_CONTEXT) }, + None, + crate::task::PrivilegeLevel::Core, + ) }; // 次のスレッドのコンテキストへのポインタとカーネルスタックトップを取得 @@ -196,6 +209,12 @@ pub unsafe fn switch_to_thread(current_id: Option, next_id: ThreadId) // SYSCALL 入口の swapgs により IA32_KERNEL_GS_BASE がユーザー値へ一時退避されるため、 // ブロッキング syscall 中に他スレッドへ切り替える前に per-CPU GS ベースへ戻しておく。 crate::percpu::install_current_cpu_gs_base(); + let predictor_domain_changed = + current_process_id != Some(next_process_id) || current_priv != next_priv; + if predictor_domain_changed { + crate::cpu::branch_predictor_barrier(); + } + crate::cpu::reassert_runtime_hardening(); // 次のスレッドの FS ベースを復元 (TLS) unsafe { @@ -268,25 +287,38 @@ pub unsafe fn switch_to_thread_from_isr( ) { let mut queue = THREAD_QUEUE.lock(); - let old_ctx_ptr = if let Some(id) = current_id { + let (old_ctx_ptr, current_process_id, current_priv) = if let Some(id) = current_id { if let Some(thread) = queue.get_mut(id) { if !thread.is_kernel_stack_guard_intact() { + let bottom = thread.kernel_stack_bottom(); + let top = thread.kernel_stack_top(); + drop(queue); crate::error!( "Kernel stack guard corrupted (ISR): tid={:?}, kstack=[{:#x}..{:#x})", id, - thread.kernel_stack_bottom(), - thread.kernel_stack_top() + bottom, + top ); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Quarantine, + "isr context switch detected kernel stack corruption", + ); + crate::task::terminate_thread(id); + return; } - thread.context_mut() as *mut Context + let pid = thread.process_id(); + let priv_level = + with_process(pid, |p| p.privilege()).unwrap_or(crate::task::PrivilegeLevel::Core); + (thread.context_mut() as *mut Context, Some(pid), priv_level) } else { return; } } else { - unsafe { core::ptr::addr_of_mut!(INITIAL_DUMMY_CONTEXT) } + ( + unsafe { core::ptr::addr_of_mut!(INITIAL_DUMMY_CONTEXT) }, + None, + crate::task::PrivilegeLevel::Core, + ) }; let (new_ctx_ptr, next_priv, next_kstack_top, next_fs_base, next_process_id, next_in_syscall) = @@ -322,6 +354,12 @@ pub unsafe fn switch_to_thread_from_isr( // SYSCALL 入口の swapgs により IA32_KERNEL_GS_BASE がユーザー値へ一時退避されるため、 // ブロッキング syscall 中に他スレッドへ切り替える前に per-CPU GS ベースへ戻しておく。 crate::percpu::install_current_cpu_gs_base(); + let predictor_domain_changed = + current_process_id != Some(next_process_id) || current_priv != next_priv; + if predictor_domain_changed { + crate::cpu::branch_predictor_barrier(); + } + crate::cpu::reassert_runtime_hardening(); // 次のスレッドの FS ベースを復元 (TLS) crate::cpu::write_fs_base(next_fs_base); diff --git a/src/core/task/scheduler.rs b/src/core/task/scheduler.rs index 5537a42..f397cdb 100644 --- a/src/core/task/scheduler.rs +++ b/src/core/task/scheduler.rs @@ -348,7 +348,11 @@ pub fn exit_current_task(exit_code: u64) -> ! { switch_to_thread(None, next_id); } - crate::sprintln!("switch_to_thread returned unexpectedly; halting."); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "scheduler switch_to_thread returned unexpectedly after exit", + ); + x86_64::instructions::interrupts::disable(); loop { x86_64::instructions::hlt(); } @@ -363,7 +367,11 @@ pub fn exit_current_task(exit_code: u64) -> ! { } // スレッドがない場合は永久にhaltして待機 - crate::sprintln!("No more user threads. Halting system."); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "scheduler observed no more user threads", + ); + x86_64::instructions::interrupts::disable(); loop { x86_64::instructions::hlt(); } @@ -416,12 +424,20 @@ pub fn start_scheduling() -> ! { } }); - crate::sprintln!("switch_to_thread returned unexpectedly; halting."); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "start_scheduling switch_to_thread returned unexpectedly", + ); + x86_64::instructions::interrupts::disable(); loop { x86_64::instructions::hlt(); } } else { - crate::sprintln!("No threads to schedule; halting system."); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "start_scheduling found no threads to schedule", + ); + x86_64::instructions::interrupts::disable(); loop { x86_64::instructions::hlt(); } diff --git a/src/core/task/thread.rs b/src/core/task/thread.rs index b80e993..1b0ae78 100644 --- a/src/core/task/thread.rs +++ b/src/core/task/thread.rs @@ -7,11 +7,11 @@ use super::ids::{ProcessId, ThreadId, ThreadState}; /// スレッド終了時に呼ばれるハンドラ /// この関数から戻ることはない extern "C" fn thread_exit_handler() -> ! { - // スレッドが終了した場合の処理 - // 通常はここに到達することはない - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "thread returned into thread_exit_handler", + ); + crate::task::exit_current_task(0) } /// スレッド構造体 @@ -290,9 +290,11 @@ impl Thread { Some(t) => t, None => { crate::warn!("usermode_entry_trampoline: No current thread"); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "usermode_entry_trampoline without current thread", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); } }; let (entry, stack) = @@ -300,9 +302,11 @@ impl Thread { Some(v) => v, None => { crate::warn!("usermode_entry_trampoline: Thread not found"); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "usermode_entry_trampoline missing thread metadata", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); } }; @@ -404,9 +408,11 @@ impl Thread { Some(t) => t, None => { crate::warn!("fork_child_trampoline: No current thread"); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "fork_child_trampoline without current thread", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); } }; let (entry, stack, rflags, fs) = match with_thread(tid, |thread| { @@ -420,9 +426,11 @@ impl Thread { Some(v) => v, None => { crate::warn!("fork_child_trampoline: Thread not found"); - loop { - x86_64::instructions::hlt(); - } + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "fork_child_trampoline missing thread metadata", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); } }; unsafe { From d7ecd6d5693e180d43c6953e528f785a9c6bf12d Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:32:36 -0400 Subject: [PATCH 07/17] build: fail fast on image assembly and library copy errors Raise the BusyBox download timeout for slower networks, surface make_image.sh failures instead of silently ignoring them, and copy newlib runtime objects via temporary files with size verification before rename. --- build.rs | 27 ++++++++++++++++++------- builders/fs_image.rs | 48 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/build.rs b/build.rs index 4412e19..485cd1d 100644 --- a/build.rs +++ b/build.rs @@ -137,7 +137,7 @@ fn ensure_busybox_binary(fs_dir: &Path) -> Result<(), String> { "--silent", "--show-error", "--max-time", - "30", + "120", "--output", ]) .arg(&temp) @@ -289,10 +289,7 @@ fn prune_stale_module_artifacts( modules: &[builders::modules::ModuleEntry], ramfs_dir: &Path, ) -> Result<(), String> { - let expected: HashSet = modules - .iter() - .map(|m| format!("{}.cext", m.name)) - .collect(); + let expected: HashSet = modules.iter().map(|m| format!("{}.cext", m.name)).collect(); let modules_dir = ramfs_dir.join("Modules"); if let Ok(entries) = fs::read_dir(&modules_dir) { for entry in entries.flatten() { @@ -495,7 +492,8 @@ fn main() { .expect("Failed to prune stale service artifacts"); let modules = default_modules(); - prune_stale_module_artifacts(&modules, &ramfs_dir).expect("Failed to prune stale module artifacts"); + prune_stale_module_artifacts(&modules, &ramfs_dir) + .expect("Failed to prune stale module artifacts"); // サービスをビルド let services_base_dir = manifest_dir.join("src/services"); @@ -593,7 +591,22 @@ fn main() { // make_image.sh を実行(UEFIイメージ作成) let mkimage_script = manifest_dir.join("scripts/make_image.sh"); if mkimage_script.exists() { - let _ = std::process::Command::new(mkimage_script).status(); + match std::process::Command::new(&mkimage_script).status() { + Ok(status) if status.success() => {} + Ok(status) => { + panic!( + "make_image.sh failed with exit code {}", + status.code().unwrap_or(-1) + ); + } + Err(e) => { + panic!( + "failed to execute make_image.sh ({}): {}", + mkimage_script.display(), + e + ); + } + } } println!("Build completed successfully!"); diff --git a/builders/fs_image.rs b/builders/fs_image.rs index f083e7f..46ba98f 100644 --- a/builders/fs_image.rs +++ b/builders/fs_image.rs @@ -334,13 +334,57 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), String> { /// newlibライブラリをディレクトリにコピー pub fn copy_newlib_libs(libc_dir: &Path, dest_dir: &Path) -> Result<(), String> { + fn copy_atomic(src: &Path, dest: &Path) -> Result<(), String> { + let tmp = dest.with_extension(format!("tmp-copy-{}", std::process::id())); + if tmp.exists() { + let _ = fs::remove_file(&tmp); + } + + fs::copy(src, &tmp).map_err(|e| { + format!( + "Failed to copy {} to temporary file {}: {}", + src.display(), + tmp.display(), + e + ) + })?; + + let src_len = fs::metadata(src) + .map_err(|e| format!("Failed to stat {}: {}", src.display(), e))? + .len(); + let tmp_len = fs::metadata(&tmp) + .map_err(|e| format!("Failed to stat {}: {}", tmp.display(), e))? + .len(); + if src_len != tmp_len { + let _ = fs::remove_file(&tmp); + return Err(format!( + "Short copy detected while copying {} to {} ({} != {})", + src.display(), + dest.display(), + src_len, + tmp_len + )); + } + + fs::rename(&tmp, dest).map_err(|e| { + let _ = fs::remove_file(&tmp); + format!( + "Failed to atomically install {} from {}: {}", + dest.display(), + src.display(), + e + ) + })?; + Ok(()) + } + fs::create_dir_all(dest_dir) .map_err(|e| format!("Failed to create {}: {}", dest_dir.display(), e))?; // crt0.oをコピー let crt0_src = libc_dir.join("crt0.o"); let crt0_dest = dest_dir.join("crt0.o"); - fs::copy(&crt0_src, &crt0_dest) + copy_atomic(&crt0_src, &crt0_dest) .map_err(|e| format!("Failed to copy crt0.o to {}: {}", dest_dir.display(), e))?; println!("Copied crt0.o to {}", dest_dir.display()); @@ -349,7 +393,7 @@ pub fn copy_newlib_libs(libc_dir: &Path, dest_dir: &Path) -> Result<(), String> for lib in &libs { let src = libc_dir.join(lib); let dest = dest_dir.join(lib); - fs::copy(&src, &dest).map_err(|e| { + copy_atomic(&src, &dest).map_err(|e| { format!( "Failed to copy {} to {}: {}. Make sure newlib is built correctly.", lib, From 762609ba2ff9512a60e3921b68e49455acdedd4f Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:36:58 -0400 Subject: [PATCH 08/17] build: parse service indexes safely and align ELF scripts with ramfs Ignore inline TOML comments without stripping quoted values, reject invalid service metadata instead of defaulting silently, and update the legacy user-app scripts to use the current ramfs layout and clearer missing-file diagnostics. --- builders/services.rs | 42 +++++++++++++++++++++++++++++++++++---- scripts/build-user-elf.sh | 7 ++++++- scripts/test_elf.sh | 14 ++++++------- src/apps/tests/build.sh | 2 +- 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/builders/services.rs b/builders/services.rs index 8688a0b..85a37ec 100644 --- a/builders/services.rs +++ b/builders/services.rs @@ -21,6 +21,29 @@ pub fn parse_service_index(index_path: &Path) -> Result, Strin let content = fs::read_to_string(index_path).map_err(|e| format!("Failed to read index.toml: {}", e))?; + fn strip_inline_comment(line: &str) -> &str { + let mut in_single = false; + let mut in_double = false; + let mut escaped = false; + for (i, ch) in line.char_indices() { + if escaped { + escaped = false; + continue; + } + if ch == '\\' { + escaped = true; + continue; + } + match ch { + '\'' if !in_double => in_single = !in_single, + '"' if !in_single => in_double = !in_double, + '#' if !in_single && !in_double => return &line[..i], + _ => {} + } + } + line + } + // 簡易的なTOML解析(tomlクレートを使わずに) let mut services = Vec::new(); @@ -31,8 +54,11 @@ pub fn parse_service_index(index_path: &Path) -> Result, Strin let mut current_autostart = false; let mut current_order = 999; - for line in content.lines() { - let line = line.trim(); + for raw_line in content.lines() { + let line = strip_inline_comment(raw_line).trim(); + if line.is_empty() { + continue; + } // [core.service] または [core.service.NAME] を解析 if line.starts_with("[core.service") && line.ends_with(']') { @@ -77,9 +103,17 @@ pub fn parse_service_index(index_path: &Path) -> Result, Strin } else if let Some(rest) = line.strip_prefix("description = ") { current_desc = rest.trim_matches('"').trim_matches('\'').to_string(); } else if let Some(rest) = line.strip_prefix("autostart = ") { - current_autostart = rest.trim().parse().unwrap_or(false); + current_autostart = rest.trim().parse().map_err(|_| { + format!( + "Invalid autostart value in {}: {}", + index_path.display(), + line + ) + })?; } else if let Some(rest) = line.strip_prefix("order = ") { - current_order = rest.trim().parse().unwrap_or(999); + current_order = rest.trim().parse().map_err(|_| { + format!("Invalid order value in {}: {}", index_path.display(), line) + })?; } } diff --git a/scripts/build-user-elf.sh b/scripts/build-user-elf.sh index 1a57d2c..8e87a59 100755 --- a/scripts/build-user-elf.sh +++ b/scripts/build-user-elf.sh @@ -2,7 +2,7 @@ set -euo pipefail ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) -INITFS_DIR="$ROOT_DIR/src/initfs" +INITFS_DIR="$ROOT_DIR/ramfs" ASM="$INITFS_DIR/hello.asm" OUT="$INITFS_DIR/hello" @@ -18,6 +18,11 @@ if ! command -v "$LD" >/dev/null 2>&1; then echo "Error: ld not found. Install binutils or pass linker as second arg." exit 1 fi +if [ ! -f "$ASM" ]; then + echo "Error: source not found: $ASM" + echo "Hint: this legacy script expects $INITFS_DIR/hello.asm" + exit 1 +fi echo "Assembling with: $NASM; linking with: $LD" diff --git a/scripts/test_elf.sh b/scripts/test_elf.sh index 6b1f844..4c91710 100755 --- a/scripts/test_elf.sh +++ b/scripts/test_elf.sh @@ -7,20 +7,20 @@ echo "" # 1. ユーザーアプリをビルド echo "[1/4] Building user application..." -cd src/user/test_app +cd src/apps/tests ./build.sh cd ../../.. # 2. initfsの内容確認 echo "" -echo "[2/4] Checking initfs contents..." -ls -lh src/initfs/ +echo "[2/4] Checking ramfs contents..." +ls -lh ramfs/ -# 3. test.elfのELFヘッダ確認 +# 3. tests.elfのELFヘッダ確認 echo "" echo "[3/4] Verifying ELF header..." -file src/initfs/test.elf -readelf -h src/initfs/test.elf | grep "Entry point" +file ramfs/tests.elf +readelf -h ramfs/tests.elf | grep "Entry point" # 4. カーネルをビルド echo "" @@ -29,4 +29,4 @@ cargo build echo "" echo "===== Build Complete =====" -echo "Run 'cargo run' to execute the kernel with test.elf" +echo "Run 'cargo run' to execute the kernel with tests.elf" diff --git a/src/apps/tests/build.sh b/src/apps/tests/build.sh index 5c09dad..be48133 100755 --- a/src/apps/tests/build.sh +++ b/src/apps/tests/build.sh @@ -18,7 +18,7 @@ cargo build --release \ -Z build-std=core,alloc \ --package "$APP_NAME" -INITFS_DIR="$PROJECT_ROOT/initfs" +INITFS_DIR="$PROJECT_ROOT/ramfs" mkdir -p "$INITFS_DIR" SOURCE_BIN="target/x86_64-mochios/release/$APP_NAME" From 383b019ae128a5bd397f1c5a822aea0eaa34c966 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:38:23 -0400 Subject: [PATCH 09/17] security: gate unfinished CFI and CET feature toggles Expose kcfi/cet feature names at the workspace and kernel crate level, immediately fail compilation if someone enables them before the end-to-end pipeline is verified, and log whether the boot CPU advertises IBT or shadow-stack support. --- Cargo.toml | 3 +++ src/core/Cargo.toml | 5 +++++ src/core/cpu.rs | 23 +++++++++++++++++++++++ src/core/lib.rs | 19 +++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index fbcf712..368436b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ bench = false [features] default = ["uefi-dep"] uefi-dep = ["dep:uefi"] +kcfi = [] +cet-ibt = [] +cet-shadow-stack = [] [dependencies] uefi = { version = "0.30", features = ["alloc", "logger", "global_allocator"], optional = true } diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 68c13db..b305ab3 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -14,6 +14,11 @@ bench = false mochios = { package = "mochiOS", path = "../..", default-features = false } linked_list_allocator = "0.10.5" +[features] +kcfi = ["mochios/kcfi"] +cet-ibt = ["mochios/cet-ibt"] +cet-shadow-stack = ["mochios/cet-shadow-stack"] + [profile.dev] panic = "abort" diff --git a/src/core/cpu.rs b/src/core/cpu.rs index 171a5f4..1eee46b 100644 --- a/src/core/cpu.rs +++ b/src/core/cpu.rs @@ -10,6 +10,8 @@ use spin::Mutex; static FSGSBASE_SUPPORTED: AtomicBool = AtomicBool::new(false); static SMAP_ENABLED: AtomicBool = AtomicBool::new(false); static IBPB_SUPPORTED: AtomicBool = AtomicBool::new(false); +static CET_IBT_SUPPORTED: AtomicBool = AtomicBool::new(false); +static CET_SHSTK_SUPPORTED: AtomicBool = AtomicBool::new(false); static SPEC_CTRL_MASK: AtomicU64 = AtomicU64::new(0); static BOOT_ENTROPY: AtomicU64 = AtomicU64::new(0); static CMOS_LOCK: Mutex<()> = Mutex::new(()); @@ -33,6 +35,7 @@ pub fn init() { enable_umip(); enable_smep_smap(); enable_speculation_controls(); + report_optional_control_flow_features(); } } @@ -194,6 +197,18 @@ unsafe fn enable_speculation_controls() { } } +unsafe fn report_optional_control_flow_features() { + let ibt = cpu_has_cet_ibt(); + let shstk = cpu_has_cet_shadow_stack(); + CET_IBT_SUPPORTED.store(ibt, Ordering::Release); + CET_SHSTK_SUPPORTED.store(shstk, Ordering::Release); + crate::info!( + "Optional CFI/CET support detected: IBT={}, shadow_stack={}", + ibt, + shstk + ); +} + fn cpuid_leaf7_ebx() -> u32 { // rbx は LLVM が予約するため xchg で保存/復元する let ebx: u64; @@ -250,6 +265,14 @@ fn cpuid_leaf7_edx() -> u32 { edx } +fn cpu_has_cet_shadow_stack() -> bool { + (cpuid_leaf7_ecx() & (1 << 7)) != 0 +} + +fn cpu_has_cet_ibt() -> bool { + (cpuid_leaf7_edx() & (1 << 20)) != 0 +} + fn cpuid_leaf7_subleaf2_edx() -> u32 { let tmp: u64; let edx: u32; diff --git a/src/core/lib.rs b/src/core/lib.rs index 8822504..c15d001 100644 --- a/src/core/lib.rs +++ b/src/core/lib.rs @@ -6,6 +6,25 @@ #![deny(clippy::expect_used)] #![deny(clippy::panic)] +#[cfg(feature = "kcfi")] +compile_error!( + "feature `kcfi` is intentionally gated off: the current mochiOS build does not have a \ + verified Rust/LLVM KCFI pipeline for this freestanding x86_64 kernel. Leaving it \ + selectable without end-to-end verification would be unsound." +); + +#[cfg(feature = "cet-ibt")] +compile_error!( + "feature `cet-ibt` is intentionally gated off: hand-written syscall/interrupt/trampoline \ + assembly has not yet been fully annotated and inspected for ENDBR64 compliance." +); + +#[cfg(feature = "cet-shadow-stack")] +compile_error!( + "feature `cet-shadow-stack` is intentionally gated off: kernel shadow-stack allocation, \ + context-switch save/restore, and signal integration are not yet complete." +); + extern crate alloc; /// エラー型定義 From b8b167a68462c16db015e1b4c86993121f625009 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:38:31 -0400 Subject: [PATCH 10/17] fs: widen path handling and cache the fs service thread Increase the shared path limit to match swiftlib, reject empty or NUL-containing paths consistently in userland helpers, add boundary tests for the path encoder, and cache rediscovery of the filesystem service thread instead of always returning None. --- src/core/syscall/fs.rs | 64 +++++++++++++++++++++++++------------- src/user/fs.rs | 70 +++++++++++++++++++++++++++++++++++++----- src/user/io.rs | 3 +- 3 files changed, 107 insertions(+), 30 deletions(-) diff --git a/src/core/syscall/fs.rs b/src/core/syscall/fs.rs index ccaf004..9e37cdf 100644 --- a/src/core/syscall/fs.rs +++ b/src/core/syscall/fs.rs @@ -36,7 +36,7 @@ where } // ファイルシステムIPC定数(swiftlib::fs_constsと同一の値を維持) -const FS_PATH_MAX: usize = 128; +const FS_PATH_MAX: usize = 512; const FS_DATA_MAX: usize = 4096; const IPC_MAX_MSG_SIZE: usize = 65536; const FS_RECV_TIMEOUT_TICKS: u64 = 500; @@ -73,8 +73,27 @@ static CACHED_FS_SERVICE_TID: core::sync::atomic::AtomicU64 = core::sync::atomic #[inline] pub(crate) fn fs_service_tid() -> Option { - let _ = &CACHED_FS_SERVICE_TID; - None + let cached = CACHED_FS_SERVICE_TID.load(core::sync::atomic::Ordering::Acquire); + if cached != 0 && crate::task::thread_id_exists(cached) { + return Some(cached); + } + + let pid = crate::task::find_process_id_by_name("fs.service") + .or_else(|| crate::task::find_process_id_by_name("fs"))?; + + let mut found_tid = None; + crate::task::for_each_thread(|t| { + if found_tid.is_none() && t.process_id() == pid { + found_tid = Some(t.id().as_u64()); + } + }); + + if let Some(tid) = found_tid { + CACHED_FS_SERVICE_TID.store(tid, core::sync::atomic::Ordering::Release); + Some(tid) + } else { + None + } } fn recv_from_fs_with_timeout(fs_tid: u64, buf: &mut [u8]) -> Result { @@ -183,6 +202,11 @@ pub(crate) fn encode_fs_path(path: &str) -> Result<[u8; FS_PATH_MAX], u64> { let mut out = [0u8; FS_PATH_MAX]; let bytes = path.as_bytes(); if bytes.is_empty() || bytes.len() >= FS_PATH_MAX { + crate::warn!( + "fs: rejected path length {} (max {})", + bytes.len(), + FS_PATH_MAX - 1 + ); return Err(EINVAL); } if bytes.iter().any(|&b| b == 0) { @@ -705,15 +729,13 @@ pub fn stat(path_ptr: u64, stat_ptr: u64) -> u64 { write_stat_buf(stat_ptr, mode_for_stat(mode), size); SUCCESS } - Err(errno) if should_fallback_to_initfs(errno) => { - match fallback_file_metadata(&resolved) { - Some((inode_mode, size)) => { - write_stat_buf(stat_ptr, mode_for_stat(inode_mode), size); - SUCCESS - } - None => ENOENT, + Err(errno) if should_fallback_to_initfs(errno) => match fallback_file_metadata(&resolved) { + Some((inode_mode, size)) => { + write_stat_buf(stat_ptr, mode_for_stat(inode_mode), size); + SUCCESS } - } + None => ENOENT, + }, Err(errno) => errno, } } @@ -1180,19 +1202,17 @@ pub fn newfstatat(dirfd: i64, path_ptr: u64, stat_ptr: u64, flags: u64) -> u64 { write_stat_buf(stat_ptr, mode_for_stat(mode), size); SUCCESS } - Err(errno) if should_fallback_to_initfs(errno) => { - match fallback_file_metadata(&full) { - Some((inode_mode, size)) => { - const STAT_SIZE: u64 = 144; - if !crate::syscall::validate_user_ptr(stat_ptr, STAT_SIZE) { - return EFAULT; - } - write_stat_buf(stat_ptr, mode_for_stat(inode_mode), size); - SUCCESS + Err(errno) if should_fallback_to_initfs(errno) => match fallback_file_metadata(&full) { + Some((inode_mode, size)) => { + const STAT_SIZE: u64 = 144; + if !crate::syscall::validate_user_ptr(stat_ptr, STAT_SIZE) { + return EFAULT; } - None => ENOENT, + write_stat_buf(stat_ptr, mode_for_stat(inode_mode), size); + SUCCESS } - } + None => ENOENT, + }, Err(errno) => errno, } } diff --git a/src/user/fs.rs b/src/user/fs.rs index 47301d6..f30581a 100644 --- a/src/user/fs.rs +++ b/src/user/fs.rs @@ -2,22 +2,36 @@ use super::sys::{syscall1, syscall2, syscall3, SyscallNumber}; use alloc::vec::Vec; +use crate::fs_consts::FS_PATH_MAX; +use crate::sys::EINVAL; -fn path_buf(path: &str) -> ([u8; 512], usize) { - let mut buf = [0u8; 512]; +fn path_buf(path: &str) -> Result<([u8; FS_PATH_MAX], usize), u64> { + let mut buf = [0u8; FS_PATH_MAX]; let bytes = path.as_bytes(); - let len = bytes.len().min(511); + if bytes.is_empty() || bytes.len() >= FS_PATH_MAX { + return Err(EINVAL); + } + if bytes.iter().any(|&b| b == 0) { + return Err(EINVAL); + } + let len = bytes.len(); buf[..len].copy_from_slice(&bytes[..len]); - (buf, len) + Ok((buf, len)) } pub fn mkdir(path: &str, mode: u32) -> u64 { - let (buf, _) = path_buf(path); + let (buf, _) = match path_buf(path) { + Ok(v) => v, + Err(errno) => return errno, + }; syscall2(SyscallNumber::Mkdir as u64, buf.as_ptr() as u64, mode as u64) } pub fn rmdir(path: &str) -> u64 { - let (buf, _) = path_buf(path); + let (buf, _) = match path_buf(path) { + Ok(v) => v, + Err(errno) => return errno, + }; syscall1(SyscallNumber::Rmdir as u64, buf.as_ptr() as u64) } @@ -31,7 +45,10 @@ pub fn readdir(fd: u64, buf: &mut [u8]) -> u64 { } pub fn chdir(path: &str) -> u64 { - let (buf, _) = path_buf(path); + let (buf, _) = match path_buf(path) { + Ok(v) => v, + Err(errno) => return errno, + }; syscall1(SyscallNumber::Chdir as u64, buf.as_ptr() as u64) } @@ -103,3 +120,42 @@ pub fn read_file_via_fs(path: &str, max_size: usize) -> Result>, close_via_fs(fd); Ok(Some(out)) } + +#[cfg(test)] +mod tests { + use super::path_buf; + use alloc::string::String; + use crate::fs_consts::FS_PATH_MAX; + + #[test] + fn path_buf_accepts_boundary_length() { + let p = alloc::format!("/{}", "a".repeat(FS_PATH_MAX - 2)); + let (buf, len) = path_buf(&p).expect("boundary path should be accepted"); + assert_eq!(len, p.len()); + assert_eq!(&buf[..len], p.as_bytes()); + } + + #[test] + fn path_buf_rejects_too_long_path() { + let p = alloc::format!("/{}", "a".repeat(FS_PATH_MAX - 1)); + assert!(path_buf(&p).is_err()); + } + + #[test] + fn path_buf_accepts_deeply_nested_path() { + let mut p = String::from("/"); + while p.len() + 3 < FS_PATH_MAX - 1 { + p.push_str("a/"); + } + p.push('z'); + let (buf, len) = path_buf(&p).expect("nested path should be accepted"); + assert_eq!(len, p.len()); + assert_eq!(&buf[..len], p.as_bytes()); + } + + #[test] + fn path_buf_rejects_empty_and_nul_path() { + assert!(path_buf("").is_err()); + assert!(path_buf("/ok\0bad").is_err()); + } +} diff --git a/src/user/io.rs b/src/user/io.rs index 69c95c4..cac0c44 100644 --- a/src/user/io.rs +++ b/src/user/io.rs @@ -1,6 +1,7 @@ //! I/O関連のシステムコールラッパー use crate::sys::{syscall1, syscall2, syscall3, SyscallNumber}; +use crate::fs_consts::FS_PATH_MAX; /// 標準出力のファイルディスクリプタ pub const STDOUT: u64 = 1; @@ -99,7 +100,7 @@ pub fn read(fd: u64, buf: &mut [u8]) -> u64 { /// ファイルディスクリプタ、またはエラーコード #[inline] pub fn open(path: &str, flags: u64) -> i64 { - let mut buf = [0u8; 512]; + let mut buf = [0u8; FS_PATH_MAX]; let bytes = path.as_bytes(); if bytes.len() >= buf.len() { return -1; From 7c57ffd661bd912aa6ba855d5e93a8ed537c0e9b Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:38:44 -0400 Subject: [PATCH 11/17] signal: install fixed sigreturn stubs and harden exec mappings Stop trusting user-provided sa_restorer pointers by mapping a kernel-chosen rt_sigreturn stub into each process, validate signal frames with a magic value and saved mask restoration, and reject zero-copy ELF segments that request writable and executable permissions at once. --- src/core/syscall/exec.rs | 64 ++++++++++++++++++++-- src/core/syscall/signal.rs | 105 ++++++++++++++++++++++++------------- src/core/task/mod.rs | 5 +- src/core/task/signal.rs | 11 +++- 4 files changed, 142 insertions(+), 43 deletions(-) diff --git a/src/core/syscall/exec.rs b/src/core/syscall/exec.rs index a1b8462..b1563ea 100644 --- a/src/core/syscall/exec.rs +++ b/src/core/syscall/exec.rs @@ -17,6 +17,8 @@ const USER_STACK_SIZE_PAGES: usize = 32; // 128KiB stack const TLS_BASE_MIN: u64 = 0x3000_0000; const TLS_ASLR_MAX_PAGES: u64 = 0x4000; // 64MiB const INITIAL_TLS_SIZE: u64 = 4096; +const SIGNAL_RESTORER_STUB_SIZE: usize = 16; +const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; struct InitialUserStack { stack_base_vaddr: u64, @@ -91,6 +93,42 @@ fn aslr_offset_pages(seed: u64, max_pages: u64) -> u64 { } } +fn map_sigreturn_stub(table_phys: u64, stack_end_vaddr: u64) -> core::result::Result<(), u64> { + let stub_addr = match crate::task::sigreturn_stub_addr(stack_end_vaddr) { + Some(v) => v, + None => return Err(crate::syscall::types::EINVAL), + }; + let sysno = crate::syscall::types::SyscallNumber::RtSigreturn as u32; + let mut stub = Vec::with_capacity(SIGNAL_RESTORER_STUB_SIZE); + + // mov eax, ; int 0x80; ud2 + stub.push(0xB8); + stub.extend_from_slice(&sysno.to_le_bytes()); + stub.extend_from_slice(&[0xCD, 0x80]); + stub.extend_from_slice(&[0x0F, 0x0B]); + + let cur = stub.len(); + + if cur > SIGNAL_RESTORER_STUB_SIZE { + return Err(crate::syscall::types::EINVAL); + } + + if stub_addr == 0 { + return Err(crate::syscall::types::EINVAL); + } + let stub_end = stub_addr + .checked_add((cur as u64).saturating_sub(1)) + .ok_or(crate::syscall::types::EINVAL)?; + if stub_end > USER_SPACE_END { + return Err(crate::syscall::types::EINVAL); + } + + crate::mem::paging::map_and_copy_segment_to( + table_phys, stub_addr, cur as u64, cur as u64, &stub, false, true, + ) + .map_err(|_| crate::syscall::types::EINVAL) +} + fn caller_can_launch_service() -> bool { let caller = crate::task::current_thread_id() .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())); @@ -642,6 +680,14 @@ pub fn exec_from_fs_stream(path_ptr: u64, args_ptr: u64) -> u64 { if ph.p_type != crate::elf::loader::PT_LOAD { continue; } + if (ph.p_flags & 0x1) != 0 && (ph.p_flags & 0x2) != 0 { + crate::warn!( + "exec: denying zero-copy mapping for writable+executable segment at {:#x}", + ph.p_vaddr + ); + misaligned = true; + break; + } let vaddr = ph.p_vaddr; let filesz = ph.p_filesz as usize; let memsz = ph.p_memsz as usize; @@ -858,6 +904,9 @@ pub fn exec_from_fs_stream(path_ptr: u64, args_ptr: u64) -> u64 { { return crate::syscall::types::EINVAL; } + if let Err(e) = map_sigreturn_stub(new_pt_phys, stack_end_vaddr) { + return e; + } const HEAP_BASE_MIN: u64 = 0x4000_0000; const HEAP_ASLR_MAX_PAGES: u64 = 0x8000; let default_heap_base = HEAP_BASE_MIN.saturating_add( @@ -1422,6 +1471,9 @@ fn exec_with_data( crate::warn!("Failed to allocate user stack top: {:?}", e); return crate::syscall::types::EINVAL; } + if let Err(e) = map_sigreturn_stub(new_pt_phys, stack_end_vaddr) { + return e; + } crate::debug!("User stack allocated successfully"); @@ -1865,6 +1917,9 @@ pub fn execve_syscall(path_ptr: u64, argv: u64, envp: u64) -> u64 { { return EINVAL; } + if let Err(e) = map_sigreturn_stub(new_pt_phys, stack_end_vaddr) { + return e; + } // 初期ヒープをASLR付きで確保 const HEAP_BASE_MIN: u64 = 0x4000_0000; @@ -2100,11 +2155,10 @@ pub fn exec_from_buffer_named_args_with_requester_syscall( }; match crate::task::with_thread(requester, |t| t.process_id()) { Some(pid) => { - let caller_is_core = - crate::task::with_process(caller_pid, |p| { - p.privilege() == crate::task::PrivilegeLevel::Core - }) - .unwrap_or(false); + let caller_is_core = crate::task::with_process(caller_pid, |p| { + p.privilege() == crate::task::PrivilegeLevel::Core + }) + .unwrap_or(false); if pid != caller_pid && !caller_is_core { return EPERM; diff --git a/src/core/syscall/signal.rs b/src/core/syscall/signal.rs index 9538baa..01946a0 100644 --- a/src/core/syscall/signal.rs +++ b/src/core/syscall/signal.rs @@ -5,8 +5,9 @@ use super::types::{EINVAL, EPERM, ESRCH, SUCCESS}; use crate::task::{ - current_thread_id, default_action, with_process, with_process_mut, DefaultAction, - PrivilegeLevel, ProcessId, SigAction, SIGCHLD, SIGKILL, SIG_DFL, SIG_IGN, + current_thread_id, default_action, sigreturn_stub_addr, with_process, with_process_mut, + DefaultAction, PrivilegeLevel, ProcessId, SigAction, SA_RESTORER, SIGCHLD, SIGKILL, + SIGNAL_FRAME_MAGIC, SIG_DFL, SIG_IGN, }; // ---- rt_sigprocmask の how 引数 ---- @@ -24,6 +25,7 @@ const UNCATCHABLE_MASK: u64 = (1u64 << (SIGKILL - 1)) | (1u64 << (SIGSTOP - 1)); // sa_restorer: [+16] u64 // sa_mask: [+24] u64 (128-bit mask, 上位64bitは今回使わない) const SIGACTION_SIZE: u64 = 32; +const SIGNAL_FRAME_SIZE: u64 = 48; /// rt_sigaction システムコール /// @@ -55,8 +57,8 @@ pub fn rt_sigaction(signum: u64, new_act_ptr: u64, old_act_ptr: u64) -> u64 { .unwrap_or(SigAction::default_action()); let mut buf = [0u8; SIGACTION_SIZE as usize]; buf[0..8].copy_from_slice(&old.handler.to_ne_bytes()); - buf[8..16].copy_from_slice(&old.flags.to_ne_bytes()); - buf[16..24].copy_from_slice(&old.restorer.to_ne_bytes()); + buf[8..16].copy_from_slice(&(old.flags & !SA_RESTORER).to_ne_bytes()); + buf[16..24].copy_from_slice(&0u64.to_ne_bytes()); buf[24..32].copy_from_slice(&old.mask.to_ne_bytes()); if crate::syscall::copy_to_user(old_act_ptr, &buf).is_err() { return super::types::EFAULT; @@ -76,7 +78,7 @@ pub fn rt_sigaction(signum: u64, new_act_ptr: u64, old_act_ptr: u64) -> u64 { Ok(v) => v, Err(e) => return e, }; - let restorer = match crate::syscall::read_user_u64(new_act_ptr + 16) { + let _restorer = match crate::syscall::read_user_u64(new_act_ptr + 16) { Ok(v) => v, Err(e) => return e, }; @@ -84,12 +86,15 @@ pub fn rt_sigaction(signum: u64, new_act_ptr: u64, old_act_ptr: u64) -> u64 { Ok(v) => v, Err(e) => return e, }; + if handler > SIG_IGN && !crate::syscall::validate_user_ptr(handler, 1) { + return super::types::EFAULT; + } // mask の uncatchable ビットは強制クリア let mask = mask & !UNCATCHABLE_MASK; let action = SigAction { handler, - flags, - restorer, + flags: flags & !SA_RESTORER, + restorer: 0, mask, }; with_process_mut(pid, |p| p.signal_state_mut().set_action(sig, action)); @@ -329,21 +334,29 @@ unsafe fn setup_signal_frame(kstack: *mut u64, sig: usize, action: &SigAction) { let user_rflags = kstack.add(17).read(); let user_rsp = kstack.add(18).read(); - // ユーザースタック上にシグナルフレームを構築 - // レイアウト(低アドレス → 高アドレス, 新 RSP は先頭): - // [new_rsp + 0]: sa_restorer ← ハンドラの戻り先(return address) - // [new_rsp + 8]: saved RIP - // [new_rsp + 16]: saved RSP - // [new_rsp + 24]: saved RFLAGS - // - // ハンドラ呼び出し規約: x86-64 では `call` の直後は RSP % 16 == 8 なので、 - // 戻り番地を積んだ直後のスタックトップとして new_rsp を渡す。 - const FRAME_BYTES: u64 = 32; + // ユーザースタック上に固定 sigreturn スタブ向けのフレームを構築する。 + // レイアウト: + // [new_rsp + 0]: fixed sigreturn stub の戻り番地 + // [new_rsp + 8]: frame magic + // [new_rsp + 16]: saved RIP + // [new_rsp + 24]: saved RSP + // [new_rsp + 32]: saved RFLAGS + // [new_rsp + 40]: saved signal mask + const FRAME_BYTES: u64 = SIGNAL_FRAME_SIZE; let aligned = (user_rsp.wrapping_sub(FRAME_BYTES)) & !15u64; let new_rsp = aligned.wrapping_sub(8); // call 直後を模倣: RSP % 16 == 8 + let old_mask = current_pid() + .and_then(|pid| with_process(pid, |p| p.signal_state().mask)) + .unwrap_or(0); + let restorer = current_pid() + .and_then(|pid| with_process(pid, |p| sigreturn_stub_addr(p.stack_top())).flatten()) + .unwrap_or(0); + if restorer == 0 || !crate::syscall::validate_user_ptr(restorer, 1) { + crate::task::exit_current_task(11); + } // フレームをユーザースタックに書き込む - let ok = write_signal_frame(new_rsp, action.restorer, user_rip, user_rsp, user_rflags); + let ok = write_signal_frame(new_rsp, restorer, user_rip, user_rsp, user_rflags, old_mask); if !ok { // ユーザースタックが不正 → 強制終了 crate::task::exit_current_task(11); // SIGSEGV @@ -369,17 +382,19 @@ fn write_signal_frame( saved_rip: u64, saved_rsp: u64, saved_rflags: u64, + saved_mask: u64, ) -> bool { - // 書き込みアドレスの検証(32バイト) - if !crate::syscall::validate_user_ptr(new_rsp, 32) { + if !crate::syscall::validate_user_ptr(new_rsp, SIGNAL_FRAME_SIZE) { return false; } - let mut frame = [0u8; 32]; + let mut frame = [0u8; SIGNAL_FRAME_SIZE as usize]; frame[0..8].copy_from_slice(&restorer.to_ne_bytes()); - frame[8..16].copy_from_slice(&saved_rip.to_ne_bytes()); - frame[16..24].copy_from_slice(&saved_rsp.to_ne_bytes()); - frame[24..32].copy_from_slice(&saved_rflags.to_ne_bytes()); - crate::syscall::copy_to_user(new_rsp, &frame).is_ok() + frame[8..16].copy_from_slice(&SIGNAL_FRAME_MAGIC.to_ne_bytes()); + frame[16..24].copy_from_slice(&saved_rip.to_ne_bytes()); + frame[24..32].copy_from_slice(&saved_rsp.to_ne_bytes()); + frame[32..40].copy_from_slice(&saved_rflags.to_ne_bytes()); + frame[40..48].copy_from_slice(&saved_mask.to_ne_bytes()); + crate::syscall::copy_to_user(new_rsp, &frame[..SIGNAL_FRAME_SIZE as usize]).is_ok() } /// rt_sigreturn システムコール @@ -390,33 +405,54 @@ fn write_signal_frame( /// # 引数 /// - `kstack`: int 0x80 割り込みスタックフレーム先頭ポインタ pub fn rt_sigreturn(kstack: *mut u64) { - // ハンドラが `ret` した後、restorer が int 0x80 (RAX=15) を実行する。 - // `ret` で sa_restorer を pop したので、user RSP は +8 されている。 - // つまり user_rsp は saved_rip の直前を指している。 + // ハンドラが `ret` した後、固定 sigreturn スタブが int 0x80 (RAX=15) を実行する。 + // `ret` 済みなので user RSP はフレーム本体を指している。 let user_rsp = unsafe { kstack.add(18).read() }; - if !crate::syscall::validate_user_ptr(user_rsp, 24) { + if !crate::syscall::validate_user_ptr(user_rsp, SIGNAL_FRAME_SIZE) { crate::task::exit_current_task(11); // SIGSEGV } - let saved_rip = match crate::syscall::read_user_u64(user_rsp) { + let frame_magic = match crate::syscall::read_user_u64(user_rsp) { Ok(v) => v, Err(_) => { crate::task::exit_current_task(11); } }; - let saved_rsp = match crate::syscall::read_user_u64(user_rsp + 8) { + if frame_magic != SIGNAL_FRAME_MAGIC { + crate::task::exit_current_task(11); + } + let saved_rip = match crate::syscall::read_user_u64(user_rsp + 8) { Ok(v) => v, Err(_) => { crate::task::exit_current_task(11); } }; - let saved_rflags = match crate::syscall::read_user_u64(user_rsp + 16) { + let saved_rsp = match crate::syscall::read_user_u64(user_rsp + 16) { Ok(v) => v, Err(_) => { crate::task::exit_current_task(11); } }; + let saved_rflags = match crate::syscall::read_user_u64(user_rsp + 24) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + let saved_mask = match crate::syscall::read_user_u64(user_rsp + 32) { + Ok(v) => v & !UNCATCHABLE_MASK, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + if saved_rip == 0 + || saved_rsp == 0 + || !crate::syscall::validate_user_ptr(saved_rip, 1) + || !crate::syscall::validate_user_ptr(saved_rsp, 1) + { + crate::task::exit_current_task(11); + } unsafe { kstack.add(15).write(saved_rip); @@ -424,10 +460,9 @@ pub fn rt_sigreturn(kstack: *mut u64) { kstack.add(18).write(saved_rsp); } - // ハンドラ実行中にブロックしていたマスクを元に戻す(簡易: マスクをクリア) - // TODO: シグナルフレームに旧マスクを保存して正確に復元する + // シグナルハンドラ実行前のマスクを復元する。 if let Some(pid) = current_pid() { - with_process_mut(pid, |p| p.signal_state_mut().mask = 0); + with_process_mut(pid, |p| p.signal_state_mut().mask = saved_mask); } } diff --git a/src/core/task/mod.rs b/src/core/task/mod.rs index 452aa6a..2b0b59c 100644 --- a/src/core/task/mod.rs +++ b/src/core/task/mod.rs @@ -27,8 +27,9 @@ pub use scheduler::{ yield_now, Scheduler, }; pub use signal::{ - default_action, DefaultAction, SigAction, SignalState, SIGCHLD, SIGINT, SIGKILL, SIGTERM, - SIG_DFL, SIG_IGN, + default_action, sigreturn_stub_addr, DefaultAction, SigAction, SignalState, SA_RESTORER, + SIGCHLD, SIGINT, SIGKILL, SIGNAL_FRAME_MAGIC, SIGTERM, SIG_DFL, SIG_IGN, + USER_SIGRETURN_STUB_OFFSET, }; pub use thread::{ add_thread, allocate_kernel_stack, count_threads_by_state, current_thread_id, for_each_thread, diff --git a/src/core/task/signal.rs b/src/core/task/signal.rs index 4750290..f5386ef 100644 --- a/src/core/task/signal.rs +++ b/src/core/task/signal.rs @@ -21,6 +21,10 @@ pub const SIGWINCH: usize = 28; // ----- SA_* フラグ ----- pub const SA_RESTORER: u64 = 0x04000000; +/// カーネルが各プロセスへ固定配置する sigreturn スタブのオフセット。 +pub const USER_SIGRETURN_STUB_OFFSET: u64 = 0x2000; +/// シグナルフレーム整合性検証用のマジック値。 +pub const SIGNAL_FRAME_MAGIC: u64 = 0x6d6f_6368_695f_7367; /// シグナルのデフォルト動作 #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -49,7 +53,7 @@ pub struct SigAction { pub handler: u64, /// SA_* フラグ pub flags: u64, - /// SA_RESTORER が設定されている場合のリストア関数ポインタ + /// Linux 互換レイアウト維持のため残すが、カーネルは信用しない。 pub restorer: u64, /// ハンドラ実行中にブロックするシグナルマスク(ビット i = シグナル i+1) pub mask: u64, @@ -76,6 +80,11 @@ impl SigAction { } } +#[inline] +pub fn sigreturn_stub_addr(stack_top: u64) -> Option { + stack_top.checked_add(USER_SIGRETURN_STUB_OFFSET) +} + /// プロセスのシグナル状態 pub struct SignalState { /// シグナルごとのアクション(インデックス 0 = SIGHUP, ... インデックス 63 = シグナル64) From 1bc166c6f2b30eabc1a3602d7df4ba01c3db0f58 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:38:56 -0400 Subject: [PATCH 12/17] security: keep trap and int80 handling on kernel page tables Add shared KPTI enter/leave helpers for traps, switch page faults to the active user page table only for address translation diagnostics, and move int 0x80 dispatch plus signal return handling into a wrapper that stays on the kernel CR3 until it is safe to return. --- src/core/interrupt/idt.rs | 55 ++++++++++++++++++- src/core/interrupt/timer.rs | 10 ++-- src/core/syscall/mod.rs | 87 +++++++++++++++++-------------- src/core/syscall/syscall_entry.rs | 19 +++++++ 4 files changed, 123 insertions(+), 48 deletions(-) diff --git a/src/core/interrupt/idt.rs b/src/core/interrupt/idt.rs index 762943b..4fe1743 100644 --- a/src/core/interrupt/idt.rs +++ b/src/core/interrupt/idt.rs @@ -9,6 +9,18 @@ use x86_64::PrivilegeLevel; static IDT: Once = Once::new(); +#[inline] +fn enter_from_user(stack_frame: &InterruptStackFrame) -> bool { + crate::syscall::syscall_entry::kpti_enter_for_trap( + stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3, + ) +} + +#[inline] +fn leave_to_user(entered_from_user: bool) { + crate::syscall::syscall_entry::kpti_leave_after_trap(entered_from_user); +} + /// IDTを初期化 pub fn init() { debug!("Initializing IDT..."); @@ -103,6 +115,7 @@ pub fn init() { /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn divide_error_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: DIVIDE ERROR ({})", @@ -128,8 +141,10 @@ extern "x86-interrupt" fn divide_error_handler(stack_frame: InterruptStackFrame) /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn debug_handler(stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&stack_frame); debug!("EXCEPTION: DEBUG"); debug!("{:#?}", stack_frame); + leave_to_user(entered_from_user); } /// NMI (Non-Maskable Interrupt) ハンドラ @@ -139,6 +154,7 @@ extern "x86-interrupt" fn debug_handler(stack_frame: InterruptStackFrame) { /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn nmi_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); error!("EXCEPTION: NON-MASKABLE INTERRUPT"); warn!("{:#?}", stack_frame); halt_cpu(); @@ -151,8 +167,10 @@ extern "x86-interrupt" fn nmi_handler(stack_frame: InterruptStackFrame) { /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&stack_frame); warn!("EXCEPTION: BREAKPOINT"); debug!("{:#?}", stack_frame); + leave_to_user(entered_from_user); } /// オーバーフロー例外ハンドラ @@ -162,6 +180,7 @@ extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn overflow_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: OVERFLOW ({})", @@ -187,6 +206,7 @@ extern "x86-interrupt" fn overflow_handler(stack_frame: InterruptStackFrame) { /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn bound_range_exceeded_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: BOUND RANGE EXCEEDED ({})", @@ -470,6 +490,7 @@ fn dump_invalid_opcode_diagnostics(stack_frame: &InterruptStackFrame) { /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); // ユーザーモードかチェック(code_segmentのRPLビットを確認) let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; @@ -481,7 +502,11 @@ extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFram "KERNEL MODE" } ); - dump_invalid_opcode_diagnostics(&stack_frame); + if is_user_mode { + error!("User-mode invalid opcode diagnostics are redacted in this path"); + } else { + dump_invalid_opcode_diagnostics(&stack_frame); + } if is_user_mode { error!("Terminating faulting user process"); @@ -498,6 +523,7 @@ extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFram /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn device_not_available_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: DEVICE NOT AVAILABLE ({})", @@ -541,6 +567,7 @@ extern "x86-interrupt" fn double_fault_handler( /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 /// - `error_code`: TSS無効例外のエラーコード(通常は0だが、特定の条件下で値が設定されることがある) extern "x86-interrupt" fn invalid_tss_handler(stack_frame: InterruptStackFrame, error_code: u64) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: INVALID TSS ({})", @@ -571,6 +598,7 @@ extern "x86-interrupt" fn segment_not_present_handler( stack_frame: InterruptStackFrame, error_code: u64, ) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: SEGMENT NOT PRESENT ({})", @@ -601,6 +629,7 @@ extern "x86-interrupt" fn stack_segment_fault_handler( stack_frame: InterruptStackFrame, error_code: u64, ) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( @@ -633,6 +662,7 @@ extern "x86-interrupt" fn general_protection_fault_handler( stack_frame: InterruptStackFrame, error_code: u64, ) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( @@ -689,6 +719,7 @@ extern "x86-interrupt" fn page_fault_handler( let faulting_addr = Cr2::read().unwrap_or(VirtAddr::new(0)); let is_user_mode = error_code.contains(x86_64::structures::idt::PageFaultErrorCode::USER_MODE); + let entered_from_user = crate::syscall::syscall_entry::kpti_enter_for_trap(is_user_mode); error!( "EXCEPTION: PAGE FAULT ({})", @@ -709,7 +740,16 @@ extern "x86-interrupt" fn page_fault_handler( error_code.contains(x86_64::structures::idt::PageFaultErrorCode::INSTRUCTION_FETCH) ); - if let Some(phys) = crate::mem::paging::translate_addr(faulting_addr) { + let translated = if is_user_mode { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.page_table()).flatten()) + .and_then(|pt| crate::mem::paging::virt_to_phys_in_table(pt, faulting_addr.as_u64())) + .map(x86_64::PhysAddr::new) + } else { + crate::mem::paging::translate_addr(faulting_addr) + }; + if let Some(phys) = translated { error!( " Virtual {:#x} is mapped to physical {:#x}", faulting_addr.as_u64(), @@ -747,6 +787,7 @@ extern "x86-interrupt" fn page_fault_handler( error_code.contains(x86_64::structures::idt::PageFaultErrorCode::PROTECTION_VIOLATION); if !is_protection_violation { if try_grow_user_stack(faulting_addr.as_u64()) { + leave_to_user(entered_from_user); return; // スタック拡張成功 → 命令を再試行 } } @@ -834,6 +875,7 @@ fn try_grow_user_stack(fault_addr: u64) -> bool { /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn x87_floating_point_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: X87 FLOATING POINT ({})", @@ -864,6 +906,7 @@ extern "x86-interrupt" fn alignment_check_handler( stack_frame: InterruptStackFrame, error_code: u64, ) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: ALIGNMENT CHECK ({})", @@ -902,6 +945,7 @@ extern "x86-interrupt" fn machine_check_handler(stack_frame: InterruptStackFrame /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn simd_floating_point_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: SIMD FLOATING POINT ({})", @@ -927,6 +971,7 @@ extern "x86-interrupt" fn simd_floating_point_handler(stack_frame: InterruptStac /// ## Arguments /// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 extern "x86-interrupt" fn virtualization_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; error!( "EXCEPTION: VIRTUALIZATION ({})", @@ -950,6 +995,7 @@ extern "x86-interrupt" fn virtualization_handler(stack_frame: InterruptStackFram /// IRQ1 をIDTに登録せずに放置するとキーストロークのたびに #GP が発生し /// OS全体が停止する (C-2修正)。このハンドラはスキャンコードを読み捨て EOI を送る。 extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&_stack_frame); let scancode: u8 = unsafe { let mut port = x86_64::instructions::port::Port::::new(0x60); port.read() @@ -959,10 +1005,12 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac unsafe { super::pic::PIC_MASTER.end_of_interrupt(); } + leave_to_user(entered_from_user); } /// マウス割り込みハンドラ (IRQ12 / ベクタ 44) extern "x86-interrupt" fn mouse_interrupt_handler(_stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&_stack_frame); let byte: u8 = unsafe { let mut port = x86_64::instructions::port::Port::::new(0x60); port.read() @@ -970,6 +1018,7 @@ extern "x86-interrupt" fn mouse_interrupt_handler(_stack_frame: InterruptStackFr crate::util::ps2mouse::push_byte(byte); // IRQ12 はスレーブPIC配下なので、スレーブ→マスターの順でEOIを送る super::send_eoi(44); + leave_to_user(entered_from_user); } /// 一般的な割り込みハンドラ(スタブ) @@ -982,6 +1031,7 @@ extern "x86-interrupt" fn mouse_interrupt_handler(_stack_frame: InterruptStackFr /// /// このハンドラは、将来的に各デバイスに対応した具体的な処理を実装するためのプレースホルダとして使用される予定 extern "x86-interrupt" fn generic_interrupt_handler(_stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&_stack_frame); debug!("INTERRUPT: GENERIC"); // マスターPICのみにEOIを送信する (LOW-01) // このハンドラはどのIRQから呼ばれるか不明のため、IRQ 0-7 (マスターのみ) を想定して @@ -990,6 +1040,7 @@ extern "x86-interrupt" fn generic_interrupt_handler(_stack_frame: InterruptStack unsafe { super::pic::PIC_MASTER.end_of_interrupt(); } + leave_to_user(entered_from_user); } /// CPU割り込みを無効化してシステムを停止 diff --git a/src/core/interrupt/timer.rs b/src/core/interrupt/timer.rs index dd115ca..4021737 100644 --- a/src/core/interrupt/timer.rs +++ b/src/core/interrupt/timer.rs @@ -14,9 +14,9 @@ static TIMER_TICKS: AtomicU64 = AtomicU64::new(0); /// ## Arguments /// - `_stack_frame`: 割り込み発生時のスタックフレーム pub extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { - // KPTI: ユーザーCR3で割り込みに入った場合、先にカーネルCR3へ切り替える - let entered_from_user = crate::syscall::syscall_entry::switch_to_kernel_page_table() != 0; - crate::cpu::reassert_runtime_hardening(); + let entered_from_user = crate::syscall::syscall_entry::kpti_enter_for_trap( + _stack_frame.code_segment.rpl() == x86_64::PrivilegeLevel::Ring3, + ); // タイマーカウンタを増加 let ticks = TIMER_TICKS @@ -39,9 +39,7 @@ pub extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptSta } // ユーザーから入ってきた場合は、復帰先スレッドに応じたユーザーCR3へ戻す - if entered_from_user { - crate::syscall::syscall_entry::switch_to_current_thread_user_page_table(); - } + crate::syscall::syscall_entry::kpti_leave_after_trap(entered_from_user); } /// 現在のタイマーティック数を取得 diff --git a/src/core/syscall/mod.rs b/src/core/syscall/mod.rs index fbedaf2..4e14e29 100644 --- a/src/core/syscall/mod.rs +++ b/src/core/syscall/mod.rs @@ -364,50 +364,17 @@ pub unsafe extern "C" fn syscall_interrupt_handler() { "push r14", "push r15", - // fork/clone のときだけ、ユーザーコンテキストを現在スレッドへ保存 - // saved stack layout: - // [rsp+112]=num(rax), [rsp+120]=user RIP, [rsp+136]=user RFLAGS, [rsp+144]=user RSP - "mov rax, [rsp + 112]", - "cmp rax, 56", - "je 2f", - "cmp rax, 57", - "jne 3f", - "2:", - "mov rdi, rax", - "mov rsi, [rsp + 120]", - "mov rdx, [rsp + 144]", - "mov rcx, [rsp + 136]", - "call {save_ctx_fn}", - "3:", - // カーネルデータセグメントをロード // (ds/esはスタックに保存しない。復元時にユーザーセグメントを再設定) "mov ax, 0x10", // カーネルデータセグメント (index=2) "mov ds, ax", "mov es, ax", - // System V AMD64 ABI: rdi=num, rsi=arg0, rdx=arg1, rcx=arg2, r8=arg3, r9=arg4 - // スタック上のオフセット (15 pushes × 8 bytes, sub rsp なし): - // [rsp+0]=r15, [rsp+8]=r14, [rsp+16]=r13, [rsp+24]=r12, [rsp+32]=r11, - // [rsp+40]=r10(arg3), [rsp+48]=r9, [rsp+56]=r8(arg4), - // [rsp+64]=rdi(arg0), [rsp+72]=rsi(arg1), [rsp+80]=rbp, [rsp+88]=rbx, - // [rsp+96]=rdx(arg2), [rsp+104]=rcx, [rsp+112]=rax(num) - "mov rdi, [rsp + 112]", // rax (syscall number) - "mov rsi, [rsp + 64]", // rdi (arg0) - "mov rdx, [rsp + 72]", // rsi (arg1) - "mov rcx, [rsp + 96]", // rdx (arg2) - "mov r8, [rsp + 40]", // r10 (arg3) - "mov r9, [rsp + 56]", // r8 (arg4) - - // Rust 関数を呼び出し (16バイトアライン済み: 160バイトオフセット) - "call {syscall_handler}", - - // シグナル送達チェック + rt_sigreturn 処理 - // signal_and_return(kstack=rsp, syscall_ret=rax) → 最終的な戻り値 - // kstack[14] (=[rsp+112]) には元の syscall 番号が残っている - "mov rsi, rax", // arg1 = syscall 戻り値 + // int 0x80 経路は、kernel CR3 に切り替えたまま dispatch と signal return を完結させる。 + // KPTI で user CR3 から kernel heap を完全に外しているため、signal 配送や + // プロセス/スレッド metadata 参照を user CR3 上で行うと kernel-mode page fault になる。 "mov rdi, rsp", // arg0 = kstack(saved registers 先頭) - "call {signal_and_return}", // signal 送達 or rt_sigreturn を処理、最終 rax を返す + "call {int80_handler}", // 最終的な戻り値を rax で返す // 戻り値 (rax) をスタック上の保存された rax の位置に書き込む "mov [rsp + 112], rax", @@ -437,10 +404,50 @@ pub unsafe extern "C" fn syscall_interrupt_handler() { // 割り込みから戻る "iretq", - save_ctx_fn = sym save_user_context_for_fork, - syscall_handler = sym syscall_handler_rust, - signal_and_return = sym signal::signal_and_return, + int80_handler = sym syscall_interrupt_handler_rust, + ); +} + +/// int 0x80 経路専用の Rust wrapper。 +/// +/// dispatch 本体だけでなく signal 配送/rt_sigreturn まで kernel CR3 上で完結させる。 +/// これにより、KPTI で user CR3 から外した kernel heap / task metadata へ +/// user CR3 のまま触れてしまう事故を防ぐ。 +extern "sysv64" fn syscall_interrupt_handler_rust(kstack: *mut u64) -> u64 { + crate::percpu::install_current_cpu_gs_base(); + + let prev_cr3 = syscall_entry::switch_to_kernel_page_table(); + crate::cpu::reassert_runtime_hardening(); + + let syscall_num = unsafe { kstack.add(14).read() }; + if syscall_num == SyscallNumber::Clone as u64 || syscall_num == SyscallNumber::Fork as u64 { + let user_rip = unsafe { kstack.add(15).read() }; + let user_rflags = unsafe { kstack.add(17).read() }; + let user_rsp = unsafe { kstack.add(18).read() }; + save_user_context_for_fork(syscall_num, user_rip, user_rsp, user_rflags); + } + + let current_tid = crate::task::current_thread_id(); + if let Some(tid) = current_tid { + crate::task::with_thread_mut(tid, |t| t.set_in_syscall(true)); + } + + let ret = dispatch( + syscall_num, + unsafe { kstack.add(8).read() }, // saved rdi = arg0 + unsafe { kstack.add(9).read() }, // saved rsi = arg1 + unsafe { kstack.add(12).read() }, // saved rdx = arg2 + unsafe { kstack.add(5).read() }, // saved r10 = arg3 + unsafe { kstack.add(7).read() }, // saved r8 = arg4 ); + + if let Some(tid) = current_tid { + crate::task::with_thread_mut(tid, |t| t.set_in_syscall(false)); + } + + let ret = signal::signal_and_return(kstack, ret); + syscall_entry::restore_page_table(prev_cr3); + ret } /// システムコールハンドラの Rust 実装 diff --git a/src/core/syscall/syscall_entry.rs b/src/core/syscall/syscall_entry.rs index f444a3c..ccea972 100644 --- a/src/core/syscall/syscall_entry.rs +++ b/src/core/syscall/syscall_entry.rs @@ -135,6 +135,25 @@ pub fn kpti_leave_for_current_thread() { restore_page_table(restore); } +/// 例外/IRQ 入口で、ユーザー文脈から入った場合のみ kernel CR3 と hardening 状態をそろえる。 +pub fn kpti_enter_for_trap(from_user: bool) -> bool { + if !from_user { + return false; + } + let previous = switch_to_kernel_page_table(); + if previous != 0 { + crate::cpu::reassert_runtime_hardening(); + } + previous != 0 +} + +/// 例外/IRQ からの復帰で、ユーザー文脈へ戻る場合は復帰先スレッドの user CR3 を再設定する。 +pub fn kpti_leave_after_trap(entered_from_user: bool) { + if entered_from_user { + switch_to_current_thread_user_page_table(); + } +} + /// SYSCALL エントリポイント (naked function) /// /// 呼ばれた時点: From abd2eba6876065e19f6c75bf6171ca85b67ef30a Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:39:08 -0400 Subject: [PATCH 13/17] mm: implement mprotect and surface unsupported POSIX setters Teach page-table updates and ELF overlap handling to reject writable-plus-executable mappings, wire the mprotect syscall through kernel and user wrappers, forward sigaction to the real syscall path, and return ENOTSUP for terminal setters or sigaltstack requests that the kernel still does not implement. --- src/core/mem/paging.rs | 101 +++++++++++++++++++++++++++++++++++-- src/core/syscall/pgroup.rs | 76 ++++++++++++++++++++++++---- src/user/posix_stubs.rs | 40 ++++++++++++--- src/user/sys.rs | 2 + 4 files changed, 196 insertions(+), 23 deletions(-) diff --git a/src/core/mem/paging.rs b/src/core/mem/paging.rs index 4ee0cab..13a0f1e 100644 --- a/src/core/mem/paging.rs +++ b/src/core/mem/paging.rs @@ -1154,15 +1154,25 @@ pub fn map_and_copy_segment_to( .. } if existing_flags.contains(Flags::USER_ACCESSIBLE) => { // 別のELFセグメントが同じページをマップ済み:パーミッションをマージする。 - // 既存マッピングが実行可能なら新セグメントのNXビットを消してEXECを保持。 + // ただし最終状態が W+X になる遷移は拒否する。 + let existing_exec = !existing_flags.contains(Flags::NO_EXECUTE); + let existing_write = existing_flags.contains(Flags::WRITABLE); + let new_exec = !final_flags.contains(Flags::NO_EXECUTE); + let new_write = final_flags.contains(Flags::WRITABLE); + if (existing_exec && new_write) || (existing_write && new_exec) { + frame::deallocate_frame(frame); + return Err(Kernel::Memory(Memory::PermissionDenied)); + } let merged = if !existing_flags.contains(Flags::NO_EXECUTE) { final_flags & !Flags::NO_EXECUTE } else { final_flags }; - pt.update_flags(page, merged) - .map_err(|_| Kernel::Memory(Memory::InvalidAddress))? - .ignore(); + unsafe { + pt.update_flags(page, merged) + .map_err(|_| Kernel::Memory(Memory::InvalidAddress))? + .ignore(); + } // 新たに確保したフレームは不要なので解放 frame::deallocate_frame(frame); // データコピー先を既存フレームに切り替える @@ -1213,6 +1223,89 @@ pub fn map_and_copy_segment_to( Ok(()) } +/// 指定したユーザー範囲のページ保護を更新する。 +/// +/// `mprotect` 用の helper で、既存マッピングを維持したまま +/// WRITABLE / NO_EXECUTE を更新する。W+X は拒否する。 +pub fn protect_user_range_in_table( + table_phys: u64, + addr: u64, + len: u64, + present: bool, + writable: bool, + executable: bool, +) -> Result<()> { + use crate::result::{Kernel, Memory}; + use x86_64::structures::paging::PageTableFlags as Flags; + + if present && writable && executable { + return Err(Kernel::Memory(Memory::PermissionDenied)); + } + if len == 0 { + return Ok(()); + } + if addr == 0 || addr > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + let end_inclusive = addr + .checked_add(len.saturating_sub(1)) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if end_inclusive > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + let start = addr & !0xfffu64; + let end = end_inclusive + .checked_add(0x1000) + .map(|v| v & !0xfffu64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let l4 = unsafe { &mut *((table_phys + phys_off) as *mut PageTable) }; + let mut pt = unsafe { OffsetPageTable::new(l4, VirtAddr::new(phys_off)) }; + let (current_cr3, _) = Cr3::read(); + let current_table = current_cr3.start_address().as_u64(); + + let mut page_addr = start; + while page_addr < end { + let page = Page::::containing_address(VirtAddr::new(page_addr)); + let existing_flags = user_page_flags_in_table(table_phys, page_addr) + .ok_or(Kernel::Memory(Memory::NotMapped))?; + if !existing_flags.contains(Flags::PRESENT) + || !existing_flags.contains(Flags::USER_ACCESSIBLE) + { + return Err(Kernel::Memory(Memory::NotMapped)); + } + + let mut new_flags = existing_flags; + new_flags + .remove(Flags::PRESENT | Flags::USER_ACCESSIBLE | Flags::WRITABLE | Flags::NO_EXECUTE); + if present { + new_flags |= Flags::PRESENT | Flags::USER_ACCESSIBLE; + if writable { + new_flags |= Flags::WRITABLE; + } + if !executable { + new_flags |= Flags::NO_EXECUTE; + } + } + + let flush = unsafe { + pt.update_flags(page, new_flags) + .map_err(|_| Kernel::Memory(Memory::InvalidAddress))? + }; + if current_table == table_phys { + flush.flush(); + } else { + flush.ignore(); + } + page_addr = page_addr + .checked_add(4096) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + } + + Ok(()) +} + /// 指定したページテーブルでユーザー範囲をアンマップし、対応フレームを解放する pub fn unmap_range_in_table(table_phys: u64, addr: u64, length: u64) -> Result<()> { if length == 0 { diff --git a/src/core/syscall/pgroup.rs b/src/core/syscall/pgroup.rs index b092885..9d9f830 100644 --- a/src/core/syscall/pgroup.rs +++ b/src/core/syscall/pgroup.rs @@ -1,6 +1,6 @@ //! プロセスグループ・セッション関連のシステムコール -use super::types::{EFAULT, EINVAL, EPERM, ESRCH, SUCCESS}; +use super::types::{EFAULT, EINVAL, ENOMEM, ENOTSUP, EPERM, ESRCH, SUCCESS}; #[inline] fn current_pid() -> Option { @@ -138,7 +138,7 @@ pub fn ioctl(fd: u64, request: u64, arg: u64) -> u64 { } SUCCESS } - TIOCSPGRP => SUCCESS, + TIOCSPGRP => ENOTSUP, TIOCGWINSZ => { // struct winsize: { ws_row(u16), ws_col(u16), ws_xpixel(u16), ws_ypixel(u16) } if arg == 0 || !crate::syscall::validate_user_ptr(arg, 8) { @@ -152,7 +152,7 @@ pub fn ioctl(fd: u64, request: u64, arg: u64) -> u64 { } SUCCESS } - TIOCSWINSZ => SUCCESS, + TIOCSWINSZ => ENOTSUP, TCGETS => { // struct termios (60 バイト) をゼロ初期化した上で最小限のフラグを設定して返す // サイズ: c_iflag(4)+c_oflag(4)+c_cflag(4)+c_lflag(4)+c_line(1)+c_cc(19)+pad(24) = 60 @@ -169,7 +169,7 @@ pub fn ioctl(fd: u64, request: u64, arg: u64) -> u64 { } SUCCESS } - TCSETS | TCSETSW | TCSETSF => SUCCESS, // termios 設定は無視して成功 + TCSETS | TCSETSW | TCSETSF => ENOTSUP, _ => EINVAL, } } @@ -262,15 +262,69 @@ pub fn nanosleep(req_ptr: u64, _rem_ptr: u64) -> u64 { SUCCESS } -/// mprotect システムコール(スタブ) +/// mprotect システムコール /// -/// addr=0(nullページ)への保護変更は EINVAL を返す。 -/// それ以外は SUCCESS を返す(未実装)。 -pub fn mprotect(addr: u64, _len: u64, _prot: u64) -> u64 { - if addr < 0x1000 { - return super::types::EINVAL; +/// x86_64 の現在のユーザー保護モデルでは READ は常に許可単位になるため、 +/// ここでは READ/WRITE/EXEC の組み合わせのうち W+X を拒否しつつ、 +/// 既存マッピングの WRITABLE / NX を更新する。 +pub fn mprotect(addr: u64, len: u64, prot: u64) -> u64 { + const PROT_READ: u64 = 0x1; + const PROT_WRITE: u64 = 0x2; + const PROT_EXEC: u64 = 0x4; + const SUPPORTED_MASK: u64 = PROT_READ | PROT_WRITE | PROT_EXEC; + const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; + + if len == 0 { + return SUCCESS; + } + if (prot & !SUPPORTED_MASK) != 0 { + return EINVAL; + } + if addr == 0 || addr > USER_SPACE_END { + return EINVAL; + } + + let end_inclusive = match addr.checked_add(len.saturating_sub(1)) { + Some(v) if v <= USER_SPACE_END => v, + _ => return EINVAL, + }; + let start = addr & !0xfffu64; + let length = match (end_inclusive & !0xfffu64) + .checked_add(4096) + .and_then(|end| end.checked_sub(start)) + { + Some(v) if v != 0 => v, + _ => return EINVAL, + }; + + let present = prot != 0; + let writable = (prot & PROT_WRITE) != 0; + let executable = (prot & PROT_EXEC) != 0; + if present && writable && executable { + return EINVAL; + } + + let pid = match current_pid() { + Some(p) => p, + None => return ESRCH, + }; + let table_phys = match crate::task::with_process(pid, |p| p.page_table()).flatten() { + Some(pt) => pt, + None => return EINVAL, + }; + + match crate::mem::paging::protect_user_range_in_table( + table_phys, start, length, present, writable, executable, + ) { + Ok(()) => SUCCESS, + Err(crate::Kernel::Memory(crate::result::Memory::NotMapped)) => EFAULT, + Err(crate::Kernel::Memory(crate::result::Memory::OutOfMemory)) => ENOMEM, + Err(crate::Kernel::Memory(crate::result::Memory::PermissionDenied)) + | Err(crate::Kernel::Memory(crate::result::Memory::InvalidAddress)) + | Err(crate::Kernel::Memory(crate::result::Memory::AlignmentError)) + | Err(crate::Kernel::InvalidParam) => EINVAL, + Err(_) => EFAULT, } - SUCCESS } /// getrlimit システムコール(リソース上限を無限大で返す) diff --git a/src/user/posix_stubs.rs b/src/user/posix_stubs.rs index 6755a85..989caf4 100644 --- a/src/user/posix_stubs.rs +++ b/src/user/posix_stubs.rs @@ -3,7 +3,7 @@ //! Rust std (build-std) がリンク時に要求する C ライブラリ関数を実装する。 //! 各関数は最小限の実装か、成功を返すスタブ。 -use crate::sys::{syscall1, syscall2, syscall6, SyscallNumber}; +use crate::sys::{syscall1, syscall2, syscall3, syscall4, syscall6, SyscallNumber}; // errno static mut ERRNO_VAL: i32 = 0; @@ -47,8 +47,19 @@ pub unsafe extern "C" fn munmap(addr: *mut u8, len: usize) -> i32 { } #[unsafe(no_mangle)] -pub unsafe extern "C" fn mprotect(_addr: *mut u8, _len: usize, _prot: i32) -> i32 { - 0 // 成功 +pub unsafe extern "C" fn mprotect(addr: *mut u8, len: usize, prot: i32) -> i32 { + let ret = syscall3( + SyscallNumber::Mprotect as u64, + addr as u64, + len as u64, + prot as u64, + ); + if (ret as i64) < 0 { + ERRNO_VAL = -(ret as i64) as i32; + -1 + } else { + 0 + } } /// C の syscall(nr, arg0, arg1, arg2, arg3, arg4, arg5) の実装 @@ -187,16 +198,29 @@ pub unsafe extern "C" fn pthread_getattr_np( #[unsafe(no_mangle)] pub unsafe extern "C" fn sigaction( - _signum: i32, - _act: *const u8, - _oldact: *mut u8, + signum: i32, + act: *const u8, + oldact: *mut u8, ) -> i32 { - 0 // 成功 + let ret = syscall4( + SyscallNumber::RtSigaction as u64, + signum as u64, + act as u64, + oldact as u64, + 8, + ); + if (ret as i64) < 0 { + ERRNO_VAL = -(ret as i64) as i32; + -1 + } else { + 0 + } } #[unsafe(no_mangle)] pub unsafe extern "C" fn sigaltstack(_ss: *const u8, _oss: *mut u8) -> i32 { - 0 + ERRNO_VAL = 95; // ENOTSUP + -1 } /// nanosleep(req, rem) - 簡易実装 (yield で代用) diff --git a/src/user/sys.rs b/src/user/sys.rs index b30a5ca..1431d9f 100644 --- a/src/user/sys.rs +++ b/src/user/sys.rs @@ -26,6 +26,8 @@ pub enum SyscallNumber { Lseek = 8, /// メモリマップ Mmap = 9, + /// メモリ保護変更 + Mprotect = 10, /// メモリアンマップ Munmap = 11, /// メモリブレーク From 98250ab86f8db7f961af349428bf3fd8fc625390 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:39:17 -0400 Subject: [PATCH 14/17] kmod: guard relocation writes against address overflow Check the relocation end address with checked_add before writing relocation targets so a malformed module cannot wrap past the validated mapping window. --- src/core/kmod/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/kmod/mod.rs b/src/core/kmod/mod.rs index a690a0e..48792ca 100644 --- a/src/core/kmod/mod.rs +++ b/src/core/kmod/mod.rs @@ -23,7 +23,8 @@ pub struct McxPath { #[repr(C)] pub struct McxFsOps { pub mount: extern "C" fn(device_id: u32) -> i32, - pub read: extern "C" fn(path: McxPath, offset: u64, buf: McxBuffer, out_read: *mut usize) -> i32, + pub read: + extern "C" fn(path: McxPath, offset: u64, buf: McxBuffer, out_read: *mut usize) -> i32, pub stat: extern "C" fn(path: McxPath, out_mode: *mut u16, out_size: *mut u64) -> i32, pub readdir: extern "C" fn(path: McxPath, buf: McxBuffer, out_len: *mut usize) -> i32, } @@ -367,7 +368,8 @@ fn apply_relocations( continue; } let reloc_vaddr = r_offset; - if reloc_vaddr < min_vaddr || reloc_vaddr + 8 > max_vaddr { + let reloc_end = reloc_vaddr.checked_add(8)?; + if reloc_vaddr < min_vaddr || reloc_end > max_vaddr { return None; } let dst = base.checked_add(reloc_vaddr.checked_sub(min_vaddr)?)? as *mut u64; From fef24630d8fb5963c946130bff361f09a2f07991 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:39:23 -0400 Subject: [PATCH 15/17] driver: restrict autoloaded binaries to approved paths Normalize relative entries from drivers.list, skip paths that are empty or traverse upward, and refuse to probe or launch anything outside /Binaries/drivers so the service only executes bundled driver images. --- src/services/driver/src/main.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/services/driver/src/main.rs b/src/services/driver/src/main.rs index f79b2c1..2a03e30 100644 --- a/src/services/driver/src/main.rs +++ b/src/services/driver/src/main.rs @@ -10,6 +10,21 @@ const OP_NOTIFY_READY: u64 = 0xFF; const DRIVER_CONFIG_PATH: &str = "/Config/drivers.list"; const DEFAULT_DRIVERS: &[&str] = &["/Binaries/drivers/usb.elf"]; +fn normalize_driver_path(path: &str) -> String { + if path.starts_with('/') { + path.to_string() + } else { + format!("/{}", path) + } +} + +fn is_allowed_driver_path(path: &str) -> bool { + if path.is_empty() || path.contains("..") { + return false; + } + path.starts_with("/Binaries/drivers/") || path.starts_with("Binaries/drivers/") +} + fn load_driver_list() -> Vec { let mut drivers = Vec::new(); @@ -20,7 +35,11 @@ fn load_driver_list() -> Vec { if line.is_empty() || line.starts_with('#') { continue; } - drivers.push(line.to_string()); + if !is_allowed_driver_path(line) { + println!("[DRIVER] Skipping disallowed driver path {}", line); + continue; + } + drivers.push(normalize_driver_path(line)); } } Err(_) => { @@ -41,6 +60,10 @@ fn load_driver_list() -> Vec { } fn start_driver(path: &str) { + if !is_allowed_driver_path(path) { + println!("[DRIVER] Skipping disallowed driver path {}", path); + return; + } let probe_fd = io::open(path, io::O_RDONLY); if probe_fd < 0 { println!("[DRIVER] Skipping missing driver binary {}", path); From d74c15ee05f54d82c991fa4422e4fe5a964521c2 Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:39:33 -0400 Subject: [PATCH 16/17] shell: remove static mut IPC buffering and parse quoted UTF-8 safely Replace the global mutable pending-message array with a small internal spin-guarded buffer, use the shared filesystem path limit, and parse quoted command lines by chars instead of raw bytes so multibyte input is preserved correctly. --- src/services/shell/src/char.rs | 71 +++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/src/services/shell/src/char.rs b/src/services/shell/src/char.rs index 465b79c..eb0c698 100644 --- a/src/services/shell/src/char.rs +++ b/src/services/shell/src/char.rs @@ -1,4 +1,9 @@ use swiftlib::{fs, io, ipc, process, task, vga}; +use core::{ + cell::UnsafeCell, + hint::spin_loop, + sync::atomic::{AtomicBool, Ordering}, +}; // 色の編集がだるっちいったらありゃしないのでgeminiに作ってもらったエディタを使ってください。 // https://gemini.google.com/share/02481dc7584f @@ -38,7 +43,7 @@ const FONT_BIN_SIZE: usize = GLYPH_COUNT * FONT_HEIGHT; const FONT_BDF_MAX_SIZE: usize = 512 * 1024; const ENV_FILE_MAX_SIZE: usize = 4096; const FONT_READ_CHUNK: usize = 512; -const FS_PATH_MAX: usize = 128; +const FS_PATH_MAX: usize = swiftlib::fs_consts::FS_PATH_MAX; const FS_REQ_TIMEOUT_MS: u64 = 2000; const IPC_MSG_MAX: usize = 4128; const PENDING_IPC_CAPACITY: usize = 32; @@ -93,13 +98,41 @@ impl PendingIpcMessage { } } -static mut PENDING_IPC_MESSAGES: [PendingIpcMessage; PENDING_IPC_CAPACITY] = - [PendingIpcMessage::new(); PENDING_IPC_CAPACITY]; +struct PendingIpcSlots { + locked: AtomicBool, + slots: UnsafeCell<[PendingIpcMessage; PENDING_IPC_CAPACITY]>, +} + +unsafe impl Sync for PendingIpcSlots {} + +impl PendingIpcSlots { + const fn new() -> Self { + Self { + locked: AtomicBool::new(false), + slots: UnsafeCell::new([PendingIpcMessage::new(); PENDING_IPC_CAPACITY]), + } + } + + fn with_mut(&self, f: impl FnOnce(&mut [PendingIpcMessage; PENDING_IPC_CAPACITY]) -> R) -> R { + while self + .locked + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + spin_loop(); + } + let result = f(unsafe { &mut *self.slots.get() }); + self.locked.store(false, Ordering::Release); + result + } +} + +static PENDING_IPC_MESSAGES: PendingIpcSlots = PendingIpcSlots::new(); fn enqueue_pending_message(sender: u64, data: &[u8], len: usize) -> bool { let copy_len = core::cmp::min(len, core::cmp::min(data.len(), IPC_MSG_MAX)); - unsafe { - for slot in &mut PENDING_IPC_MESSAGES { + PENDING_IPC_MESSAGES.with_mut(|slots| { + for slot in slots.iter_mut() { if !slot.used { slot.used = true; slot.sender = sender; @@ -110,13 +143,13 @@ fn enqueue_pending_message(sender: u64, data: &[u8], len: usize) -> bool { return true; } } - } - false + false + }) } fn take_pending_message(buf: &mut [u8]) -> Option<(u64, usize)> { - unsafe { - for slot in &mut PENDING_IPC_MESSAGES { + PENDING_IPC_MESSAGES.with_mut(|slots| { + for slot in slots.iter_mut() { if slot.used { let copy_len = core::cmp::min(slot.len, buf.len()); if copy_len > 0 { @@ -129,8 +162,8 @@ fn take_pending_message(buf: &mut [u8]) -> Option<(u64, usize)> { return Some((sender, copy_len)); } } - } - None + None + }) } fn read_file(path: &str, max_size: usize) -> Option> { @@ -1443,26 +1476,26 @@ impl Terminal { fn parse_command_line(line: &str) -> Vec { let mut tokens = Vec::new(); let mut current = String::new(); - let mut quote: Option = None; + let mut quote: Option = None; - for b in line.bytes() { + for ch in line.chars() { match quote { Some(q) => { - if b == q { + if ch == q { quote = None; } else { - current.push(b as char); + current.push(ch); } } - None => match b { - b'"' | b'\'' => quote = Some(b), - b' ' | b'\t' => { + None => match ch { + '"' | '\'' => quote = Some(ch), + ' ' | '\t' => { if !current.is_empty() { tokens.push(current); current = String::new(); } } - _ => current.push(b as char), + _ => current.push(ch), }, } } From d0dbeb5e0a152ed84ac42e431838c6e989e9fcbd Mon Sep 17 00:00:00 2001 From: minto-dane Date: Fri, 10 Apr 2026 16:39:50 -0400 Subject: [PATCH 17/17] docs: update the hardening checklist and normalize repo metadata Mark the completed security tasks in todo.md, normalize a few formatting-only files, keep the README with a trailing newline, and add the repository-level .codex marker file that was present in the working tree. --- .codex | 0 README.md | 2 +- builders/mod.rs | 4 ++-- src/boot/loader.rs | 1 - todo.md | 8 ++++---- 5 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 .codex diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index e8294f1..83f9ea7 100644 --- a/README.md +++ b/README.md @@ -63,4 +63,4 @@ mochiOSはハイブリッドアーキテクチャを採用した、新しいOS
mochimochi-kun < みんなの貢献待ってるよ! -
\ No newline at end of file + diff --git a/builders/mod.rs b/builders/mod.rs index 82fd09f..7616663 100644 --- a/builders/mod.rs +++ b/builders/mod.rs @@ -1,14 +1,14 @@ pub mod apps; pub mod drivers; pub mod fs_image; -pub mod newlib; pub mod modules; +pub mod newlib; pub mod services; pub mod utils; pub use apps::{build_apps, build_utils}; pub use drivers::build_drivers; pub use fs_image::{copy_newlib_libs, create_ext2_image, create_initfs_image, setup_fs_layout}; -pub use newlib::{build_newlib, build_user_libs}; pub use modules::{build_module, default_modules}; +pub use newlib::{build_newlib, build_user_libs}; pub use services::{build_service, parse_service_index}; diff --git a/src/boot/loader.rs b/src/boot/loader.rs index 2eb3d22..29645b6 100644 --- a/src/boot/loader.rs +++ b/src/boot/loader.rs @@ -155,7 +155,6 @@ unsafe fn load_initfs( (0, 0) } - /// 指定ハンドルから任意ファイルをページ単位でロードし (物理アドレス, サイズ) を返す unsafe fn try_load_raw( bt: &BootServices, diff --git a/todo.md b/todo.md index fdf7e69..18980a9 100644 --- a/todo.md +++ b/todo.md @@ -45,7 +45,7 @@ - 悪性/暴走プロセスが単独でシステム全体を枯渇させられない #### 高優先 -- [ ] シグナル復帰アドレスを安全にする +- [x] シグナル復帰アドレスを安全にする - 対象: `src/core/syscall/signal.rs` - 内容: - `sa_restorer` を無検証で信頼しない @@ -53,7 +53,7 @@ - 完了条件: - 任意 `restorer` 指定で制御フローを奪えない -- [ ] ELFロード後の W^X を徹底する +- [x] ELFロード後の W^X を徹底する - 対象: `src/core/task/elf.rs`, `src/core/syscall/exec.rs`, `src/core/mem/paging.rs` - 内容: - ロード中のみRW、実行時は`PF_W`に応じて最終保護へ変更 @@ -61,7 +61,7 @@ - 完了条件: - 実行セグメントが常時writableにならない -- [ ] 例外経路のKPTI/SMAP適用を統一する +- [x] 例外経路のKPTI/SMAP適用を統一する - 対象: `src/core/interrupt/idt.rs`, `src/core/syscall/syscall_entry.rs`, `src/core/syscall/mod.rs` - 内容: - SYSCALL経路と同等に、例外/IRQでもCR3切替とユーザメモリアクセス制御を統一 @@ -76,7 +76,7 @@ - 完了条件: - 非許可主体からのIPCリクエストが拒否される -- [ ] `mprotect` を実装し、メモリ保護変更を本物にする +- [x] `mprotect` を実装し、メモリ保護変更を本物にする - 対象: `src/core/syscall/pgroup.rs`, `src/core/mem/paging.rs` - 内容: - 現在のスタブ (`SUCCESS`返却中心) を廃止