diff --git a/docs/docs/debug/check-mechanisms-summary.md b/docs/docs/debug/check-mechanisms-summary.md index 917b530d37..0a18511115 100644 --- a/docs/docs/debug/check-mechanisms-summary.md +++ b/docs/docs/debug/check-mechanisms-summary.md @@ -101,6 +101,26 @@ boot/current 栈,也不覆盖未来可能引入的独立 IRQ stack、exception 或 overflow stack。这个边界与 `plat-dyn` / 非 `plat-dyn` 无直接绑定: 两种平台模式下,动态任务栈都覆盖,borrowed 栈都暂不覆盖。 +Linux 的栈保护包含两层不同机制。`STACK_END_MAGIC` 用于检查任务栈底是否 +被覆盖,作用与当前 `stack-canary` 接近;`CONFIG_STACKPROTECTOR` / +`CONFIG_STACKPROTECTOR_STRONG` 则依赖编译器在函数栈帧中插入 canary, +函数返回前比较保存值和运行时 guard,失败时调用 `__stack_chk_fail()`。 +后者可以发现尚未一路覆盖到任务栈底的函数局部栈溢出,是当前机制尚未覆盖 +的方向。 + +项目后续可参照 Linux 分阶段增强栈帧级保护。第一阶段优先实现跨架构的 +全局 guard 方案:通过 opt-in hardening 开关在构建系统中注入 +`-Z stack-protector=strong`,并在内核运行时提供 `__stack_chk_guard` +和 `__stack_chk_fail()`。当前 nightly 对项目使用的 +`x86_64-unknown-none`、`riscv64gc-unknown-none-elf`、 +`aarch64-unknown-none-softfloat`、`loongarch64-unknown-none-softfloat` +四个目标都接受 `-Z stack-protector=strong`,生成对象也统一依赖 +`__stack_chk_guard` / `__stack_chk_fail`,因此全局 guard 方案可以作为 +四架构共同的最小闭环。第二阶段再评估 Linux 风格 per-task 或 per-cpu +guard:x86_64、riscv64、aarch64 可结合各自 percpu / thread pointer / +系统寄存器约定逐步设计;loongarch64 在 Linux 6.12 中也主要体现为全局 +`__stack_chk_guard` 路径,建议放在全局方案稳定后再单独评估。 + 平台栈边界需要按平台类型区分。静态平台可以使用 linker script 中的 `boot_stack` / `boot_stack_top` 符号作为主 CPU boot stack 的边界; `plat-dyn` 下这两个符号只是兼容占位,并不表示真实栈空间。`plat-dyn` @@ -123,6 +143,9 @@ boot/current 栈,也不覆盖未来可能引入的独立 IRQ stack、exception - 在更多边界点触发检查,例如任务退出、panic 前诊断或长时间运行的 idle 路径。 - 持续完善动态任务栈 guard page 的 SMP shootdown、跨架构 QEMU 回归和 fault 诊断。 +- 增加 opt-in 的编译器栈帧级 stack protector,先采用四架构通用的 + 全局 `__stack_chk_guard` / `__stack_chk_fail` 方案,再评估 per-task + 或 per-cpu guard。 - 后续在 `axmm` 上补 kernel vmap allocator,把 guard page 从额外物理页演进为仅占虚拟地址空间的空洞。 - 在 vmap-style 栈和 stack metadata 稳定后,再评估 borrowed boot/current 栈、secondary boot 栈以及专用 IRQ/exception/overflow 栈的 guard page 接入。 - 完善不同架构和不同平台栈布局的文档,明确 canary 写入位置和误报边界。 diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 186cadc556..7c1e8b5e8e 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -46,6 +46,7 @@ sg2002-wifi = [ "ax-feat/aic8800-wifi", ] stack-guard-page = ["ax-feat/stack-guard-page"] +stack-protector = ["ax-feat/stack-protector"] [dependencies] ax-feat = { workspace = true, features = [ diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index dfdac8aa4e..0d79f0ce2f 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -50,6 +50,7 @@ plat-dyn = ["dep:axplat-dyn", "starry-kernel/plat-dyn"] rknpu = ["ax-driver/rknpu", "starry-kernel/rknpu"] std-compat = ["starry-kernel/std-compat"] +stack-protector = ["ax-std/stack-protector", "starry-kernel/stack-protector"] vf2 = ["ax-hal/riscv64-visionfive2"] [[bin]] diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index ba588c937f..a44d842015 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -60,6 +60,7 @@ stack-guard-page = [ "ax-task/stack-guard-page", "ax-runtime/stack-guard-page", ] +stack-protector = ["ax-runtime/stack-protector"] # File system fs = [ diff --git a/os/arceos/api/axfeat/src/lib.rs b/os/arceos/api/axfeat/src/lib.rs index e32d3570e5..603b3e4fff 100644 --- a/os/arceos/api/axfeat/src/lib.rs +++ b/os/arceos/api/axfeat/src/lib.rs @@ -17,6 +17,7 @@ //! - `sched-fifo`: Use the FIFO cooperative scheduler. //! - `sched-rr`: Use the Round-robin preemptive scheduler. //! - `sched-cfs`: Use the Completely Fair Scheduler (CFS) preemptive scheduler. +//! - `stack-protector`: Enable compiler-inserted stack frame canary checks. //! - Upperlayer stacks (fs, net, display) //! - `fs`: Enable file system support. //! - `net`: Enable networking support. diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 0bb708c5c4..f56cc7c782 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -26,6 +26,7 @@ paging = ["ax-hal/paging", "dep:ax-mm", "dep:axklib"] rtc = ["ax-driver/rtc"] smp = ["ax-hal/smp", "ax-task?/smp"] stack-guard-page = ["multitask", "paging", "ax-task/stack-guard-page"] +stack-protector = [] tls = ["ax-hal/tls", "ax-task?/tls"] plat-dyn = [ diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 9810d0d861..994bb1c9a9 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -40,6 +40,12 @@ extern crate ax_driver as _; #[cfg(all(target_os = "none", not(feature = "std-compat"), not(test)))] mod lang_items; +#[cfg(all( + feature = "stack-protector", + any(target_os = "none", target_env = "musl"), + not(test) +))] +mod stack_protector; #[cfg(feature = "smp")] mod mp; diff --git a/os/arceos/modules/axruntime/src/stack_protector.rs b/os/arceos/modules/axruntime/src/stack_protector.rs new file mode 100644 index 0000000000..90159178ce --- /dev/null +++ b/os/arceos/modules/axruntime/src/stack_protector.rs @@ -0,0 +1,12 @@ +//! Runtime support for compiler-inserted stack protector checks. + +const STACK_CHK_GUARD: usize = 0x57AC_CE11_5A7A_CE11usize; + +#[unsafe(no_mangle)] +#[used] +pub static __stack_chk_guard: usize = STACK_CHK_GUARD; + +#[unsafe(no_mangle)] +pub extern "C" fn __stack_chk_fail() -> ! { + panic!("stack-protector: kernel stack is corrupted") +} diff --git a/os/arceos/ulib/axlibc/Cargo.toml b/os/arceos/ulib/axlibc/Cargo.toml index 8b61c0a0ec..c40a3b8c97 100644 --- a/os/arceos/ulib/axlibc/Cargo.toml +++ b/os/arceos/ulib/axlibc/Cargo.toml @@ -42,6 +42,7 @@ tls = ["alloc", "ax-feat/tls"] # Multi-task multitask = ["ax-feat/multitask", "ax-posix-api/multitask"] lockdep = ["ax-posix-api/lockdep", "ax-feat/lockdep"] +stack-protector = ["ax-feat/stack-protector"] # File system fs = ["ax-posix-api/fs", "fd"] diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index 5df5a08e01..07f2192f35 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -62,6 +62,7 @@ sched-fifo = ["ax-feat/sched-fifo"] sched-rr = ["ax-feat/sched-rr"] sched-cfs = ["ax-feat/sched-cfs"] stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] +stack-protector = ["ax-feat/stack-protector"] # File system fs = [ diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 76b680590e..b3786baf08 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -50,6 +50,7 @@ sdmmc = [ ] rockchip-pm = ["ax-driver/rockchip-pm"] serial = [] +stack-protector = ["ax-std/stack-protector"] [dependencies] spin = { workspace = true } diff --git a/scripts/axbuild/src/arceos/build.rs b/scripts/axbuild/src/arceos/build.rs index c110389ab6..e553b102ca 100644 --- a/scripts/axbuild/src/arceos/build.rs +++ b/scripts/axbuild/src/arceos/build.rs @@ -194,7 +194,7 @@ pub(crate) fn load_c_app_cargo_config(request: &ResolvedBuildRequest) -> anyhow: plat_dyn, metadata, ); - let rustflags = build::toolchain_rustflags(&build_info.env); + let rustflags = build::toolchain_rustflags_for_features(&build_info.env, &build_info.features); let args = ArceosBuildInfo::build_cargo_args(&request.target, &rustflags); build_info.prepare_log_env(); diff --git a/scripts/axbuild/src/axvisor/build.rs b/scripts/axbuild/src/axvisor/build.rs index 1d586a8994..6304146d0d 100644 --- a/scripts/axbuild/src/axvisor/build.rs +++ b/scripts/axbuild/src/axvisor/build.rs @@ -130,6 +130,13 @@ fn to_cargo_config( metadata: &cargo_metadata::Metadata, ) -> anyhow::Result { config.target = request.target.clone(); + let makefile_features = crate::build::makefile_features_from_env(); + crate::build::apply_makefile_features_with_metadata( + &mut config.build_info, + &request.package, + &makefile_features, + metadata, + ); let known_platforms = platform_feature_names(metadata); reject_unsupported_nested_platform_features(&config.build_info.features, &known_platforms)?; let plat_dyn = config @@ -571,12 +578,48 @@ fn load_build_config(request: &ResolvedAxvisorRequest) -> anyhow::Result> = LazyLock::new(|| Mutex::new(())); + + struct TempEnvVar { + key: &'static str, + original: Option, + } + + impl TempEnvVar { + fn set(key: &'static str, value: impl AsRef) -> Self { + let original = env::var_os(key); + unsafe { + env::set_var(key, value); + } + Self { key, original } + } + } + + impl Drop for TempEnvVar { + fn drop(&mut self) { + match self.original.as_ref() { + Some(value) => unsafe { + env::set_var(self.key, value); + }, + None => unsafe { + env::remove_var(self.key); + }, + } + } + } + fn write_board(axvisor_dir: &Path, name: &str, body: &str) -> PathBuf { let path = axvisor_dir .join("configs/board") @@ -1095,6 +1138,45 @@ log = "Info" assert!(!cargo.features.contains(&"ax-hal/x86-pc".to_string())); } + #[test] + fn load_cargo_config_applies_stack_protector_from_makefile_features() { + let _env_lock = ENV_LOCK.lock().unwrap(); + let _features = TempEnvVar::set("FEATURES", "stack-protector"); + let root = tempdir().unwrap(); + let config_path = root.path().join(".build.toml"); + fs::write( + &config_path, + r#" +features = ["ept-level-4", "fs", "vmx"] +log = "Info" +"#, + ) + .unwrap(); + + let cargo = load_cargo_config(&ResolvedAxvisorRequest { + package: AXVISOR_PACKAGE.to_string(), + axvisor_dir: root.path().join("os/axvisor"), + arch: "x86_64".to_string(), + target: "x86_64-unknown-none".to_string(), + plat_dyn: None, + smp: None, + debug: false, + build_info_path: config_path, + qemu_config: None, + uboot_config: None, + vmconfigs: vec![], + }) + .unwrap(); + + assert!( + cargo + .features + .contains(&"ax-std/stack-protector".to_string()) + ); + let config = fs::read_to_string(cargo.extra_config.unwrap()).unwrap(); + assert!(config.contains(r#""-Zstack-protector=strong""#)); + } + #[test] fn load_cargo_config_keeps_loongarch_axvisor_as_elf() { let root = tempdir().unwrap(); diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index e354f0bb39..76d762e1e8 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -46,6 +46,29 @@ pub(crate) fn toolchain_rustflags(env: &HashMap) -> Vec flags } +fn features_enable_stack_protector(features: &[String]) -> bool { + features.iter().any(|feature| { + matches!( + feature.as_str(), + "stack-protector" + | "ax-std/stack-protector" + | "ax-feat/stack-protector" + | "starry-kernel/stack-protector" + ) + }) +} + +pub(crate) fn toolchain_rustflags_for_features( + env: &HashMap, + features: &[String], +) -> Vec { + let mut flags = toolchain_rustflags(env); + if features_enable_stack_protector(features) { + flags.push("-Zstack-protector=strong".to_string()); + } + flags +} + /// Whether the build config enables target backtrace support (frame pointers / unwind). /// /// Matches [`toolchain_rustflags`]: `BACKTRACE=y` or `DWARF=y` in `[env]`. @@ -225,7 +248,7 @@ impl BuildInfo { .display() .to_string(), ); - let rustflags = toolchain_rustflags(&cargo.env); + let rustflags = toolchain_rustflags_for_features(&cargo.env, &cargo.features); cargo.extra_config = Some( std_cargo_config_path(&std_target.target_name, &wrapper, plat_dyn, &rustflags)? .display() @@ -1295,6 +1318,7 @@ fn is_known_axstd_feature(feature: &str) -> bool { | "sched-rr" | "sched-cfs" | "stack-guard-page" + | "stack-protector" | "fs" | "ext4fs" | "fatfs" @@ -2098,6 +2122,36 @@ log = "Info" ); } + #[test] + fn toolchain_rustflags_enable_stack_protector_from_features() { + let env = HashMap::from([("BACKTRACE".to_string(), "y".to_string())]); + let features = vec!["ax-std/stack-protector".to_string()]; + + assert_eq!( + toolchain_rustflags_for_features(&env, &features), + vec![ + "-Cforce-frame-pointers=yes".to_string(), + "-Zstack-protector=strong".to_string(), + ] + ); + } + + #[test] + fn stack_protector_feature_detection_accepts_supported_surfaces() { + for feature in [ + "stack-protector", + "ax-std/stack-protector", + "ax-feat/stack-protector", + "starry-kernel/stack-protector", + ] { + assert!(features_enable_stack_protector(&[feature.to_string()])); + } + + assert!(!features_enable_stack_protector(&[ + "stack-guard-page".to_string() + ])); + } + #[test] fn std_build_nested_features_are_passed_through_not_enabled_on_app() { let mut envs = HashMap::new(); @@ -2552,6 +2606,32 @@ log = "Info" assert!(config.contains(r#""-Cforce-frame-pointers=yes""#)); } + #[test] + fn std_build_config_enables_stack_protector_from_feature() { + let metadata = repo_metadata(); + let info = BuildInfo { + features: vec!["stack-protector".to_string()], + ..BuildInfo::default() + }; + + let cargo = info + .into_prepared_base_cargo_config_with_metadata( + "arceos-helloworld", + "x86_64-unknown-none", + None, + &metadata, + ) + .unwrap(); + + assert!( + cargo + .features + .contains(&"ax-std/stack-protector".to_string()) + ); + let config = std::fs::read_to_string(cargo.extra_config.unwrap()).unwrap(); + assert!(config.contains(r#""-Zstack-protector=strong""#)); + } + #[test] fn std_build_target_maps_arceos_targets_to_linux_musl_by_link_mode() { let cases = [