1 #include <darwintest.h>
2 #include <stdint.h>
3 #include <bsm/libbsm.h>
4 #include <System/sys/codesign.h>
5 #include <kern/cs_blobs.h>
6 #include <sys/errno.h>
7 #include <stdio.h>
8 #include <unistd.h>
9 #include <TargetConditionals.h>
10
11 #define MAXBUFLEN 1024 * 1024
12
13 T_GLOBAL_META(
14 T_META_NAMESPACE("xnu.codesigning"),
15 T_META_RADAR_COMPONENT_NAME("xnu"),
16 T_META_RADAR_COMPONENT_VERSION("codesigning"),
17 T_META_OWNER("rkendallkuppe"));
18
19 int
get_blob(pid_t pid,int op)20 get_blob(pid_t pid, int op)
21 {
22 uint8_t header[8];
23 unsigned int cnt;
24 int rcent;
25
26 for (cnt = 0; cnt < sizeof(header); cnt++) {
27 rcent = csops(pid, op, header, cnt);
28 if (rcent != -1 && errno != ERANGE) {
29 T_ASSERT_FAIL("errno != ERANGE for short header");
30 }
31 }
32
33 rcent = csops(pid, op, header, sizeof(header));
34 if (rcent == -1 && errno == ERANGE) {
35 uint32_t len, bufferlen, bufferlen2;
36
37 memcpy(&len, &header[4], 4);
38 bufferlen = ntohl(len);
39
40 T_LOG("Checking bufferlen on blob from kernel for csop %d", op);
41 T_ASSERT_LE_INT(bufferlen, MAXBUFLEN, "Buffer length %zu on blob from kernel can't exceed %zu",
42 (size_t)bufferlen, MAXBUFLEN);
43 T_ASSERT_NE_INT(bufferlen, 0, "Buffer length %zu on blob from kernel can't be zero",
44 (size_t)bufferlen);
45 T_ASSERT_GE_INT(bufferlen, 8, "Buffer length %zu on blob from kernel can't be less than a byte",
46 (size_t)bufferlen);
47
48 uint8_t buffer[bufferlen + 1];
49
50 rcent = csops(pid, op, buffer, bufferlen - 1);
51 T_ASSERT_POSIX_ERROR(errno, ERANGE, "Performing CS OPS csops with a full buffer - 1");
52
53 rcent = csops(pid, op, buffer, bufferlen);
54 T_ASSERT_EQ_INT(rcent, 0, "Performing CS OPS with full buffer.");
55
56 memcpy(&len, &buffer[4], 4);
57 bufferlen2 = ntohl(len);
58
59 if (op == CS_OPS_BLOB) {
60 T_ASSERT_LE_INT(bufferlen2, bufferlen, "Checking %zu is %zu larger on second try",
61 (size_t)bufferlen2, (size_t)bufferlen);
62
63 /*
64 * CS_OPS_BLOB may want a bigger buffer than the size
65 * of the actual blob. If the blob came in through a
66 * load command for example, then CS_OPS_BLOB will
67 * want to copy out the whole buffer that the load
68 * command points to, which is usually an estimated
69 * size. The actual blob, and therefore the size in
70 * the blob's header, may be smaller.
71 */
72 T_LOG("Blob is smaller (%zu) than expected (%zu). This is fine.",
73 (size_t)bufferlen2, (size_t)bufferlen);
74 } else {
75 T_ASSERT_EQ_INT(bufferlen2, bufferlen, "Checking bufferlen sizes are different");
76 }
77
78 rcent = csops(pid, op, buffer, bufferlen + 1);
79 T_ASSERT_EQ_INT(rcent, 0, "Performing CS OPS with a full buffer + 1");
80
81 return 0;
82 } else if (rcent == 0) {
83 return 0;
84 } else {
85 return 1;
86 }
87 }
88
89 /*
90 * This source is compiled with different names and build flags.
91 * Makefile has the detail of the TESTNAME.
92 */
93
94 T_DECL(TESTNAME, "CS OP, code sign operations test")
95 {
96 uint32_t status;
97 int rcent;
98 pid_t pid;
99 csops_cdhash_t cdhash_info = {0};
100 uint8_t cdhash[CS_CDHASH_LEN] = {0};
101
102 pid = getpid();
103
104 rcent = get_blob(pid, CS_OPS_ENTITLEMENTS_BLOB);
105 T_ASSERT_EQ_INT(rcent, 0, "Getting CS OPS entitlements blob");
106
107 rcent = get_blob(0, CS_OPS_ENTITLEMENTS_BLOB);
108 T_ASSERT_EQ_INT(rcent, 0, "Getting CS OPS entitlements blob");
109
110 rcent = get_blob(pid, CS_OPS_BLOB);
111
112 T_ASSERT_EQ_INT(rcent, 0, "Getting CS OPS blob");
113
114 rcent = get_blob(0, CS_OPS_BLOB);
115 T_ASSERT_EQ_INT(rcent, 0, "Getting CS OPS blob");
116
117 rcent = get_blob(pid, CS_OPS_IDENTITY);
118 T_ASSERT_EQ_INT(rcent, 0, "Getting CS OPs identity");
119
120 rcent = get_blob(0, CS_OPS_IDENTITY);
121 T_ASSERT_EQ_INT(rcent, 0, "Getting CS OPs identity");
122
123 rcent = csops(pid, CS_OPS_SET_STATUS, &status, sizeof(status) - 1);
124 T_ASSERT_NE_INT(rcent, 0, "Checking CS OPs set status by setting buffer to (status - 1)");
125
126 status = CS_RESTRICT;
127 rcent = csops(pid, CS_OPS_SET_STATUS, &status, sizeof(status));
128 T_ASSERT_EQ_INT(rcent, 0, "Checking CS OPs set status by setting proc RESTRICTED");
129
130 rcent = csops(pid, CS_OPS_STATUS, &status, sizeof(status));
131 T_ASSERT_EQ_INT(rcent, 0, "Getting CS OPs status of process");
132
133 rcent = csops(pid, CS_OPS_CDHASH, cdhash, sizeof(cdhash));
134 T_ASSERT_EQ_INT(rcent, 0, "Getting CS_OPS_CDHASH");
135
136 rcent = csops(pid, CS_OPS_CDHASH_WITH_INFO, &cdhash_info, sizeof(cdhash_info));
137 T_ASSERT_EQ_INT(rcent, 0, "Getting CS_OPS_CDHASH_WITH_INFO");
138
139 /* Verify the returned CDHashes match and are the correct type */
140 T_ASSERT_EQ_INT(memcmp(cdhash_info.hash, cdhash, sizeof(cdhash)), 0, "Comparing CDHashes");
141
142 #if TARGET_OS_WATCH
143 /* watchOS prefers SHA1 hashes for now */
144 T_ASSERT_EQ_INT(cdhash_info.type, CS_HASHTYPE_SHA1, "Checking returned CDHash type [SHA1]");
145 #else
146 T_ASSERT_EQ_INT(cdhash_info.type, CS_HASHTYPE_SHA256, "Checking returned CDHash type [SHA256]");
147 #endif
148
149 /*
150 * Only run the following tests if not HARD since otherwise
151 * we'll just die when marking ourself invalid.
152 */
153
154 if ((status & CS_KILL) == 0) {
155 rcent = csops(pid, CS_OPS_MARKINVALID, NULL, 0);
156 T_ASSERT_POSIX_ZERO(rcent, 0, "Setting CS OPs mark proc invalid");
157
158 status = CS_ENFORCEMENT;
159 rcent = csops(pid, CS_OPS_SET_STATUS, &status, sizeof(status));
160 T_ASSERT_POSIX_ERROR(rcent, -1, "Managed to set flags on an INVALID proc");
161
162 rcent = get_blob(pid, CS_OPS_ENTITLEMENTS_BLOB);
163 T_ASSERT_POSIX_ERROR(rcent, 1, "Got entitlements while invalid");
164
165 rcent = get_blob(pid, CS_OPS_IDENTITY);
166 T_ASSERT_POSIX_ERROR(rcent, 1, "Getting CS OPS identity");
167
168 rcent = get_blob(0, CS_OPS_IDENTITY);
169 T_ASSERT_POSIX_ERROR(rcent, 1, "Getting CS OPS identity");
170
171 rcent = get_blob(0, CS_OPS_BLOB);
172 T_ASSERT_POSIX_ERROR(rcent, 1, "Geting CS OPS blob");
173 }
174 }
175