I find out that it's possibly to read GDTR by SGDT assembly command. Inserting this piece of assembly in my C code I get Error: operand type mismatch for 'sgdt'
unsigned long j;
asm("sgdt %0" : "=r"(j));
I find out that it's possibly to read GDTR by SGDT assembly command. Inserting this piece of assembly in my C code I get Error: operand type mismatch for 'sgdt'
unsigned long j;
asm("sgdt %0" : "=r"(j));
sgdt
can only take a memory operand, not a register, so it has to be "=m"
. The operand-size is 2+8 bytes (for x86-64; limit then address in that order) so you need a struct; using a long
will result in storing outside the object.
Read the manual! https://www.felixcloutier.com/x86/sgdt
Other caveats:
UMIP (User Mode Instruction Prevention) lets a kernel stop user-space (privilege level 3) from running this instruction, because it only helps user-space defeat kernel ASLR or with other exploits; user-space has no legitimate use for this address. Under a normal kernel like Linux, user-space can't dereference the virtual address it gets from this. So Linux does enable UMIP if supported by hardware (Zen 2, Cannon Lake, Goldmont Plus).
The Linux kernel has a macro for this: store_gdt(dtr)
, which uses
asm volatile("sgdt %0":"=m" (*dtr));
with struct desc_ptr *dtr
.
On my Linux 5.18 system with a Skylake CPU (no UMIP support), I put sgdt [rsp]
(NASM syntax) into a static executable so I could single-step it with GDB (starti
/ stepi
). After that instruction:
x /1hx $rsp
shows the limit was 0x007f
(stored in 2 bytes, what GDB calls a half-word, what Intel calls a word).x /1gx $rsp+2
shows the qword address happened to be 0xfffffe00000ed000
, which is a valid kernel address (48-bit sign-extended, but fairly far from the very top of the upper half of the canonical range of address space.) According to docs/x86/x86-64/mm.txt
, the 0.5TB starting at fffffe0000000000
holds the cpu_entry_area
mapping, so that's an unsurprising place to find the GDT, along with other kernel stuff whose address is exposed to user-space (on CPUs without UMIP), and has to be mapped all the time, even on CPUs with Meltdown.By the way, in the Linux kernel it's also possible to perform the same action by already defined macros store_gdt(dtr)
. It contains the same inline-assembly code inside. Header of macros is asm/desc.h