summaryrefslogtreecommitdiff
path: root/usr/src/test/bhyve-tests/tests/memmap.c
blob: c5c2f179dabd2511219da5633ae37f3304814f28 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2022 Oxide Computer Company
 */


#include <stdio.h>
#include <unistd.h>
#include <stropts.h>
#include <strings.h>
#include <signal.h>
#include <setjmp.h>

#include <sys/vmm.h>
#include <sys/vmm_dev.h>
#include <sys/mman.h>
#include <vmmapi.h>

/* Half of a leaf page table is 256 pages */
#define	LOWER_SZ	(256 * 4096)
#define	UPPER_SZ	LOWER_SZ
#define	TOTAL_SZ	(LOWER_SZ + UPPER_SZ)

#define	LOWER_OFF	0
#define	UPPER_OFF	LOWER_SZ

#define	PROT_ALL	(PROT_READ | PROT_WRITE | PROT_EXEC)

enum test_memsegs {
	MSEG_LOW = 0,
	MSEG_HIGH = 1,
};

struct vmctx *
create_test_vm()
{
	char name[VM_MAX_NAMELEN];
	int res;

	(void) snprintf(name, sizeof (name), "bhyve-test-memmap-%d", getpid());

	res = vm_create(name, 0);
	if (res != 0) {
		return (NULL);
	}

	return (vm_open(name));
}

int
alloc_memseg(struct vmctx *ctx, int segid, size_t len, const char *name)
{
	struct vm_memseg memseg = {
		.segid = segid,
		.len = len,
	};
	(void) strlcpy(memseg.name, name, sizeof (memseg.name));

	int fd = vm_get_device_fd(ctx);

	return (ioctl(fd, VM_ALLOC_MEMSEG, &memseg));
}

static sigjmp_buf segv_env;

void
sigsegv_handler(int sig)
{
	siglongjmp(segv_env, 1);
}


int
main(int argc, char *argv[])
{
	struct vmctx *ctx;
	int res, fd;
	void *guest_mem;

	ctx = create_test_vm();
	fd = vm_get_device_fd(ctx);

	res = alloc_memseg(ctx, MSEG_LOW, LOWER_SZ, "mseg_low");
	if (res != 0) {
		perror("could not alloc low memseg");
		goto bail;
	}
	res = alloc_memseg(ctx, MSEG_HIGH, UPPER_SZ, "mseg_high");
	if (res != 0) {
		perror("could not alloc high memseg");
		goto bail;
	}


	res = vm_mmap_memseg(ctx, LOWER_OFF, MSEG_LOW, 0, LOWER_SZ, PROT_ALL);
	if (res != 0) {
		perror("could not map low memseg");
		goto bail;
	}
	res = vm_mmap_memseg(ctx, UPPER_OFF, MSEG_HIGH, 0, UPPER_SZ, PROT_ALL);
	if (res != 0) {
		perror("could not map high memseg");
		goto bail;
	}

	guest_mem = mmap(NULL, TOTAL_SZ, PROT_READ | PROT_WRITE, MAP_SHARED,
	    fd, 0);
	if (guest_mem == MAP_FAILED) {
		perror("could not mmap guest memory");
		goto bail;
	}

	/* Fill memory with 0xff */
	for (uintptr_t gpa = 0; gpa < TOTAL_SZ; gpa++) {
		uint8_t *ptr = guest_mem + gpa;
		*ptr = 0xff;
	}

	/* Unmap the lower memseg */
	res = vm_munmap_memseg(ctx, LOWER_OFF, LOWER_SZ);
	if (guest_mem == NULL) {
		perror("could not unmap lower memseg");
		goto bail;
	}

	/* Confirm upper contents are still correct/accessible */
	for (uintptr_t gpa = UPPER_OFF; gpa < UPPER_OFF + UPPER_SZ; gpa++) {
		uint8_t *ptr = guest_mem + gpa;
		if (*ptr != 0xff) {
			(void) printf("invalid mem contents at GPA %lx: %x\n",
			    gpa, *ptr);
			goto bail;
		}
		*ptr = 0xee;
	}

	/*
	 * Attempt to access the lower contents, which should result in an
	 * expected (and thus handled) SIGSEGV.
	 */
	struct sigaction sa = {
		.sa_handler = sigsegv_handler,
	};
	struct sigaction old_sa;
	res = sigaction(SIGSEGV, &sa, &old_sa);
	if (res != 0) {
		perror("could not prep signal handling for bad access");
		goto bail;
	}

	if (sigsetjmp(segv_env, 1) == 0) {
		volatile uint8_t *ptr = guest_mem;

		/*
		 * This access to the guest space should fail, since the memseg
		 * covering the lower part of the VM space has been unmapped.
		 */
		uint8_t tmp = *ptr;

		(void) printf("access to %p (%x) should have failed\n", tmp);
		goto bail;
	}

	/*
	 * Unmap and remap the space so any cached entries are dropped for the
	 * portion we expect is still accessible.
	 */
	res = munmap(guest_mem, TOTAL_SZ);
	if (res != 0) {
		perror("could not unmap lower memseg");
		goto bail;
	}
	guest_mem = mmap(NULL, TOTAL_SZ, PROT_READ | PROT_WRITE, MAP_SHARED,
	    fd, 0);
	if (guest_mem == MAP_FAILED) {
		perror("could not re-mmap guest memory");
		goto bail;
	}

	/* Check the upper portion for accessibility. */
	if (sigsetjmp(segv_env, 1) == 0) {
		volatile uint8_t *ptr = guest_mem + UPPER_OFF;

		uint8_t tmp = *ptr;
		if (tmp != 0xee) {
			(void) printf("unexpected value at %p (%x)\n", ptr,
			    tmp);
			goto bail;
		}

		res = sigaction(SIGSEGV, &old_sa, NULL);
		if (res != 0) {
			perror("could not restore SIGSEGV handler");
			goto bail;
		}
	} else {
		(void) printf("unexpected fault in upper mapping\n");
		goto bail;
	}


	/* Unmap the upper memseg */
	res = vm_munmap_memseg(ctx, UPPER_OFF, UPPER_SZ);
	if (guest_mem == NULL) {
		perror("could not unmap upper memseg");
		goto bail;
	}

	/* mission accomplished */
	vm_destroy(ctx);
	return (0);

bail:
	vm_destroy(ctx);
	return (1);
}