fix(paging): validate non-leaf page-table entries before dereference
_set_page_writable / MakePTWritable derived table pointers with entry & ~0xFFF, which keeps reserved bits 52..62 and NX. An invalid entry (P=1, PS=0, reserved bits set) produced 0xfffffffffffff000 and a boot-time #PF in _set_page_writable. Add _table_phys: keep address bits 12..51 and require reserved bits 52..62 to be zero. Use it at every non-leaf level in both walkers.
This commit is contained in:
1 parent
da4b0e47b7
commit
f9cf26e59a
1 file changed
+38
-32
@@ -274,73 +274,79 @@ def EnablePaging():
|
||||
global pml4, pdp, pd, pt
|
||||
__asm__(f"mov cr3, {asminp(uint64(pml4), ASM_DESCR.REG_ANY)}")
|
||||
|
||||
def _set_page_writable(phys_addr: uint64):
|
||||
"""把物理页 phys_addr(identity 映射,虚拟=物理)的 PTE 置 W 位
|
||||
# Extract the physical address of a non-leaf page-table entry.
|
||||
# Bits 12..51 hold the address; bits 52..62 are reserved and must be zero.
|
||||
# Bit 63 (NX) is ignored for non-leaf entries. Returns 0 for unusable entries.
|
||||
def _table_phys(entry: uint64) -> uint64:
|
||||
if (entry & PTE_PRESENT) == 0: return 0
|
||||
if (entry & uint64(0x7FF0000000000000)) != 0: return 0
|
||||
return entry & uint64(0x000FFFFFFFFFF000)
|
||||
|
||||
页表页(bootloader 建立的 pml4/pdp/pd/pt 位于 0xf8xxxx)默认只读,
|
||||
而内核运行时 MapPage/clone 需修改页表 → 必须先把这些页表页自身设为可写,
|
||||
否则 CR0.WP=1 下写页表触发 #PF(slog.so 映射 0x600000 新建页表项时
|
||||
cr2=0xf801000 err=0x3 根因)。
|
||||
def _set_page_writable(phys_addr: uint64):
|
||||
"""Mark the identity-mapped page containing phys_addr writable via its PTE.
|
||||
|
||||
Page-table pages created by the bootloader are read-only; MapPage and
|
||||
clone need to modify them, so they must be made writable while CR0.WP
|
||||
is clear. Non-leaf entries are validated with _table_phys before being
|
||||
dereferenced.
|
||||
"""
|
||||
global pml4
|
||||
if not pml4: return
|
||||
va: uint64 = phys_addr & ~uint64(0xFFF)
|
||||
if va == 0: return
|
||||
pml4_idx: uint64 = (va >> 39) & 0x1FF
|
||||
if not (pml4.entries[pml4_idx] & PTE_PRESENT): return
|
||||
pdp_s: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF))
|
||||
pdp_phys: uint64 = _table_phys(pml4.entries[pml4_idx])
|
||||
if pdp_phys == 0: return
|
||||
pdp_s: _p* = _p*(pdp_phys)
|
||||
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 位。
|
||||
# 1GB page: the W bit lives in this entry; there is no lower PD.
|
||||
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_phys: uint64 = _table_phys(pdp_s.entries[pdp_idx])
|
||||
if pd_phys == 0: return
|
||||
pd_s: _p* = _p*(pd_phys)
|
||||
pd_idx: uint64 = (va >> 21) & 0x1FF
|
||||
if not (pd_s.entries[pd_idx] & PTE_PRESENT): return
|
||||
if pd_s.entries[pd_idx] & PTE_PS:
|
||||
# 2MB 大页:W 位在 PD 条目自身(无下层 PT),直接置位
|
||||
# 2MB page: the W bit lives in this entry; there is no lower PT.
|
||||
pd_s.entries[pd_idx] = pd_s.entries[pd_idx] | uint64(PTE_WRITABLE)
|
||||
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
|
||||
return
|
||||
pt_s: _p* = _p*(pd_s.entries[pd_idx] & ~uint64(0xFFF))
|
||||
pt_phys: uint64 = _table_phys(pd_s.entries[pd_idx])
|
||||
if pt_phys == 0: return
|
||||
pt_s: _p* = _p*(pt_phys)
|
||||
pt_idx: uint64 = (va >> 12) & 0x1FF
|
||||
pt_s.entries[pt_idx] = pt_s.entries[pt_idx] | uint64(PTE_WRITABLE)
|
||||
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
|
||||
|
||||
def MakePTWritable():
|
||||
"""把当前页表层次所有页表页(pml4 自身 + pdp/pd/pt)设为可写
|
||||
"""Make all page-table pages currently reachable from pml4 writable.
|
||||
|
||||
内核运行时需修改页表(MapPage/clone_for_process),而 bootloader 建立的
|
||||
页表页映射为只读——CR0.WP=1 下必须先放开页表页自身,否则写页表即 #PF。
|
||||
Non-leaf entries are validated with _table_phys. Huge pages (PS=1) are
|
||||
leaves, so they are not walked; _set_page_writable updates their own W bit.
|
||||
"""
|
||||
global pml4
|
||||
if not pml4: return
|
||||
_set_page_writable(uint64(pml4))
|
||||
for pi in range(512):
|
||||
if not (pml4.entries[pi] & PTE_PRESENT): continue
|
||||
pdp_m: _p* = _p*(pml4.entries[pi] & ~uint64(0xFFF))
|
||||
pdp_phys: uint64 = _table_phys(pml4.entries[pi])
|
||||
if pdp_phys == 0: continue
|
||||
pdp_m: _p* = _p*(pdp_phys)
|
||||
_set_page_writable(uint64(pdp_m))
|
||||
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))
|
||||
pd_phys: uint64 = _table_phys(pdp_m.entries[pj])
|
||||
if pd_phys == 0: continue
|
||||
pd_m: _p* = _p*(pd_phys)
|
||||
_set_page_writable(uint64(pd_m))
|
||||
pk: uint64
|
||||
for pk in range(512):
|
||||
if not (pd_m.entries[pk] & PTE_PRESENT): continue
|
||||
if pd_m.entries[pk] & PTE_PS: continue
|
||||
pt_m: _p* = _p*(pd_m.entries[pk] & ~uint64(0xFFF))
|
||||
_set_page_writable(uint64(pt_m))
|
||||
pt_phys: uint64 = _table_phys(pd_m.entries[pk])
|
||||
if pt_phys == 0: continue
|
||||
_set_page_writable(pt_phys)
|
||||
|
||||
def MakeRangeRO(vaddr_start: uint64, vaddr_end: uint64):
|
||||
"""把 [vaddr_start, vaddr_end) 范围内所有页的 PTE 清 W 位(只读)
|
||||
|
||||
Reference in new issue
Block a user