xref: /xnu-11417.121.6/tests/vm/test_vm_no_pager_helper.c (revision a1e26a70f38d1d7daa7b49b258e2f8538ad81650)
1 #include <errno.h>
2 #include <fcntl.h>
3 #include <signal.h>
4 #include <stdbool.h>
5 #include <stdarg.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <stdio.h>
9 #include <unistd.h>
10 
11 #include <sys/attr.h>
12 #include <sys/ioctl.h>
13 #include <sys/stat.h>
14 #include <sys/mman.h>
15 
16 #include <darwintest.h>
17 #include <darwintest_utils.h>
18 
19 #pragma clang diagnostic ignored "-Wformat-nonliteral"
20 
21 static int verbose = 0;
22 
23 #define PRINTF(...) \
24     do {            \
25 	if (verbose) { \
26 	        printf(__VA_ARGS__); \
27 	}                               \
28     } while (0)
29 
30 #define ASSERT(cond, ...)       \
31     if (!(cond)) {                \
32 	        printf(__VA_ARGS__); \
33     }                           \
34 
35 typedef struct {
36 	uint64_t graft_dir_id; // If this is 0, grafting will be performed on the parent directory of the graft file
37 	uint64_t flags;
38 } apfs_graft_params_t;
39 
40 #define APFSIOC_GRAFT_AN_FS _IOW('J', 99, apfs_graft_params_t)
41 #define APFSIOC_UNGRAFT_AN_FS _IOW('J', 100, uint64_t)
42 
43 #define MAIN_DIR "/tmp/"
44 // Force unmount
45 #define FUNMOUNT_IMAGE_NAME "TestImage.dmg"
46 #define FUNMOUNT_IMAGE MAIN_DIR FUNMOUNT_IMAGE_NAME
47 #define FUNMOUNT_VOL_NAME "TestImage"
48 #define FUNMOUNT_MOUNT_POINT MAIN_DIR FUNMOUNT_VOL_NAME "/"
49 #define FUNMOUNT_FILE_NAME "test.txt"
50 #define FUNMOUNT_FILE FUNMOUNT_MOUNT_POINT FUNMOUNT_FILE_NAME
51 // Ungraft
52 #define HOST_DMG MAIN_DIR "VmNoPagerHostMount.dmg"
53 #define HOST_MOUNT_POINT MAIN_DIR "TestVmNoPagerHostMount/"
54 #define GRAFT_MOUNT_POINT HOST_MOUNT_POINT "graft_mount_point/"
55 #define GRAFT_DMG_NAME "VmNoPagerGraftImage.dmg"
56 #define GRAFT_DMG GRAFT_MOUNT_POINT GRAFT_DMG_NAME
57 #define GRAFT_TMP_MOUNT_POINT MAIN_DIR "tmp_graft_mount_point/"
58 #define TEXT_FILE_NAME "graft_test_file.txt"
59 #define HOST_VOL_NAME "TestNoPagerHostVol"
60 #define GRAFT_VOL_NAME "TestNoPagerGraftVol"
61 
62 
63 /*
64  * No system(3c) on watchOS, so provide our own.
65  * returns -1 if fails to run
66  * returns 0 if process exits normally.
67  * returns +n if process exits due to signal N
68  */
69 static int
alt_system(const char * command)70 alt_system(const char *command)
71 {
72 	pid_t pid;
73 	int status = 0;
74 	int signal = 0;
75 	int ret;
76 	const char *argv[] = {
77 		"/bin/sh",
78 		"-c",
79 		command,
80 		NULL
81 	};
82 
83 	if (dt_launch_tool(&pid, (char **)(void *)argv, FALSE, NULL, NULL)) {
84 		return -1;
85 	}
86 
87 	ret = dt_waitpid(pid, &status, &signal, 100);
88 	if (signal != 0) {
89 		return signal;
90 	} else if (status != 0) {
91 		return status;
92 	}
93 	return 0;
94 }
95 
96 static int
my_system(const char * cmd)97 my_system(const char* cmd)
98 {
99 	char quiet_cmd[1024];
100 
101 	snprintf(quiet_cmd, sizeof(quiet_cmd), "%s%s", cmd, verbose ? "" : " > /dev/null");
102 	PRINTF("Execute: '%s'\n", quiet_cmd);
103 
104 	return alt_system(quiet_cmd);
105 }
106 
107 static int
exec_cmd(const char * fmt,...)108 exec_cmd(const char* fmt, ...)
109 {
110 	char cmd[512];
111 
112 	va_list args;
113 	va_start(args, fmt);
114 	vsnprintf(cmd, sizeof(cmd), fmt, args);
115 
116 	int retval = my_system(cmd);
117 
118 	va_end(args);
119 	return retval;
120 }
121 
122 static int
execute_and_get_output(char * buf,size_t len,const char * fmt,...)123 execute_and_get_output(char* buf, size_t len, const char* fmt, ...)
124 {
125 	char tmp[512];
126 	FILE* popen_stream;
127 
128 	va_list args;
129 	va_start(args, fmt);
130 	vsnprintf(tmp, sizeof(tmp), fmt, args);
131 	va_end(args);
132 
133 	PRINTF("Execute and get output: '%s'\n", tmp);
134 	popen_stream = popen(tmp, "r");
135 	if (popen_stream == NULL) {
136 		printf("popen for command `%s` failed\n", tmp);
137 	}
138 	if (fgets(buf, (int)len, popen_stream) == NULL) {
139 		pclose(popen_stream);
140 		printf("Getting output from popen for command `%s` failed\n", tmp);
141 	}
142 	pclose(popen_stream);
143 
144 	return 0;
145 }
146 
147 static int
disk_image_attach(const char * image_path,const char * mount_point)148 disk_image_attach(const char* image_path, const char* mount_point)
149 {
150 	return exec_cmd("diskutil image attach --mountPoint \"%s\" %s", mount_point, image_path);
151 }
152 
153 static int
disk_image_mount(const char * mount_point,const char * device_name)154 disk_image_mount(const char* mount_point, const char* device_name)
155 {
156 	return exec_cmd("diskutil mount -mountPoint %s %s", mount_point, device_name);
157 }
158 
159 static int
disk_image_unmount(const char * device_name)160 disk_image_unmount(const char* device_name)
161 {
162 	return exec_cmd("diskutil unmountDisk %s", device_name);
163 }
164 
165 static int
disk_image_unmount_forced(const char * device_name)166 disk_image_unmount_forced(const char* device_name)
167 {
168 	return exec_cmd("diskutil unmountDisk force %s", device_name);
169 }
170 
171 static int
disk_image_eject(const char * device_name)172 disk_image_eject(const char* device_name)
173 {
174 	disk_image_unmount_forced(device_name);
175 	return exec_cmd("diskutil eject %s", device_name);
176 }
177 
178 static void
fork_and_crash(void (f_ptr)(char *,char *),char * file_path,char * device_identifier)179 fork_and_crash(void(f_ptr)(char*, char*), char* file_path, char* device_identifier)
180 {
181 	pid_t pid = fork();
182 	if (pid == 0) {
183 		// Should induce a crash
184 		f_ptr(file_path, device_identifier);
185 	} else {
186 		int status;
187 		if (waitpid(pid, &status, 0) == -1) {
188 			printf("waitpid to wait for child(pid: '%d') failed", pid);
189 		}
190 
191 		if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGBUS) {
192 			printf("Child process didn't get a SIGBUS\n");
193 		} else {
194 			PRINTF("Child process SIGBUS'd\n");
195 		}
196 	}
197 }
198 
199 static void
read_and_cause_unmount_crash(char * file_path,char * device_identifier)200 read_and_cause_unmount_crash(char* file_path, char* device_identifier)
201 {
202 	int test_file_fd;
203 
204 	if ((test_file_fd = open(file_path, O_RDWR)) == -1) {
205 		printf("couldn't open file '%s'\n", file_path);
206 	}
207 
208 	char* mapped = mmap(0, 1024, PROT_WRITE, MAP_SHARED, test_file_fd, 0);;
209 	if (mapped == MAP_FAILED) {
210 		close(test_file_fd);
211 		printf("couldn't mmap file '%s', errno %d\n", file_path, errno);
212 	} else {
213 		PRINTF("mmap'd file: '%s'\n", file_path);
214 	}
215 
216 	// force unmount
217 	ASSERT(!disk_image_eject(device_identifier), "Failed to force unmount device '%s'", device_identifier);
218 	mapped[0] = 'A'; // cause page fault and crash
219 	printf("Unexpectedly didn't crash after write (after force unmount)");
220 
221 	close(test_file_fd);
222 }
223 
224 static void
read_and_cause_ungraft_crash(char * file_path,__unused char * unused)225 read_and_cause_ungraft_crash(char* file_path, __unused char* unused)
226 {
227 	int test_file_fd;
228 	int retval;
229 
230 	if ((test_file_fd = open(file_path, O_RDWR)) == -1) {
231 		printf("couldn't open %s\n", file_path);
232 	}
233 
234 	char* mapped = mmap(0, 1024, PROT_WRITE, MAP_SHARED, test_file_fd, 0);;
235 	if (mapped == MAP_FAILED) {
236 		close(test_file_fd);
237 		printf("couldn't mmap, errno %d\n", errno);
238 	} else {
239 		PRINTF("mmap'd file: '%s'\n", file_path);
240 	}
241 
242 	// ungraft
243 	apfs_graft_params_t graft_params = {};
244 	retval = fsctl(GRAFT_MOUNT_POINT, APFSIOC_UNGRAFT_AN_FS, &graft_params, 0);
245 	PRINTF("fsctl ungraft result: %d\n", retval);
246 
247 	PRINTF("child about to crash\n");
248 	mapped[0] = 'A'; // cause page fault and crash
249 	printf("Unexpectedly didn't crash after write (after ungraft)");
250 	close(test_file_fd);
251 }
252 
253 static void
setup_unmount_image(char * disk_name,size_t len)254 setup_unmount_image(char* disk_name, size_t len)
255 {
256 	ASSERT(!exec_cmd("diskutil image create blank --size 10m --volumeName %s %s", FUNMOUNT_VOL_NAME, FUNMOUNT_IMAGE), "Disk image creation failed (%s)\n", FUNMOUNT_IMAGE);
257 	ASSERT(!exec_cmd("mkdir -p %s", FUNMOUNT_MOUNT_POINT), "Unable to mkdir mount point");
258 	ASSERT(!disk_image_attach(FUNMOUNT_IMAGE, FUNMOUNT_MOUNT_POINT), "Attaching and mounting disk image during creation failed (%s)\n", FUNMOUNT_IMAGE);
259 	execute_and_get_output(disk_name, len, "diskutil list | grep %s | awk {'print $7'}", FUNMOUNT_VOL_NAME);
260 	ASSERT(strlen(disk_name) != 0, "disk_name is empty");
261 	ASSERT(!my_system("echo 'abcdefghijk' > " FUNMOUNT_FILE), "Creating file '%s' failed.\n", FUNMOUNT_FILE);
262 	ASSERT(!exec_cmd("diskutil eject %s", disk_name), "Disk image detach/eject during creation failed (%s)\n", disk_name);
263 }
264 
265 static void
forced_unmount_crash_test(void)266 forced_unmount_crash_test(void)
267 {
268 	char device_identifier[128];
269 
270 	setup_unmount_image(device_identifier, sizeof(device_identifier));
271 	ASSERT(!disk_image_attach(FUNMOUNT_IMAGE, FUNMOUNT_MOUNT_POINT), "attaching and mounting image '%s' failed\n", FUNMOUNT_IMAGE);
272 	fork_and_crash(read_and_cause_unmount_crash, FUNMOUNT_FILE, device_identifier);
273 
274 	// Cleanup
275 	my_system("rm -f " FUNMOUNT_IMAGE);
276 	disk_image_eject(device_identifier);
277 	rmdir(FUNMOUNT_MOUNT_POINT);
278 }
279 
280 static void
forced_unmount_panic_test(void)281 forced_unmount_panic_test(void)
282 {
283 	char device_identifier[128];
284 	char *file_path;
285 	int test_file_fd;
286 	int ret;
287 	char *mapped;
288 
289 	setup_unmount_image(device_identifier, sizeof(device_identifier));
290 	ASSERT(!disk_image_attach(FUNMOUNT_IMAGE, FUNMOUNT_MOUNT_POINT), "attaching and mounting image '%s' failed\n", FUNMOUNT_IMAGE);
291 
292 	// open file for write
293 	file_path = FUNMOUNT_FILE;
294 	if ((test_file_fd = open(file_path, O_RDWR)) == -1) {
295 		printf("couldn't open file '%s'\n", file_path);
296 	}
297 	ret = ftruncate(test_file_fd, 12);
298 	if (ret < 0) {
299 		printf("ftruncate() errno %d\n", errno);
300 	}
301 	// map it for write
302 	mapped = mmap(0, 1024, PROT_WRITE, MAP_SHARED, test_file_fd, 0);;
303 	if (mapped == MAP_FAILED) {
304 		close(test_file_fd);
305 		printf("couldn't mmap file '%s', errno %d\n", file_path, errno);
306 	} else {
307 		PRINTF("mmap'd file: '%s'\n", file_path);
308 	}
309 	// add contents to 1st page
310 	*mapped = 'A';
311 	// flush page
312 	ret = msync(mapped, 1024, MS_SYNC | MS_INVALIDATE);
313 	if (ret < 0) {
314 		printf("msync() error %d\n", errno);
315 	}
316 	ret = munmap(mapped, 1024);
317 	if (ret < 0) {
318 		printf("munmap() error %d\n", errno);
319 	}
320 	close(test_file_fd);
321 	// re-open file for read only
322 	if ((test_file_fd = open(file_path, O_RDONLY)) == -1) {
323 		printf("couldn't open file '%s'\n", file_path);
324 	}
325 	// map file read-only
326 	mapped = mmap(0, 1024, PROT_READ, MAP_SHARED, test_file_fd, 0);
327 	if (mapped == MAP_FAILED) {
328 		close(test_file_fd);
329 		printf("couldn't mmap file '%s' read-only, errno %d\n", file_path, errno);
330 	} else {
331 		PRINTF("mmap'd file: '%s'\n", file_path);
332 	}
333 	// close file
334 	close(test_file_fd);
335 	// page 1st page back in
336 	printf("mapped[0] = '%c'\n", mapped[0]);
337 	// wire page
338 	ret = mlock(mapped, 1024);
339 	if (ret < 0) {
340 		printf("mlock() errno %d\n", errno);
341 	}
342 	// force unmount
343 	printf("force unmount...\n");
344 	ASSERT(!disk_image_eject(device_identifier), "Failed to force unmount device '%s'", device_identifier);
345 	// unwire page
346 	ret = munlock(mapped, 1024);
347 	if (ret < 0) {
348 		printf("munlock() errno %d\n", errno);
349 	}
350 	// force object cache eviction
351 	printf("object cache evict\n");
352 	fflush(stdout);
353 	ret = sysctlbyname("vm.object_cache_evict", NULL, NULL, NULL, 0);
354 	if (ret < 0) {
355 		printf("sysctl(vm.object_cache_evict) errno %d\n", errno);
356 	} else {
357 		printf("object cache eviction did not cause a panic!\n");
358 	}
359 	// crash
360 	printf("mapped[0] = '%c'\n", mapped[0]);
361 
362 	// Cleanup
363 	my_system("rm -f " FUNMOUNT_IMAGE);
364 	disk_image_eject(device_identifier);
365 	rmdir(FUNMOUNT_MOUNT_POINT);
366 }
367 
368 static void
create_disk_image(const char * image_path,const char * volume_name,bool use_gpt,char * device_name_out,size_t device_len,char * partition_name_out,size_t partition_len)369 create_disk_image(const char* image_path, const char* volume_name, bool use_gpt, char* device_name_out, size_t device_len, char* partition_name_out, size_t partition_len)
370 {
371 	char buf[512];
372 	char* device_name;
373 	char* end_ptr;
374 	char partition_name[32];
375 
376 	// Create image
377 	ASSERT(!exec_cmd("diskutil image create blank --size 10m --volumeName %s %s", volume_name, image_path), "Image creation at `%s` failed\n", image_path);
378 
379 	// Attach
380 	ASSERT(!execute_and_get_output(buf, sizeof(buf), "diskutil image attach --noMount %s | head -1 | awk {'print $1'}", image_path), "Attaching image (nomount) at %s failed\n", image_path);
381 	ASSERT(strstr(buf, "/dev/disk"), "Didn't get expected device identifier after attaching. Got: `%s`\n", buf);
382 	if ((end_ptr = strchr(buf, '\n'))) {
383 		*end_ptr = '\0';
384 	}
385 	device_name = strdup(buf);
386 	strncpy(device_name_out, device_name, device_len);
387 
388 	// Partition and format
389 	if (use_gpt) {
390 		ASSERT(!exec_cmd("mkutil partition %s --map GPT --type Apple_APFS", device_name), "partition failed\n");
391 
392 		snprintf(partition_name, sizeof(partition_name), "%ss2", device_name);
393 		struct stat sb;
394 		if (stat(partition_name, &sb)) {
395 			ASSERT(errno == ENOENT, "Device `%s` exists, while we expect only the `s1` suffix to exist\n", partition_name);
396 			partition_name[strlen(partition_name) - 1] = '1';
397 		}
398 	} else {
399 		snprintf(partition_name, sizeof(partition_name), "%s", device_name);
400 	}
401 	ASSERT(!exec_cmd("newfs_apfs -v %s %s", volume_name, partition_name), "Formatting volume with APFS failed\n");
402 
403 	// Grab the name for ungrafting later on
404 	ASSERT(!execute_and_get_output(buf, sizeof(buf), "diskutil list | grep '%s' | awk {'print $7'}", volume_name), "Getting parition name failed\n");
405 	if ((end_ptr = strchr(buf, '\n'))) {
406 		*end_ptr = '\0';
407 	}
408 	strncpy(partition_name_out, buf, partition_len);
409 }
410 
411 static void
setup_host_image(char * device_identifier,size_t len)412 setup_host_image(char* device_identifier, size_t len)
413 {
414 	char partition_name[128];
415 
416 	printf("Setting up host\n");
417 	create_disk_image(HOST_DMG, HOST_VOL_NAME, true /* use_gpt */, device_identifier, len, partition_name, sizeof(partition_name));
418 	my_system("mkdir -p " HOST_MOUNT_POINT);
419 	disk_image_mount(HOST_MOUNT_POINT, partition_name);
420 }
421 
422 static void
setup_graft_image(char * device_identifier,size_t len)423 setup_graft_image(char* device_identifier, size_t len)
424 {
425 	char partition_name[128];
426 
427 	// Create graft image, mount it to create the text file, then unmount so it can be grafted
428 	printf("Setting up graft\n");
429 	my_system("mkdir -p " GRAFT_TMP_MOUNT_POINT);
430 	my_system("mkdir -p " GRAFT_MOUNT_POINT);
431 	create_disk_image(GRAFT_DMG, GRAFT_VOL_NAME, false /* use_gpt*/, device_identifier, len, partition_name, sizeof(partition_name));
432 	ASSERT(!disk_image_mount(GRAFT_TMP_MOUNT_POINT, partition_name), "Failed to mount partition `%s` before file creation\n", partition_name);
433 	ASSERT(!exec_cmd("echo 'fsafasfasdg' > %s", GRAFT_TMP_MOUNT_POINT TEXT_FILE_NAME), "Failed to create file %s\n", GRAFT_TMP_MOUNT_POINT TEXT_FILE_NAME);
434 	disk_image_unmount(GRAFT_TMP_MOUNT_POINT);
435 
436 	// Graft
437 	apfs_graft_params_t graft_params = {};
438 	__unused uint64_t ungraft_params = 0;
439 	int retval = fsctl(GRAFT_DMG, APFSIOC_GRAFT_AN_FS, &graft_params, 0);
440 	PRINTF("fsctl graft result: %d\n", retval);
441 }
442 
443 static void
cleanup_ungraft(char * graft_disk_identifier,char * host_disk_identifier)444 cleanup_ungraft(char* graft_disk_identifier, char* host_disk_identifier)
445 {
446 	disk_image_eject(graft_disk_identifier);
447 	disk_image_eject(host_disk_identifier);
448 	rmdir(GRAFT_TMP_MOUNT_POINT);
449 	rmdir(HOST_MOUNT_POINT);
450 	my_system("rm -f " HOST_DMG);
451 }
452 
453 static void
ungraft_crash_test(void)454 ungraft_crash_test(void)
455 {
456 	char host_disk_identifier[128];
457 	char graft_disk_identifier[128];
458 
459 	setup_host_image(host_disk_identifier, sizeof(host_disk_identifier));
460 	setup_graft_image(graft_disk_identifier, sizeof(graft_disk_identifier));
461 
462 	fork_and_crash(read_and_cause_ungraft_crash, GRAFT_MOUNT_POINT TEXT_FILE_NAME, host_disk_identifier);
463 
464 	cleanup_ungraft(graft_disk_identifier, host_disk_identifier);
465 }
466 
467 int
main(int argc,char ** argv)468 main(int argc, char** argv)
469 {
470 	if (argc < 2) {
471 		printf("Usage: test_vm_no_pager_helper 1|2 [-v]\n");
472 		exit(1);
473 	}
474 
475 	__unused char* unused;
476 	int test_to_run = (int)strtol(argv[1], NULL, 10);
477 	if (errno == EINVAL) {
478 		printf("Bad test argument passed\n");
479 		exit(1);
480 	}
481 
482 	if (geteuid() != 0) {
483 		PRINTF("Crash test not running as root\n");
484 		exit(1);
485 	}
486 
487 	printf("running test %d\n", test_to_run);
488 
489 	if (argc > 2 && strcmp(argv[2], "-v") == 0) {
490 		verbose = 1;
491 		printf("Running in verbose mode\n");
492 	}
493 
494 	switch (test_to_run) {
495 	case 1:
496 		forced_unmount_crash_test();
497 		break;
498 	case 2:
499 		ungraft_crash_test();
500 		break;
501 	case 3:
502 		forced_unmount_panic_test();
503 		break;
504 	default:
505 		printf("Invalid test number passed (%d)\n", test_to_run);
506 		exit(1);
507 	}
508 }
509