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 29 static void sigsegv(int sig, siginfo_t *si, void *ctx_void) 30 { 31 siglongjmp(jmpbuf, 1); 32 } 33 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 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 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 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 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 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 */ 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 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