SEGMEXEC设计文档

原文:SEGMEXEC设计文档

作者:The PaX team

译者:n3o4po11o

1. 设计

SEGMEXEC的目标是通过IA-32构架CPU的内存分段实现不可执行页(Non-executable Page).

在IA-32环境下,Linux运行在保护模式下并开启了内存分页。这意味着,所有访问内存的操作都必须经过两步的地址转换。第一步从指令中解码出逻辑地址并将其转换为线性地址(也就是我们常说的虚拟地址).这个转换通过内存分段来完成,具体细节在另外的文档进行描述。

由于Linux高效地通过不使用分段而是通过分别为代码和数据的访问创建基址为0,段界限为4GB的内存段(因此,逻辑地址和线性地址一致),这使得通过设置段来实现不可执行页成为了可能。

基本的思路是将3GB用户态的线性地址空间平均分成两半,其中一半用来放置数据的映射(那就是我们定义了一个可以涵盖0-1.5GB范围的线性地址的数据段描述符),而另外一半用来放置代码的映射(那就是我们定义了一个可以涵盖1.5-3GB范围的线性地址的代码段描述符)。由于一个可执行的映射也可以用来进行数据的存取。我们必须保证这样的映射对两个段都可见,在两个段中相对应的地址都有这些数据的映射。这样的设置会在取指令时将数据存取的操作分离开来。因此能够基于访问的类型进行控制或者干预。而当一个只会出现在0-1.5GB线性地址范围的数据,有了试图执行的行为,就会抛出页错误(page fault, PF)

2. 实现

SEGMEXEC的核心为虚拟内存空间镜像(vma mirroring),其具体细节在单独的文件中进行讨论.可执行文件映射的镜像除了有RANDEXEC的情况下,是通过do_mmap()(位于include/linux/mm.h)来完成的。do_mmap()是用户态和内核态用来发起映射请求的函数。

这样特殊的代码段描述符和数据段描述符放置在arch/i386/kernel/head.S中一个叫做gdt_table2的新全局描述符表(Global Descriptor Table)中。另外的全局描述表存在有两个原因:第一是为了简化实现,这样的话用户态的CS/SS选择器就不需要有任何改变;第二是能够防止在单独一个全局描述表中简单的通过滥用retf或者其他指令打破SEGMEXEC中受限制的代码段的攻击。而GDT保存的用户态的代码/数据描述符与SEGMEXEC事务中的不一致。我们必须对/arch/i386/kernel/process.c中对底层内容进行转换代码的__switch_to()函数以及在fs/bin/fmt_elf.c(其在用户态中对可执行文件的执行进行初始准备)中的local_elf_binary()函数的最后一步进行修改。

全局描述符表拥有在运行时进行设置的高级电源管理特定的描述符,而这些描述符也必须传递给第二个全局描述符表(通过arch/i386/kernel/apm.c)。最后,原有的全局描述符表必须将储存的每个CPU任务状态描述符(TSS)与本地描述符表描述符与第二个全局描述符表进行同步。(通过arch/i386/kernel/traps.c中的set_tss_desc()与set_ldt_desc()函数来完成)

由于内核允许用户态在本地描述符表中自己定义代码段描述符,我们必须将这个功能 禁止,防止用来打破激活SEGMEXEC下代码段的限制。(通过arch/i386/kernel/ldt.c中 的write_ldt()进行额外的检查)