1/*
2 * ioperm.c - Test case for ioperm(2)
3 * Copyright (c) 2015 Andrew Lutomirski
4 */
5
6#define _GNU_SOURCE
7#include <err.h>
8#include <stdio.h>
9#include <stdint.h>
10#include <signal.h>
11#include <setjmp.h>
12#include <stdlib.h>
13#include <string.h>
14#include <errno.h>
15#include <unistd.h>
16#include <sys/types.h>
17#include <sys/wait.h>
18#include <stdbool.h>
19#include <sched.h>
20#include <sys/io.h>
21
22static int nerrs = 0;
23
24static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
25		       int flags)
26{
27	struct sigaction sa;
28	memset(&sa, 0, sizeof(sa));
29	sa.sa_sigaction = handler;
30	sa.sa_flags = SA_SIGINFO | flags;
31	sigemptyset(&sa.sa_mask);
32	if (sigaction(sig, &sa, 0))
33		err(1, "sigaction");
34
35}
36
37static void clearhandler(int sig)
38{
39	struct sigaction sa;
40	memset(&sa, 0, sizeof(sa));
41	sa.sa_handler = SIG_DFL;
42	sigemptyset(&sa.sa_mask);
43	if (sigaction(sig, &sa, 0))
44		err(1, "sigaction");
45}
46
47static jmp_buf jmpbuf;
48
49static void sigsegv(int sig, siginfo_t *si, void *ctx_void)
50{
51	siglongjmp(jmpbuf, 1);
52}
53
54static bool try_outb(unsigned short port)
55{
56	sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
57	if (sigsetjmp(jmpbuf, 1) != 0) {
58		return false;
59	} else {
60		asm volatile ("outb %%al, %w[port]"
61			      : : [port] "Nd" (port), "a" (0));
62		return true;
63	}
64	clearhandler(SIGSEGV);
65}
66
67static void expect_ok(unsigned short port)
68{
69	if (!try_outb(port)) {
70		printf("[FAIL]\toutb to 0x%02hx failed\n", port);
71		exit(1);
72	}
73
74	printf("[OK]\toutb to 0x%02hx worked\n", port);
75}
76
77static void expect_gp(unsigned short port)
78{
79	if (try_outb(port)) {
80		printf("[FAIL]\toutb to 0x%02hx worked\n", port);
81		exit(1);
82	}
83
84	printf("[OK]\toutb to 0x%02hx failed\n", port);
85}
86
87int main(void)
88{
89	cpu_set_t cpuset;
90	CPU_ZERO(&cpuset);
91	CPU_SET(0, &cpuset);
92	if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
93		err(1, "sched_setaffinity to CPU 0");
94
95	expect_gp(0x80);
96	expect_gp(0xed);
97
98	/*
99	 * Probe for ioperm support.  Note that clearing ioperm bits
100	 * works even as nonroot.
101	 */
102	printf("[RUN]\tenable 0x80\n");
103	if (ioperm(0x80, 1, 1) != 0) {
104		printf("[OK]\tioperm(0x80, 1, 1) failed (%d) -- try running as root\n",
105		       errno);
106		return 0;
107	}
108	expect_ok(0x80);
109	expect_gp(0xed);
110
111	printf("[RUN]\tdisable 0x80\n");
112	if (ioperm(0x80, 1, 0) != 0) {
113		printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno);
114		return 1;
115	}
116	expect_gp(0x80);
117	expect_gp(0xed);
118
119	/* Make sure that fork() preserves ioperm. */
120	if (ioperm(0x80, 1, 1) != 0) {
121		printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno);
122		return 1;
123	}
124
125	pid_t child = fork();
126	if (child == -1)
127		err(1, "fork");
128
129	if (child == 0) {
130		printf("[RUN]\tchild: check that we inherited permissions\n");
131		expect_ok(0x80);
132		expect_gp(0xed);
133		return 0;
134	} else {
135		int status;
136		if (waitpid(child, &status, 0) != child ||
137		    !WIFEXITED(status)) {
138			printf("[FAIL]\tChild died\n");
139			nerrs++;
140		} else if (WEXITSTATUS(status) != 0) {
141			printf("[FAIL]\tChild failed\n");
142			nerrs++;
143		} else {
144			printf("[OK]\tChild succeeded\n");
145		}
146	}
147
148	/* Test the capability checks. */
149
150	printf("\tDrop privileges\n");
151	if (setresuid(1, 1, 1) != 0) {
152		printf("[WARN]\tDropping privileges failed\n");
153		return 0;
154	}
155
156	printf("[RUN]\tdisable 0x80\n");
157	if (ioperm(0x80, 1, 0) != 0) {
158		printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno);
159		return 1;
160	}
161	printf("[OK]\tit worked\n");
162
163	printf("[RUN]\tenable 0x80 again\n");
164	if (ioperm(0x80, 1, 1) == 0) {
165		printf("[FAIL]\tit succeeded but should have failed.\n");
166		return 1;
167	}
168	printf("[OK]\tit failed\n");
169	return 0;
170}
171