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 static int nerrs = 0; 24 25 static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), 26 int flags) 27 { 28 struct sigaction sa; 29 memset(&sa, 0, sizeof(sa)); 30 sa.sa_sigaction = handler; 31 sa.sa_flags = SA_SIGINFO | flags; 32 sigemptyset(&sa.sa_mask); 33 if (sigaction(sig, &sa, 0)) 34 err(1, "sigaction"); 35 36 } 37 38 static void clearhandler(int sig) 39 { 40 struct sigaction sa; 41 memset(&sa, 0, sizeof(sa)); 42 sa.sa_handler = SIG_DFL; 43 sigemptyset(&sa.sa_mask); 44 if (sigaction(sig, &sa, 0)) 45 err(1, "sigaction"); 46 } 47 48 static jmp_buf jmpbuf; 49 50 static void sigsegv(int sig, siginfo_t *si, void *ctx_void) 51 { 52 siglongjmp(jmpbuf, 1); 53 } 54 55 static bool try_outb(unsigned short port) 56 { 57 sethandler(SIGSEGV, sigsegv, SA_RESETHAND); 58 if (sigsetjmp(jmpbuf, 1) != 0) { 59 return false; 60 } else { 61 asm volatile ("outb %%al, %w[port]" 62 : : [port] "Nd" (port), "a" (0)); 63 return true; 64 } 65 clearhandler(SIGSEGV); 66 } 67 68 static void expect_ok_outb(unsigned short port) 69 { 70 if (!try_outb(port)) { 71 printf("[FAIL]\toutb to 0x%02hx failed\n", port); 72 exit(1); 73 } 74 75 printf("[OK]\toutb to 0x%02hx worked\n", port); 76 } 77 78 static void expect_gp_outb(unsigned short port) 79 { 80 if (try_outb(port)) { 81 printf("[FAIL]\toutb to 0x%02hx worked\n", port); 82 nerrs++; 83 } 84 85 printf("[OK]\toutb to 0x%02hx failed\n", port); 86 } 87 88 static bool try_cli(void) 89 { 90 sethandler(SIGSEGV, sigsegv, SA_RESETHAND); 91 if (sigsetjmp(jmpbuf, 1) != 0) { 92 return false; 93 } else { 94 asm volatile ("cli"); 95 return true; 96 } 97 clearhandler(SIGSEGV); 98 } 99 100 static bool try_sti(void) 101 { 102 sethandler(SIGSEGV, sigsegv, SA_RESETHAND); 103 if (sigsetjmp(jmpbuf, 1) != 0) { 104 return false; 105 } else { 106 asm volatile ("sti"); 107 return true; 108 } 109 clearhandler(SIGSEGV); 110 } 111 112 static void expect_gp_sti(void) 113 { 114 if (try_sti()) { 115 printf("[FAIL]\tSTI worked\n"); 116 nerrs++; 117 } else { 118 printf("[OK]\tSTI faulted\n"); 119 } 120 } 121 122 static void expect_gp_cli(void) 123 { 124 if (try_cli()) { 125 printf("[FAIL]\tCLI worked\n"); 126 nerrs++; 127 } else { 128 printf("[OK]\tCLI faulted\n"); 129 } 130 } 131 132 int main(void) 133 { 134 cpu_set_t cpuset; 135 136 CPU_ZERO(&cpuset); 137 CPU_SET(0, &cpuset); 138 if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) 139 err(1, "sched_setaffinity to CPU 0"); 140 141 /* Probe for iopl support. Note that iopl(0) works even as nonroot. */ 142 switch(iopl(3)) { 143 case 0: 144 break; 145 case -ENOSYS: 146 printf("[OK]\tiopl() nor supported\n"); 147 return 0; 148 default: 149 printf("[OK]\tiopl(3) failed (%d) -- try running as root\n", 150 errno); 151 return 0; 152 } 153 154 /* Make sure that CLI/STI are blocked even with IOPL level 3 */ 155 expect_gp_cli(); 156 expect_gp_sti(); 157 expect_ok_outb(0x80); 158 159 /* Establish an I/O bitmap to test the restore */ 160 if (ioperm(0x80, 1, 1) != 0) 161 err(1, "ioperm(0x80, 1, 1) failed\n"); 162 163 /* Restore our original state prior to starting the fork test. */ 164 if (iopl(0) != 0) 165 err(1, "iopl(0)"); 166 167 /* 168 * Verify that IOPL emulation is disabled and the I/O bitmap still 169 * works. 170 */ 171 expect_ok_outb(0x80); 172 expect_gp_outb(0xed); 173 /* Drop the I/O bitmap */ 174 if (ioperm(0x80, 1, 0) != 0) 175 err(1, "ioperm(0x80, 1, 0) failed\n"); 176 177 pid_t child = fork(); 178 if (child == -1) 179 err(1, "fork"); 180 181 if (child == 0) { 182 printf("\tchild: set IOPL to 3\n"); 183 if (iopl(3) != 0) 184 err(1, "iopl"); 185 186 printf("[RUN]\tchild: write to 0x80\n"); 187 asm volatile ("outb %%al, $0x80" : : "a" (0)); 188 189 return 0; 190 } else { 191 int status; 192 if (waitpid(child, &status, 0) != child || 193 !WIFEXITED(status)) { 194 printf("[FAIL]\tChild died\n"); 195 nerrs++; 196 } else if (WEXITSTATUS(status) != 0) { 197 printf("[FAIL]\tChild failed\n"); 198 nerrs++; 199 } else { 200 printf("[OK]\tChild succeeded\n"); 201 } 202 } 203 204 printf("[RUN]\tparent: write to 0x80 (should fail)\n"); 205 206 expect_gp_outb(0x80); 207 expect_gp_cli(); 208 expect_gp_sti(); 209 210 /* Test the capability checks. */ 211 printf("\tiopl(3)\n"); 212 if (iopl(3) != 0) 213 err(1, "iopl(3)"); 214 215 printf("\tDrop privileges\n"); 216 if (setresuid(1, 1, 1) != 0) { 217 printf("[WARN]\tDropping privileges failed\n"); 218 goto done; 219 } 220 221 printf("[RUN]\tiopl(3) unprivileged but with IOPL==3\n"); 222 if (iopl(3) != 0) { 223 printf("[FAIL]\tiopl(3) should work if iopl is already 3 even if unprivileged\n"); 224 nerrs++; 225 } 226 227 printf("[RUN]\tiopl(0) unprivileged\n"); 228 if (iopl(0) != 0) { 229 printf("[FAIL]\tiopl(0) should work if iopl is already 3 even if unprivileged\n"); 230 nerrs++; 231 } 232 233 printf("[RUN]\tiopl(3) unprivileged\n"); 234 if (iopl(3) == 0) { 235 printf("[FAIL]\tiopl(3) should fail if when unprivileged if iopl==0\n"); 236 nerrs++; 237 } else { 238 printf("[OK]\tFailed as expected\n"); 239 } 240 241 done: 242 return nerrs ? 1 : 0; 243 } 244