summaryrefslogtreecommitdiffstats
path: root/segregs.c
blob: db11cd1b003365bd22b68996260baa59f5cb9ced (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*
 * 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;
}