fix(vkernel/paging): 补 1GB 大页(PTE_PS)判据,消除偶发 #GP(0) 与任意内存写坏

- 现场:rip=0x11a93e 在 _set_page_writable 的 testb $1,(%rax,%rcx,8);rax 由 PDPTE & ~0xFFF 得到。固件 identity 映射的 1GB 大页条目会置 NX(bit63),& ~0xFFF 把它一起带出 → 派生“PD 基址”非规范 → 读表项即 #GP(0)(偶发取决于该 VA 是否落进 1GB 大页)

- _set_page_writable:PDP 层补 PTE_PS 分支(1GB 大页无下层 PD,W 位在条目自身),与既有 2MB 分支同口径

- MakePTWritable / MakeRangeRO:PDP 层遇 PTE_PS 跳过 —— 把 1GB 大页当 PD 解引用会把大页内存内容当表项(约一半 qword bit0=1),据此递归/写页表 → 任意内存被改(偶发数据损坏)并算出非规范指针
This commit is contained in:
TermiNexus committed 2026-10-14 18:12:15 +08:00
1 parent f42d8a5248
commit 9119c74232
1 file changed
+18
+18
View File
@@ -205,6 +205,14 @@ def _set_page_writable(phys_addr: uint64):
pdp_s: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF))
pdp_idx: uint64 = (va >> 30) & 0x1FF
if not (pdp_s.entries[pdp_idx] & PTE_PRESENT): return
if pdp_s.entries[pdp_idx] & PTE_PS:
# 1GB 大页(PDPTE.PS=1,固件 identity 映射常见):没有下层 PD,W 位在本条目自身。
# 若仍按 `& ~0xFFF` 取"PD 基址",条目位 63(NX,固件对 1GB 数据页常置 1)会一起被带出来
# → 派生指针非规范 → 读 %rax+idx*8 即 #GP(0)(现场 rip=0x11a93e,偶发取决于该 VA 是否
# 落进 1GB 大页)。与下面 2MB 分支同口径:直接置本条目 W 位。
pdp_s.entries[pdp_idx] = pdp_s.entries[pdp_idx] | uint64(PTE_WRITABLE)
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
return
pd_s: _p* = _p*(pdp_s.entries[pdp_idx] & ~uint64(0xFFF))
pd_idx: uint64 = (va >> 21) & 0x1FF
if not (pd_s.entries[pd_idx] & PTE_PRESENT): return
@@ -234,6 +242,12 @@ def MakePTWritable():
pj: uint64
for pj in range(512):
if not (pdp_m.entries[pj] & PTE_PRESENT): continue
# 1GB 大页(PDPTE.PS=1)不是页表页:把它当 PD 解引用,会把大页内存内容当表项读
# (其中约一半 qword 的 bit0=1)→ 按"表项"递归 _set_page_writable 随机地址 → 任意
# 内存被置 W 位(偶发数据损坏)且可算出非规范指针 → #GP。跳过(与下面 2MB 的
# `if PTE_PS: continue` 同口径);若页表页本身落在 1GB 大页里,则由上面
# _set_page_writable(该页地址) 命中 1GB 分支把大页条目的 W 位置起。
if pdp_m.entries[pj] & PTE_PS: continue
pd_m: _p* = _p*(pdp_m.entries[pj] & ~uint64(0xFFF))
_set_page_writable(uint64(pd_m))
pk: uint64
@@ -264,6 +278,10 @@ def MakeRangeRO(vaddr_start: uint64, vaddr_end: uint64):
pdp_r: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF))
pdp_idx: uint64 = (va >> 30) & 0x1FF
if not (pdp_r.entries[pdp_idx] & PTE_PRESENT): va += 0x1000; continue
# 1GB 大页(PDPTE.PS=1):同样不是 PD,当 PD 用会把大页内容当表项读(并据其"表项"
# 写页表 → 写坏任意内存)。1GB 大页拆分需新建 1 个 PD + 若干 PT(513 页),此处不支持
# → 跳过该页。后果:若 .rodata 落在 1GB 大页内则该段保持可写(不保护),但绝不再写坏内存。
if pdp_r.entries[pdp_idx] & PTE_PS: va += 0x1000; continue
pd_r: _p* = _p*(pdp_r.entries[pdp_idx] & ~uint64(0xFFF))
pd_idx: uint64 = (va >> 21) & 0x1FF
if not (pd_r.entries[pd_idx] & PTE_PRESENT): va += 0x1000; continue