1#include "tests.h"
2
3#include <errno.h>
4#include <fcntl.h>
5#include <stdlib.h>
6#include <sys/mount.h>
7#include <sys/wait.h>
8
9#include <IOKit/IOKitLib.h>
10#include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
11#include <Kernel/sys/content_protection.h>
12
13/* Note that this test (due to the need to lock/unlock the device on demand, and the
14   need to manipulate the passcode) has the unfortunate effect of link xnu_quick_test
15   to the IOKit Framework. */
16
17/* TODO: Change the test to use a single cleanup label. */
18
19#define CPT_IO_SIZE      4096
20#define CPT_AKS_BUF_SIZE 256
21#define CPT_MAX_PASS_LEN 64
22
23#define GET_PROT_CLASS(fd)             fcntl((fd), F_GETPROTECTIONCLASS)
24#define SET_PROT_CLASS(fd, prot_class) fcntl((fd), F_SETPROTECTIONCLASS, (prot_class))
25
26#define PRINT_LOCK_FAIL   printf("%s, line %d: failed to lock the device.\n", cpt_fail_header, __LINE__);
27#define PRINT_UNLOCK_FAIL printf("%s, line %d: failed to unlock the device.\n", cpt_fail_header, __LINE__);
28
29extern char g_target_path[PATH_MAX];
30
31char * cpt_fail_header = "Content protection test failed";
32char * keystorectl_path = "/usr/local/bin/keystorectl";
33
34/* Shamelessly ripped from keystorectl routines; a wrapper for invoking the AKS user client. */
35int apple_key_store(uint32_t command,
36                    uint64_t * inputs,
37                    uint32_t input_count,
38                    void * input_structs,
39                    size_t input_struct_count,
40                    uint64_t * outputs,
41                    uint32_t * output_count)
42{
43	int result = -1;
44	io_connect_t connection = IO_OBJECT_NULL;
45	io_registry_entry_t apple_key_bag_service = IO_OBJECT_NULL;
46	kern_return_t k_result = KERN_FAILURE;
47	IOReturn io_result = IO_OBJECT_NULL;
48
49	apple_key_bag_service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kAppleKeyStoreServiceName));
50
51	if (apple_key_bag_service == IO_OBJECT_NULL)
52	{
53		printf("FAILURE: failed to match kAppleKeyStoreServiceName.\n");
54		goto end;
55	}
56
57	k_result = IOServiceOpen(apple_key_bag_service, mach_task_self(), 0, &connection);
58
59	if (k_result != KERN_SUCCESS)
60	{
61		printf("FAILURE: failed to open AppleKeyStore.\n");
62		goto end;
63	}
64
65	k_result = IOConnectCallMethod(connection, kAppleKeyStoreUserClientOpen, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
66
67	if (k_result != KERN_SUCCESS)
68	{
69		printf("FAILURE: call to AppleKeyStore method kAppleKeyStoreUserClientOpen failed.\n");
70		goto close;
71	}
72
73	io_result = IOConnectCallMethod(connection, command, inputs, input_count, input_structs, input_struct_count, outputs, output_count, NULL, NULL);
74
75	if (io_result != kIOReturnSuccess)
76	{
77		printf("FAILURE: call to AppleKeyStore method %d failed.\n", command);
78		goto close;
79	}
80
81	result = 0;
82
83close:
84	IOServiceClose(apple_key_bag_service);
85
86end:
87	return(result);
88}
89
90#ifndef   KEYBAG_ENTITLEMENTS
91/* Just a wrapper around forking to exec keystorectl for commands requiring entitlements. */
92int keystorectl(char * const command[])
93{
94	int child_result = -1;
95	int result = -1;
96	pid_t child = -1;
97
98	child = fork();
99
100	if (child == -1)
101	{
102		printf("FAILURE: failed to fork.\n");
103		goto end;
104	}
105	else if (child == 0)
106	{
107		/* TODO: This keeps keystorectl from bombarding us with key state changes, but
108		   there must be a better way of doing this; killing stderr is a bit nasty,
109		   and if keystorectl fails, we want all the information we can get. */
110		fclose(stderr);
111		fclose(stdin);
112		execv(keystorectl_path, command);
113		printf("FAILURE: child failed to execv keystorectl, errno = %s.\n",
114		  strerror(errno));
115		exit(EXIT_FAILURE);
116	}
117
118	if ((waitpid(child, &child_result, 0) != child) || WEXITSTATUS(child_result))
119	{
120		printf("FAILURE: keystorectl failed.\n");
121		result = -1;
122	}
123	else
124	{
125		result = 0;
126	}
127
128end:
129	return(result);
130}
131#endif /* KEYBAG_ENTITLEMENTS */
132
133/* Code based on Mobile Key Bag; specifically MKBDeviceSupportsContentProtection
134   and MKBDeviceFormattedForContentProtection. */
135/* We want to verify that we support content protection, and that
136   we are formatted for it. */
137int supports_content_prot()
138{
139	int local_result = -1;
140	int result = -1;
141	uint32_t buffer_size = 1;
142	char buffer[buffer_size];
143	io_registry_entry_t defaults = IO_OBJECT_NULL;
144	kern_return_t k_result = KERN_FAILURE;
145	struct statfs statfs_results;
146
147	defaults = IORegistryEntryFromPath(kIOMasterPortDefault, kIODeviceTreePlane ":/defaults");
148
149	if (defaults == IO_OBJECT_NULL)
150	{
151		printf("FAILURE: failed to find defaults registry entry.\n");
152		goto end;
153	}
154
155	k_result = IORegistryEntryGetProperty(defaults, "content-protect", buffer, &buffer_size);
156
157	if (k_result != KERN_SUCCESS)
158	{	/* This isn't a failure; it means the entry doesn't exist, so we assume CP
159		   is unsupported. */
160		result = 0;
161		goto end;
162	}
163
164	/* At this point, we SUPPORT content protection... but are we formatted for it? */
165	/* This is ugly; we should be testing the file system we'll be testing in, not
166	   just /tmp/. */
167	local_result = statfs(g_target_path, &statfs_results);
168
169	if (local_result == -1)
170	{
171		printf("FAILURE: failed to statfs the test directory, errno = %s.\n",
172		  strerror(errno));
173	}
174	else if (statfs_results.f_flags & MNT_CPROTECT)
175	{
176		result = 1;
177	}
178	else
179	{	/* This isn't a failure, it means the filesystem isn't formatted for CP. */
180		result = 0;
181	}
182
183end:
184	return(result);
185}
186
187#if 0
188int device_lock_state()
189{
190	/* TODO: Actually implement this. */
191	/* We fail if a passcode already exists, and the methods being used to lock/unlock
192	   the device in this test appear to be synchronous... do we need this function? */
193	int result = -1;
194
195	return(result);
196}
197#endif
198
199int lock_device()
200{
201	int result = -1;
202
203#ifdef    KEYBAG_ENTITLEMENTS
204	/* If we're entitled, we can lock the device ourselves. */
205	uint64_t inputs[] = {device_keybag_handle};
206	uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
207	result = apple_key_store(kAppleKeyStoreKeyBagLock, inputs, input_count, NULL, 0, NULL, NULL);
208#else
209	/* If we aren't entitled, we'll need to use keystorectl to lock the device. */
210	/* keystorectl seems to have a bus error (though it locks successfully) unless
211	   lock is passed an argument, so we'll also pass it the empty string. */
212	char * const keystorectl_args[] = {keystorectl_path, "lock", "", NULL};
213	result = keystorectl(keystorectl_args);
214#endif /* KEYBAG_ENTITLEMENTS */
215
216	return(result);
217}
218
219int unlock_device(char * passcode)
220{
221	int result = -1;
222
223#ifdef    KEYBAG_ENTITLEMENTS
224	/* If we're entitled, we can unlock the device ourselves. */
225	uint64_t inputs[] = {device_keybag_handle};
226	uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
227	size_t input_struct_count = 0;
228
229	if ((passcode == NULL) || ((input_struct_count = strnlen(passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN))
230	{
231		passcode = "";
232		input_struct_count = 0;
233	}
234
235	result = apple_key_store(kAppleKeyStoreKeyBagUnlock, inputs, input_count, passcode, input_struct_count, NULL, NULL);
236#else
237	/* If we aren't entitled, we'll need to use keystorectl to unlock the device. */
238	if ((passcode == NULL) || (strnlen(passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN))
239	{
240		passcode = "";
241	}
242
243	char * const keystorectl_args[] = {keystorectl_path, "unlock", passcode, NULL};
244	result = keystorectl(keystorectl_args);
245#endif /* KEYBAG_ENTITLEMENTS */
246
247	return(result);
248}
249
250int set_passcode(char * new_passcode, char * old_passcode)
251{
252	int result = -1;
253
254#ifdef    KEYBAG_ENTITLEMENTS
255	/* If we're entitled, we can set the passcode ourselves. */
256	uint64_t inputs[] = {device_keybag_handle};
257	uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
258	void * input_structs = NULL;
259	size_t input_struct_count = 0;
260	char buffer[CPT_AKS_BUF_SIZE];
261	char * buffer_ptr = buffer;
262	uint32_t old_passcode_len = 0;
263	uint32_t new_passcode_len = 0;
264
265	if ((old_passcode == NULL) || ((old_passcode_len = strnlen(old_passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN))
266	{
267		old_passcode = "";
268		old_passcode_len = 0;
269	}
270
271	if ((new_passcode == NULL) || ((new_passcode_len = strnlen(new_passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN))
272	{
273		new_passcode = "";
274		new_passcode_len = 0;
275	}
276
277	*((uint32_t *) buffer_ptr) = ((uint32_t) 2);
278	buffer_ptr += sizeof(uint32_t);
279	*((uint32_t *) buffer_ptr) = old_passcode_len;
280	buffer_ptr += sizeof(uint32_t);
281	memcpy(buffer_ptr, old_passcode, old_passcode_len);
282	buffer_ptr += ((old_passcode_len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1));
283	*((uint32_t *) buffer_ptr) = new_passcode_len;
284	buffer_ptr += sizeof(uint32_t);
285	memcpy(buffer_ptr, new_passcode, new_passcode_len);
286	buffer_ptr += ((new_passcode_len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1));
287	input_structs = buffer;
288	input_struct_count = (buffer_ptr - buffer);
289
290	result = apple_key_store(kAppleKeyStoreKeyBagSetPasscode, inputs, input_count, input_structs, input_struct_count, NULL, NULL);
291#else
292	/* If we aren't entitled, we'll need to use keystorectl to set the passcode. */
293	if ((old_passcode == NULL) || (strnlen(old_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN))
294	{
295		old_passcode = "";
296	}
297
298	if ((new_passcode == NULL) || (strnlen(new_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN))
299	{
300		new_passcode = "";
301	}
302
303	char * const keystorectl_args[] = {keystorectl_path, "change-password", old_passcode, new_passcode, NULL};
304	result = keystorectl(keystorectl_args);
305#endif /* KEYBAG_ENTITLEMENTS */
306
307	return(result);
308}
309
310int clear_passcode(char * passcode)
311{
312	/* For the moment, this will set the passcode to the empty string (a known value);
313	   this will most likely need to change, or running this test may ruin everything(tm). */
314	int result = -1;
315
316	result = set_passcode(NULL, passcode);
317
318	return(result);
319}
320
321#if 0
322/* Determines if we will try to test class C semanatics. */
323int unlocked_since_boot()
324{
325	/* TODO: Actually implement this. */
326	/* The actual semantics for CP mean that even with this primative, we would need
327	   set a passcode and then reboot the device in order to test this; this function
328	   will probably be rather worthless as a result. */
329	int result = 1;
330
331	return(result);
332}
333#endif
334
335/* If the device has a passcode when we want to test it, things are going to go wrong.
336   As such, we'll assume the device never has a passcode.
337   No, not even then.
338   Or we could just try "" to ""; it works. */
339int has_passcode()
340{
341	int result = -1;
342
343	result = set_passcode(NULL, NULL);
344
345	return(result);
346}
347
348int content_protection_test(void * argp)
349{
350	#pragma unused (argp)
351	int init_result = 0;
352	int local_result = -1;
353	int test_result = -1;
354	int fd = -1;
355	int dir_fd = -1;
356	int subdir_fd = -1;
357	int new_prot_class = -1;
358	int old_prot_class = -1;
359	int current_byte = 0;
360	char filepath[PATH_MAX];
361	char dirpath[PATH_MAX];
362	char subdirpath[PATH_MAX];
363	char rd_buffer[CPT_IO_SIZE];
364	char wr_buffer[CPT_IO_SIZE];
365	char * passcode = "IAmASecurePassword";
366
367	/* Do some initial setup (names). */
368	bzero(filepath, PATH_MAX);
369	bzero(dirpath, PATH_MAX);
370	bzero(subdirpath, PATH_MAX);
371
372	/* This is just easier than checking each result individually. */
373	init_result |= (strlcat(filepath, g_target_path, PATH_MAX) == PATH_MAX);
374	init_result |= (strlcat(filepath, "/", PATH_MAX) == PATH_MAX);
375	init_result |= (strlcpy(dirpath, filepath, PATH_MAX) == PATH_MAX);
376	init_result |= (strlcat(filepath, "cpt_test_file", PATH_MAX) == PATH_MAX);
377	init_result |= (strlcat(dirpath, "cpt_test_dir/", PATH_MAX) == PATH_MAX);
378	init_result |= (strlcpy(subdirpath, dirpath, PATH_MAX) == PATH_MAX);
379	init_result |= (strlcat(subdirpath, "cpt_test_subdir/", PATH_MAX) == PATH_MAX);
380
381	if (init_result)
382	{	/* If any of the initialization failed, we're just going to fail now. */
383		printf("%s, line %d: failed to initialize test strings.\n",
384		  cpt_fail_header, __LINE__);
385		goto end;
386	}
387
388	local_result = supports_content_prot();
389
390	if (local_result == -1)
391	{
392		printf("%s, line %d: failed to determine if content protection is supported.\n",
393		  cpt_fail_header, __LINE__);
394		goto end;
395	}
396	else if (local_result == 0)
397	{	/* If we don't support content protection at the moment, pass the test. */
398		printf("This device does not support or is not formatted for content protection.\n");
399		test_result = 0;
400		goto end;
401	}
402
403	/* If we support content protection, we'll need to be able to set the passcode. */
404	local_result = has_passcode();
405
406	if (local_result == -1)
407	{
408		printf("%s, line %d: the device appears to have a passcode.\n",
409		  cpt_fail_header, __LINE__);
410		goto end;
411	}
412
413	if (set_passcode(passcode, NULL))
414	{
415		printf("%s, line %d: failed to set a new passcode.\n",
416		  cpt_fail_header, __LINE__);
417		goto end;
418	}
419
420	fd = open(filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC);
421
422	if (fd == -1)
423	{
424		printf("%s, line %d: failed to create the test file, errno = %s.\n",
425		  cpt_fail_header, __LINE__, strerror(errno));
426		goto remove_passcode;
427	}
428
429	/* Ensure we can freely read and change protection classes when unlocked. */
430	for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_F; new_prot_class++)
431	{
432		old_prot_class = GET_PROT_CLASS(fd);
433
434		if (old_prot_class == -1)
435		{
436			printf("%s, line %d: failed to get protection class when unlocked, errno = %s.\n",
437			  cpt_fail_header, __LINE__, strerror(errno));
438			goto cleanup_file;
439		}
440
441		if (SET_PROT_CLASS(fd, new_prot_class))
442		{
443			printf("%s, line %d: failed to change protection class from %d to %d during unlock, errno = %s.\n",
444			  cpt_fail_header, __LINE__, old_prot_class, new_prot_class, strerror(errno));
445			goto cleanup_file;
446		}
447	}
448
449	if (SET_PROT_CLASS(fd, PROTECTION_CLASS_D))
450	{
451		printf("%s, line %d: failed to change protection class from F to D when unlocked, errno = %s.\n",
452		  cpt_fail_header, __LINE__, strerror(errno));
453		goto cleanup_file;
454	}
455
456	/* Try making a class A file while locked. */
457	if (lock_device())
458	{
459		PRINT_LOCK_FAIL;
460		goto cleanup_file;
461	}
462
463	if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_A))
464	{
465		printf("%s, line %d: was able to change protection class from D to A when locked.\n",
466		  cpt_fail_header, __LINE__);
467		goto cleanup_file;
468	}
469
470	if (unlock_device(passcode))
471	{
472		PRINT_UNLOCK_FAIL;
473		goto cleanup_file;
474	}
475
476	/* Attempt opening/IO to a class A file while unlocked. */
477	if (SET_PROT_CLASS(fd, PROTECTION_CLASS_A))
478	{
479		printf("%s, line %d: failed to change protection class from D to A when unlocked, errno = %s.\n",
480		  cpt_fail_header, __LINE__, strerror(errno));
481		goto cleanup_file;
482	}
483
484	close(fd);
485	fd = open(filepath, O_RDWR | O_CLOEXEC);
486
487	if (fd == -1)
488	{
489		printf("%s, line %d: failed to open a class A file when unlocked, errno = %s.\n",
490		  cpt_fail_header, __LINE__, strerror(errno));
491		goto remove_file;
492	}
493
494	/* TODO: Write specific data we can check for.
495	   If we're going to do that, the write scheme should be deliberately ugly. */
496	current_byte = 0;
497
498	while (current_byte < CPT_IO_SIZE)
499	{
500		local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
501
502		if (local_result == -1)
503		{
504			printf("%s, line %d: failed to write to class A file when unlocked, errno = %s.\n",
505			  cpt_fail_header, __LINE__, strerror(errno));
506			goto cleanup_file;
507		}
508
509		current_byte += local_result;
510	}
511
512	current_byte = 0;
513
514	while (current_byte < CPT_IO_SIZE)
515	{
516		local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
517
518		if (local_result == -1)
519		{
520			printf("%s, line %d: failed to read from class A file when unlocked, errno = %s.\n",
521			  cpt_fail_header, __LINE__, strerror(errno));
522			goto cleanup_file;
523		}
524
525		current_byte += local_result;
526	}
527
528	/* Again, but now while locked; and try to change the file class as well. */
529	if (lock_device())
530	{
531		PRINT_LOCK_FAIL;
532		goto cleanup_file;
533	}
534
535	if (pread(fd, rd_buffer, CPT_IO_SIZE, 0) > 0)
536	{
537		printf("%s, line %d: was able to read from a class A file when locked.\n",
538		  cpt_fail_header, __LINE__);
539		goto cleanup_file;
540	}
541
542	if (pwrite(fd, wr_buffer, CPT_IO_SIZE, 0) > 0)
543	{
544		printf("%s, line %d: was able to write to a class A file when locked.\n",
545		  cpt_fail_header, __LINE__);
546		goto cleanup_file;
547	}
548
549	if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_D))
550	{
551		printf("%s, line %d: was able to change protection class from A to D when locked.\n",
552		  cpt_fail_header, __LINE__);
553		goto cleanup_file;
554	}
555
556	/* Try to open and truncate the file. */
557	close(fd);
558	fd = open(filepath, O_RDWR | O_TRUNC | O_CLOEXEC);
559
560	if (fd != -1)
561	{
562		printf("%s, line %d: was able to open and truncate a class A file when locked.\n",
563		  cpt_fail_header, __LINE__);
564		goto cleanup_file;
565	}
566
567	/* Try to open the file */
568	fd = open(filepath, O_RDWR | O_CLOEXEC);
569
570	if (fd != -1)
571	{
572		printf("%s, line %d: was able to open a class A file when locked.\n",
573		  cpt_fail_header, __LINE__);
574		goto cleanup_file;
575	}
576
577	/* What about class B files? */
578	if (unlock_device(passcode))
579	{
580		PRINT_UNLOCK_FAIL;
581		goto cleanup_file;
582	}
583
584	fd = open(filepath, O_RDWR | O_CLOEXEC);
585
586	if (fd == -1)
587	{
588		printf("%s, line %d: was unable to open a class A file when unlocked.\n",
589		  cpt_fail_header, __LINE__);
590		goto cleanup_file;
591	}
592
593	if (SET_PROT_CLASS(fd, PROTECTION_CLASS_D))
594	{
595		printf("%s, line %d: failed to change protection class from A to D when unlocked, errno = %s.\n",
596		  cpt_fail_header, __LINE__, strerror(errno));
597		goto cleanup_file;
598	}
599
600	if (lock_device())
601	{
602		PRINT_LOCK_FAIL;
603		goto cleanup_file;
604	}
605
606	/* Can we create a class B file while locked? */
607	if (SET_PROT_CLASS(fd, PROTECTION_CLASS_B))
608	{
609		printf("%s, line %d: failed to change protection class from D to B when locked, errno = %s.\n",
610		  cpt_fail_header, __LINE__, strerror(errno));
611		goto cleanup_file;
612	}
613
614	/* We should also be able to read/write to the file descriptor while it is open. */
615	current_byte = 0;
616
617	while (current_byte < CPT_IO_SIZE)
618	{
619		local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
620
621		if (local_result == -1)
622		{
623			printf("%s, line %d: failed to write to new class B file when locked, errno = %s.\n",
624			  cpt_fail_header, __LINE__, strerror(errno));
625			goto cleanup_file;
626		}
627
628		current_byte += local_result;
629	}
630
631	current_byte = 0;
632
633	while (current_byte < CPT_IO_SIZE)
634	{
635		local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
636
637		if (local_result == -1)
638		{
639			printf("%s, line %d: failed to read from new class B file when locked, errno = %s.\n",
640			  cpt_fail_header, __LINE__, strerror(errno));
641			goto cleanup_file;
642		}
643
644		current_byte += local_result;
645	}
646
647	/* We should not be able to open a class B file under lock. */
648	close(fd);
649	fd = open(filepath, O_RDWR | O_CLOEXEC);
650
651	if (fd != -1)
652	{
653		printf("%s, line %d: was able to open a class B file when locked.\n",
654		  cpt_fail_header, __LINE__);
655		goto cleanup_file;
656	}
657
658	unlink(filepath);
659
660	/* We still need to test directory semantics. */
661	if (mkdir(dirpath, 0x0777) == -1)
662	{
663		printf("%s, line %d: failed to create a new directory when locked, errno = %s.\n",
664		  cpt_fail_header, __LINE__, strerror(errno));
665		goto remove_passcode;
666	}
667
668	/* The newly created directory should not have a protection class. */
669	dir_fd = open(dirpath, O_RDONLY | O_CLOEXEC);
670
671	if (dir_fd == -1)
672	{
673		printf("%s, line %d: failed to open an unclassed directory when locked, errno = %s.\n",
674		  cpt_fail_header, __LINE__, strerror(errno));
675		goto remove_dir;
676	}
677
678	if (GET_PROT_CLASS(dir_fd) != PROTECTION_CLASS_D)
679	{
680		printf("%s, line %d: newly created directory had a non-D protection class.\n",
681		  cpt_fail_header, __LINE__);
682		goto cleanup_dir;
683	}
684
685	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A))
686	{
687		printf("%s, line %d: was unable to change a directory from class D to class A during lock.\n",
688		  cpt_fail_header, __LINE__);
689		goto cleanup_dir;
690	}
691
692	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_D))
693	{
694		printf("%s, line %d: failed to change a directory from class A to class D during lock, errno = %s.\n",
695		  cpt_fail_header, __LINE__, strerror(errno));
696		goto cleanup_dir;
697	}
698
699	/* Do all files created in the directory properly inherit the directory's protection class? */
700	if ((strlcpy(filepath, dirpath, PATH_MAX) == PATH_MAX) || (strlcat(filepath, "cpt_test_file", PATH_MAX) == PATH_MAX))
701	{
702		printf("%s, line %d: failed to construct the path for a file in the directory.\n",
703		  cpt_fail_header, __LINE__);
704		goto cleanup_dir;
705	}
706
707	if (unlock_device(passcode))
708	{
709		PRINT_UNLOCK_FAIL;
710		goto cleanup_dir;
711	}
712
713	for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_E; new_prot_class++)
714	{
715		old_prot_class = GET_PROT_CLASS(dir_fd);
716
717		if (old_prot_class == -1)
718		{
719			printf("%s, line %d: failed to get the protection class for the directory, errno = %s.\n",
720			  cpt_fail_header, __LINE__, strerror(errno));
721			goto cleanup_dir;
722		}
723
724		if (SET_PROT_CLASS(dir_fd, new_prot_class))
725		{
726			printf("%s, line %d: failed to change the protection class for the directory from %d to %d, errno = %s.\n",
727			  cpt_fail_header, __LINE__, old_prot_class, new_prot_class, strerror(errno));
728			goto cleanup_dir;
729		}
730
731		fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC);
732
733		if (fd == -1)
734		{
735			printf("%s, line %d: failed to create a file in a class %d directory when unlocked, errno = %s.\n",
736			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
737			goto cleanup_dir;
738		}
739
740		local_result = GET_PROT_CLASS(fd);
741
742		if (local_result == -1)
743		{
744			printf("%s, line %d: failed to get the new file's protection class, errno = %s.\n",
745			  cpt_fail_header, __LINE__, strerror(errno));
746			goto cleanup_file;
747		}
748		else if (local_result != new_prot_class)
749		{
750			printf("%s, line %d: new file did not inherit the directory's protection class.\n",
751			  cpt_fail_header, __LINE__, strerror(errno));
752			goto cleanup_file;
753		}
754
755		close(fd);
756		unlink(filepath);
757	}
758
759	/* Do we disallow creation of a class F directory? */
760	if (!SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_F))
761	{
762		printf("%s, line %d: creation of a class F directory did not fail as expected.\n",
763		  cpt_fail_header, __LINE__);
764		goto cleanup_dir;
765	}
766
767	/* And are class A and class B semantics followed for when we create these files during lock? */
768	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A))
769	{
770		printf("%s, line %d: failed to change directory class from F to A when unlocked, errno = %s.\n",
771		  cpt_fail_header, __LINE__, strerror(errno));
772		goto cleanup_dir;
773	}
774
775	if (lock_device())
776	{
777		PRINT_LOCK_FAIL;
778		goto cleanup_dir;
779	}
780
781	fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC);
782
783	if (fd != -1)
784	{
785		printf("%s, line %d: was able to create a new file in a class A directory when locked.\n",
786		  cpt_fail_header, __LINE__, strerror(errno));
787		goto cleanup_file;
788	}
789
790	if (unlock_device(passcode))
791	{
792		PRINT_UNLOCK_FAIL;
793		goto cleanup_dir;
794	}
795
796	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_B))
797	{
798		printf("%s, line %d: failed to change directory class from A to B when unlocked, errno = %s.\n",
799		  cpt_fail_header, __LINE__, strerror(errno));
800		goto cleanup_dir;
801	}
802
803	if (lock_device())
804	{
805		PRINT_LOCK_FAIL;
806		goto cleanup_dir;
807	}
808
809	fd = open(filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC);
810
811	if (fd == -1)
812	{
813		printf("%s, line %d: failed to create new file in class B directory when locked, errno = %s.\n",
814		  cpt_fail_header, __LINE__, strerror(errno));
815		goto cleanup_dir;
816	}
817
818	local_result = GET_PROT_CLASS(fd);
819
820	if (local_result == -1)
821	{
822		printf("%s, line %d: failed to get protection class for a new file when locked, errno = %s.\n",
823		  cpt_fail_header, __LINE__, strerror(errno));
824		goto cleanup_file;
825	}
826	else if (local_result != PROTECTION_CLASS_B)
827	{
828		printf("%s, line %d: new file in class B directory did not inherit protection class.\n",
829		  cpt_fail_header, __LINE__, strerror(errno));
830		goto cleanup_file;
831	}
832
833	/* What happens when we try to create new subdirectories? */
834	if (unlock_device(passcode))
835	{
836		PRINT_UNLOCK_FAIL;
837		goto cleanup_file;
838	}
839
840	for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_E; new_prot_class++)
841	{
842		if (SET_PROT_CLASS(dir_fd, new_prot_class))
843		{
844			printf("%s, line %d: failed to change directory to class %d, errno = %s.\n",
845			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
846			goto cleanup_file;
847		}
848
849		local_result = mkdir(subdirpath, 0x0777);
850
851		if (local_result == -1)
852		{
853			printf("%s, line %d: failed to create subdirectory in class %d directory, errno = %s.\n",
854			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
855			goto cleanup_file;
856		}
857
858		subdir_fd = open(subdirpath, O_RDONLY | O_CLOEXEC);
859
860		if (subdir_fd == -1)
861		{
862			printf("%s, line %d: failed to open subdirectory in class %d directory, errno = %s.\n",
863			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
864			goto remove_subdir;
865		}
866
867		local_result = GET_PROT_CLASS(subdir_fd);
868
869		if (local_result == -1)
870		{
871			printf("%s, line %d: failed to get class of new subdirectory of class %d directory, errno = %s.\n",
872			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
873			goto cleanup_subdir;
874		}
875		else if (local_result != new_prot_class)
876		{
877			printf("%s, line %d: new subdirectory had different class than class %d parent.\n",
878			  cpt_fail_header, __LINE__, new_prot_class);
879			goto cleanup_subdir;
880		}
881
882		close(subdir_fd);
883		rmdir(subdirpath);
884	}
885
886	/* If we've made it this far, the test was successful. */
887	test_result = 0;
888
889cleanup_subdir:
890	close(subdir_fd);
891
892remove_subdir:
893	rmdir(subdirpath);
894
895cleanup_file:
896	close(fd);
897
898remove_file:
899	unlink(filepath);
900
901cleanup_dir:
902	close(dir_fd);
903
904remove_dir:
905	rmdir(dirpath);
906
907remove_passcode:
908	/* Try to unlock the device (no ramifications if it isn't locked when we try) and remove the passcode. */
909	if (unlock_device(passcode))
910	{
911		printf("WARNING: failed to unlock the device.\n");
912	}
913
914	if (clear_passcode(passcode))
915	{
916		printf("WARNING: failed to clear the passcode.\n");
917	}
918
919end:
920	return(test_result);
921}
922
923