【发布时间】:2016-04-20 21:53:31
【问题描述】:
我对全局描述符表 (GDT) 的位置感到困惑。根据从 i386 到更早版本的 Intel 手册,GDTR 寄存器包含 GDT 表的基地址,该基地址被伪装成线性地址。 按照 Intel 的约定,线性地址需要分页。
不过,我想知道考虑了哪个地址空间。 Ring 3(用户级)程序完全可以修改某些段选择器(例如 ES)。此修改应触发处理器从 GDT 中的相应条目加载段描述符,该条目使用 GDTR 寄存器给出的线性地址计算基地址。
由于线性地址需要分页,我从英特尔手册中了解到,段描述符加载通过当前进程的内存分页进行。因为 Linux 肯定不想将 GDT 结构暴露给用户级程序,所以我认为它设法在用户级进程的地址空间中引入了一个漏洞。阻止这些进程读取 GDT,同时允许处理器读取它以进行段重载。
我使用以下代码进行了检查,这表明我对 GDTR 的基本线性地址完全错误。
int
main()
{
struct
{
uint16_t pad;
uint16_t size;
uintptr_t base;
} gdt_info;
__asm__ volatile ("sgdt %0" : "=m" (gdt_info.size) );
void* try_mmgdt = (void*)( gdt_info.base & ~0xfff );
void* chk_mmgdt = mmap(try_mmgdt, 0x4000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
std::cout << "gdt size: \t" << std::dec << gdt_info.size << std::endl;
std::cout << "gdt base: \t" << std::hex << gdt_info.base << std::endl;
std::cout << "mmgdt try:\t" << std::hex << uintptr_t(try_mmgdt) << std::endl;
std::cout << "mmgdt chk:\t" << std::hex << uintptr_t(chk_mmgdt) << std::endl;
return 0;
}
我机器上的程序输出(i386-compiled)是:
gdt size: 127
gdt base: 1dd89000
mmgdt try: 1dd89000
mmgdt chk: 1dd89000
GDT 条目的线性地址和 mmap 块的线性地址完全重叠。然而 mmap 块显然与 GDT 无关。
所以我的问题最后是:哪种Intel/linux机制使GDTR的线性地址和当前进程的线性地址指向不同的内存区域?
【问题讨论】: