Skip to content

导引:点亮什么、隔离面怎么铺

之前内核和用户态之间的硬件隔离,基本只靠分页(用户页映射进用户地址空间、内核页不在用户空间露面)。但分页管的是「地址」,管不了「这一页能不能被执行」「内核能不能碰用户那一页」。这一章把 x86_64 的三个硬件保护位真正拨开:NX(用户数据页不可执行,落实 W^X)、SMEP(内核不可执行用户页)、SMAP(内核不可读写用户页)。三个位各管一格,合起来把「内核碰用户页」这个方向、连同「用户数据页不可执行」一起堵死。验证靠一个机制回读测试——读 EFER 看 NXE 设上、读 CR4 看 SMEP/SMAP 跟着 CPUID 走;再靠跑真程序(shell、GUI)开完这三 位不炸,证明内核路径没踩雷。

一条诚实的边界先说在前头:本机是 WSL2 嵌套 KVM,它不透传 CPUID.07H:EBX,所以 SMEP/SMAP 在开发机上验证不了生效——代码的 CPUID-gated 逻辑会正确地跳过它们。NX 不受影响(EFER 位是 x86_64 baseline,WSL2 透传),真生效。真机、完整 KVM、或 TCG 会暴露 SMEP/SMAP,这三个位就都活。这一章讲的是代码怎么做对,不是「在本机看到 SMEP 拦住了一次攻击」。

这章咱们要点亮什么

  1. 三个位各管什么:NX 管「用户数据页能不能被执行」,SMEP 管「内核能不能执行用户页」,SMAP 管「内核能不能读写用户页」——执行 + 访问,用户 + 内核,正好把隔离面铺满。
  2. NX 为什么不用 CPUID gate,而后两个必须 gate:NX 是 x86_64 baseline,SMEP/SMAP 是 2011+ 的可选特性,写不支持的 CR4 位会直接 #GP。
  3. SMAP 的 stac/clac 之舞:内核要碰用户内存(读参数、写返回值)时,得显式 stac 临时放行、用完 clac 关上;这条纪律得挂在所有从用户态进来的入口。
  4. 怎么验证一个「开了但不一定生效」的机制:靠机制回读(EFER/CR4 位)+ CPUID 跟随,而不是靠「触发一次攻击看它拦没拦」。

先把隔离面铺清楚:三个位各管一格

把内核态(Ring 0)和用户态(Ring 3)之间的硬件隔离拆开看,有两个维度:能不能执行能不能访问(读写)。再乘上「谁对谁」(用户对用户数据、内核对用户页),就铺出一张表。NX、SMEP、SMAP 各占一格:

执行访问(读/写)
用户态碰用户数据页NX 管(数据页不可执行)(本就允许,无保护)
内核态碰用户页SMEP 管(内核不可执行用户页)SMAP 管(内核不可读写用户页)

NX 是「用户能不能把自己的数据页当代码跑」(W^X 的那一半:写了的数据不能执行)。SMEP/SMAP 是反方向的:防止内核——通常是因为 bug 或被攻击者诱导——去执行/访问用户空间的页。经典提权攻击的路子就是诱骗内核跳到用户提前布置好的代码页去执行;SMEP 把这条路堵死(内核一执行用户页就 #PF),SMAP 连「读」也堵死(内核一碰用户页就 #PF,得显式放行才行)。三个位互补,缺哪个哪条路就漏。

下面挨个看怎么开。

035_multi_terminal-45-gf25de18 · f25de18 · 2026-08-04