xref: /xnu-10063.101.15/tests/vm/test_vm_no_pager_helper.c (revision 94d3b452840153a99b38a3a9659680b2a006908e)
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 #pragma clang diagnostic ignored "-Wformat-nonliteral"
17 
18 static int verbose = 0;
19 
20 #define PRINTF(...) \
21     do {            \
22 	if (verbose) { \
23 	        printf(__VA_ARGS__); \
24 	}                               \
25     } while (0)
26 
27 #define ASSERT(cond, ...)       \
28     if (!(cond)) {                \
29 	        printf(__VA_ARGS__); \
30     }                           \
31 
32 typedef struct {
33 	uint64_t graft_dir_id; // If this is 0, grafting will be performed on the parent directory of the graft file
34 	uint64_t flags;
35 } apfs_graft_params_t;
36 
37 #define APFSIOC_GRAFT_AN_FS _IOW('J', 99, apfs_graft_params_t)
38 #define APFSIOC_UNGRAFT_AN_FS _IOW('J', 100, uint64_t)
39 
40 #define MAIN_DIR "/tmp/"
41 // Force unmount
42 #define FUNMOUNT_IMAGE_NAME "TestImage.dmg"
43 #define FUNMOUNT_IMAGE MAIN_DIR FUNMOUNT_IMAGE_NAME
44 #define FUNMOUNT_VOL_NAME "TestImage"
45 #define FUNMOUNT_MOUNT_POINT "/Volumes/" FUNMOUNT_VOL_NAME "/"
46 #define FUNMOUNT_FILE_NAME "test.txt"
47 #define FUNMOUNT_FILE FUNMOUNT_MOUNT_POINT FUNMOUNT_FILE_NAME
48 // Ungraft
49 #define HOST_DMG MAIN_DIR "VmNoPagerHostMount.dmg"
50 #define HOST_MOUNT_POINT MAIN_DIR "TestVmNoPagerHostMount/"
51 #define GRAFT_MOUNT_POINT HOST_MOUNT_POINT "graft_mount_point/"
52 #define GRAFT_DMG_NAME "VmNoPagerGraftImage.dmg"
53 #define GRAFT_DMG GRAFT_MOUNT_POINT GRAFT_DMG_NAME
54 #define GRAFT_TMP_MOUNT_POINT MAIN_DIR "tmp_graft_mount_point/"
55 #define TEXT_FILE_NAME "graft_test_file.txt"
56 #define HOST_VOL_NAME "TestNoPagerHostVol"
57 #define GRAFT_VOL_NAME "TestNoPagerGraftVol"
58 
59 static int
my_system(const char * cmd)60 my_system(const char* cmd)
61 {
62 	char quiet_cmd[1024];
63 
64 	snprintf(quiet_cmd, sizeof(quiet_cmd), "%s%s", cmd, verbose ? "" : " > /dev/null");
65 	PRINTF("Execute: '%s'\n", quiet_cmd);
66 	return system(quiet_cmd);
67 }
68 
69 static int
exec_cmd(const char * fmt,...)70 exec_cmd(const char* fmt, ...)
71 {
72 	char cmd[512];
73 
74 	va_list args;
75 	va_start(args, fmt);
76 	vsnprintf(cmd, sizeof(cmd), fmt, args);
77 
78 	int retval = my_system(cmd);
79 
80 	va_end(args);
81 	return retval;
82 }
83 
84 static int
execute_and_get_output(char * buf,size_t len,const char * fmt,...)85 execute_and_get_output(char* buf, size_t len, const char* fmt, ...)
86 {
87 	char tmp[512];
88 	FILE* popen_stream;
89 
90 	va_list args;
91 	va_start(args, fmt);
92 	vsnprintf(tmp, sizeof(tmp), fmt, args);
93 	va_end(args);
94 
95 	PRINTF("Execute and get output: '%s'\n", tmp);
96 	popen_stream = popen(tmp, "r");
97 	if (popen_stream == NULL) {
98 		printf("popen for command `%s` failed\n", tmp);
99 	}
100 	if (fgets(buf, (int)len, popen_stream) == NULL) {
101 		pclose(popen_stream);
102 		printf("Getting output from popen for command `%s` failed\n", tmp);
103 	}
104 	pclose(popen_stream);
105 
106 	return 0;
107 }
108 
109 static int
disk_image_attach(const char * image_path)110 disk_image_attach(const char* image_path)
111 {
112 	return exec_cmd("diskimagetool attach %s", image_path);
113 }
114 
115 static int
disk_image_mount(const char * mount_point,const char * device_name)116 disk_image_mount(const char* mount_point, const char* device_name)
117 {
118 	return exec_cmd("diskutil mount -mountPoint %s %s", mount_point, device_name);
119 }
120 
121 static int
disk_image_unmount(const char * device_name)122 disk_image_unmount(const char* device_name)
123 {
124 	return exec_cmd("diskutil unmountDisk %s", device_name);
125 }
126 
127 static int
disk_image_unmount_forced(const char * device_name)128 disk_image_unmount_forced(const char* device_name)
129 {
130 	return exec_cmd("diskutil unmountDisk force %s", device_name);
131 }
132 
133 static int
disk_image_eject(const char * device_name)134 disk_image_eject(const char* device_name)
135 {
136 	disk_image_unmount_forced(device_name);
137 	return exec_cmd("diskutil eject %s", device_name);
138 }
139 
140 static void
fork_and_crash(void (f_ptr)(char *,char *),char * file_path,char * device_identifier)141 fork_and_crash(void(f_ptr)(char*, char*), char* file_path, char* device_identifier)
142 {
143 	pid_t pid = fork();
144 	if (pid == 0) {
145 		// Should induce a crash
146 		f_ptr(file_path, device_identifier);
147 	} else {
148 		int status;
149 		if (waitpid(pid, &status, 0) == -1) {
150 			printf("waitpid to wait for child(pid: '%d') failed", pid);
151 		}
152 
153 		if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGBUS) {
154 			printf("Child process didn't get a SIGBUS\n");
155 		} else {
156 			PRINTF("Child process SIGBUS'd\n");
157 		}
158 	}
159 }
160 
161 static void
read_and_cause_unmount_crash(char * file_path,char * device_identifier)162 read_and_cause_unmount_crash(char* file_path, char* device_identifier)
163 {
164 	int test_file_fd;
165 
166 	if ((test_file_fd = open(file_path, O_RDWR)) == -1) {
167 		printf("couldn't open file '%s'\n", file_path);
168 	}
169 
170 	char* mapped = mmap(0, 1024, PROT_WRITE, MAP_SHARED, test_file_fd, 0);;
171 	if (mapped == MAP_FAILED) {
172 		close(test_file_fd);
173 		printf("couldn't mmap file '%s', errno %d\n", file_path, errno);
174 	} else {
175 		PRINTF("mmap'd file: '%s'\n", file_path);
176 	}
177 
178 	// force unmount
179 	ASSERT(!disk_image_eject(device_identifier), "Failed to force unmount device '%s'", device_identifier);
180 	mapped[0] = 'A'; // cause page fault and crash
181 	printf("Unexpectedly didn't crash after write (after force unmount)");
182 
183 	close(test_file_fd);
184 }
185 
186 static void
read_and_cause_ungraft_crash(char * file_path,__unused char * unused)187 read_and_cause_ungraft_crash(char* file_path, __unused char* unused)
188 {
189 	int test_file_fd;
190 	int retval;
191 
192 	if ((test_file_fd = open(file_path, O_RDWR)) == -1) {
193 		printf("couldn't open %s\n", file_path);
194 	}
195 
196 	char* mapped = mmap(0, 1024, PROT_WRITE, MAP_SHARED, test_file_fd, 0);;
197 	if (mapped == MAP_FAILED) {
198 		close(test_file_fd);
199 		printf("couldn't mmap, errno %d\n", errno);
200 	} else {
201 		PRINTF("mmap'd file: '%s'\n", file_path);
202 	}
203 
204 	// ungraft
205 	apfs_graft_params_t graft_params = {};
206 	retval = fsctl(GRAFT_MOUNT_POINT, APFSIOC_UNGRAFT_AN_FS, &graft_params, 0);
207 	PRINTF("fsctl ungraft result: %d\n", retval);
208 
209 	PRINTF("child about to crash\n");
210 	mapped[0] = 'A'; // cause page fault and crash
211 	printf("Unexpectedly didn't crash after write (after ungraft)");
212 	close(test_file_fd);
213 }
214 
215 static void
setup_unmount_image(char * disk_name,size_t len)216 setup_unmount_image(char* disk_name, size_t len)
217 {
218 	ASSERT(!exec_cmd("hdiutil create -size 100m -fs apfs -volname %s %s", FUNMOUNT_VOL_NAME, FUNMOUNT_IMAGE), "Disk image creation failed (%s)\n", FUNMOUNT_IMAGE);
219 	ASSERT(!disk_image_attach(FUNMOUNT_IMAGE), "Attaching and mounting disk image during creation failed (%s)\n", FUNMOUNT_IMAGE);
220 	execute_and_get_output(disk_name, len, "diskutil list | grep %s | awk {'print $7'}", FUNMOUNT_VOL_NAME);
221 	ASSERT(strlen(disk_name) != 0, "disk_name is empty");
222 	ASSERT(!my_system("echo 'abcdefghijk' > " FUNMOUNT_FILE), "Creating file '%s' failed.\n", FUNMOUNT_FILE);
223 	ASSERT(!exec_cmd("diskutil eject %s", disk_name), "Disk image detach/eject during creation failed (%s)\n", disk_name);
224 }
225 
226 static void
forced_unmount_crash_test(void)227 forced_unmount_crash_test(void)
228 {
229 	char device_identifier[128];
230 
231 	setup_unmount_image(device_identifier, sizeof(device_identifier));
232 	ASSERT(!disk_image_attach(FUNMOUNT_IMAGE), "attaching and mounting image '%s' failed\n", FUNMOUNT_IMAGE);
233 	fork_and_crash(read_and_cause_unmount_crash, FUNMOUNT_FILE, device_identifier);
234 
235 	// Cleanup
236 	my_system("rm -f " FUNMOUNT_IMAGE);
237 	disk_image_eject(device_identifier);
238 }
239 
240 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)241 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)
242 {
243 	char buf[512];
244 	char* device_name;
245 	char* end_ptr;
246 	char partition_name[32];
247 
248 	// Create image
249 	ASSERT(!exec_cmd("diskimagetool create -size 100m -fs none %s", image_path), "Image creation at `%s` failed\n", image_path);
250 
251 	// Attach
252 	ASSERT(!execute_and_get_output(buf, sizeof(buf), "diskimagetool attach -nomount %s", image_path), "Attaching image (nomount) at %s failed\n", image_path);
253 	ASSERT(strstr(buf, "/dev/disk"), "Didn't get expected device identifier after attaching. Got: `%s`\n", buf);
254 	if ((end_ptr = strchr(buf, '\n'))) {
255 		*end_ptr = '\0';
256 	}
257 	device_name = strdup(buf);
258 	strncpy(device_name_out, device_name, device_len);
259 
260 	// Partition and format
261 	if (use_gpt) {
262 		ASSERT(!exec_cmd("mkutil partition %s --map GPT --type Apple_APFS", device_name), "partition failed\n");
263 
264 		snprintf(partition_name, sizeof(partition_name), "%ss2", device_name);
265 		struct stat sb;
266 		if (stat(partition_name, &sb)) {
267 			ASSERT(errno == ENOENT, "Device `%s` exists, while we expect only the `s1` suffix to exist\n", partition_name);
268 			partition_name[strlen(partition_name) - 1] = '1';
269 		}
270 	} else {
271 		snprintf(partition_name, sizeof(partition_name), "%s", device_name);
272 	}
273 	ASSERT(!exec_cmd("newfs_apfs -v %s %s", volume_name, partition_name), "Formatting volume with APFS failed\n");
274 
275 	// Grab the name for ungrafting later on
276 	ASSERT(!execute_and_get_output(buf, sizeof(buf), "diskutil list | grep '%s' | awk {'print $7'}", volume_name), "Getting parition name failed\n");
277 	if ((end_ptr = strchr(buf, '\n'))) {
278 		*end_ptr = '\0';
279 	}
280 	strncpy(partition_name_out, buf, partition_len);
281 }
282 
283 static void
setup_host_image(char * device_identifier,size_t len)284 setup_host_image(char* device_identifier, size_t len)
285 {
286 	char partition_name[128];
287 
288 	printf("Setting up host\n");
289 	create_disk_image(HOST_DMG, HOST_VOL_NAME, true /* use_gpt */, device_identifier, len, partition_name, sizeof(partition_name));
290 	my_system("mkdir -p " HOST_MOUNT_POINT);
291 	disk_image_mount(HOST_MOUNT_POINT, partition_name);
292 }
293 
294 static void
setup_graft_image(char * device_identifier,size_t len)295 setup_graft_image(char* device_identifier, size_t len)
296 {
297 	char partition_name[128];
298 
299 	// Create graft image, mount it to create the text file, then unmount so it can be grafted
300 	printf("Setting up graft\n");
301 	my_system("mkdir -p " GRAFT_TMP_MOUNT_POINT);
302 	my_system("mkdir -p " GRAFT_MOUNT_POINT);
303 	create_disk_image(GRAFT_DMG, GRAFT_VOL_NAME, false /* use_gpt*/, device_identifier, len, partition_name, sizeof(partition_name));
304 	ASSERT(!disk_image_mount(GRAFT_TMP_MOUNT_POINT, partition_name), "Failed to mount partition `%s` before file creation\n", partition_name);
305 	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);
306 	disk_image_unmount(GRAFT_TMP_MOUNT_POINT);
307 
308 	// Graft
309 	apfs_graft_params_t graft_params = {};
310 	__unused uint64_t ungraft_params = 0;
311 	int retval = fsctl(GRAFT_DMG, APFSIOC_GRAFT_AN_FS, &graft_params, 0);
312 	PRINTF("fsctl graft result: %d\n", retval);
313 }
314 
315 static void
cleanup_ungraft(char * graft_disk_identifier,char * host_disk_identifier)316 cleanup_ungraft(char* graft_disk_identifier, char* host_disk_identifier)
317 {
318 	disk_image_eject(graft_disk_identifier);
319 	disk_image_eject(host_disk_identifier);
320 	my_system("rm -rf " GRAFT_TMP_MOUNT_POINT);
321 	my_system("rm -rf " HOST_MOUNT_POINT);
322 	my_system("rm -f " HOST_DMG);
323 }
324 
325 static void
ungraft_crash_test(void)326 ungraft_crash_test(void)
327 {
328 	char host_disk_identifier[128];
329 	char graft_disk_identifier[128];
330 
331 	setup_host_image(host_disk_identifier, sizeof(host_disk_identifier));
332 	setup_graft_image(graft_disk_identifier, sizeof(graft_disk_identifier));
333 
334 	fork_and_crash(read_and_cause_ungraft_crash, GRAFT_MOUNT_POINT TEXT_FILE_NAME, host_disk_identifier);
335 
336 	cleanup_ungraft(graft_disk_identifier, host_disk_identifier);
337 }
338 
339 int
main(int argc,char ** argv)340 main(int argc, char** argv)
341 {
342 	if (argc < 2) {
343 		printf("Usage: test_vm_no_pager_helper 1|2 [-v]\n");
344 		exit(1);
345 	}
346 
347 	__unused char* unused;
348 	int test_to_run = (int)strtol(argv[1], NULL, 10);
349 	if (errno == EINVAL) {
350 		printf("Bad test argument passed\n");
351 		exit(1);
352 	}
353 
354 	if (geteuid() != 0) {
355 		PRINTF("Crash test not running as root\n");
356 		exit(1);
357 	}
358 
359 	printf("running test %d\n", test_to_run);
360 
361 	if (argc > 2) {
362 		printf("%s", argv[2]);
363 	}
364 
365 	if (argc > 2 && strcmp(argv[2], "-v") == 0) {
366 		verbose = 1;
367 	}
368 
369 	switch (test_to_run) {
370 	case 1:
371 		forced_unmount_crash_test();
372 		break;
373 	case 2:
374 		ungraft_crash_test();
375 		break;
376 	default:
377 		printf("Invalid test number passed'n");
378 		exit(1);
379 	}
380 }
381