xref: /openbsd/regress/sys/arch/amd64/vmm/vcpu.c (revision 6822f9c8)
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
main(int argc,char ** argv)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