282 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * iopl.c - Test case for a Linux on Xen 64-bit bug4 * Copyright (c) 2015 Andrew Lutomirski5 */6 7#define _GNU_SOURCE8#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 23static int nerrs = 0;24 25static 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 38static 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 48static jmp_buf jmpbuf;49 50static void sigsegv(int sig, siginfo_t *si, void *ctx_void)51{52 siglongjmp(jmpbuf, 1);53}54 55static 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 68static 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 78static 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#define RET_FAULTED 089#define RET_FAIL 190#define RET_EMUL 291 92static int try_cli(void)93{94 unsigned long flags;95 96 sethandler(SIGSEGV, sigsegv, SA_RESETHAND);97 if (sigsetjmp(jmpbuf, 1) != 0) {98 return RET_FAULTED;99 } else {100 asm volatile("cli; pushf; pop %[flags]"101 : [flags] "=rm" (flags));102 103 /* X86_FLAGS_IF */104 if (!(flags & (1 << 9)))105 return RET_FAIL;106 else107 return RET_EMUL;108 }109 clearhandler(SIGSEGV);110}111 112static int try_sti(bool irqs_off)113{114 unsigned long flags;115 116 sethandler(SIGSEGV, sigsegv, SA_RESETHAND);117 if (sigsetjmp(jmpbuf, 1) != 0) {118 return RET_FAULTED;119 } else {120 asm volatile("sti; pushf; pop %[flags]"121 : [flags] "=rm" (flags));122 123 /* X86_FLAGS_IF */124 if (irqs_off && (flags & (1 << 9)))125 return RET_FAIL;126 else127 return RET_EMUL;128 }129 clearhandler(SIGSEGV);130}131 132static void expect_gp_sti(bool irqs_off)133{134 int ret = try_sti(irqs_off);135 136 switch (ret) {137 case RET_FAULTED:138 printf("[OK]\tSTI faulted\n");139 break;140 case RET_EMUL:141 printf("[OK]\tSTI NOPped\n");142 break;143 default:144 printf("[FAIL]\tSTI worked\n");145 nerrs++;146 }147}148 149/*150 * Returns whether it managed to disable interrupts.151 */152static bool test_cli(void)153{154 int ret = try_cli();155 156 switch (ret) {157 case RET_FAULTED:158 printf("[OK]\tCLI faulted\n");159 break;160 case RET_EMUL:161 printf("[OK]\tCLI NOPped\n");162 break;163 default:164 printf("[FAIL]\tCLI worked\n");165 nerrs++;166 return true;167 }168 169 return false;170}171 172int main(void)173{174 cpu_set_t cpuset;175 176 CPU_ZERO(&cpuset);177 CPU_SET(0, &cpuset);178 if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)179 err(1, "sched_setaffinity to CPU 0");180 181 /* Probe for iopl support. Note that iopl(0) works even as nonroot. */182 switch(iopl(3)) {183 case 0:184 break;185 case -ENOSYS:186 printf("[OK]\tiopl() nor supported\n");187 return 0;188 default:189 printf("[OK]\tiopl(3) failed (%d) -- try running as root\n",190 errno);191 return 0;192 }193 194 /* Make sure that CLI/STI are blocked even with IOPL level 3 */195 expect_gp_sti(test_cli());196 expect_ok_outb(0x80);197 198 /* Establish an I/O bitmap to test the restore */199 if (ioperm(0x80, 1, 1) != 0)200 err(1, "ioperm(0x80, 1, 1) failed\n");201 202 /* Restore our original state prior to starting the fork test. */203 if (iopl(0) != 0)204 err(1, "iopl(0)");205 206 /*207 * Verify that IOPL emulation is disabled and the I/O bitmap still208 * works.209 */210 expect_ok_outb(0x80);211 expect_gp_outb(0xed);212 /* Drop the I/O bitmap */213 if (ioperm(0x80, 1, 0) != 0)214 err(1, "ioperm(0x80, 1, 0) failed\n");215 216 pid_t child = fork();217 if (child == -1)218 err(1, "fork");219 220 if (child == 0) {221 printf("\tchild: set IOPL to 3\n");222 if (iopl(3) != 0)223 err(1, "iopl");224 225 printf("[RUN]\tchild: write to 0x80\n");226 asm volatile ("outb %%al, $0x80" : : "a" (0));227 228 return 0;229 } else {230 int status;231 if (waitpid(child, &status, 0) != child ||232 !WIFEXITED(status)) {233 printf("[FAIL]\tChild died\n");234 nerrs++;235 } else if (WEXITSTATUS(status) != 0) {236 printf("[FAIL]\tChild failed\n");237 nerrs++;238 } else {239 printf("[OK]\tChild succeeded\n");240 }241 }242 243 printf("[RUN]\tparent: write to 0x80 (should fail)\n");244 245 expect_gp_outb(0x80);246 expect_gp_sti(test_cli());247 248 /* Test the capability checks. */249 printf("\tiopl(3)\n");250 if (iopl(3) != 0)251 err(1, "iopl(3)");252 253 printf("\tDrop privileges\n");254 if (setresuid(1, 1, 1) != 0) {255 printf("[WARN]\tDropping privileges failed\n");256 goto done;257 }258 259 printf("[RUN]\tiopl(3) unprivileged but with IOPL==3\n");260 if (iopl(3) != 0) {261 printf("[FAIL]\tiopl(3) should work if iopl is already 3 even if unprivileged\n");262 nerrs++;263 }264 265 printf("[RUN]\tiopl(0) unprivileged\n");266 if (iopl(0) != 0) {267 printf("[FAIL]\tiopl(0) should work if iopl is already 3 even if unprivileged\n");268 nerrs++;269 }270 271 printf("[RUN]\tiopl(3) unprivileged\n");272 if (iopl(3) == 0) {273 printf("[FAIL]\tiopl(3) should fail if when unprivileged if iopl==0\n");274 nerrs++;275 } else {276 printf("[OK]\tFailed as expected\n");277 }278 279done:280 return nerrs ? 1 : 0;281}282