首页 > 教程资料

windows内核保护模式段权限检查(CPL / RPL / DPL)

malloc 2026-09-21 16:56:23 人看过

选择子里有一个请求特权级,描述符里有一个描述符特权级。前面把这两个字段的位置都点过了,但一直没讲它们到底怎么用。

这一篇把这件事补完。CPU 往段寄存器里装选择子,不是随手一写。它先比三个数——CPL、RPL、DPL——过了才装得进去。数据段和栈段的规则不一样。

方向先钉死,全文只说这一次:0 最特权,3 最不特权,数字越大权限越低。


一、装段不是随手一写

保护模式下,往 CSSSDSESFSGS 里装选择子,处理器会先取出目标描述符,比三个数。通过了才改段寄存器;通不过就抛 #GP,段寄存器原封不动。

三个数各住在不同的地方:

  • CPL:当前是谁在执行,住在当前 CS 的低 2 位
  • RPL:这一次以谁的名义提出,住在要装的那张选择子的低 2 位
  • DPL:目标段允许到哪一层,焊在描述符 Access 里

日常写内存不会再把这三个数拿出来比一遍。检查发生在装段那一刻。所以这一篇的主语是「装段」,不是「访问内存」。


二、Intel 画了四环,Windows 只用 0 和 3

手册把特权级画成四个同心圆。圆心 Level 0 是内核,最外圈 Level 3 是应用程序,中间 1 和 2 留给操作系统服务。

Intel SDM Vol. 3A Figure 6-3 保护环

图 1:Vol. 3A §6.5 的 Figure 6-3「Protection Rings」(印刷页 6-7)。数字越大,越不特权。

手册原话把方向钉死:4 个特权级,编号从 0 到 3,数字越大特权越低。同一段还有一句括号:只打算用两级的系统,应当使用 0 和 3。

Windows 照做。Win7、Win10、Win11,x86 和 x64,日常执行层级都只留 0 和 3。1 和 2 在硬件上还在,操作系统没有给它们通电。所以后面所有例子只出现 0 和 3。

日常说的「零环」「三环」,就是这张图上的 Level 0 和 Level 3。


三、当前在哪一环,看 CS 的低两位

CPL 没有单独的寄存器。它就住在当前 CS 选择子的低 2 位。换代码段,CPL 才变。改 DSES,人还站在原来那一环。

课上特别强调过一句:「零环程序」「三环程序」这种说法不准确。

一个普通程序从三环拉起来,但很多功能最终要进内核才能做。进内核的那一刻断下来,CPU 正在执行零环代码,CPL 就是 0;返回用户态再断,CPL 又是 3。变的不是这条进程的身份证件,是 CS 被换成了另一段代码。

所以要问的不是「这是一个零环程序还是三环程序」,而是:这一刻,CPU 在执行哪一环的代码。

看选择子低两位就能读出来。不同系统、32 位和 64 位,具体数字可以不一样,只看最后两位:

现场常见选择子低 2 位
用户态代码CS = 00233
用户态栈SS = 002B3
内核代码CS = 00080
内核栈SS = 00100

CSSS 的特权级必然相同。代码跑在零环,却拿着三环的栈,硬件不允许。零环有零环的栈,三环有三环的栈。

64 位 Windows 上,用户态常见 CS = 0033,内核常见 CS = 0010。数字换了,读法没换。


四、三个问句,两张手册图

三个名字不是一个概念写了三遍。用三句问句分开:

名称问句住在哪
CPL当前是谁在执行?当前 CS 低 2 位
RPL这次访问以谁的名义提出?段选择子低 2 位
DPL目标段允许到哪一层?描述符 Access 的 DPL

RPL 和 DPL 的住址,手册各有一张图。

Intel SDM Figure 3-6 段选择子,右边两位是 RPL

图 2:Vol. 3A Figure 3-6「Segment Selector」(印刷页 3-7)。最右边两位就是 RPL。

同一条描述符可以有四张不同的选择子。0018001B 指向 GDT 里同一项,差别只在 RPL 是 0 还是 3。装段时,硬件认的是这一次手里拿的那张。

Intel SDM Figure 3-8 段描述符,Access 里有 DPL

图 3:Vol. 3A Figure 3-8「Segment Descriptor」(印刷页 3-10)。DPL 在 Access 字节里 P 下面那两位。

DPL 焊在描述符上,建 GDT 的时候就写死了。内核段写成 0,用户段写成 3。它不是这次访问临时算出来的。

CPL 其实也是从选择子低两位读出来的——当前 CS 的 RPL,在正常执行时就是 CPL。但必须先定「人现在站在哪」,再拿这一次的 RPL 和目标 DPL 去比。


五、数据看 max,栈要同级

数据段写在手册 §6.6。装 DSESFSGS 的条件是:

max(CPL, RPL) ≤ DPL

max 取数字更大的那个,也就是更不特权的那个。谁更不特权,就按谁算。

Windows 只用 0 和 3,左边两个数的组合其实很少。

两边都是 0,max 就是 0,任何 DPL 都过——人在零环,拿的也是零环的选择子,内核数据、用户数据这一关都放行。

左边有一个是 3,max 就是 3。3 小于等于 0?不成立。DPL=0 的内核段装不进去,只能去装 DPL=3 的用户数据段。

三条例子对着数字看:

情形CPLRPLDPL结果
内核读用户数据003过。0 ≤ 3
三环装自己的数据段333过。3 ≤ 3
三环装内核段300不过。3 ≤ 0

第三行把 RPL 改成 3,还是过不了。挡下来的是 CPL 已经大于 DPL。

栈段不要套这条 max。手册 §6.7 说,装 SS 的时候,CPL、RPL、DPL 三个数必须相同。差一个,直接 #GP

原因在第三节已经见过:CSSS 必须同级。代码跑在零环,不能去用三环的栈;反过来也不行。栈是压返回地址的地方,用错了环,等于把现场写到别人能改的内存里。

所以不是「够特权就行」。零环去装用户栈,特权够高,按数据段公式能过,按栈规则照样失败。

代码段的检查另有说法,而且会碰到门。那一块还没讲,本篇先停在数据和栈。


总结

第一句:Windows 只使用 0 和 3。

第二句:CPL 在当前 CS,RPL 在选择子,DPL 在描述符。先看这一刻 CS 的低两位,再看要装的那张选择子,最后看目标段自己的门槛。

第三句:数据看 max,栈段要同级。检查只发生在装段寄存器的时候,不是每条访存指令都做一次。

上面结论对照 Intel SDM 第 091 版核过,主线落在 Vol. 3A 的 §6.5、§6.6、§6.7。文中三张图均从该版手册截取,未做改动。

觉得这种把一条规则拆到手册原图上的写法有用,就点个「在看」。代码段怎么换、人要进圆心的合法入口在哪里,下一篇再拆。

版权声明:倡导尊重与保护知识产权。未经许可,任何人不得复制、转载、或以其他方式使用本站《原创》内容,违者将追究其法律责任。本站文章内容,部分图片来源于网络,如有侵权,请联系我们修改或者删除处理。

编辑推荐

热门文章