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, 0777);
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	/* Query the filesystem for the default CP level (Is it C?) */
450#ifndef F_GETDEFAULTPROTLEVEL
451#define F_GETDEFAULTPROTLEVEL 79
452#endif
453
454	old_prot_class = fcntl(fd, F_GETDEFAULTPROTLEVEL);
455	if (old_prot_class == -1) {
456		printf("%s , line %d: failed to acquire default protection level for filesystem , errno = %s \n",
457				cpt_fail_header, __LINE__, strerror(errno));
458		goto cleanup_file;
459	}
460
461	/* XXX: Do we want to do anything with the level? What should it be? */
462
463
464	/*
465	 * files are allowed to move into F, but not out of it.  They can also only do so
466	 * when they do not have content.
467	 */
468	close (fd);
469	unlink (filepath);
470
471
472	/* re-create the file */
473	fd = open (filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC);
474	if (fd == -1) {
475		printf("%s, line %d: failed to create the test file, errno = %s.\n",
476					cpt_fail_header, __LINE__, strerror(errno));
477		goto cleanup_file;
478	}
479
480	/* Try making a class A file while locked. */
481	if (lock_device())
482	{
483		PRINT_LOCK_FAIL;
484		goto cleanup_file;
485	}
486
487	if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_A))
488	{
489		printf("%s, line %d: was able to change protection class from D to A when locked.\n",
490		  cpt_fail_header, __LINE__);
491		goto cleanup_file;
492	}
493
494	if (unlock_device(passcode))
495	{
496		PRINT_UNLOCK_FAIL;
497		goto cleanup_file;
498	}
499
500	/* Attempt opening/IO to a class A file while unlocked. */
501	if (SET_PROT_CLASS(fd, PROTECTION_CLASS_A))
502	{
503		printf("%s, line %d: failed to change protection class from D to A when unlocked, errno = %s.\n",
504		  cpt_fail_header, __LINE__, strerror(errno));
505		goto cleanup_file;
506	}
507
508	close(fd);
509	fd = open(filepath, O_RDWR | O_CLOEXEC);
510
511	if (fd == -1)
512	{
513		printf("%s, line %d: failed to open a class A file when unlocked, errno = %s.\n",
514		  cpt_fail_header, __LINE__, strerror(errno));
515		goto remove_file;
516	}
517
518	/* TODO: Write specific data we can check for.
519	   If we're going to do that, the write scheme should be deliberately ugly. */
520	current_byte = 0;
521
522	while (current_byte < CPT_IO_SIZE)
523	{
524		local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
525
526		if (local_result == -1)
527		{
528			printf("%s, line %d: failed to write to class A file when unlocked, errno = %s.\n",
529			  cpt_fail_header, __LINE__, strerror(errno));
530			goto cleanup_file;
531		}
532
533		current_byte += local_result;
534	}
535
536	current_byte = 0;
537
538	while (current_byte < CPT_IO_SIZE)
539	{
540		local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
541
542		if (local_result == -1)
543		{
544			printf("%s, line %d: failed to read from class A file when unlocked, errno = %s.\n",
545			  cpt_fail_header, __LINE__, strerror(errno));
546			goto cleanup_file;
547		}
548
549		current_byte += local_result;
550	}
551
552	/* Again, but now while locked; and try to change the file class as well. */
553	if (lock_device())
554	{
555		PRINT_LOCK_FAIL;
556		goto cleanup_file;
557	}
558
559	if (pread(fd, rd_buffer, CPT_IO_SIZE, 0) > 0)
560	{
561		printf("%s, line %d: was able to read from a class A file when locked.\n",
562		  cpt_fail_header, __LINE__);
563		goto cleanup_file;
564	}
565
566	if (pwrite(fd, wr_buffer, CPT_IO_SIZE, 0) > 0)
567	{
568		printf("%s, line %d: was able to write to a class A file when locked.\n",
569		  cpt_fail_header, __LINE__);
570		goto cleanup_file;
571	}
572
573	if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_D))
574	{
575		printf("%s, line %d: was able to change protection class from A to D when locked.\n",
576		  cpt_fail_header, __LINE__);
577		goto cleanup_file;
578	}
579
580	/* Try to open and truncate the file. */
581	close(fd);
582	fd = open(filepath, O_RDWR | O_TRUNC | O_CLOEXEC);
583
584	if (fd != -1)
585	{
586		printf("%s, line %d: was able to open and truncate a class A file when locked.\n",
587		  cpt_fail_header, __LINE__);
588		goto cleanup_file;
589	}
590
591	/* Try to open the file */
592	fd = open(filepath, O_RDWR | O_CLOEXEC);
593
594	if (fd != -1)
595	{
596		printf("%s, line %d: was able to open a class A file when locked.\n",
597		  cpt_fail_header, __LINE__);
598		goto cleanup_file;
599	}
600
601	/* What about class B files? */
602	if (unlock_device(passcode))
603	{
604		PRINT_UNLOCK_FAIL;
605		goto cleanup_file;
606	}
607
608	fd = open(filepath, O_RDWR | O_CLOEXEC);
609
610	if (fd == -1)
611	{
612		printf("%s, line %d: was unable to open a class A file when unlocked.\n",
613		  cpt_fail_header, __LINE__);
614		goto cleanup_file;
615	}
616
617	if (SET_PROT_CLASS(fd, PROTECTION_CLASS_D))
618	{
619		printf("%s, line %d: failed to change protection class from A to D when unlocked, errno = %s.\n",
620		  cpt_fail_header, __LINE__, strerror(errno));
621		goto cleanup_file;
622	}
623
624	if (lock_device())
625	{
626		PRINT_LOCK_FAIL;
627		goto cleanup_file;
628	}
629
630	/* Can we create a class B file while locked? */
631	if (SET_PROT_CLASS(fd, PROTECTION_CLASS_B))
632	{
633		printf("%s, line %d: failed to change protection class from D to B when locked, errno = %s.\n",
634		  cpt_fail_header, __LINE__, strerror(errno));
635		goto cleanup_file;
636	}
637
638	if (GET_PROT_CLASS (fd) != PROTECTION_CLASS_B) {
639		printf("%s, line %d: Failed to switch to class B file \n",
640				cpt_fail_header, __LINE__ );
641		goto cleanup_file;
642	}
643
644
645	/* We should also be able to read/write to the file descriptor while it is open. */
646	current_byte = 0;
647
648	while (current_byte < CPT_IO_SIZE)
649	{
650		local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
651
652		if (local_result == -1)
653		{
654			printf("%s, line %d: failed to write to new class B file when locked, errno = %s.\n",
655			  cpt_fail_header, __LINE__, strerror(errno));
656			goto cleanup_file;
657		}
658
659		current_byte += local_result;
660	}
661
662	current_byte = 0;
663
664	while (current_byte < CPT_IO_SIZE)
665	{
666		local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte);
667
668		if (local_result == -1)
669		{
670			printf("%s, line %d: failed to read from new class B file when locked, errno = %s.\n",
671			  cpt_fail_header, __LINE__, strerror(errno));
672			goto cleanup_file;
673		}
674
675		current_byte += local_result;
676	}
677
678	/* We should not be able to open a class B file under lock. */
679	close(fd);
680	fd = open(filepath, O_RDWR | O_CLOEXEC);
681
682	if (fd != -1)
683	{
684		printf("%s, line %d: was able to open a class B file when locked.\n",
685		  cpt_fail_header, __LINE__);
686		goto cleanup_file;
687	}
688
689	unlink(filepath);
690
691	/* We still need to test directory semantics. */
692	if (mkdir(dirpath, 0x0777) == -1)
693	{
694		printf("%s, line %d: failed to create a new directory when locked, errno = %s.\n",
695		  cpt_fail_header, __LINE__, strerror(errno));
696		goto remove_passcode;
697	}
698
699	/* The newly created directory should not have a protection class. */
700	dir_fd = open(dirpath, O_RDONLY | O_CLOEXEC);
701
702	if (dir_fd == -1)
703	{
704		printf("%s, line %d: failed to open an unclassed directory when locked, errno = %s.\n",
705		  cpt_fail_header, __LINE__, strerror(errno));
706		goto remove_dir;
707	}
708
709	if ((GET_PROT_CLASS(dir_fd) != PROTECTION_CLASS_D) && (GET_PROT_CLASS(dir_fd) != PROTECTION_CLASS_DIR_NONE))
710	{
711		printf("%s, line %d: newly created directory had a non-D and non-NONE protection class.\n",
712		  cpt_fail_header, __LINE__);
713		goto cleanup_dir;
714	}
715
716	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A))
717	{
718		printf("%s, line %d: was unable to change a directory from class D to class A during lock.\n",
719		  cpt_fail_header, __LINE__);
720		goto cleanup_dir;
721	}
722
723	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_D))
724	{
725		printf("%s, line %d: failed to change a directory from class A to class D during lock, errno = %s.\n",
726		  cpt_fail_header, __LINE__, strerror(errno));
727		goto cleanup_dir;
728	}
729
730	/* Do all files created in the directory properly inherit the directory's protection class? */
731	if ((strlcpy(filepath, dirpath, PATH_MAX) == PATH_MAX) || (strlcat(filepath, "cpt_test_file", PATH_MAX) == PATH_MAX))
732	{
733		printf("%s, line %d: failed to construct the path for a file in the directory.\n",
734		  cpt_fail_header, __LINE__);
735		goto cleanup_dir;
736	}
737
738	if (unlock_device(passcode))
739	{
740		PRINT_UNLOCK_FAIL;
741		goto cleanup_dir;
742	}
743
744	for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_D; new_prot_class++)
745	{
746		int getclass_dir;
747		old_prot_class = GET_PROT_CLASS(dir_fd);
748
749		if (old_prot_class == -1)
750		{
751			printf("%s, line %d: failed to get the protection class for the directory, errno = %s.\n",
752			  cpt_fail_header, __LINE__, strerror(errno));
753			goto cleanup_dir;
754		}
755
756		if (SET_PROT_CLASS(dir_fd, new_prot_class))
757		{
758			printf("%s, line %d: failed to change the protection class for the directory from %d to %d, errno = %s.\n",
759			  cpt_fail_header, __LINE__, old_prot_class, new_prot_class, strerror(errno));
760			goto cleanup_dir;
761		}
762
763		getclass_dir = GET_PROT_CLASS(dir_fd);
764		if (getclass_dir != new_prot_class) {
765			printf("%s, line %d: failed to get the new protection class for the directory %d (got %d) \n",
766					cpt_fail_header, __LINE__, new_prot_class, getclass_dir);
767			goto cleanup_dir;
768
769		}
770
771		fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777);
772
773		if (fd == -1)
774		{
775			printf("%s, line %d: failed to create a file in a class %d directory when unlocked, errno = %s.\n",
776			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
777			goto cleanup_dir;
778		}
779
780		local_result = GET_PROT_CLASS(fd);
781
782		if (local_result == -1)
783		{
784			printf("%s, line %d: failed to get the new file's protection class, errno = %s.\n",
785			  cpt_fail_header, __LINE__, strerror(errno));
786			goto cleanup_file;
787		}
788		else if (local_result != new_prot_class)
789		{
790
791			printf("%s, line %d: new file (%d) did not inherit the directory's protection class (%d) .\n",
792					cpt_fail_header, __LINE__, local_result, new_prot_class);
793			goto cleanup_file;
794		}
795
796		close(fd);
797		unlink(filepath);
798	}
799
800	/* Do we disallow creation of a class F directory? */
801	if (!SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_F))
802	{
803		printf("%s, line %d: creation of a class F directory did not fail as expected.\n",
804		  cpt_fail_header, __LINE__);
805		goto cleanup_dir;
806	}
807
808	/* And are class A and class B semantics followed for when we create these files during lock? */
809	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A))
810	{
811		printf("%s, line %d: failed to change directory class from F to A when unlocked, errno = %s.\n",
812		  cpt_fail_header, __LINE__, strerror(errno));
813		goto cleanup_dir;
814	}
815
816	if (lock_device())
817	{
818		PRINT_LOCK_FAIL;
819		goto cleanup_dir;
820	}
821
822	fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777);
823
824	if (fd != -1)
825	{
826		printf("%s, line %d: was able to create a new file in a class A directory when locked.\n",
827		  cpt_fail_header, __LINE__, strerror(errno));
828		goto cleanup_file;
829	}
830
831	if (unlock_device(passcode))
832	{
833		PRINT_UNLOCK_FAIL;
834		goto cleanup_dir;
835	}
836
837	if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_B))
838	{
839		printf("%s, line %d: failed to change directory class from A to B when unlocked, errno = %s.\n",
840		  cpt_fail_header, __LINE__, strerror(errno));
841		goto cleanup_dir;
842	}
843
844	if (lock_device())
845	{
846		PRINT_LOCK_FAIL;
847		goto cleanup_dir;
848	}
849
850	fd = open(filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777);
851
852	if (fd == -1)
853	{
854		printf("%s, line %d: failed to create new file in class B directory when locked, errno = %s.\n",
855		  cpt_fail_header, __LINE__, strerror(errno));
856		goto cleanup_dir;
857	}
858
859	local_result = GET_PROT_CLASS(fd);
860
861	if (local_result == -1)
862	{
863		printf("%s, line %d: failed to get protection class for a new file when locked, errno = %s.\n",
864		  cpt_fail_header, __LINE__, strerror(errno));
865		goto cleanup_file;
866	}
867	else if (local_result != PROTECTION_CLASS_B)
868	{
869		printf("%s, line %d: new file in class B directory did not inherit protection class.\n",
870		  cpt_fail_header, __LINE__, strerror(errno));
871		goto cleanup_file;
872	}
873
874	/* What happens when we try to create new subdirectories? */
875	if (unlock_device(passcode))
876	{
877		PRINT_UNLOCK_FAIL;
878		goto cleanup_file;
879	}
880
881	for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_D; new_prot_class++)
882	{
883		if (SET_PROT_CLASS(dir_fd, new_prot_class))
884		{
885			printf("%s, line %d: failed to change directory to class %d, errno = %s.\n",
886			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
887			goto cleanup_file;
888		}
889
890		local_result = mkdir(subdirpath, 0x0777);
891
892		if (local_result == -1)
893		{
894			printf("%s, line %d: failed to create subdirectory in class %d directory, errno = %s.\n",
895			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
896			goto cleanup_file;
897		}
898
899		subdir_fd = open(subdirpath, O_RDONLY | O_CLOEXEC);
900
901		if (subdir_fd == -1)
902		{
903			printf("%s, line %d: failed to open subdirectory in class %d directory, errno = %s.\n",
904			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
905			goto remove_subdir;
906		}
907
908		local_result = GET_PROT_CLASS(subdir_fd);
909
910		if (local_result == -1)
911		{
912			printf("%s, line %d: failed to get class of new subdirectory of class %d directory, errno = %s.\n",
913			  cpt_fail_header, __LINE__, new_prot_class, strerror(errno));
914			goto cleanup_subdir;
915		}
916		else if (local_result != new_prot_class)
917		{
918			printf("%s, line %d: new subdirectory had different class than class %d parent.\n",
919			  cpt_fail_header, __LINE__, new_prot_class);
920			goto cleanup_subdir;
921		}
922
923		close(subdir_fd);
924		rmdir(subdirpath);
925	}
926
927	/* If we've made it this far, the test was successful. */
928	test_result = 0;
929
930cleanup_subdir:
931	close(subdir_fd);
932
933remove_subdir:
934	rmdir(subdirpath);
935
936cleanup_file:
937	close(fd);
938
939remove_file:
940	unlink(filepath);
941
942cleanup_dir:
943	close(dir_fd);
944
945remove_dir:
946	rmdir(dirpath);
947
948remove_passcode:
949	/* Try to unlock the device (no ramifications if it isn't locked when we try) and remove the passcode. */
950	if (unlock_device(passcode))
951	{
952		printf("WARNING: failed to unlock the device.\n");
953	}
954
955	if (clear_passcode(passcode))
956	{
957		printf("WARNING: failed to clear the passcode.\n");
958	}
959
960end:
961	return(test_result);
962}
963
964