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 2024 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
should_eq_u32(const char * field_name,uint32_t a,uint32_t b)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
test_size_boundaries(int vmfd)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
test_vm_classes(int vmfd)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 .vdx_vcpuid = -1,
149 };
150
151 /* First do a read */
152 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
153 err(EXIT_FAILURE,
154 "VM_DATA_READ failed class:%u version:%u",
155 vdx.vdx_class, vdx.vdx_version);
156 }
157 if (vdx.vdx_class == VDC_VERSION ||
158 vdx.vdx_class == VDC_VMM_ARCH) {
159 /*
160 * Skip classes which contain some (or all) bits which
161 * are read-only.
162 */
163 continue;
164 }
165
166 /* Write the same data back */
167 vdx.vdx_len = vdx.vdx_result_len;
168 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
169 err(EXIT_FAILURE,
170 "VM_DATA_WRITE failed class:%u version:%u",
171 vdx.vdx_class, vdx.vdx_version);
172 }
173 }
174 free(buf);
175 }
176
177 static void
test_vcpu_classes(int vmfd)178 test_vcpu_classes(int vmfd)
179 {
180 const struct class_test_case cases[] = {
181 { VDC_MSR, 1 },
182 { VDC_LAPIC, 1 },
183 { VDC_VMM_ARCH, 1 },
184
185 /*
186 * Although these classes are per-vCPU, they have not yet been
187 * implemented in the vmm-data system, so are ignored for now:
188 *
189 * - VDC_REGISTER
190 * - VDC_FPU
191 * - VDC_LAPIC
192 */
193 };
194
195 /* A page should be large enough for all classes (for now) */
196 const size_t bufsz = PAGESIZE;
197 uint8_t *buf = malloc(bufsz);
198
199 for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) {
200 struct vm_data_xfer vdx = {
201 .vdx_class = cases[i].ctc_class,
202 .vdx_version = cases[i].ctc_version,
203 .vdx_len = bufsz,
204 .vdx_data = buf,
205 .vdx_vcpuid = 0,
206 };
207
208 /* First do a read */
209 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
210 err(EXIT_FAILURE,
211 "VM_DATA_READ failed class:%u version:%u",
212 vdx.vdx_class, vdx.vdx_version);
213 }
214
215 if (vdx.vdx_class == VDC_VMM_ARCH) {
216 /*
217 * There are some read-only fields in VMM_ARCH which we
218 * do not want to attempt to write back.
219 */
220 continue;
221 }
222
223 /* Write the same data back */
224 vdx.vdx_len = vdx.vdx_result_len;
225 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
226 err(EXIT_FAILURE,
227 "VM_DATA_WRITE failed class:%u version:%u",
228 vdx.vdx_class, vdx.vdx_version);
229 }
230 }
231 free(buf);
232 }
233
234 static void
test_bogus_class(int vmfd)235 test_bogus_class(int vmfd)
236 {
237 const size_t bufsz = PAGESIZE;
238 uint8_t *buf = malloc(bufsz);
239
240 struct vm_data_xfer vdx = {
241 .vdx_class = 10000,
242 .vdx_version = 1,
243 .vdx_len = bufsz,
244 .vdx_data = buf,
245 };
246
247 /* Try to read with an absurd data class */
248 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
249 errx(EXIT_FAILURE,
250 "VM_DATA_READ should fail for absurd vdx_class");
251 }
252
253 /* Same for data version */
254 vdx.vdx_class = VDC_VERSION;
255 vdx.vdx_version = 10000;
256 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
257 errx(EXIT_FAILURE,
258 "VM_DATA_READ should fail for absurd vdx_version");
259 }
260
261 free(buf);
262 }
263
264 static void
test_vcpuid_combos(int vmfd)265 test_vcpuid_combos(int vmfd)
266 {
267 const size_t bufsz = PAGESIZE;
268 uint8_t *buf = malloc(bufsz);
269
270 struct vm_data_xfer vdx = {
271 .vdx_class = VDC_LAPIC,
272 .vdx_version = 1,
273 .vdx_len = bufsz,
274 .vdx_data = buf,
275 };
276
277 /* Try with -1 sentinel, too-negative, and too-positive values */
278 const int bad_per_vcpu[] = { -1, -5, 1000 };
279 for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) {
280 vdx.vdx_vcpuid = bad_per_vcpu[i];
281 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
282 errx(EXIT_FAILURE,
283 "VM_DATA_READ should fail for bad vcpuid %d",
284 vdx.vdx_vcpuid);
285 }
286 }
287
288 /*
289 * Valid vcpuid should be fine still. Reading valid data into the
290 * buffer will be useful to subsequently test writes.
291 */
292 vdx.vdx_vcpuid = 0;
293 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
294 err(EXIT_FAILURE, "failed VM_DATA_READ with valid vcpuid");
295 }
296
297 /* Repeat the same checks for writes */
298 for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) {
299 vdx.vdx_vcpuid = bad_per_vcpu[i];
300 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
301 errx(EXIT_FAILURE,
302 "VM_DATA_WRITE should fail for bad vcpuid %d",
303 vdx.vdx_vcpuid);
304 }
305 }
306
307 vdx.vdx_vcpuid = 0;
308 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
309 err(EXIT_FAILURE, "failed VM_DATA_WRITE with valid vcpuid");
310 }
311
312 vdx.vdx_class = VDC_VERSION;
313 vdx.vdx_version = 1;
314
315 /*
316 * VM-wide classes should work fine with the -1 sentinel. For now,
317 * passing an otherwise valid vcpuid will still work, but that id is
318 * ignored.
319 */
320 const int good_vm_wide[] = { -1, 0, 1 };
321 for (uint_t i = 0; i < ARRAY_SIZE(good_vm_wide); i++) {
322 vdx.vdx_vcpuid = good_vm_wide[i];
323 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
324 err(EXIT_FAILURE,
325 "failed VM-wide VM_DATA_READ with vcpuid %d",
326 vdx.vdx_vcpuid);
327 }
328 }
329
330 /* Bogus values should still fail */
331 const int bad_vm_wide[] = { -5, 1000 };
332 for (uint_t i = 0; i < ARRAY_SIZE(bad_vm_wide); i++) {
333 vdx.vdx_vcpuid = bad_vm_wide[i];
334 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
335 errx(EXIT_FAILURE,
336 "VM_DATA_READ should fail for bad vcpuid %d",
337 vdx.vdx_vcpuid);
338 }
339 }
340
341 free(buf);
342 }
343
344 static void
test_vcpuid_time(int vmfd)345 test_vcpuid_time(int vmfd)
346 {
347 struct vdi_time_info_v1 data;
348 struct vm_data_xfer vdx = {
349 .vdx_class = VDC_VMM_TIME,
350 .vdx_version = 1,
351 .vdx_len = sizeof (data),
352 .vdx_data = &data,
353 };
354
355 /* This should work with the system-wide vcpuid */
356 vdx.vdx_vcpuid = -1;
357 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) {
358 err(EXIT_FAILURE, "VM_DATA_READ failed for valid vcpuid");
359 }
360
361 /* But fail for other vcpuids */
362 vdx.vdx_vcpuid = 0;
363 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) {
364 err(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d",
365 vdx.vdx_vcpuid);
366 }
367
368 /*
369 * Perform same check for writes
370 *
371 * Normally this would require care to handle hosts which lack frequency
372 * scaling functionality, but since we are writing back the same data,
373 * the guest frequency should match that of the host, requiring no real
374 * scaling be done for the instance.
375 */
376 vdx.vdx_vcpuid = -1;
377 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) {
378 err(EXIT_FAILURE, "VM_DATA_WRITE failed for valid vcpuid");
379 }
380 vdx.vdx_vcpuid = 0;
381 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
382 errx(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d",
383 vdx.vdx_vcpuid);
384 }
385 }
386
387 int
main(int argc,char * argv[])388 main(int argc, char *argv[])
389 {
390 const char *suite_name = basename(argv[0]);
391 struct vmctx *ctx;
392
393 ctx = create_test_vm(suite_name);
394 if (ctx == NULL) {
395 errx(EXIT_FAILURE, "could not open test VM");
396 }
397
398 /*
399 * Check that vmm_data import/export facility is robust in the face of
400 * potentially invalid inputs
401 */
402 const int vmfd = vm_get_device_fd(ctx);
403
404 /* Test varies edge cases around data transfer sizes */
405 test_size_boundaries(vmfd);
406
407 /* Check that known VM-wide data classes can be accessed */
408 test_vm_classes(vmfd);
409
410 /* Check that known per-vCPU data classes can be accessed */
411 test_vcpu_classes(vmfd);
412
413 /* Try some bogus class/version combos */
414 test_bogus_class(vmfd);
415
416 /* Try some weird vdx_vcpuid cases */
417 test_vcpuid_combos(vmfd);
418
419 /* VMM_TIME is picky about vcpuid */
420 test_vcpuid_time(vmfd);
421
422 vm_destroy(ctx);
423 (void) printf("%s\tPASS\n", suite_name);
424 return (EXIT_SUCCESS);
425 }
426