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:
Andy Lutomirski 2016-04-26 12:23:31 -07:00 committed by Ingo Molnar
parent c9867f863e
commit d63f4b5269
1 changed files with 250 additions and 0 deletions

View File

@ -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;
}