feat: debug kernel stack (#1176)

* feat: debug kernel stack

Signed-off-by: Godones <chenlinfeng25@outlook.com>

* fix: Create guard pages for the kstack through remap

Signed-off-by: Godones <chenlinfeng25@outlook.com>

* feat(arch/x86_64): 启用内核态写保护功能

添加enable_kernel_wp函数来设置CR0的WP位,防止内核错误写入只读页面

Signed-off-by: longjin <longjin@DragonOS.org>

* fix(x86_64/mm): 在内核地址错误处理中添加RIP寄存器信息

Signed-off-by: longjin <longjin@DragonOS.org>

* fix: Fixed the error introduced by enabling WP flag on x86

Restore accidentally deleted functions.

Signed-off-by: Godones <chenlinfeng25@outlook.com>

* refactor: 移除kstack_protect默认特性并优化内存管理

- 从default特性中移除kstack_protect
- 为X86_64MMBootstrapInfo添加kernel_rodata_start字段
- 调整内核页标志对rodata区域的判断逻辑

Signed-off-by: longjin <longjin@DragonOS.org>

* fix(mm): 解决加载二进制文件到用户空间的时候,忘记关闭wp的问题

Signed-off-by: longjin <longjin@DragonOS.org>

* fix

Signed-off-by: longjin <longjin@DragonOS.org>

---------

Signed-off-by: Godones <chenlinfeng25@outlook.com>
Signed-off-by: longjin <longjin@DragonOS.org>
Co-authored-by: longjin <longjin@DragonOS.org>
This commit is contained in:
linfeng 2025-06-14 20:42:58 +08:00 committed by GitHub
parent bb79f7fe3c
commit 799e573259
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 293 additions and 72 deletions

View File

@ -25,6 +25,9 @@ driver_ps2_mouse = []
kprobe_test = []
static_keys_test = []
# kstack_protect 开启该功能后,会开启内核栈保护功能。用于辅助检测栈溢出。(内核栈占用会*2)
kstack_protect = []
# 运行时依赖项
[dependencies]
acpi = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git", rev = "282df2af7b" }
@ -79,7 +82,9 @@ static-keys = { version = "=0.7" }
defer = "0.2.1"
cfg-if = { version = "1.0.0" }
derive_builder = { version = "0.20.2", default-features = false, features = ["alloc"] }
derive_builder = { version = "0.20.2", default-features = false, features = [
"alloc",
] }
# target为x86_64时使用下面的依赖

View File

@ -137,6 +137,10 @@ impl MemoryManagementArch for LoongArch64MMArch {
fn make_entry(paddr: crate::mm::PhysAddr, page_flags: usize) -> usize {
todo!()
}
fn enable_kernel_wp() {}
fn disable_kernel_wp() {}
}
/// 获取内核地址默认的页面标志

View File

@ -284,6 +284,10 @@ impl MemoryManagementArch for RiscV64MMArch {
const PAGE_READONLY_EXEC: usize = 0;
const PROTECTION_MAP: [EntryFlags<MMArch>; 16] = protection_map();
fn enable_kernel_wp() {}
fn disable_kernel_wp() {}
}
const fn protection_map() -> [EntryFlags<MMArch>; 16] {

View File

@ -149,18 +149,37 @@ impl X86_64MMArch {
/// - `error_code`: 错误标志
/// - `address`: 发生缺页异常的虚拟地址
pub fn do_kern_addr_fault(
_regs: &'static TrapFrame,
regs: &'static TrapFrame,
error_code: X86PfErrorCode,
address: VirtAddr,
) {
unsafe { crate::debug::traceback::lookup_kallsyms(regs.rip, 0xff) };
let pcb = crate::process::ProcessManager::current_pcb();
let kstack_guard_addr = pcb.kernel_stack().guard_page_address();
if let Some(guard_page) = kstack_guard_addr {
let guard_page_size = pcb.kernel_stack().guard_page_size().unwrap();
if address.data() >= guard_page.data()
&& address.data() < guard_page.data() + guard_page_size
{
// 发生在内核栈保护页上
error!(
"kernel stack guard page fault at {:#x}, guard page range: {:#x} - {:#x}",
address.data(),
guard_page.data(),
guard_page.data() + guard_page_size
);
}
}
panic!(
"do_kern_addr_fault has not yet been implemented,
fault address: {:#x},
fault address: {:#x},
rip: {:#x},
error_code: {:#b},
pid: {}\n",
address.data(),
regs.rip,
error_code,
crate::process::ProcessManager::current_pid().data()
pcb.pid().data()
);
//TODO https://code.dragonos.org.cn/xref/linux-6.6.21/arch/x86/mm/fault.c#do_kern_addr_fault
}

View File

@ -42,12 +42,14 @@ static mut INITIAL_CR3_VALUE: PhysAddr = PhysAddr::new(0);
static INNER_ALLOCATOR: SpinLock<Option<BuddyAllocator<MMArch>>> = SpinLock::new(None);
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub struct X86_64MMBootstrapInfo {
kernel_load_base_paddr: usize,
kernel_code_start: usize,
kernel_code_end: usize,
kernel_data_end: usize,
kernel_rodata_start: usize,
kernel_rodata_end: usize,
start_brk: usize,
}
@ -134,6 +136,7 @@ impl MemoryManagementArch for X86_64MMArch {
fn _text();
fn _etext();
fn _edata();
fn _rodata();
fn _erodata();
fn _end();
fn _default_kernel_load_base();
@ -146,6 +149,7 @@ impl MemoryManagementArch for X86_64MMArch {
kernel_code_start: _text as usize,
kernel_code_end: _etext as usize,
kernel_data_end: _edata as usize,
kernel_rodata_start: _rodata as usize,
kernel_rodata_end: _erodata as usize,
start_brk: _end as usize,
};
@ -158,15 +162,14 @@ impl MemoryManagementArch for X86_64MMArch {
boot_callbacks()
.early_init_memory_blocks()
.expect("init memory area failed");
debug!("bootstrap info: {:?}", unsafe { BOOTSTRAP_MM_INFO });
debug!("bootstrap info: {:#x?}", unsafe { BOOTSTRAP_MM_INFO });
debug!("phys[0]=virt[0x{:x}]", unsafe {
MMArch::phys_2_virt(PhysAddr::new(0)).unwrap().data()
});
// 初始化内存管理器
unsafe { allocator_init() };
Self::enable_kernel_wp();
send_to_default_serial8250_port("x86 64 mm init done\n\0".as_bytes());
}
@ -366,10 +369,35 @@ impl MemoryManagementArch for X86_64MMArch {
const PAGE_WRITE: usize = 0;
const PAGE_WRITE_EXEC: usize = 0;
const PAGE_EXEC: usize = 0;
/// 启用 内核态的 Write Protect
/// 这样即使在内核态CPU也会检查页面的写保护位
/// 防止内核错误地写入只读页面
fn enable_kernel_wp() {
unsafe {
use x86::controlregs::{cr0, cr0_write, Cr0};
let mut cr0_val = cr0();
cr0_val.insert(Cr0::CR0_WRITE_PROTECT);
cr0_write(cr0_val);
// log::debug!("CR0.WP bit enabled for kernel write protection");
}
}
/// 禁用 内核态的 Write Protect
fn disable_kernel_wp() {
unsafe {
use x86::controlregs::{cr0, cr0_write, Cr0};
let mut cr0_val = cr0();
cr0_val.remove(Cr0::CR0_WRITE_PROTECT);
cr0_write(cr0_val);
// log::debug!("CR0.WP bit disabled for kernel write protection");
}
}
}
/// 获取保护标志的映射表
///
/// 参考: https://code.dragonos.org.cn/xref/linux-6.6.21/arch/x86/mm/pgprot.c#8
///
/// ## 返回值
/// - `[usize; 16]`: 长度为16的映射表
@ -681,7 +709,7 @@ pub unsafe fn kernel_page_flags<A: MemoryManagementArch>(virt: VirtAddr) -> Entr
if virt.data() >= info.kernel_code_start && virt.data() < info.kernel_code_end {
// Remap kernel code execute
return EntryFlags::new().set_execute(true).set_write(true);
} else if virt.data() >= info.kernel_data_end && virt.data() < info.kernel_rodata_end {
} else if virt.data() >= info.kernel_rodata_start && virt.data() < info.kernel_rodata_end {
// Remap kernel rodata read only
return EntryFlags::new().set_execute(true);
} else {

View File

@ -347,6 +347,7 @@ impl KernelCmdlineManager {
log::warn!("cmdline: parameter {} is set twice", p.name);
continue;
}
p.value = Some(CString::new(value.unwrap()).unwrap());
p.initialized = true;
}

View File

@ -220,7 +220,10 @@ impl ElfLoader {
map_flags: &MapFlags,
total_size: usize,
) -> Result<(VirtAddr, bool), SystemError> {
// debug!("load_elf_segment: addr_to_map={:?}", addr_to_map);
// log::debug!("load_elf_segment: addr_to_map={:?}", addr_to_map);
// defer!({
// log::debug!("load_elf_segment done");
// });
// 映射位置的偏移量(页内偏移)
let beginning_page_offset = Self::elf_page_offset(addr_to_map);
@ -343,6 +346,9 @@ impl ElfLoader {
load_bias: usize,
) -> Result<BinaryLoaderResult, ExecError> {
// log::debug!("loading elf interp");
// defer!({
// log::debug!("load_elf_interp done");
// });
let mut head_buf = [0u8; 512];
interp_elf_ex
.file_mut()

View File

@ -653,50 +653,8 @@ impl MMIOSpaceGuard {
/// 传入的物理地址【一定要是设备的物理地址】。
/// 如果物理地址是从内存分配器中分配的那么会造成内存泄露。因为mmio_release的时候只取消映射不会释放内存。
pub unsafe fn map_phys(&self, paddr: PhysAddr, length: usize) -> Result<(), SystemError> {
if length > self.size {
return Err(SystemError::EINVAL);
}
let check = self
.mapped
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst);
if check.is_err() {
return Err(SystemError::EINVAL);
}
let flags = EntryFlags::mmio_flags();
let mut kernel_mapper = KernelMapper::lock();
let r = kernel_mapper.map_phys_with_size(self.vaddr, paddr, length, flags, true);
return r;
}
/// 将物理地址填写到虚拟地址空间中
///
/// ## Safety
///
/// 传入的物理地址【一定要是设备的物理地址】。
/// 如果物理地址是从内存分配器中分配的那么会造成内存泄露。因为mmio_release的时候只取消映射不会释放内存。
pub unsafe fn map_phys_with_flags(
&self,
paddr: PhysAddr,
length: usize,
flags: EntryFlags<MMArch>,
) -> Result<(), SystemError> {
if length > self.size {
return Err(SystemError::EINVAL);
}
let check = self
.mapped
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst);
if check.is_err() {
return Err(SystemError::EINVAL);
}
let mut kernel_mapper = KernelMapper::lock();
let r = kernel_mapper.map_phys_with_size(self.vaddr, paddr, length, flags, true);
return r;
self.map_phys_with_flags(paddr, length, flags)
}
/// # map_any_phys - 将任意物理地址映射到虚拟地址
@ -734,6 +692,33 @@ impl MMIOSpaceGuard {
return Ok(vaddr);
}
/// 将物理地址填写到虚拟地址空间中
///
/// ## Safety
///
/// 传入的物理地址【一定要是设备的物理地址】。
/// 如果物理地址是从内存分配器中分配的那么会造成内存泄露。因为mmio_release的时候只取消映射不会释放内存。
pub unsafe fn map_phys_with_flags(
&self,
paddr: PhysAddr,
length: usize,
flags: EntryFlags<MMArch>,
) -> Result<(), SystemError> {
if length > self.size {
return Err(SystemError::EINVAL);
}
let check = self
.mapped
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst);
if check.is_err() {
return Err(SystemError::EINVAL);
}
let mut kernel_mapper = KernelMapper::lock();
kernel_mapper.map_phys_with_size(self.vaddr, paddr, length, flags, true)
}
/// 泄露一个MMIO space guard不会释放映射的空间
pub unsafe fn leak(self) {
core::mem::forget(self);

View File

@ -694,6 +694,14 @@ pub trait MemoryManagementArch: Clone + Copy + Debug {
}
ret
}
/// 启用 内核态的 Write Protect
/// 这样即使在内核态CPU也会检查页面的写保护位
/// 防止内核错误地写入只读页面
fn enable_kernel_wp();
/// 禁用 内核态的 Write Protect
fn disable_kernel_wp();
}
/// @brief 虚拟地址范围

View File

@ -26,7 +26,7 @@ pub fn do_execve(
}
})?;
// debug!("load binary file done");
// log::debug!("load binary file done");
// debug!("argv: {:?}, envp: {:?}", argv, envp);
param.init_info_mut().args = argv;
param.init_info_mut().envs = envp;

View File

@ -54,7 +54,7 @@ use crate::{
percpu::{PerCpu, PerCpuVar},
set_IDLE_PROCESS_ADDRESS_SPACE,
ucontext::AddressSpace,
VirtAddr,
PhysAddr, VirtAddr,
},
namespaces::{mnt_namespace::FsStruct, pid_namespace::PidStrcut, NsProxy},
net::socket::SocketInode,
@ -1596,24 +1596,143 @@ impl ProcessSchedulerInfo {
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct KernelStack {
stack: Option<AlignedBox<[u8; KernelStack::SIZE], { KernelStack::ALIGN }>>,
/// 标记该内核栈是否可以被释放
can_be_freed: bool,
ty: KernelStackType,
}
#[derive(Debug)]
pub enum KernelStackType {
KernelSpace(VirtAddr, PhysAddr),
Static,
Dynamic,
}
// 为什么需要这个锁?
// alloc_from_kernel_space 使用该函数分配内核栈时,如果该函数被中断打断,
// 而切换的任务使用dealloc_from_kernel_space回收内核栈
// KernelMapper的可变引用获取将会失败造成错误
static KSTACK_LOCK: SpinLock<()> = SpinLock::new(());
unsafe fn alloc_from_kernel_space() -> (VirtAddr, PhysAddr) {
use crate::arch::MMArch;
use crate::mm::allocator::page_frame::{allocate_page_frames, PageFrameCount};
use crate::mm::kernel_mapper::KernelMapper;
use crate::mm::page::EntryFlags;
use crate::mm::MemoryManagementArch;
// Layout
// ---------------
// | KernelStack |
// | guard page | size == KernelStack::SIZE
// | KernelStack |
// | guard page |
// | .......... |
// ---------------
let _guard = KSTACK_LOCK.try_lock_irqsave().unwrap();
let need_size = KernelStack::SIZE * 2;
let page_num = PageFrameCount::new(need_size.div_ceil(MMArch::PAGE_SIZE).next_power_of_two());
let (paddr, _count) = allocate_page_frames(page_num).expect("kernel stack alloc failed");
let guard_vaddr = MMArch::phys_2_virt(paddr).unwrap();
let _kstack_paddr = paddr + KernelStack::SIZE;
let kstack_vaddr = guard_vaddr + KernelStack::SIZE;
core::ptr::write_bytes(kstack_vaddr.data() as *mut u8, 0, KernelStack::SIZE);
let guard_flags = EntryFlags::new();
let mut kernel_mapper = KernelMapper::lock();
let kernel_mapper = kernel_mapper.as_mut().unwrap();
for i in 0..KernelStack::SIZE / MMArch::PAGE_SIZE {
let guard_page_vaddr = guard_vaddr + i * MMArch::PAGE_SIZE;
// Map the guard page
let flusher = kernel_mapper.remap(guard_page_vaddr, guard_flags).unwrap();
flusher.flush();
}
// unsafe {
// log::debug!(
// "trigger kernel stack guard page :{:#x}",
// (kstack_vaddr.data() - 8)
// );
// let guard_ptr = (kstack_vaddr.data() - 8) as *mut usize;
// guard_ptr.write(0xfff); // Invalid
// }
// log::info!(
// "[kernel stack alloc]: virt: {:#x}, phy: {:#x}",
// kstack_vaddr.data(),
// _kstack_paddr.data()
// );
(guard_vaddr, paddr)
}
unsafe fn dealloc_from_kernel_space(vaddr: VirtAddr, paddr: PhysAddr) {
use crate::arch::mm::kernel_page_flags;
use crate::arch::MMArch;
use crate::mm::allocator::page_frame::{deallocate_page_frames, PageFrameCount, PhysPageFrame};
use crate::mm::kernel_mapper::KernelMapper;
use crate::mm::MemoryManagementArch;
let _guard = KSTACK_LOCK.try_lock_irqsave().unwrap();
let need_size = KernelStack::SIZE * 2;
let page_num = PageFrameCount::new(need_size.div_ceil(MMArch::PAGE_SIZE).next_power_of_two());
// log::info!(
// "[kernel stack dealloc]: virt: {:#x}, phy: {:#x}",
// vaddr.data(),
// paddr.data()
// );
let mut kernel_mapper = KernelMapper::lock();
let kernel_mapper = kernel_mapper.as_mut().unwrap();
// restore the guard page flags
for i in 0..KernelStack::SIZE / MMArch::PAGE_SIZE {
let guard_page_vaddr = vaddr + i * MMArch::PAGE_SIZE;
let flusher = kernel_mapper
.remap(guard_page_vaddr, kernel_page_flags(vaddr))
.unwrap();
flusher.flush();
}
// release the physical page
unsafe { deallocate_page_frames(PhysPageFrame::new(paddr), page_num) };
}
impl KernelStack {
pub const SIZE: usize = 0x8000;
pub const ALIGN: usize = 0x8000;
pub const SIZE: usize = 0x4000;
pub const ALIGN: usize = 0x4000;
pub fn new() -> Result<Self, SystemError> {
return Ok(Self {
stack: Some(
AlignedBox::<[u8; KernelStack::SIZE], { KernelStack::ALIGN }>::new_zeroed()?,
),
can_be_freed: true,
});
if cfg!(feature = "kstack_protect") {
unsafe {
let (kstack_vaddr, kstack_paddr) = alloc_from_kernel_space();
let real_kstack_vaddr = kstack_vaddr + KernelStack::SIZE;
Ok(Self {
stack: Some(
AlignedBox::<[u8; KernelStack::SIZE], { KernelStack::ALIGN }>::new_unchecked(
real_kstack_vaddr.data() as *mut [u8; KernelStack::SIZE],
),
),
ty: KernelStackType::KernelSpace(kstack_vaddr, kstack_paddr),
})
}
} else {
Ok(Self {
stack: Some(
AlignedBox::<[u8; KernelStack::SIZE], { KernelStack::ALIGN }>::new_zeroed()?,
),
ty: KernelStackType::Dynamic,
})
}
}
/// 根据已有的空间,构造一个内核栈结构体
@ -1624,14 +1743,38 @@ impl KernelStack {
return Err(SystemError::EFAULT);
}
return Ok(Self {
Ok(Self {
stack: Some(
AlignedBox::<[u8; KernelStack::SIZE], { KernelStack::ALIGN }>::new_unchecked(
base.data() as *mut [u8; KernelStack::SIZE],
),
),
can_be_freed: false,
});
ty: KernelStackType::Static,
})
}
pub fn guard_page_address(&self) -> Option<VirtAddr> {
match self.ty {
KernelStackType::KernelSpace(kstack_virt_addr, _) => {
return Some(kstack_virt_addr);
}
_ => {
// 静态内核栈和动态内核栈没有guard page
return None;
}
}
}
pub fn guard_page_size(&self) -> Option<usize> {
match self.ty {
KernelStackType::KernelSpace(_, _) => {
return Some(KernelStack::SIZE);
}
_ => {
// 静态内核栈和动态内核栈没有guard page
return None;
}
}
}
/// 返回内核栈的起始虚拟地址(低地址)
@ -1708,10 +1851,20 @@ impl Drop for KernelStack {
drop(pcb_ptr);
}
}
// 如果该内核栈不可以被释放那么这里就forget不调用AlignedBox的drop函数
if !self.can_be_freed {
let bx = self.stack.take();
core::mem::forget(bx);
match self.ty {
KernelStackType::KernelSpace(kstack_virt_addr, kstack_phy_addr) => {
// 释放内核栈
unsafe {
dealloc_from_kernel_space(kstack_virt_addr, kstack_phy_addr);
}
let bx = self.stack.take();
core::mem::forget(bx);
}
KernelStackType::Static => {
let bx = self.stack.take();
core::mem::forget(bx);
}
KernelStackType::Dynamic => {}
}
}
}

View File

@ -7,8 +7,12 @@ use core::{
};
use alloc::{ffi::CString, vec::Vec};
use defer::defer;
use crate::mm::{verify_area, VirtAddr};
use crate::{
arch::MMArch,
mm::{verify_area, MemoryManagementArch, VirtAddr},
};
use super::SystemError;
@ -37,6 +41,10 @@ pub unsafe fn clear_user(dest: VirtAddr, len: usize) -> Result<usize, SystemErro
pub unsafe fn copy_to_user(dest: VirtAddr, src: &[u8]) -> Result<usize, SystemError> {
verify_area(dest, src.len()).map_err(|_| SystemError::EFAULT)?;
MMArch::disable_kernel_wp();
defer!({
MMArch::enable_kernel_wp();
});
let p = dest.data() as *mut u8;
// 拷贝数据