blob: db11cd1b003365bd22b68996260baa59f5cb9ced [file] [log] [blame]
/*
* Copyright (c) 2014-2015 Andy Lutomirski
* GPL v2
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <err.h>
#include <asm/ldt.h>
#include <sys/syscall.h>
static unsigned short GDT3(int idx)
{
return (idx << 3) | 3;
}
static unsigned short LDT3(int idx)
{
return (idx << 3) | 7;
}
static int create_tls(int idx, unsigned int base)
{
struct user_desc desc = {
.entry_number = idx,
.base_addr = base,
.limit = 0xfffff,
.seg_32bit = 1,
.contents = 0, /* Data, grow-up */
.read_exec_only = 0,
.limit_in_pages = 1,
.seg_not_present = 0,
.useable = 0,
};
if (syscall(SYS_set_thread_area, &desc) != 0)
err(1, "set_thread_area");
return desc.entry_number;
}
static void do_gs_test(int idx, int np)
{
unsigned short orig_gs, new_gs;
int ax;
/*
* Install a valid LDT entry (set_thread_area's hardening measures
* defeat the #NP part of this test).
*/
struct user_desc desc;
memset(&desc, 0, sizeof(desc));
desc.entry_number = 0;
desc.limit = 0xffff;
desc.seg_32bit = 1;
desc.contents = 0; /* Data, grow-up */
desc.read_exec_only = 0;
desc.limit_in_pages = 1;
desc.seg_not_present = 0;
if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) != 0)
err(1, "modify_ldt");
if (np) {
/* valid but not present */
desc.seg_not_present = 1;
} else {
/* "empty" user_desc -> destroy the segment. */
desc.limit = 0;
desc.seg_32bit = 0;
desc.limit_in_pages = 0;
desc.read_exec_only = 1;
desc.seg_not_present = 1;
}
struct timespec req = {
.tv_sec = 0,
.tv_nsec = 100000,
};
printf("[RUN]\tGS %s\n", (np ? "not present" : "deleted"));
asm volatile ("mov %%gs,%0" : "=rm" (orig_gs));
asm volatile ("mov %0,%%gs" : : "rm" (LDT3(0)));
asm volatile ("int $0x80"
: "=a" (ax)
: "a" (SYS_modify_ldt), "b" (1), "c" (&desc),
"d" (sizeof(desc)));
if (ax != 0)
err(1, "set_thread_area");
/*
* Force rescheduling. On 32-bit kernels, fast syscalls
* destroy DS and ES, so force int 80.
*/
asm volatile ("int $0x80"
: "=a" (ax)
: "a" (SYS_nanosleep), "b" (&req),
"c" (0));
asm volatile ("mov %%gs,%0" : "=rm" (new_gs));
asm volatile ("mov %0,%%gs" : : "rm" (orig_gs));
printf("[OK]\tGS changed from %x to %x\n", (unsigned)GDT3(idx),
(unsigned)new_gs);
}
int main()
{
int ret;
int idx = create_tls(-1, 0);
printf("Allocated GDT index %d\n", idx);
unsigned short orig_es;
asm volatile ("mov %%es,%0" : "=rm" (orig_es));
int errors = 0;
int total = 1000;
for (int i = 0; i < total; i++) {
struct timespec req = {
.tv_sec = 0,
.tv_nsec = 100000,
};
int ret;
asm volatile ("mov %0,%%es" : : "rm" (GDT3(idx)));
/*
* Force rescheduling. On 32-bit kernels, fast syscalls
* destroy DS and ES, so force int 80.
*/
asm volatile ("int $0x80"
: "=a" (ret)
: "a" (SYS_nanosleep), "b" (&req),
"c" (0));
unsigned short es;
asm volatile ("mov %%es,%0" : "=rm" (es));
asm volatile ("mov %0,%%es" : : "rm" (orig_es));
if (es != GDT3(idx)) {
if (errors == 0)
printf("[FAIL]\tES changed from 0x%hx to 0x%hx\n",
GDT3(idx), es);
errors++;
}
}
if (errors) {
printf("[FAIL]\tES was corrupted %d/%d times\n", errors, total);
ret = 1;
} else {
printf("[OK]\tES was preserved\n");
ret = 0;
}
do_gs_test(idx, 1);
create_tls(idx, 0);
do_gs_test(idx, 0);
return ret;
}