diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 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/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/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, 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/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" 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/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/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/cpu.rs b/src/core/cpu.rs index 7a28fd2..1eee46b 100644 --- a/src/core/cpu.rs +++ b/src/core/cpu.rs @@ -9,18 +9,33 @@ 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(()); -/// 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(); + report_optional_control_flow_features(); } } @@ -65,6 +80,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 +115,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 +151,62 @@ 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"); + } +} + +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 { @@ -134,6 +227,155 @@ 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 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; + 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 +396,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 +416,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 +501,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 +632,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/init/mod.rs b/src/core/init/mod.rs index 16995e5..b6821f2 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(); crate::kmod::load_modules(); 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 4a61887..4021737 100644 --- a/src/core/interrupt/timer.rs +++ b/src/core/interrupt/timer.rs @@ -14,8 +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; + 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 @@ -38,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/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; diff --git a/src/core/lib.rs b/src/core/lib.rs index 001a4ea..c15d001 100644 --- a/src/core/lib.rs +++ b/src/core/lib.rs @@ -6,11 +6,33 @@ #![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; /// エラー型定義 pub mod result; +/// 監査ログ +pub mod audit; + /// 割込み管理 pub mod interrupt; 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/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..13a0f1e 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 { @@ -1048,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); // データコピー先を既存フレームに切り替える @@ -1107,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 { @@ -1520,6 +1719,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/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); diff --git a/src/core/syscall/exec.rs b/src/core/syscall/exec.rs index cc5b100..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())); @@ -152,17 +190,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) { @@ -504,7 +544,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; @@ -614,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; @@ -830,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( @@ -1394,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"); @@ -1616,9 +1696,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; } @@ -1836,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; @@ -1920,14 +2004,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, @@ -1964,10 +2044,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, @@ -2016,10 +2095,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, @@ -2063,10 +2141,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); @@ -2078,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/fs.rs b/src/core/syscall/fs.rs index ff90b3e..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) { @@ -612,24 +636,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システムコール @@ -714,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, } } @@ -779,10 +792,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 { @@ -804,10 +816,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 } @@ -868,10 +879,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 } @@ -911,10 +924,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; } @@ -939,10 +951,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 } @@ -1191,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, } } @@ -1344,37 +1353,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/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/syscall/mod.rs b/src/core/syscall/mod.rs index a229243..4e14e29 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; /// システムコールのディスパッチ @@ -369,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", @@ -442,12 +404,52 @@ 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 実装 extern "C" fn syscall_handler_rust( num: u64, @@ -460,6 +462,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/syscall/pgroup.rs b/src/core/syscall/pgroup.rs index 49b1fdf..9d9f830 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, ENOMEM, ENOTSUP, EPERM, ESRCH, SUCCESS}; #[inline] fn current_pid() -> Option { @@ -133,26 +133,26 @@ 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, + 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) { 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, + 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 @@ -160,18 +160,16 @@ 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 設定は無視して成功 + TCSETS | TCSETSW | TCSETSF => ENOTSUP, _ => EINVAL, } } @@ -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; } @@ -261,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 システムコール(リソース上限を無限大で返す) @@ -278,10 +333,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/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/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..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 システムコール /// @@ -53,13 +55,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 & !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; + } } // 新アクションをカーネルに保存 @@ -67,21 +70,31 @@ 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, + }; + 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)); @@ -108,9 +121,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 +132,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 +268,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, @@ -320,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 @@ -360,19 +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; } - 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; SIGNAL_FRAME_SIZE as usize]; + frame[0..8].copy_from_slice(&restorer.to_ne_bytes()); + 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 システムコール @@ -383,19 +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, 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 frame_magic = match crate::syscall::read_user_u64(user_rsp) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + 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_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); @@ -403,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/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) /// /// 呼ばれた時点: 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, + } } /// フレームバッファ物理メモリを呼び出し元プロセスのアドレス空間にマップする 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/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)?; } } diff --git a/src/core/task/fd_table.rs b/src/core/task/fd_table.rs index 8bed372..fae1642 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 { 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/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/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) 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 { 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); 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), }, } } 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; 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, /// メモリブレーク 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`返却中心) を廃止