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