1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * iopl.c - Test case for a Linux on Xen 64-bit bug
4  * Copyright (c) 2015 Andrew Lutomirski
5  */
6 
7 #define _GNU_SOURCE
8 #include <err.h>
9 #include <stdio.h>
10 #include <stdint.h>
11 #include <signal.h>
12 #include <setjmp.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <unistd.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <stdbool.h>
20 #include <sched.h>
21 #include <sys/io.h>
22 
23 #include "helpers.h"
24 
25 static int nerrs = 0;
26 
27 static jmp_buf jmpbuf;
28 
sigsegv(int sig,siginfo_t * si,void * ctx_void)29 static void sigsegv(int sig, siginfo_t *si, void *ctx_void)
30 {
31 	siglongjmp(jmpbuf, 1);
32 }
33 
try_outb(unsigned short port)34 static bool try_outb(unsigned short port)
35 {
36 	sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
37 	if (sigsetjmp(jmpbuf, 1) != 0) {
38 		return false;
39 	} else {
40 		asm volatile ("outb %%al, %w[port]"
41 			      : : [port] "Nd" (port), "a" (0));
42 		return true;
43 	}
44 	clearhandler(SIGSEGV);
45 }
46 
expect_ok_outb(unsigned short port)47 static void expect_ok_outb(unsigned short port)
48 {
49 	if (!try_outb(port)) {
50 		printf("[FAIL]\toutb to 0x%02hx failed\n", port);
51 		exit(1);
52 	}
53 
54 	printf("[OK]\toutb to 0x%02hx worked\n", port);
55 }
56 
expect_gp_outb(unsigned short port)57 static void expect_gp_outb(unsigned short port)
58 {
59 	if (try_outb(port)) {
60 		printf("[FAIL]\toutb to 0x%02hx worked\n", port);
61 		nerrs++;
62 	}
63 
64 	printf("[OK]\toutb to 0x%02hx failed\n", port);
65 }
66 
67 #define RET_FAULTED	0
68 #define RET_FAIL	1
69 #define RET_EMUL	2
70 
try_cli(void)71 static int try_cli(void)
72 {
73 	unsigned long flags;
74 
75 	sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
76 	if (sigsetjmp(jmpbuf, 1) != 0) {
77 		return RET_FAULTED;
78 	} else {
79 		asm volatile("cli; pushf; pop %[flags]"
80 				: [flags] "=rm" (flags));
81 
82 		/* X86_FLAGS_IF */
83 		if (!(flags & (1 << 9)))
84 			return RET_FAIL;
85 		else
86 			return RET_EMUL;
87 	}
88 	clearhandler(SIGSEGV);
89 }
90 
try_sti(bool irqs_off)91 static int try_sti(bool irqs_off)
92 {
93 	unsigned long flags;
94 
95 	sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
96 	if (sigsetjmp(jmpbuf, 1) != 0) {
97 		return RET_FAULTED;
98 	} else {
99 		asm volatile("sti; pushf; pop %[flags]"
100 				: [flags] "=rm" (flags));
101 
102 		/* X86_FLAGS_IF */
103 		if (irqs_off && (flags & (1 << 9)))
104 			return RET_FAIL;
105 		else
106 			return RET_EMUL;
107 	}
108 	clearhandler(SIGSEGV);
109 }
110 
expect_gp_sti(bool irqs_off)111 static void expect_gp_sti(bool irqs_off)
112 {
113 	int ret = try_sti(irqs_off);
114 
115 	switch (ret) {
116 	case RET_FAULTED:
117 		printf("[OK]\tSTI faulted\n");
118 		break;
119 	case RET_EMUL:
120 		printf("[OK]\tSTI NOPped\n");
121 		break;
122 	default:
123 		printf("[FAIL]\tSTI worked\n");
124 		nerrs++;
125 	}
126 }
127 
128 /*
129  * Returns whether it managed to disable interrupts.
130  */
test_cli(void)131 static bool test_cli(void)
132 {
133 	int ret = try_cli();
134 
135 	switch (ret) {
136 	case RET_FAULTED:
137 		printf("[OK]\tCLI faulted\n");
138 		break;
139 	case RET_EMUL:
140 		printf("[OK]\tCLI NOPped\n");
141 		break;
142 	default:
143 		printf("[FAIL]\tCLI worked\n");
144 		nerrs++;
145 		return true;
146 	}
147 
148 	return false;
149 }
150 
main(void)151 int main(void)
152 {
153 	cpu_set_t cpuset;
154 
155 	CPU_ZERO(&cpuset);
156 	CPU_SET(0, &cpuset);
157 	if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
158 		err(1, "sched_setaffinity to CPU 0");
159 
160 	/* Probe for iopl support.  Note that iopl(0) works even as nonroot. */
161 	switch(iopl(3)) {
162 	case 0:
163 		break;
164 	case -ENOSYS:
165 		printf("[OK]\tiopl() nor supported\n");
166 		return 0;
167 	default:
168 		printf("[OK]\tiopl(3) failed (%d) -- try running as root\n",
169 		       errno);
170 		return 0;
171 	}
172 
173 	/* Make sure that CLI/STI are blocked even with IOPL level 3 */
174 	expect_gp_sti(test_cli());
175 	expect_ok_outb(0x80);
176 
177 	/* Establish an I/O bitmap to test the restore */
178 	if (ioperm(0x80, 1, 1) != 0)
179 		err(1, "ioperm(0x80, 1, 1) failed\n");
180 
181 	/* Restore our original state prior to starting the fork test. */
182 	if (iopl(0) != 0)
183 		err(1, "iopl(0)");
184 
185 	/*
186 	 * Verify that IOPL emulation is disabled and the I/O bitmap still
187 	 * works.
188 	 */
189 	expect_ok_outb(0x80);
190 	expect_gp_outb(0xed);
191 	/* Drop the I/O bitmap */
192 	if (ioperm(0x80, 1, 0) != 0)
193 		err(1, "ioperm(0x80, 1, 0) failed\n");
194 
195 	pid_t child = fork();
196 	if (child == -1)
197 		err(1, "fork");
198 
199 	if (child == 0) {
200 		printf("\tchild: set IOPL to 3\n");
201 		if (iopl(3) != 0)
202 			err(1, "iopl");
203 
204 		printf("[RUN]\tchild: write to 0x80\n");
205 		asm volatile ("outb %%al, $0x80" : : "a" (0));
206 
207 		return 0;
208 	} else {
209 		int status;
210 		if (waitpid(child, &status, 0) != child ||
211 		    !WIFEXITED(status)) {
212 			printf("[FAIL]\tChild died\n");
213 			nerrs++;
214 		} else if (WEXITSTATUS(status) != 0) {
215 			printf("[FAIL]\tChild failed\n");
216 			nerrs++;
217 		} else {
218 			printf("[OK]\tChild succeeded\n");
219 		}
220 	}
221 
222 	printf("[RUN]\tparent: write to 0x80 (should fail)\n");
223 
224 	expect_gp_outb(0x80);
225 	expect_gp_sti(test_cli());
226 
227 	/* Test the capability checks. */
228 	printf("\tiopl(3)\n");
229 	if (iopl(3) != 0)
230 		err(1, "iopl(3)");
231 
232 	printf("\tDrop privileges\n");
233 	if (setresuid(1, 1, 1) != 0) {
234 		printf("[WARN]\tDropping privileges failed\n");
235 		goto done;
236 	}
237 
238 	printf("[RUN]\tiopl(3) unprivileged but with IOPL==3\n");
239 	if (iopl(3) != 0) {
240 		printf("[FAIL]\tiopl(3) should work if iopl is already 3 even if unprivileged\n");
241 		nerrs++;
242 	}
243 
244 	printf("[RUN]\tiopl(0) unprivileged\n");
245 	if (iopl(0) != 0) {
246 		printf("[FAIL]\tiopl(0) should work if iopl is already 3 even if unprivileged\n");
247 		nerrs++;
248 	}
249 
250 	printf("[RUN]\tiopl(3) unprivileged\n");
251 	if (iopl(3) == 0) {
252 		printf("[FAIL]\tiopl(3) should fail if when unprivileged if iopl==0\n");
253 		nerrs++;
254 	} else {
255 		printf("[OK]\tFailed as expected\n");
256 	}
257 
258 done:
259 	return nerrs ? 1 : 0;
260 }
261