selftests/x86/ldt_gdt: Test set_thread_area() deletion of an active segment
Now that set_thread_area() is supposed to give deterministic behavior when it modifies in-use segments, test it. Signed-off-by: Andy Lutomirski <luto@kernel.org> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Link: http://lkml.kernel.org/r/f2bc11af1ee1a0f815ed910840cbdba06b640a20.1461698311.git.luto@kernel.org Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
parent
c9867f863e
commit
d63f4b5269
@ -21,6 +21,9 @@
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <linux/futex.h>
|
||||
#include <sys/mman.h>
|
||||
#include <asm/prctl.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#define AR_ACCESSED (1<<8)
|
||||
|
||||
@ -44,6 +47,12 @@
|
||||
|
||||
static int nerrs;
|
||||
|
||||
/* Points to an array of 1024 ints, each holding its own index. */
|
||||
static const unsigned int *counter_page;
|
||||
static struct user_desc *low_user_desc;
|
||||
static struct user_desc *low_user_desc_clear; /* Use to delete GDT entry */
|
||||
static int gdt_entry_num;
|
||||
|
||||
static void check_invalid_segment(uint16_t index, int ldt)
|
||||
{
|
||||
uint32_t has_limit = 0, has_ar = 0, limit, ar;
|
||||
@ -561,16 +570,257 @@ static void do_exec_test(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_counter_page(void)
|
||||
{
|
||||
unsigned int *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
|
||||
if (page == MAP_FAILED)
|
||||
err(1, "mmap");
|
||||
|
||||
for (int i = 0; i < 1024; i++)
|
||||
page[i] = i;
|
||||
counter_page = page;
|
||||
}
|
||||
|
||||
static int invoke_set_thread_area(void)
|
||||
{
|
||||
int ret;
|
||||
asm volatile ("int $0x80"
|
||||
: "=a" (ret), "+m" (low_user_desc) :
|
||||
"a" (243), "b" (low_user_desc)
|
||||
: "flags");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void setup_low_user_desc(void)
|
||||
{
|
||||
low_user_desc = mmap(NULL, 2 * sizeof(struct user_desc),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
|
||||
if (low_user_desc == MAP_FAILED)
|
||||
err(1, "mmap");
|
||||
|
||||
low_user_desc->entry_number = -1;
|
||||
low_user_desc->base_addr = (unsigned long)&counter_page[1];
|
||||
low_user_desc->limit = 0xfffff;
|
||||
low_user_desc->seg_32bit = 1;
|
||||
low_user_desc->contents = 0; /* Data, grow-up*/
|
||||
low_user_desc->read_exec_only = 0;
|
||||
low_user_desc->limit_in_pages = 1;
|
||||
low_user_desc->seg_not_present = 0;
|
||||
low_user_desc->useable = 0;
|
||||
|
||||
if (invoke_set_thread_area() == 0) {
|
||||
gdt_entry_num = low_user_desc->entry_number;
|
||||
printf("[NOTE]\tset_thread_area is available; will use GDT index %d\n", gdt_entry_num);
|
||||
} else {
|
||||
printf("[NOTE]\tset_thread_area is unavailable\n");
|
||||
}
|
||||
|
||||
low_user_desc_clear = low_user_desc + 1;
|
||||
low_user_desc_clear->entry_number = gdt_entry_num;
|
||||
low_user_desc_clear->read_exec_only = 1;
|
||||
low_user_desc_clear->seg_not_present = 1;
|
||||
}
|
||||
|
||||
static void test_gdt_invalidation(void)
|
||||
{
|
||||
if (!gdt_entry_num)
|
||||
return; /* 64-bit only system -- we can't use set_thread_area */
|
||||
|
||||
unsigned short prev_sel;
|
||||
unsigned short sel;
|
||||
unsigned int eax;
|
||||
const char *result;
|
||||
#ifdef __x86_64__
|
||||
unsigned long saved_base;
|
||||
unsigned long new_base;
|
||||
#endif
|
||||
|
||||
/* Test DS */
|
||||
invoke_set_thread_area();
|
||||
eax = 243;
|
||||
sel = (gdt_entry_num << 3) | 3;
|
||||
asm volatile ("movw %%ds, %[prev_sel]\n\t"
|
||||
"movw %[sel], %%ds\n\t"
|
||||
#ifdef __i386__
|
||||
"pushl %%ebx\n\t"
|
||||
#endif
|
||||
"movl %[arg1], %%ebx\n\t"
|
||||
"int $0x80\n\t" /* Should invalidate ds */
|
||||
#ifdef __i386__
|
||||
"popl %%ebx\n\t"
|
||||
#endif
|
||||
"movw %%ds, %[sel]\n\t"
|
||||
"movw %[prev_sel], %%ds"
|
||||
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
|
||||
"+a" (eax)
|
||||
: "m" (low_user_desc_clear),
|
||||
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
|
||||
: "flags");
|
||||
|
||||
if (sel != 0) {
|
||||
result = "FAIL";
|
||||
nerrs++;
|
||||
} else {
|
||||
result = "OK";
|
||||
}
|
||||
printf("[%s]\tInvalidate DS with set_thread_area: new DS = 0x%hx\n",
|
||||
result, sel);
|
||||
|
||||
/* Test ES */
|
||||
invoke_set_thread_area();
|
||||
eax = 243;
|
||||
sel = (gdt_entry_num << 3) | 3;
|
||||
asm volatile ("movw %%es, %[prev_sel]\n\t"
|
||||
"movw %[sel], %%es\n\t"
|
||||
#ifdef __i386__
|
||||
"pushl %%ebx\n\t"
|
||||
#endif
|
||||
"movl %[arg1], %%ebx\n\t"
|
||||
"int $0x80\n\t" /* Should invalidate es */
|
||||
#ifdef __i386__
|
||||
"popl %%ebx\n\t"
|
||||
#endif
|
||||
"movw %%es, %[sel]\n\t"
|
||||
"movw %[prev_sel], %%es"
|
||||
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
|
||||
"+a" (eax)
|
||||
: "m" (low_user_desc_clear),
|
||||
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
|
||||
: "flags");
|
||||
|
||||
if (sel != 0) {
|
||||
result = "FAIL";
|
||||
nerrs++;
|
||||
} else {
|
||||
result = "OK";
|
||||
}
|
||||
printf("[%s]\tInvalidate ES with set_thread_area: new ES = 0x%hx\n",
|
||||
result, sel);
|
||||
|
||||
/* Test FS */
|
||||
invoke_set_thread_area();
|
||||
eax = 243;
|
||||
sel = (gdt_entry_num << 3) | 3;
|
||||
#ifdef __x86_64__
|
||||
syscall(SYS_arch_prctl, ARCH_GET_FS, &saved_base);
|
||||
#endif
|
||||
asm volatile ("movw %%fs, %[prev_sel]\n\t"
|
||||
"movw %[sel], %%fs\n\t"
|
||||
#ifdef __i386__
|
||||
"pushl %%ebx\n\t"
|
||||
#endif
|
||||
"movl %[arg1], %%ebx\n\t"
|
||||
"int $0x80\n\t" /* Should invalidate fs */
|
||||
#ifdef __i386__
|
||||
"popl %%ebx\n\t"
|
||||
#endif
|
||||
"movw %%fs, %[sel]\n\t"
|
||||
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
|
||||
"+a" (eax)
|
||||
: "m" (low_user_desc_clear),
|
||||
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
|
||||
: "flags");
|
||||
|
||||
#ifdef __x86_64__
|
||||
syscall(SYS_arch_prctl, ARCH_GET_FS, &new_base);
|
||||
#endif
|
||||
|
||||
/* Restore FS/BASE for glibc */
|
||||
asm volatile ("movw %[prev_sel], %%fs" : : [prev_sel] "rm" (prev_sel));
|
||||
#ifdef __x86_64__
|
||||
if (saved_base)
|
||||
syscall(SYS_arch_prctl, ARCH_SET_FS, saved_base);
|
||||
#endif
|
||||
|
||||
if (sel != 0) {
|
||||
result = "FAIL";
|
||||
nerrs++;
|
||||
} else {
|
||||
result = "OK";
|
||||
}
|
||||
printf("[%s]\tInvalidate FS with set_thread_area: new FS = 0x%hx\n",
|
||||
result, sel);
|
||||
|
||||
#ifdef __x86_64__
|
||||
if (sel == 0 && new_base != 0) {
|
||||
nerrs++;
|
||||
printf("[FAIL]\tNew FSBASE was 0x%lx\n", new_base);
|
||||
} else {
|
||||
printf("[OK]\tNew FSBASE was zero\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Test GS */
|
||||
invoke_set_thread_area();
|
||||
eax = 243;
|
||||
sel = (gdt_entry_num << 3) | 3;
|
||||
#ifdef __x86_64__
|
||||
syscall(SYS_arch_prctl, ARCH_GET_GS, &saved_base);
|
||||
#endif
|
||||
asm volatile ("movw %%gs, %[prev_sel]\n\t"
|
||||
"movw %[sel], %%gs\n\t"
|
||||
#ifdef __i386__
|
||||
"pushl %%ebx\n\t"
|
||||
#endif
|
||||
"movl %[arg1], %%ebx\n\t"
|
||||
"int $0x80\n\t" /* Should invalidate gs */
|
||||
#ifdef __i386__
|
||||
"popl %%ebx\n\t"
|
||||
#endif
|
||||
"movw %%gs, %[sel]\n\t"
|
||||
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
|
||||
"+a" (eax)
|
||||
: "m" (low_user_desc_clear),
|
||||
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
|
||||
: "flags");
|
||||
|
||||
#ifdef __x86_64__
|
||||
syscall(SYS_arch_prctl, ARCH_GET_GS, &new_base);
|
||||
#endif
|
||||
|
||||
/* Restore GS/BASE for glibc */
|
||||
asm volatile ("movw %[prev_sel], %%gs" : : [prev_sel] "rm" (prev_sel));
|
||||
#ifdef __x86_64__
|
||||
if (saved_base)
|
||||
syscall(SYS_arch_prctl, ARCH_SET_GS, saved_base);
|
||||
#endif
|
||||
|
||||
if (sel != 0) {
|
||||
result = "FAIL";
|
||||
nerrs++;
|
||||
} else {
|
||||
result = "OK";
|
||||
}
|
||||
printf("[%s]\tInvalidate GS with set_thread_area: new GS = 0x%hx\n",
|
||||
result, sel);
|
||||
|
||||
#ifdef __x86_64__
|
||||
if (sel == 0 && new_base != 0) {
|
||||
nerrs++;
|
||||
printf("[FAIL]\tNew GSBASE was 0x%lx\n", new_base);
|
||||
} else {
|
||||
printf("[OK]\tNew GSBASE was zero\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 1 && !strcmp(argv[0], "ldt_gdt_test_exec"))
|
||||
return finish_exec_test();
|
||||
|
||||
setup_counter_page();
|
||||
setup_low_user_desc();
|
||||
|
||||
do_simple_tests();
|
||||
|
||||
do_multicpu_tests();
|
||||
|
||||
do_exec_test();
|
||||
|
||||
test_gdt_invalidation();
|
||||
|
||||
return nerrs ? 1 : 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user