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