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 #include <stdio.h>
17 #include <unistd.h>
18 #include <stdlib.h>
19 #include <fcntl.h>
20 #include <libgen.h>
21 #include <sys/stat.h>
22 #include <errno.h>
23 #include <err.h>
24 #include <assert.h>
25 #include <sys/sysmacros.h>
26 #include <stdbool.h>
27 
28 #include <sys/vmm.h>
29 #include <sys/vmm_dev.h>
30 #include <sys/vmm_data.h>
31 #include <vmmapi.h>
32 
33 #include "common.h"
34 
35 static void
36 should_eq_u32(const char *field_name, uint32_t a, uint32_t b)
37 {
38 	if (a != b) {
39 		errx(EXIT_FAILURE, "unexpected %s %u != %u",
40 		    field_name, a, b);
41 	}
42 }
43 
44 static void
45 test_size_boundaries(int vmfd)
46 {
47 	uint8_t buf[sizeof (struct vdi_atpic_v1) + sizeof (int)];
48 	struct vm_data_xfer vdx = {
49 		.vdx_class = VDC_ATPIC,
50 		.vdx_version = 1,
51 		.vdx_len = sizeof (struct vdi_atpic_v1),
52 		.vdx_data = buf,
53 	};
54 
55 	/* Attempt a valid-sized read first */
56 	if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
57 		err(EXIT_FAILURE, "valid VM_DATA_READ failed");
58 	}
59 	should_eq_u32("vdx_result_len", vdx.vdx_result_len,
60 	    sizeof (struct vdi_atpic_v1));
61 
62 	/* And check that we can write it back */
63 	vdx.vdx_result_len = 0;
64 	if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
65 		err(EXIT_FAILURE, "valid VM_DATA_WRITE failed");
66 	}
67 
68 	/* ... then too-small ... */
69 	vdx.vdx_len = sizeof (struct vdi_atpic_v1) - sizeof (int);
70 	vdx.vdx_result_len = 0;
71 	if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
72 		errx(EXIT_FAILURE, "invalid VM_DATA_READ should have failed");
73 	}
74 	int error = errno;
75 	if (error != ENOSPC) {
76 		errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error);
77 	}
78 	/* the "correct" vdx_result_len should still be communicated out */
79 	should_eq_u32("vdx_result_len", vdx.vdx_result_len,
80 	    sizeof (struct vdi_atpic_v1));
81 
82 	/* Repeat with too-small write */
83 	vdx.vdx_result_len = 0;
84 	if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
85 		errx(EXIT_FAILURE, "invalid VM_DATA_WRITE should have failed");
86 	}
87 	error = errno;
88 	if (error != ENOSPC) {
89 		errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error);
90 	}
91 	should_eq_u32("vdx_result_len", vdx.vdx_result_len,
92 	    sizeof (struct vdi_atpic_v1));
93 
94 	/*
95 	 * ... and too-big to round it out.
96 	 *
97 	 * This should pass, but still set vdx_result_len to the actual length
98 	 */
99 	vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int);
100 	vdx.vdx_result_len = 0;
101 	if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
102 		err(EXIT_FAILURE, "too-large (but valid) VM_DATA_READ failed");
103 	}
104 	should_eq_u32("vdx_result_len", vdx.vdx_result_len,
105 	    sizeof (struct vdi_atpic_v1));
106 
107 	/* ... and repeated as a write */
108 	vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int);
109 	vdx.vdx_result_len = 0;
110 	if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
111 		err(EXIT_FAILURE,
112 		    "too-large (but valid) VM_DATA_WRITE failed");
113 	}
114 	should_eq_u32("vdx_result_len", vdx.vdx_result_len,
115 	    sizeof (struct vdi_atpic_v1));
116 }
117 
118 struct class_test_case {
119 	uint16_t	ctc_class;
120 	uint16_t	ctc_version;
121 };
122 
123 static void
124 test_vm_classes(int vmfd)
125 {
126 	const struct class_test_case cases[] = {
127 		{ VDC_VERSION, 1 },
128 		{ VDC_VMM_ARCH, 1 },
129 		{ VDC_IOAPIC, 1 },
130 		{ VDC_ATPIT, 1 },
131 		{ VDC_ATPIC, 1 },
132 		{ VDC_HPET, 1 },
133 		{ VDC_PM_TIMER, 1 },
134 		{ VDC_RTC, 2 },
135 		{ VDC_VMM_TIME, 1 },
136 	};
137 
138 	/* A page should be large enough for all classes (for now) */
139 	const size_t bufsz = PAGESIZE;
140 	uint8_t *buf = malloc(bufsz);
141 
142 	for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) {
143 		struct vm_data_xfer vdx = {
144 			.vdx_class = cases[i].ctc_class,
145 			.vdx_version = cases[i].ctc_version,
146 			.vdx_len = bufsz,
147 			.vdx_data = buf,
148 		};
149 
150 		/* First do a read */
151 		if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
152 			err(EXIT_FAILURE,
153 			    "VM_DATA_READ failed class:%u version:%u",
154 			    vdx.vdx_class, vdx.vdx_version);
155 		}
156 		if (vdx.vdx_class == VDC_VERSION ||
157 		    vdx.vdx_class == VDC_VMM_ARCH) {
158 			/*
159 			 * Skip classes which contain some (or all) bits which
160 			 * are read-only.
161 			 */
162 			continue;
163 		}
164 
165 		/* Write the same data back */
166 		vdx.vdx_len = vdx.vdx_result_len;
167 		if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
168 			err(EXIT_FAILURE,
169 			    "VM_DATA_WRITE failed class:%u version:%u",
170 			    vdx.vdx_class, vdx.vdx_version);
171 		}
172 	}
173 	free(buf);
174 }
175 
176 static void
177 test_vcpu_classes(int vmfd)
178 {
179 	const struct class_test_case cases[] = {
180 		{ VDC_MSR, 1 },
181 		{ VDC_LAPIC, 1 },
182 		{ VDC_VMM_ARCH, 1 },
183 
184 		/*
185 		 * Although these classes are per-vCPU, they have not yet been
186 		 * implemented in the vmm-data system, so are ignored for now:
187 		 *
188 		 * - VDC_REGISTER
189 		 * - VDC_FPU
190 		 * - VDC_LAPIC
191 		 */
192 	};
193 
194 	/* A page should be large enough for all classes (for now) */
195 	const size_t bufsz = PAGESIZE;
196 	uint8_t *buf = malloc(bufsz);
197 
198 	for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) {
199 		struct vm_data_xfer vdx = {
200 			.vdx_class = cases[i].ctc_class,
201 			.vdx_version = cases[i].ctc_version,
202 			.vdx_len = bufsz,
203 			.vdx_data = buf,
204 			.vdx_vcpuid = 0,
205 		};
206 
207 		/* First do a read */
208 		if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
209 			err(EXIT_FAILURE,
210 			    "VM_DATA_READ failed class:%u version:%u",
211 			    vdx.vdx_class, vdx.vdx_version);
212 		}
213 
214 		if (vdx.vdx_class == VDC_VMM_ARCH) {
215 			/*
216 			 * There are some read-only fields in VMM_ARCH which we
217 			 * do not want to attempt to write back.
218 			 */
219 			continue;
220 		}
221 
222 		/* Write the same data back */
223 		vdx.vdx_len = vdx.vdx_result_len;
224 		if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
225 			err(EXIT_FAILURE,
226 			    "VM_DATA_WRITE failed class:%u version:%u",
227 			    vdx.vdx_class, vdx.vdx_version);
228 		}
229 	}
230 	free(buf);
231 }
232 
233 static void
234 test_bogus_class(int vmfd)
235 {
236 	const size_t bufsz = PAGESIZE;
237 	uint8_t *buf = malloc(bufsz);
238 
239 	struct vm_data_xfer vdx = {
240 		.vdx_class = 10000,
241 		.vdx_version = 1,
242 		.vdx_len = bufsz,
243 		.vdx_data = buf,
244 	};
245 
246 	/* Try to read with an absurd data class */
247 	if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
248 		errx(EXIT_FAILURE,
249 		    "VM_DATA_READ should fail for absurd vdx_class");
250 	}
251 
252 	/* Same for data version */
253 	vdx.vdx_class = VDC_VERSION;
254 	vdx.vdx_version = 10000;
255 	if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
256 		errx(EXIT_FAILURE,
257 		    "VM_DATA_READ should fail for absurd vdx_version");
258 	}
259 
260 	free(buf);
261 }
262 
263 static void
264 test_vcpuid_combos(int vmfd)
265 {
266 	const size_t bufsz = PAGESIZE;
267 	uint8_t *buf = malloc(bufsz);
268 
269 	struct vm_data_xfer vdx = {
270 		.vdx_class = VDC_LAPIC,
271 		.vdx_version = 1,
272 		.vdx_len = bufsz,
273 		.vdx_data = buf,
274 	};
275 
276 	/* Try with -1 sentinel, too-negative, and too-positive values */
277 	const int bad_per_vcpu[] = { -1, -5, 1000 };
278 	for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) {
279 		vdx.vdx_vcpuid = bad_per_vcpu[i];
280 		if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
281 			errx(EXIT_FAILURE,
282 			    "VM_DATA_READ should fail for bad vcpuid %d",
283 			    vdx.vdx_vcpuid);
284 		}
285 	}
286 
287 	/*
288 	 * Valid vcpuid should be fine still.  Reading valid data into the
289 	 * buffer will be useful to subsequently test writes.
290 	 */
291 	vdx.vdx_vcpuid = 0;
292 	if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
293 		err(EXIT_FAILURE, "failed VM_DATA_READ with valid vcpuid");
294 	}
295 
296 	/* Repeat the same checks for writes */
297 	for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) {
298 		vdx.vdx_vcpuid = bad_per_vcpu[i];
299 		if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
300 			errx(EXIT_FAILURE,
301 			    "VM_DATA_WRITE should fail for bad vcpuid %d",
302 			    vdx.vdx_vcpuid);
303 		}
304 	}
305 
306 	vdx.vdx_vcpuid = 0;
307 	if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
308 		err(EXIT_FAILURE, "failed VM_DATA_WRITE with valid vcpuid");
309 	}
310 
311 	vdx.vdx_class = VDC_VERSION;
312 	vdx.vdx_version = 1;
313 
314 	/*
315 	 * VM-wide classes should work fine with the -1 sentinel.  For now,
316 	 * passing an otherwise valid vcpuid will still work, but that id is
317 	 * ignored.
318 	 */
319 	const int good_vm_wide[] = { -1, 0, 1 };
320 	for (uint_t i = 0; i < ARRAY_SIZE(good_vm_wide); i++) {
321 		vdx.vdx_vcpuid = good_vm_wide[i];
322 		if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
323 			err(EXIT_FAILURE,
324 			    "failed VM-wide VM_DATA_READ with vcpuid %d",
325 			    vdx.vdx_vcpuid);
326 		}
327 	}
328 
329 	/* Bogus values should still fail */
330 	const int bad_vm_wide[] = { -5, 1000 };
331 	for (uint_t i = 0; i < ARRAY_SIZE(bad_vm_wide); i++) {
332 		vdx.vdx_vcpuid = bad_vm_wide[i];
333 		if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
334 			errx(EXIT_FAILURE,
335 			    "VM_DATA_READ should fail for bad vcpuid %d",
336 			    vdx.vdx_vcpuid);
337 		}
338 	}
339 
340 	free(buf);
341 }
342 
343 int
344 main(int argc, char *argv[])
345 {
346 	const char *suite_name = basename(argv[0]);
347 	struct vmctx *ctx;
348 
349 	ctx = create_test_vm(suite_name);
350 	if (ctx == NULL) {
351 		errx(EXIT_FAILURE, "could not open test VM");
352 	}
353 
354 	/*
355 	 * Check that vmm_data import/export facility is robust in the face of
356 	 * potentially invalid inputs
357 	 */
358 	const int vmfd = vm_get_device_fd(ctx);
359 
360 	/* Test varies edge cases around data transfer sizes */
361 	test_size_boundaries(vmfd);
362 
363 	/* Check that known VM-wide data classes can be accessed */
364 	test_vm_classes(vmfd);
365 
366 	/* Check that known per-vCPU data classes can be accessed */
367 	test_vcpu_classes(vmfd);
368 
369 	/* Try some bogus class/version combos */
370 	test_bogus_class(vmfd);
371 
372 	/* Try some weird vdx_vcpuid cases */
373 	test_vcpuid_combos(vmfd);
374 
375 	vm_destroy(ctx);
376 	(void) printf("%s\tPASS\n", suite_name);
377 	return (EXIT_SUCCESS);
378 }
379