Commit d63f4b52 authored by Andy Lutomirski's avatar Andy Lutomirski Committed by Ingo Molnar
Browse files

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: default avatarAndy 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: default avatarIngo Molnar <mingo@kernel.org>
parent c9867f86
Loading
Loading
Loading
Loading
+250 −0
Original line number Diff line number Diff line
@@ -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;
}