1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2023 Oxide Computer Company
14  */
15 
16 /*
17  * The purpose of this test is to go through and construct ucontext_t xsave
18  * states that we expect to be invalid and therefore cause setcontext(2) to
19  * fail. We only assume that %ymm state is present here with respect to writing
20  * invalid tests. As if this test runs at all, that will be present.
21  *
22  * This is structured a little differently as we expect this program to fail to
23  * execute and that libc will cause us to abort() if we don't correctly return
24  * from setcontext.
25  */
26 
27 #include <ucontext.h>
28 #include <err.h>
29 #include <stdlib.h>
30 #include <strings.h>
31 #include <errno.h>
32 #include <sys/sysmacros.h>
33 #include <sys/debug.h>
34 #include <sys/x86_archext.h>
35 #include <sys/mman.h>
36 #include <unistd.h>
37 
38 #include "xsave_util.h"
39 
40 static void *xsave_buf;
41 static volatile const char *curtest;
42 
43 static void
bad_success(void)44 bad_success(void)
45 {
46 	errx(EXIT_FAILURE, "TEST FAILED: %s setcontext, took us to success",
47 	    curtest);
48 }
49 
50 static void
test_bad_version(ucontext_t * ctx)51 test_bad_version(ucontext_t *ctx)
52 {
53 	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
54 	xc->ucx_vers = 23;
55 }
56 
57 static void
test_bad_length_small(ucontext_t * ctx)58 test_bad_length_small(ucontext_t *ctx)
59 {
60 	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
61 	xc->ucx_len = 0;
62 }
63 
64 static void
test_bad_length_large(ucontext_t * ctx)65 test_bad_length_large(ucontext_t *ctx)
66 {
67 	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
68 	xc->ucx_len = INT32_MAX;
69 }
70 
71 /*
72  * As this can run on multiple different systems, we explicitly use bit 8 which
73  * is reserved for a supervisor feature and so should never be valid in this
74  * context.
75  */
76 static void
test_bad_vector(ucontext_t * ctx)77 test_bad_vector(ucontext_t *ctx)
78 {
79 	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
80 	xc->ucx_bv |= (1 << 8);
81 }
82 
83 static void
test_context_too_short(ucontext_t * ctx)84 test_context_too_short(ucontext_t *ctx)
85 {
86 	uc_xsave_t *xc = (uc_xsave_t *)ctx->uc_xsave;
87 
88 	bcopy(xc, xsave_buf, xc->ucx_len);
89 	ctx->uc_xsave = (long)(uintptr_t)xsave_buf;
90 	xc = (uc_xsave_t *)ctx->uc_xsave;
91 	xc->ucx_bv |= XFEATURE_AVX;
92 	xc->ucx_len = sizeof (uc_xsave_t) + 0x10;
93 }
94 
95 static void
test_context_badptr0(ucontext_t * ctx)96 test_context_badptr0(ucontext_t *ctx)
97 {
98 	ctx->uc_xsave = 0;
99 }
100 
101 static void
test_context_badptr1(ucontext_t * ctx)102 test_context_badptr1(ucontext_t *ctx)
103 {
104 	void *addr = mmap(NULL, sysconf(_SC_PAGESIZE), PROT_NONE,
105 	    MAP_PRIVATE | MAP_ANON, -1, 0);
106 	if (addr == NULL) {
107 		err(EXIT_FAILURE, "failed to get unmapped page");
108 	}
109 
110 	ctx->uc_xsave = (long)(uintptr_t)addr;
111 }
112 
113 static void
test_context_badptr2(ucontext_t * ctx)114 test_context_badptr2(ucontext_t *ctx)
115 {
116 	long pgsz = sysconf(_SC_PAGESIZE);
117 	void *addr = mmap(NULL, pgsz * 2, PROT_NONE, MAP_PRIVATE | MAP_ANON,
118 	    -1, 0);
119 	if (addr == NULL) {
120 		errx(EXIT_FAILURE, "failed to get unmapped page");
121 	}
122 
123 	if (mprotect((void *)((uintptr_t)addr + pgsz), pgsz, PROT_NONE) != 0) {
124 		err(EXIT_FAILURE, "failed to mprotect second page");
125 	}
126 
127 	ctx->uc_xsave = (uintptr_t)addr;
128 	ctx->uc_xsave += pgsz - sizeof (uint64_t);
129 }
130 
131 static ucontext_t *
setup_context(void)132 setup_context(void)
133 {
134 	ucontext_t *ctx = ucontext_alloc(0);
135 	if (ctx == NULL) {
136 		errx(EXIT_FAILURE, "failed to get allocate ucontext_t");
137 	}
138 
139 	if (getcontext_extd(ctx, 0) != 0) {
140 		err(EXIT_FAILURE, "failed to get extended context");
141 	}
142 	xsu_ustack_alloc(ctx);
143 	makecontext(ctx, bad_success, 0);
144 
145 	return (ctx);
146 }
147 
148 typedef struct {
149 	void (*bct_func)(ucontext_t *);
150 	const char *bct_test;
151 	int bct_errno;
152 } bad_ucontext_test_t;
153 
154 /*
155  * Do not use single quote characters in tests below, that'll break the shell
156  * wrapper.
157  */
158 static const bad_ucontext_test_t tests[] = {
159 	{ test_bad_version, "invalid version", EINVAL },
160 	{ test_bad_length_small, "invalid length (small)", EINVAL },
161 	{ test_bad_length_large, "invalid length (large)", EINVAL },
162 	{ test_bad_vector, "invalid xbv", EINVAL },
163 	{ test_context_too_short, "length does not cover AVX", EOVERFLOW },
164 	{ test_context_badptr0, "invalid uc_xsave pointer (NULL)", EINVAL },
165 	{ test_context_badptr1, "invalid uc_xsave pointer (unmapped page)",
166 	    EFAULT },
167 	{ test_context_badptr2, "partially invalid uc_xsave (hit "
168 	    "unmapped page)", EFAULT },
169 };
170 
171 int
main(int argc,char * argv[])172 main(int argc, char *argv[])
173 {
174 	int c;
175 	char *eptr;
176 	unsigned long l;
177 	const char *testno = NULL;
178 	boolean_t do_info = B_FALSE, do_run = B_FALSE;
179 
180 	if (argc < 2) {
181 		(void) fprintf(stderr, "Usage:  %s [-c] [-i testno] "
182 		    "[-r testno]\n", argv[0]);
183 	}
184 
185 	while ((c = getopt(argc, argv, ":ci:r:")) != -1) {
186 		switch (c) {
187 		case 'c':
188 			(void) printf("%zu\n", ARRAY_SIZE(tests));
189 			return (0);
190 		case 'i':
191 			testno = optarg;
192 			do_info = B_TRUE;
193 			break;
194 		case 'r':
195 			testno = optarg;
196 			do_run = B_TRUE;
197 			break;
198 		case ':':
199 			errx(EXIT_FAILURE, "Option -%c requires an operand\n",
200 			    optopt);
201 			break;
202 		case '?':
203 			errx(EXIT_FAILURE, "Unknown option: -%c\n", optopt);
204 			break;
205 		}
206 	}
207 
208 	if (testno == NULL) {
209 		errx(EXIT_FAILURE, "one of -r and -i must be specified");
210 	}
211 
212 	if (do_run && do_info) {
213 		errx(EXIT_FAILURE, "only one of -r and -i may be specified");
214 	}
215 
216 	errno = 0;
217 	l = strtoul(testno, &eptr, 0);
218 	if (*eptr != 0 || errno != 0) {
219 		errx(EXIT_FAILURE, "failed to parse test number: %s", argv[1]);
220 	}
221 
222 	if (l >= ARRAY_SIZE(tests)) {
223 		errx(EXIT_FAILURE, "test number %lu is too large\n", l);
224 	}
225 
226 	if (do_info) {
227 		/*
228 		 * Output info for our wrapper shell script in a way that's not
229 		 * too bad to eval.
230 		 */
231 		(void) printf("errno=%u\ndesc='%s'\n", tests[l].bct_errno,
232 		    tests[l].bct_test);
233 		return (0);
234 	}
235 
236 	/*
237 	 * This is a little gross, but we know right now that the extended
238 	 * context is going to be the approximate size that we need for
239 	 * operations on the system.
240 	 */
241 	xsave_buf = ucontext_alloc(0);
242 	if (xsave_buf == NULL) {
243 		err(EXIT_FAILURE, "failed to alternative xsave buf");
244 	}
245 
246 	ucontext_t *ctx = setup_context();
247 	VERIFY3U(ctx->uc_xsave, !=, 0);
248 	curtest = tests[l].bct_test;
249 	tests[l].bct_func(ctx);
250 	(void) setcontext(ctx);
251 	errx(EXIT_FAILURE, "TEST FAILED: setcontext returned despite us "
252 	    "expecting a core");
253 }
254