1 /* $OpenBSD: vcpu.c,v 1.8 2024/08/23 12:56:26 anton Exp $ */ 2 3 /* 4 * Copyright (c) 2022 Dave Voutila <dv@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/ioctl.h> 21 #include <sys/mman.h> 22 23 #include <machine/specialreg.h> 24 #include <machine/vmmvar.h> 25 26 #include <dev/vmm/vmm.h> 27 28 #include <err.h> 29 #include <errno.h> 30 #include <fcntl.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 36 #define KIB 1024 37 #define MIB (1UL << 20) 38 #define GIB (1024 * MIB) 39 #define VMM_NODE "/dev/vmm" 40 41 #define LOW_MEM 0 42 #define UPPER_MEM 1 43 44 #define PCKBC_AUX 0x61 45 #define PCJR_DISKCTRL 0xF0 46 47 const char *VM_NAME = "regress"; 48 49 const uint8_t PUSHW_DX[] = { 0x66, 0x52 }; // pushw %dx 50 const uint8_t INS[] = { 0x6C }; // ins es:[di],dx 51 const uint8_t IN_PCJR[] = { 0xE4, 0xF0 }; // in 0xF0 52 53 /* Originally from vmd(8)'s vm.c */ 54 const struct vcpu_reg_state vcpu_init_flat16 = { 55 .vrs_gprs[VCPU_REGS_RFLAGS] = 0x2, 56 .vrs_gprs[VCPU_REGS_RIP] = 0xFFF0, 57 .vrs_gprs[VCPU_REGS_RDX] = PCKBC_AUX, /* Port used by INS */ 58 .vrs_gprs[VCPU_REGS_RSP] = 0x800, /* Set our stack in low mem. */ 59 .vrs_crs[VCPU_REGS_CR0] = 0x60000010, 60 .vrs_sregs[VCPU_REGS_CS] = { 0xF000, 0xFFFF, 0x0093, 0xFFFF0000}, 61 .vrs_sregs[VCPU_REGS_DS] = { 0x0, 0xFFFF, 0x0093, 0x0}, 62 .vrs_sregs[VCPU_REGS_ES] = { 0x0, 0xFFFF, 0x0093, 0x0}, 63 .vrs_sregs[VCPU_REGS_FS] = { 0x0, 0xFFFF, 0x0093, 0x0}, 64 .vrs_sregs[VCPU_REGS_GS] = { 0x0, 0xFFFF, 0x0093, 0x0}, 65 .vrs_sregs[VCPU_REGS_SS] = { 0x0, 0xFFFF, 0x0093, 0x0}, 66 .vrs_gdtr = { 0x0, 0xFFFF, 0x0082, 0x0}, 67 .vrs_idtr = { 0x0, 0xFFFF, 0x0082, 0x0}, 68 .vrs_sregs[VCPU_REGS_LDTR] = { 0x0, 0xFFFF, 0x0082, 0x0}, 69 .vrs_sregs[VCPU_REGS_TR] = { 0x0, 0xFFFF, 0x008B, 0x0}, 70 .vrs_drs[VCPU_REGS_DR6] = 0xFFFF0FF0, 71 .vrs_drs[VCPU_REGS_DR7] = 0x400, 72 .vrs_crs[VCPU_REGS_XCR0] = XFEATURE_X87, 73 }; 74 75 struct intr_handler { 76 uint16_t offset; 77 uint16_t segment; 78 }; 79 80 const struct intr_handler ivt[256] = { 81 [VMM_EX_GP] = { .segment = 0x0, .offset = 0x0B5D }, 82 }; 83 84 int 85 main(int argc, char **argv) 86 { 87 struct vm_create_params vcp; 88 struct vm_exit *exit = NULL; 89 struct vm_info_params vip; 90 struct vm_info_result *info = NULL, *ours = NULL; 91 struct vm_resetcpu_params vresetp; 92 struct vm_run_params vrunp; 93 struct vm_terminate_params vtp; 94 struct vm_sharemem_params vsp; 95 96 struct vm_mem_range *vmr; 97 int fd, ret = 1; 98 size_t i; 99 off_t off, reset = 0xFFFFFFF0, stack = 0x800; 100 void *p; 101 102 fd = open(VMM_NODE, O_RDWR); 103 if (fd == -1) 104 err(1, "open %s", VMM_NODE); 105 106 /* 107 * 1. Create our VM with 1 vcpu and 64 MiB of memory. 108 */ 109 memset(&vcp, 0, sizeof(vcp)); 110 strlcpy(vcp.vcp_name, VM_NAME, sizeof(vcp.vcp_name)); 111 vcp.vcp_ncpus = 1; 112 113 /* Split into two ranges, similar to how vmd(8) might do it. */ 114 vcp.vcp_nmemranges = 2; 115 vcp.vcp_memranges[LOW_MEM].vmr_gpa = 0x0; 116 vcp.vcp_memranges[LOW_MEM].vmr_size = 640 * KIB; 117 vcp.vcp_memranges[UPPER_MEM].vmr_size = (64 * MIB) - (640 * KIB); 118 vcp.vcp_memranges[UPPER_MEM].vmr_gpa = (4 * GIB) 119 - vcp.vcp_memranges[UPPER_MEM].vmr_size; 120 121 /* Allocate and Initialize our guest memory. */ 122 for (i = 0; i < vcp.vcp_nmemranges; i++) { 123 vmr = &vcp.vcp_memranges[i]; 124 if (vmr->vmr_size % 2 != 0) 125 errx(1, "memory ranges must be multiple of 2"); 126 127 p = mmap(NULL, vmr->vmr_size, PROT_READ | PROT_WRITE, 128 MAP_PRIVATE | MAP_ANON, -1, 0); 129 if (p == MAP_FAILED) 130 err(1, "mmap"); 131 132 vmr->vmr_va = (vaddr_t)p; 133 printf("created mapped region %zu: { gpa: 0x%08lx, size: %lu," 134 " hva: 0x%lx }\n", i, vmr->vmr_gpa, vmr->vmr_size, 135 vmr->vmr_va); 136 137 /* Fill with int3 instructions. */ 138 memset(p, 0xcc, vmr->vmr_size); 139 140 if (i == LOW_MEM) { 141 /* Write our IVT. */ 142 memcpy(p, &ivt, sizeof(ivt)); 143 144 /* 145 * Set up a #GP handler that does a read from a 146 * non-existent PC Jr. Disk Controller. 147 */ 148 p = (uint8_t*)((uint8_t*)p + 0xb5d); 149 memcpy(p, IN_PCJR, sizeof(IN_PCJR)); 150 } else { 151 /* 152 * Write our code to the reset vector: 153 * PUSHW %dx ; inits the stack 154 * INS dx, es:[di] ; read from port in dx 155 */ 156 off = reset - vmr->vmr_gpa; 157 p = (uint8_t*)p + off; 158 memcpy(p, PUSHW_DX, sizeof(PUSHW_DX)); 159 p = (uint8_t*)p + sizeof(PUSHW_DX); 160 memcpy(p, INS, sizeof(INS)); 161 } 162 } 163 164 if (ioctl(fd, VMM_IOC_CREATE, &vcp) == -1) 165 err(1, "VMM_IOC_CREATE"); 166 printf("created vm %d named \"%s\"\n", vcp.vcp_id, vcp.vcp_name); 167 168 /* 169 * 2. Check we can create shared memory mappings. 170 */ 171 memset(&vsp, 0, sizeof(vsp)); 172 vsp.vsp_nmemranges = vcp.vcp_nmemranges; 173 memcpy(&vsp.vsp_memranges, &vcp.vcp_memranges, 174 sizeof(vsp.vsp_memranges)); 175 vsp.vsp_vm_id = vcp.vcp_id; 176 177 /* Find some new va ranges... */ 178 for (i = 0; i < vsp.vsp_nmemranges; i++) { 179 vmr = &vsp.vsp_memranges[i]; 180 p = mmap(NULL, vmr->vmr_size, PROT_READ | PROT_WRITE, 181 MAP_PRIVATE | MAP_ANON, -1, 0); 182 if (p == MAP_FAILED) 183 err(1, "mmap"); 184 vmr->vmr_va = (vaddr_t)p; 185 } 186 187 /* Release our mappings so vmm can replace them. */ 188 for (i = 0; i < vsp.vsp_nmemranges; i++) { 189 vmr = &vsp.vsp_memranges[i]; 190 munmap((void*)vmr->vmr_va, vmr->vmr_size); 191 } 192 193 /* Perform the shared mapping. */ 194 if (ioctl(fd, VMM_IOC_SHAREMEM, &vsp) == -1) 195 err(1, "VMM_IOC_SHAREMEM"); 196 printf("created shared memory mappings\n"); 197 198 /* We should see our reset vector instructions in the new mappings. */ 199 for (i = 0; i < vsp.vsp_nmemranges; i++) { 200 vmr = &vsp.vsp_memranges[i]; 201 p = (void*)vmr->vmr_va; 202 203 if (i == LOW_MEM) { 204 /* Check if our IVT is there. */ 205 if (memcmp(&ivt, p, sizeof(ivt)) != 0) { 206 warnx("invalid ivt"); 207 goto out; 208 } 209 } else { 210 /* Check our code at the reset vector. */ 211 212 } 213 printf("checked shared region %zu: { gpa: 0x%08lx, size: %lu," 214 " hva: 0x%lx }\n", i, vmr->vmr_gpa, vmr->vmr_size, 215 vmr->vmr_va); 216 } 217 printf("validated shared memory mappings\n"); 218 219 /* 220 * 3. Check that our VM exists. 221 */ 222 memset(&vip, 0, sizeof(vip)); 223 vip.vip_size = 0; 224 info = NULL; 225 226 if (ioctl(fd, VMM_IOC_INFO, &vip) == -1) { 227 warn("VMM_IOC_INFO(1)"); 228 goto out; 229 } 230 231 if (vip.vip_size == 0) { 232 warn("no vms found"); 233 goto out; 234 } 235 236 info = malloc(vip.vip_size); 237 if (info == NULL) { 238 warn("malloc"); 239 goto out; 240 } 241 242 /* Second request that retrieves the VMs. */ 243 vip.vip_info = info; 244 if (ioctl(fd, VMM_IOC_INFO, &vip) == -1) { 245 warn("VMM_IOC_INFO(2)"); 246 goto out; 247 } 248 249 for (i = 0; i * sizeof(*info) < vip.vip_size; i++) { 250 if (info[i].vir_id == vcp.vcp_id) { 251 ours = &info[i]; 252 break; 253 } 254 } 255 if (ours == NULL) { 256 warn("failed to find vm %uz", vcp.vcp_id); 257 goto out; 258 } 259 260 if (ours->vir_id != vcp.vcp_id) { 261 warnx("expected vm id %uz, got %uz", vcp.vcp_id, ours->vir_id); 262 goto out; 263 } 264 if (strncmp(ours->vir_name, VM_NAME, strlen(VM_NAME)) != 0) { 265 warnx("expected vm name \"%s\", got \"%s\"", VM_NAME, 266 ours->vir_name); 267 goto out; 268 } 269 printf("found vm %d named \"%s\"\n", vcp.vcp_id, ours->vir_name); 270 ours = NULL; 271 272 /* 273 * 4. Reset our VCPU and initialize register state. 274 */ 275 memset(&vresetp, 0, sizeof(vresetp)); 276 vresetp.vrp_vm_id = vcp.vcp_id; 277 vresetp.vrp_vcpu_id = 0; /* XXX SP */ 278 memcpy(&vresetp.vrp_init_state, &vcpu_init_flat16, 279 sizeof(vcpu_init_flat16)); 280 281 if (ioctl(fd, VMM_IOC_RESETCPU, &vresetp) == -1) { 282 warn("VMM_IOC_RESETCPU"); 283 goto out; 284 } 285 printf("reset vcpu %d for vm %d\n", vresetp.vrp_vcpu_id, 286 vresetp.vrp_vm_id); 287 288 /* 289 * 5. Run the vcpu, expecting an immediate exit for IO assist. 290 */ 291 exit = malloc(sizeof(*exit)); 292 if (exit == NULL) { 293 warn("failed to allocate memory for vm_exit"); 294 goto out; 295 } 296 297 memset(&vrunp, 0, sizeof(vrunp)); 298 vrunp.vrp_exit = exit; 299 vrunp.vrp_vcpu_id = 0; /* XXX SP */ 300 vrunp.vrp_vm_id = vcp.vcp_id; 301 vrunp.vrp_irqready = 1; 302 303 if (ioctl(fd, VMM_IOC_RUN, &vrunp) == -1) { 304 warn("VMM_IOC_RUN"); 305 goto out; 306 } 307 308 if (vrunp.vrp_vm_id != vcp.vcp_id) { 309 warnx("expected vm id %uz, got %uz", vcp.vcp_id, 310 vrunp.vrp_vm_id); 311 goto out; 312 } 313 314 switch (vrunp.vrp_exit_reason) { 315 case SVM_VMEXIT_IOIO: 316 case VMX_EXIT_IO: 317 printf("vcpu %d on vm %d exited for io assist @ ip = 0x%llx, " 318 "cs.base = 0x%llx, ss.base = 0x%llx, rsp = 0x%llx\n", 319 vrunp.vrp_vcpu_id, vrunp.vrp_vm_id, 320 vrunp.vrp_exit->vrs.vrs_gprs[VCPU_REGS_RIP], 321 vrunp.vrp_exit->vrs.vrs_sregs[VCPU_REGS_CS].vsi_base, 322 vrunp.vrp_exit->vrs.vrs_sregs[VCPU_REGS_SS].vsi_base, 323 vrunp.vrp_exit->vrs.vrs_gprs[VCPU_REGS_RSP]); 324 break; 325 default: 326 warnx("unexpected vm exit reason: 0%04x", 327 vrunp.vrp_exit_reason); 328 goto out; 329 } 330 331 exit = vrunp.vrp_exit; 332 if (exit->vei.vei_port != PCKBC_AUX) { 333 warnx("expected io port to be PCKBC_AUX, got 0x%02x", 334 exit->vei.vei_port); 335 goto out; 336 } 337 if (exit->vei.vei_string != 1) { 338 warnx("expected string instruction (INS)"); 339 goto out; 340 } else 341 printf("got expected string instruction\n"); 342 343 /* Advance RIP? */ 344 printf("insn_len = %u\n", exit->vei.vei_insn_len); 345 exit->vrs.vrs_gprs[VCPU_REGS_RIP] += exit->vei.vei_insn_len; 346 347 /* 348 * Inject a #GP and see if we end up at our isr. 349 */ 350 vrunp.vrp_inject.vie_vector = VMM_EX_GP; 351 vrunp.vrp_inject.vie_errorcode = 0x11223344; 352 vrunp.vrp_inject.vie_type = VCPU_INJECT_EX; 353 printf("injecting exception 0x%x\n", vrunp.vrp_inject.vie_vector); 354 if (ioctl(fd, VMM_IOC_RUN, &vrunp) == -1) { 355 warn("VMM_IOC_RUN 2"); 356 goto out; 357 } 358 359 switch (vrunp.vrp_exit_reason) { 360 case SVM_VMEXIT_IOIO: 361 case VMX_EXIT_IO: 362 printf("vcpu %d on vm %d exited for io assist @ ip = 0x%llx, " 363 "cs.base = 0x%llx\n", vrunp.vrp_vcpu_id, vrunp.vrp_vm_id, 364 vrunp.vrp_exit->vrs.vrs_gprs[VCPU_REGS_RIP], 365 vrunp.vrp_exit->vrs.vrs_sregs[VCPU_REGS_CS].vsi_base); 366 break; 367 default: 368 warnx("unexpected vm exit reason: 0%04x", 369 vrunp.vrp_exit_reason); 370 goto out; 371 } 372 373 if (exit->vei.vei_port != PCJR_DISKCTRL) { 374 warnx("expected NMI handler to poke PCJR_DISKCTLR, got 0x%02x", 375 exit->vei.vei_port); 376 printf("rip = 0x%llx\n", exit->vrs.vrs_gprs[VCPU_REGS_RIP]); 377 goto out; 378 } 379 printf("exception handler called\n"); 380 381 /* 382 * If we made it here, we're close to passing. Any failures during 383 * cleanup will reset ret back to non-zero. 384 */ 385 ret = 0; 386 387 out: 388 printf("--- RESET VECTOR @ gpa 0x%llx ---\n", reset); 389 for (i=0; i<10; i++) { 390 if (i > 0) 391 printf(" "); 392 printf("%02x", *(uint8_t*) 393 (vsp.vsp_memranges[UPPER_MEM].vmr_va + off + i)); 394 } 395 printf("\n--- STACK @ gpa 0x%llx ---\n", stack); 396 for (i=0; i<16; i++) { 397 if (i > 0) 398 printf(" "); 399 printf("%02x", *(uint8_t*)(vsp.vsp_memranges[LOW_MEM].vmr_va 400 + stack - i - 1)); 401 } 402 printf("\n"); 403 404 /* 405 * 6. Terminate our VM and clean up. 406 */ 407 memset(&vtp, 0, sizeof(vtp)); 408 vtp.vtp_vm_id = vcp.vcp_id; 409 if (ioctl(fd, VMM_IOC_TERM, &vtp) == -1) { 410 warn("VMM_IOC_TERM"); 411 ret = 1; 412 } else 413 printf("terminated vm %d\n", vtp.vtp_vm_id); 414 415 close(fd); 416 free(info); 417 free(exit); 418 419 /* Unmap memory. */ 420 for (i = 0; i < vcp.vcp_nmemranges; i++) { 421 vmr = &vcp.vcp_memranges[i]; 422 if (vmr->vmr_va) { 423 if (munmap((void *)vmr->vmr_va, vmr->vmr_size)) { 424 warn("failed to unmap orginal region %zu @ hva " 425 "0x%lx", i, vmr->vmr_va); 426 ret = 1; 427 } else 428 printf("unmapped origin region %zu @ hva " 429 "0x%lx\n", i, vmr->vmr_va); 430 } 431 vmr = &vsp.vsp_memranges[i]; 432 if (vmr->vmr_va) { 433 if (munmap((void *)vmr->vmr_va, vmr->vmr_size)) { 434 warn("failed to unmap shared region %zu @ hva " 435 "0x%lx", i, vmr->vmr_va); 436 ret = 1; 437 } else 438 printf("unmapped shared region %zu @ hva " 439 "0x%lx\n", i, vmr->vmr_va); 440 } 441 } 442 443 return (ret); 444 } 445