fix(kernel): 分裂 2MB 大页使 .rodata 真正只读,加页表与格式串校验诊断并禁用 App 自动启动

This commit is contained in:
TermiNexus committed 2026-08-30 07:27:45 +08:00
1 parent efa99a8c4b
commit 2a03330a9d
4 files changed
+110 -73

No files matched your search

+31 -18
View File
@@ -124,6 +124,15 @@ def isrCommonhandler(interrupt: t.CInt):
buf: t.CArray[t.CChar, 100]
viperlib.snprintf(c.Addr(buf), 100, "[PF] cr2=0x%lx rip=0x%lx err=0x%lx rsp=0x%lx\n", cr2, fault_rip, fault_err, fault_rsp)
serial.puts(buf)
# [CR3] 当前 pml4:定位栈区/对象写缺页是否因 pml4 被 switch_pid 切走
# (内核 pml4=0xf801000,用户 pml4=0x9781xxx —— 若显示用户 pml4 而 rsp
# 在 bootloader 栈区 0x60000-0x80000,则是进程切换后未切回内核页表)。
cur_cr3: t.CUInt64T = 0
c.Asm(f"mov {c.AsmOut(cur_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3",
op=[t.ASM_DESCR.CLOBBER_RAX])
cr3_buf: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(cr3_buf), 80, "[CR3] pml4=0x%lx\n", cur_cr3)
serial.puts(cr3_buf)
# [RAX] 崩溃时 rax(isr.s 保存):定位 cr2 与 rax 关系(rax-偏移=cr2)
f_rax: t.CUInt64T = 0
c.Asm(f"""mov {c.AsmOut(f_rax, t.ASM_DESCR.OUTPUT_REG)}, [_fault_rax]""",
@@ -132,34 +141,38 @@ def isrCommonhandler(interrupt: t.CInt):
viperlib.snprintf(c.Addr(rax_buf), 80, "[RAX] rax=0x%lx\n", f_rax)
serial.puts(rax_buf)
# [STK] rbp 链回溯(定位栈损坏源:崩溃帧 + 4 级调用链的返回地址)
# 先读崩溃 rbp 判断(用户 rbp 不回溯,避免读用户地址嵌套 #PF)
fr0: t.CUInt64T = 0
fr1: t.CUInt64T = 0
fr2: t.CUInt64T = 0
fr3: t.CUInt64T = 0
fr4: t.CUInt64T = 0
c.Asm(f"""mov rax, [_fault_rbp]
mov {c.AsmOut(fr0, t.ASM_DESCR.OUTPUT_REG)}, rax
mov rax, [rax + 8]
mov {c.AsmOut(fr1, t.ASM_DESCR.OUTPUT_REG)}, rax
mov rax, [_fault_rbp]
mov rax, [rax]
mov {c.AsmOut(fr2, t.ASM_DESCR.OUTPUT_REG)}, rax
mov rax, [rax + 8]
mov {c.AsmOut(fr3, t.ASM_DESCR.OUTPUT_REG)}, rax
mov rax, [_fault_rbp]
mov rax, [rax]
mov rax, [rax]
mov rax, [rax + 8]
mov {c.AsmOut(fr4, t.ASM_DESCR.OUTPUT_REG)}, rax""",
mov {c.AsmOut(fr0, t.ASM_DESCR.OUTPUT_REG)}, rax""",
op=[t.ASM_DESCR.CLOBBER_RAX])
stk_buf: t.CArray[t.CChar, 160]
if fr0 >= 0x80000000:
# 用户态 rbp(如 0x7fffff...):回溯会读用户栈地址 → 嵌套 #PF 死循环
viperlib.snprintf(c.Addr(stk_buf), 160, "[STK] user rbp=0x%lx, chain skipped\n", fr0)
serial.puts(stk_buf)
else:
fr1: t.CUInt64T = 0
fr2: t.CUInt64T = 0
fr3: t.CUInt64T = 0
fr4: t.CUInt64T = 0
c.Asm(f"""mov rax, [_fault_rbp]
mov rax, [rax + 8]
mov {c.AsmOut(fr1, t.ASM_DESCR.OUTPUT_REG)}, rax
mov rax, [_fault_rbp]
mov rax, [rax]
mov {c.AsmOut(fr2, t.ASM_DESCR.OUTPUT_REG)}, rax
mov rax, [rax + 8]
mov {c.AsmOut(fr3, t.ASM_DESCR.OUTPUT_REG)}, rax
mov rax, [_fault_rbp]
mov rax, [rax]
mov rax, [rax]
mov rax, [rax + 8]
mov {c.AsmOut(fr4, t.ASM_DESCR.OUTPUT_REG)}, rax""",
op=[t.ASM_DESCR.CLOBBER_RAX])
viperlib.snprintf(c.Addr(stk_buf), 160, "[STK] rbp=0x%lx ret: 0x%lx 0x%lx 0x%lx 0x%lx\n",
fr0, fr1, fr2, fr3, fr4)
serial.puts(stk_buf)
serial.puts(stk_buf)
elif interrupt == 13:
gp_rip: t.CUInt64T = 0
gp_err: t.CUInt64T = 0
+8 -53
View File
@@ -179,7 +179,11 @@ class ViperKernel:
serial.puts("[main] paging and mm init done\n")
# 把 .rodata(字符串常量/vtable)映射只读 + CR0.WP=1:
# 越界写立即 #PF 暴露 cr2/rip(此前静默破坏格式串/路径/符号名 → P2/P3/P4/P5 连锁)
paging.ProtectKernelRO()
paging.ProtectKernelRO()
# [PG] 页表诊断:确认 .rodata 是否真正只读(2MB 大页会令 MakeRangeRO 失效)
paging.DumpMapping(0x100000, "kern")
paging.DumpMapping(0x60000, "bstk")
paging.DumpMapping(0x80000000, "fb")
proc.ProcessManager()
serial.puts("[main] process manager init done\n")
@@ -237,59 +241,10 @@ class ViperKernel:
else:
serial.puts("[main] APPS dir not found\n")
# [DIAG] app 自动启动已禁用:不 spawn 任何 .elf,不加载任何 .so,
# 专注排查内核自身的概率性崩溃(sched 启动 / desktop 启动后 30s)。
sched.Scheduler.disable()
hello_pid: t.CInt = elf.spawn_elf("/APPS/hello.elf")
if hello_pid < 0:
serial.puts("[main] Hello spawn failed\n")
else:
slog: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(slog), 64, "[main] Hello spawned as pid=%d\n", hello_pid)
serial.puts(slog)
term_pid: t.CInt = elf.spawn_elf("/APPS/terminal.elf")
# [DIAG] spawn_elf 返回 pid=1 但 main 收到 0 —— 跨模块返回值间歇性丢失
_dbtp: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(_dbtp), 64, "[main] term_pid=%d\n", term_pid)
serial.puts(c.Addr(_dbtp))
if term_pid < 0:
serial.puts("[main] Terminal spawn failed\n")
else:
tlog: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(tlog), 64, "[main] Terminal spawned as pid=%d\n", term_pid)
serial.puts(tlog)
garg_pid: t.CInt = elf.spawn_elf("/APPS/gargantua.elf")
if garg_pid < 0:
serial.puts("[main] Gargantua spawn failed\n")
else:
glog: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(glog), 64, "[main] Gargantua spawned as pid=%d\n", garg_pid)
serial.puts(glog)
s3d_pid: t.CInt = elf.spawn_elf("/APPS/scene3d.elf")
if s3d_pid < 0:
serial.puts("[main] Scene3D spawn failed\n")
else:
s3d_log: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(s3d_log), 64, "[main] Scene3D spawned as pid=%d\n", s3d_pid)
serial.puts(s3d_log)
so_base: t.CVoid | t.CPtr = elf.load_so("/LIBS/SLOG.SO")
if so_base is not None:
serial.puts("[main] slog.so loaded, calling log_info...\n")
log_info_fn: t.CVoid | t.CPtr = elf.get_so_symbol(so_base, "log_info", "/LIBS/SLOG.SO")
if log_info_fn is not None:
msg: t.CConst | t.CChar | t.CPtr = "Hello from shared library!"
c.Asm(f"""mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(log_info_fn, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
else:
serial.puts("[main] log_info symbol not found\n")
else:
serial.puts("[main] failed to load slog.so\n")
serial.puts("[main] app autostart disabled\n")
sched.Scheduler.enable()
# FAT32 destructive tests removed to prevent disk.img corruption
+58 -1
View File
@@ -266,7 +266,26 @@ def MakeRangeRO(vaddr_start: t.CUInt64T, vaddr_end: t.CUInt64T):
pd_r: _p | t.CPtr = t.CType(pdp_r.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pd_idx: t.CUInt64T = (va >> 21) & 0x1FF
if not (pd_r.entries[pd_idx] & PTE_PRESENT): va += 0x1000; continue
if pd_r.entries[pd_idx] & PTE_PS: va += 0x200000; continue
if pd_r.entries[pd_idx] & PTE_PS:
# 2MB 大页:直接跳过会让 .rodata 保持可写——越界写被静默吞掉
# (perf 格式串 win=%lu/work=%lu 被逐步破坏的根因)。拆分为 512
# 个 4KB 页(复用 MapPage 的分裂逻辑),再走下方常规路径清 W 位。
# WP=1 后内核态写只读页即 #PF → isrCommonhandler 打印 cr2/rip 暴露写源。
big_phys: t.CUInt64T = pd_r.entries[pd_idx] & ~t.CUInt64T(0x1FFFFF)
inherit_flags: t.CUInt64T = pd_r.entries[pd_idx] & (PTE_PCD | PTE_PWT | PTE_USER | t.CUInt64T(0x8000000000000000))
pt_split: _p | t.CPtr = AllocPage()
if not pt_split:
va += 0x1000
continue
split_i: t.CInt = 0
while split_i < 512:
pt_split.entries[split_i] = (big_phys + t.CUInt64T(split_i) * t.CUInt64T(0x1000)) \
| PTE_PRESENT | PTE_WRITABLE | inherit_flags
split_i += 1
pd_r.entries[pd_idx] = (t.CUInt64T(pt_split) & ~t.CUInt64T(0xFFF)) \
| PTE_PRESENT | PTE_WRITABLE | (pd_r.entries[pd_idx] & PTE_USER)
c.Asm(f"invlpg [{c.AsmInp(va, t.ASM_DESCR.REG_ANY)}]")
# 分裂后落入下方 4KB 常规路径,继续清当前页 W 位
pt_r: _p | t.CPtr = t.CType(pd_r.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pt_idx: t.CUInt64T = (va >> 12) & 0x1FF
pt_r.entries[pt_idx] = pt_r.entries[pt_idx] & ~t.CUInt64T(PTE_WRITABLE)
@@ -291,6 +310,44 @@ def ProtectKernelRO():
mov {c.AsmOut(ro_end, t.ASM_DESCR.OUTPUT_REG)}, rax""",
op=[t.ASM_DESCR.CLOBBER_RAX])
MakeRangeRO(ro_start, ro_end)
# [PG] 打印 .rodata 起始页映射:确认只读真正生效。
# ps=1 说明仍在 2MB 大页(分裂失败)→ .rodata 保持可写 → 越界写继续被静默吞掉。
DumpMapping(ro_start, "ro_s")
def DumpMapping(va: t.CUInt64T, tag: t.CConst | str):
"""[PG] 打印指定 VA 的 pml4/pdp/pd/pt 条目,定位大页(PS)与权限位
内核区域若为 UEFI 2MB 大页,MakeRangeRO 无法只读 .rodata(越界写静默破坏
格式串);1GB 页同理(PS 位在 pdpe)。W=1 且 PS=1 → 该页可写且是大页。
"""
global pml4
if not pml4: return
pml4_i: t.CUInt64T = (va >> 39) & 0x1FF
pdp_i: t.CUInt64T = (va >> 30) & 0x1FF
pd_i: t.CUInt64T = (va >> 21) & 0x1FF
pt_i: t.CUInt64T = (va >> 12) & 0x1FF
pml4_e: t.CUInt64T = pml4.entries[pml4_i]
pdp_e: t.CUInt64T = 0
pd_e: t.CUInt64T = 0
pt_e: t.CUInt64T = 0
if pml4_e & PTE_PRESENT:
pdp_r: _p | t.CPtr = t.CType(pml4_e & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pdp_e = pdp_r.entries[pdp_i]
if pdp_e & PTE_PRESENT:
if pdp_e & PTE_PS:
pd_e = pdp_e
else:
pd_r: _p | t.CPtr = t.CType(pdp_e & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pd_e = pd_r.entries[pd_i]
if (pd_e & PTE_PRESENT) and not (pd_e & PTE_PS):
pt_r: _p | t.CPtr = t.CType(pd_e & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pt_e = pt_r.entries[pt_i]
buf_d: t.CArray[t.CChar, 200]
viperlib.snprintf(c.Addr(buf_d), 200, "[PG] %s va=0x%lx pdpe=0x%lx pde=0x%lx pte=0x%lx (ps=%d W=%d)\n",
tag, va, pdp_e, pd_e, pt_e,
1 if (pd_e & PTE_PS) else 0,
1 if (pt_e & PTE_WRITABLE) or (pd_e & PTE_WRITABLE) or (pdp_e & PTE_WRITABLE) else 0)
serial.puts(c.Addr(buf_d))
def GetCurrentPml4() -> _p | t.CPtr:
global pml4, pdp, pd, pt
+13 -1
View File
@@ -1816,7 +1816,19 @@ def desktop_thread(arg: t.CVoid | t.CPtr) -> t.CInt:
if _elapsed_ms > 0:
_fps_val = t.CUInt32T(t.CUInt64T(_perf_frames) * 1000 / _elapsed_ms)
string.memset(c.Addr(_perf_buf), 0, 256)
viperlib.snprintf(c.Addr(_perf_buf), 256, "[perf] fps=%u wall=%lu work=%lu mouse=%lu kbd=%lu render=%lu(bg=%lu win=%lu cur=%lu fb=%lu) dirty=%d vsync=%d\n", _fps_val, t.CUInt64T(_elapsed_ms), t.CUInt64T(_perf_t_total), t.CUInt64T(_perf_t_mouse), t.CUInt64T(_perf_t_kbd), t.CUInt64T(_perf_t_render), t.CUInt64T(_perf_t_bg), t.CUInt64T(_perf_t_win), t.CUInt64T(_perf_t_cur), t.CUInt64T(_perf_t_fb), _dirty_count, _vblank_ok)
# [DIAG] .rodata 格式串校验:win=%lu/work=%lu 曾出现 ' / , 字节破坏(越界写)。
# 校验和变化 → 确认为真实内存破坏(配合 .rodata 只读后 #PF 定位写源);
# 校验和不变 → 疑似 printf/串口输出伪影,走另一条排查线。
_perf_fmt_str: str = "[perf] fps=%u wall=%lu work=%lu mouse=%lu kbd=%lu render=%lu(bg=%lu win=%lu cur=%lu fb=%lu) dirty=%d vsync=%d\n"
_fmt_sum: t.CUInt32T = 0
_fi: t.CInt = 0
while _fi < 110:
_fmt_sum = _fmt_sum + t.CUInt32T(t.CUInt8T(_perf_fmt_str[_fi]))
_fi += 1
_chk_buf: t.CArray[t.CChar, 48]
viperlib.snprintf(c.Addr(_chk_buf), 48, "[chk] sum=%u\n", _fmt_sum)
serial.puts(c.Addr(_chk_buf))
viperlib.snprintf(c.Addr(_perf_buf), 256, _perf_fmt_str, _fps_val, t.CUInt64T(_elapsed_ms), t.CUInt64T(_perf_t_total), t.CUInt64T(_perf_t_mouse), t.CUInt64T(_perf_t_kbd), t.CUInt64T(_perf_t_render), t.CUInt64T(_perf_t_bg), t.CUInt64T(_perf_t_win), t.CUInt64T(_perf_t_cur), t.CUInt64T(_perf_t_fb), _dirty_count, _vblank_ok)
serial.puts(_perf_buf)
_perf_t_mouse = 0
_perf_t_kbd = 0