OS & Assembly:是什么阻止了用户模式将选择器设置为任意值?

OS & Assembly: What prevents user mode from setting selector to arbitrary value?

我知道操作系统通过使用分段和特权级别来限制对内核代码和数据的访问。但是,用户可以更改段寄存器的值,如果以下代码执行成功,我们似乎可以访问内核数据:

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]

所以我想知道阻止这段代码成功执行的机制是什么?

在受保护和 64 位模式下,mov Sreg, reg 出现 #GP(selector) 错误,如果:

  • If segment selector index is outside descriptor table limits.
  • ...
  • If the DS, ES, FS, or GS register is being loaded and the segment pointed to is a data or nonconforming code segment, and either the RPL or the CPL is greater than the DPL.

但是 OS 控制 GDT 的内容,并且 GDT 条目有一个描述符权限级别字段,甚至需要将其加载到段 reg 中。 (https://wiki.osdev.org/Global_Descriptor_Table)。 OS 可以使某些 GDT 条目无法用于用户-space。

(此外,ring 3 user-space 由于类似的检查不能直接跳到 ring-0 代码段。)

如果 GDT 在用户-space 没有写权限的内存中,OS 可以保持控制。 (当然对于 LDT 也是如此)。一些 OSes,例如Linux,有一个 modify_ldt 系统调用,特权用户 - space 可以用来请求 OS 设置 LDT 条目。


由于大多数 OSes 使用平面内存模型(base=0 limit=-1)并通过分页进行内存保护,因此无需阻止用户-space 配置数据细分他们想要的。 (seg:off 到线性发生在 virt->phys 之前。即,如果启用分页,线性地址是虚拟的。)

但是单独的分段确实为 OS 提供了一种机制来阻止非特权 ring3 用户-space 使用任意条目。


另请注意,您的序列不会使用 新修改的 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]

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 特权级别”中:

  • Requested privilege level (RPL) — [...] Even if the program or task requesting access to a segment has sufficient privilege to access the segment, access is denied if the RPL is not of sufficient privilege level. That is, if the RPL of a segment selector is numerically greater than the CPL, the RPL overrides the CPL, and vice versa. [...]

选择器的 RPL 字段只允许将有效特权级别增加到数字上比 DPL 更高或更低的特权级别。如果您将其设置为较低的数值级别,则没有任何效果。

换句话说,如果选择器 0x10 引用内核模式数据段 (DPL = 0),那么您的代码将会崩溃。如果选择器 0x10 是用户模式数据段 (DPL = 3),它的处理方式与使用 0x13 (RPL = 3) 相同。


请注意,实际上这并不重要,因为所有现代操作系统都使用平面段模型,每个段的基数为 0 并且可以访问整个线性地址 space。用户模式代码实际上并没有通过段检查限制访问内核代码和数据,而是通过页面保护。这些仅使用 CPL 来确定是否应授予对主管模式(内核)页面的访问权限。