我知道操作系统通过使用分段和特权级别来限制对内核代码和数据的访问。但是,用户可以更改段寄存器的值,如果以下代码执行成功,我们似乎可以访问内核数据:
mov eax, 0x10
mov es, ax #point selector to the item 2 in GDT with RPL 0, which is the data segment
les bx, [0]
所以我想知道阻止这段代码成功执行的机制是什么?
我知道操作系统通过使用分段和特权级别来限制对内核代码和数据的访问。但是,用户可以更改段寄存器的值,如果以下代码执行成功,我们似乎可以访问内核数据:
mov eax, 0x10
mov es, ax #point selector to the item 2 in GDT with RPL 0, which is the data segment
les bx, [0]
所以我想知道阻止这段代码成功执行的机制是什么?
该mov es, ax
指令将导致一般保护 (#GP) 错误,因为当前特权级别 (CPL) 大于描述符的特权级别 (DPL),或者请求的特权级别 (RPL) 将被忽略,因为它在数值上不高于 DPL . 在您的示例中,由于它在用户模式下运行,因此 CPL 为 3。这意味着描述符的 DPL 也必须为 3,否则指令将出错。如果 DPL 为 3,则不会出现故障,但 RPL 被有效忽略,因为它不能高于 DPL。
(请注意,段特权级别检查仅在加载段寄存器时执行,因此只有mov es, ax
指令可能因此而崩溃。)
英特尔软件开发人员手册中 MOV 指令的文档解释了加载段寄存器时何时会导致#GP错误:
IF DS, ES, FS, or GS is loaded with non-NULL selector THEN IF segment selector index is outside descriptor table limits or segment is not a data or readable code segment or ((segment is a data or nonconforming code segment) or ((RPL > DPL) and (CPL > DPL)) THEN #GP(selector); FI; IF segment not marked present THEN #NP(selector); ELSE SegmentRegister ← segment selector; SegmentRegister ← segment descriptor; FI; FI;
正在使用的最高 DPL 和 RPL 的行为记录在英特尔 SDM 第 3 卷“5.5 PRIVILEGE LEVELS”中:
- 请求的特权级别 (RPL) — [...] 即使请求访问段的程序或任务有足够的特权来访问该段,如果 RPL 没有足够的特权级别,访问也会被拒绝。也就是说,如果段选择器的 RPL 在数值上大于 CPL,则 RPL 会覆盖 CPL,反之亦然。[...]
选择器的 RPL 字段仅允许将有效特权级别增加到比 DPL 更高或更低的特权级别。如果您将其设置为数值较低的级别,则它无效。
换句话说,如果选择器 0x10 引用内核模式数据段 (DPL = 0),那么您的代码将崩溃。如果选择器 0x10 是用户模式数据段 (DPL = 3),则它的处理方式与使用 0x13 (RPL = 3) 相同。
请注意,实际上这并不重要,因为所有现代操作系统都使用平面段模型,每个段的基数都为 0,并且可以访问整个线性地址空间。用户模式代码实际上并不受限制通过段检查访问内核代码和数据,而是通过页面保护。这些仅使用 CPL 来确定是否应授予对主管模式(内核)页面的访问权限。
在受保护和 64 位模式下,如果出现以下mov Sreg, reg
故障#GP(selector)
:
- 如果段选择器索引超出描述符表限制。
- ...
- 如果正在加载 DS、ES、FS 或 GS 寄存器并且指向的段是数据段或不合格代码段,并且 RPL 或 CPL 大于 DPL。
但是操作系统控制 GDT 的内容,并且 GDT 条目有一个描述符特权级字段,甚至需要将其加载到段 reg 中。(https://wiki.osdev.org/Global_Descriptor_Table)。 操作系统可以使一些 GDT 条目对用户空间不可用。
(此外,由于类似的检查,ring 3 用户空间不能只是远跳转到 ring-0 代码段。)
如果 GDT 位于用户空间没有写入权限的内存中,则操作系统可以保持控制。(当然 LDT 也是如此)。某些操作系统,例如 Linux,有一个modify_ldt
系统调用,特权用户空间可以使用该系统调用来要求操作系统设置 LDT 条目。
由于大多数操作系统使用平面内存模型(base=0 限制=-1)并通过分页进行内存保护,因此无需阻止用户空间配置数据段。(seg:off to linear 发生在 virt->phys 之前。即如果启用分页,线性地址是虚拟的。)
但是单独的分段确实为操作系统提供了一种机制来阻止非特权 ring3 用户空间使用任意条目。
另请注意,您的序列不使用新修改的 ES 寄存器,而是覆盖它。仔细看看https://www.felixcloutier.com/x86/lds:les:lfs:lgs:lss
les bx, [0] # Load a seg:off from memory at DS:0 into ES:BX
也许你想要mov bx, [es:0]