1 #include <errno.h>
2 #include <stdlib.h>
3 #include <libgen.h>
4 #include <limits.h>
5 #include <mach-o/dyld.h>
6 #include <sys/types.h>
7 #include <sys/sysctl.h>
8 #include <xlocale.h>
9
10 #include <darwintest.h>
11 #include <darwintest_utils.h>
12
13 #include "drop_priv.h"
14 #include "test_utils.h"
15
16 #if ENTITLED
17 #define SET_TREATMENT_ID set_treatment_id_entitled
18 #define SET_TREATMENT_ID_DESCR "Can set treatment id with entitlement"
19 #else /* ENTITLED */
20 #define SET_TREATMENT_ID set_treatment_id_unentitled
21 #define SET_TREATMENT_ID_DESCR "Can't set treatment id without entitlement"
22 #endif /* ENTITLED */
23
24 T_DECL(SET_TREATMENT_ID, "Verifies that EXPERIMENT sysctls can only be set with the entitlement", T_META_ASROOT(false))
25 {
26 #define TEST_STR "testing"
27 #define IDENTIFIER_LENGTH 36
28
29 int ret;
30 errno_t err;
31 char val[IDENTIFIER_LENGTH + 1] = {0};
32 size_t len = sizeof(val);
33 char new_val[IDENTIFIER_LENGTH + 1] = {0};
34
35 if (!is_development_kernel()) {
36 T_SKIP("skipping test on release kernel");
37 }
38
39 strlcpy(new_val, TEST_STR, sizeof(new_val));
40 if (running_as_root()) {
41 drop_priv();
42 }
43
44 ret = sysctlbyname("kern.trial_treatment_id", val, &len, new_val, strlen(new_val));
45 err = errno;
46 #if ENTITLED
47 len = sizeof(val);
48 memset(new_val, 0, sizeof(new_val));
49 T_ASSERT_POSIX_SUCCESS(ret, "set kern.trial_treatment_id");
50 /* Cleanup. Set it back to the empty string. */
51 ret = sysctlbyname("kern.trial_treatment_id", val, &len, new_val, 1);
52 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "reset kern.trial_treatment_id");
53 #else
54 T_ASSERT_POSIX_FAILURE(ret, EPERM, "set kern.trial_treatment_id");
55 #endif /* ENTITLED */
56 }
57
58 #if ENTITLED
59 /* Check min and max value limits on numeric factors */
60 T_DECL(experiment_factor_numeric_limits,
61 "Can only set factors within the legal range.",
62 T_META_ASROOT(false))
63 {
64 #define kMinVal 5 /* The min value allowed for the testing factor. */
65 #define kMaxVal 10 /* The max value allowed for the testing factor. */
66 errno_t err;
67 int ret;
68 unsigned int current_val;
69 size_t len = sizeof(current_val);
70 unsigned int new_val;
71
72 if (running_as_root()) {
73 drop_priv();
74 }
75 new_val = kMinVal - 1;
76 ret = sysctlbyname("kern.testing_experiment_factor", ¤t_val, &len, &new_val, sizeof(new_val));
77 err = errno;
78 T_ASSERT_POSIX_FAILURE(ret, EINVAL, "set kern.testing_experiment_factor below range.");
79
80 new_val = kMaxVal + 1;
81 ret = sysctlbyname("kern.testing_experiment_factor", ¤t_val, &len, &new_val, sizeof(new_val));
82 err = errno;
83 T_ASSERT_POSIX_FAILURE(ret, EINVAL, "set kern.testing_experiment_factor above range.");
84
85 new_val = kMaxVal;
86 ret = sysctlbyname("kern.testing_experiment_factor", ¤t_val, &len, &new_val, sizeof(new_val));
87 T_ASSERT_POSIX_SUCCESS(ret, "set kern.testing_experiment_factor at top of range.");
88
89 new_val = kMinVal;
90 ret = sysctlbyname("kern.testing_experiment_factor", ¤t_val, &len, &new_val, sizeof(new_val));
91 T_ASSERT_POSIX_SUCCESS(ret, "set kern.testing_experiment_factor at bottom of range.");
92 }
93
94 static uint64_t original_libmalloc_experiment_value = 0;
95
96 static void
reset_libmalloc_experiment(void)97 reset_libmalloc_experiment(void)
98 {
99 int ret = sysctlbyname("kern.libmalloc_experiments", NULL, NULL, &original_libmalloc_experiment_value, sizeof(original_libmalloc_experiment_value));
100 T_ASSERT_POSIX_SUCCESS(ret, "reset kern.libmalloc_experiments");
101 }
102
103 static void
set_libmalloc_experiment(uint64_t val)104 set_libmalloc_experiment(uint64_t val)
105 {
106 T_LOG("Setting kern.libmalloc_experiments to %llu", val);
107 size_t len = sizeof(original_libmalloc_experiment_value);
108 int ret = sysctlbyname("kern.libmalloc_experiments", &original_libmalloc_experiment_value, &len, &val, sizeof(val));
109 T_ASSERT_POSIX_SUCCESS(ret, "set kern.libmalloc_experiments");
110 T_ATEND(reset_libmalloc_experiment);
111 }
112
113 #define PRINT_APPLE_ARRAY_TOOL "tools/print_apple_array"
114 /*
115 * Spawns a new binary and returns the contents of its apple array
116 * (after libsystem initialization).
117 */
118 static char **
get_apple_array(size_t * num_array_entries,const char * filename)119 get_apple_array(size_t *num_array_entries, const char * filename)
120 {
121 if (filename == NULL) {
122 filename = PRINT_APPLE_ARRAY_TOOL;
123 }
124 int ret;
125 char stdout_path[MAXPATHLEN] = "apple_array.txt";
126 dt_resultfile(stdout_path, MAXPATHLEN);
127 int exit_status = 0, signum = 0;
128 char binary_path[MAXPATHLEN], binary_dir[MAXPATHLEN];
129 char *char_ret;
130 const static size_t kMaxNumArguments = 256;
131 size_t linecap = 0;
132 ssize_t linelen = 0;
133 char **apple_array;
134 char **line = NULL;
135 size_t num_lines = 0;
136 FILE *stdout_f = NULL;
137 uint32_t name_size = MAXPATHLEN;
138
139 ret = _NSGetExecutablePath(binary_path, &name_size);
140 T_QUIET; T_ASSERT_EQ(ret, 0, "_NSGetExecutablePath");
141 char_ret = dirname_r(binary_path, binary_dir);
142 T_QUIET; T_ASSERT_TRUE(char_ret != NULL, "dirname_r");
143 snprintf(binary_path, MAXPATHLEN, "%s/%s", binary_dir, filename);
144
145 char *launch_tool_args[] = {
146 binary_path,
147 NULL
148 };
149 pid_t child_pid;
150 ret = dt_launch_tool(&child_pid, launch_tool_args, false, stdout_path, NULL);
151 T_WITH_ERRNO; T_ASSERT_EQ(ret, 0, "dt_launch_tool: %s", binary_path);
152
153 ret = dt_waitpid(child_pid, &exit_status, &signum, 60 * 5);
154 T_ASSERT_EQ(ret, 1, "dt_waitpid");
155 T_QUIET; T_ASSERT_EQ(exit_status, 0, "dt_waitpid: exit_status");
156 T_QUIET; T_ASSERT_EQ(signum, 0, "dt_waitpid: signum");
157
158 stdout_f = fopen(stdout_path, "r");
159 T_WITH_ERRNO; T_ASSERT_NOTNULL(stdout_f, "open(%s)", stdout_path);
160 apple_array = calloc(kMaxNumArguments, sizeof(char *));
161 T_QUIET; T_ASSERT_NOTNULL(apple_array, "calloc: %lu\n", sizeof(char *) * kMaxNumArguments);
162 while (num_lines < kMaxNumArguments) {
163 line = &(apple_array[num_lines++]);
164 linecap = 0;
165 linelen = getline(line, &linecap, stdout_f);
166 if (linelen == -1) {
167 break;
168 }
169 }
170 *num_array_entries = num_lines - 1;
171
172 ret = fclose(stdout_f);
173 T_ASSERT_POSIX_SUCCESS(ret, "fclose(%s)", stdout_path);
174
175 return apple_array;
176 }
177
178 #define LIBMALLOC_EXPERIMENT_FACTORS_KEY "MallocExperiment="
179
180 #define HARDENED_RUNTIME_KEY "HardenedRuntime="
181
182 #define HARDENED_HEAP_KEY "hardened_heap="
183
184
185 /*
186 * Get the value of the key in the apple array.
187 * Returns true iff the key is present.
188 */
189 static bool
get_apple_array_key(char ** apple_array,size_t num_array_entries,uint64_t * factors,const char * key)190 get_apple_array_key(char **apple_array, size_t num_array_entries, uint64_t *factors, const char *key)
191 {
192 bool found = false;
193 for (size_t i = 0; i < num_array_entries; i++) {
194 char *str = apple_array[i];
195 if (strstr(str, key)) {
196 found = true;
197 if (factors != NULL) {
198 str = strchr(str, '=');
199 T_ASSERT_NOTNULL(str, "skip over =");
200 ++str;
201 *factors = strtoull_l(str, NULL, 16, NULL);
202 }
203 break;
204 }
205 }
206 return found;
207 }
208
209 /* libmalloc relies on these values not changing. If they change,
210 * you need to update the values in that project as well */
211 __options_decl(HR_flags_t, uint32_t, {
212 BrowserHostEntitlementMask = 0x01,
213 BrowserGPUEntitlementMask = 0x02,
214 BrowserNetworkEntitlementMask = 0x04,
215 BrowserWebContentEntitlementMask = 0x08,
216 });
217
218 T_DECL(libmalloc_hardened_binary_present,
219 "hardened binary flags show up in apple array",
220 T_META_ASROOT(false))
221 {
222 uint64_t apple_array_val = 0;
223 size_t num_array_entries = 0;
224 char **apple_array;
225 bool found = false;
226
227 /* These are the entitlements on the HR1 binary */
228 uint32_t mask_val = BrowserHostEntitlementMask | BrowserGPUEntitlementMask | BrowserWebContentEntitlementMask;
229 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_HR1");
230 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_RUNTIME_KEY);
231 T_ASSERT_TRUE(found, "Found " HARDENED_RUNTIME_KEY " in apple array");
232 T_ASSERT_EQ(apple_array_val, mask_val, "Bitmask value matches");
233 free(apple_array);
234
235 /* These are the entitlements on the HR2 binary */
236 mask_val = BrowserGPUEntitlementMask | BrowserNetworkEntitlementMask;
237 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_HR2");
238 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_RUNTIME_KEY);
239 T_ASSERT_TRUE(found, "Found " HARDENED_RUNTIME_KEY " in apple array");
240 T_ASSERT_EQ(apple_array_val, mask_val, "Bitmask value matches");
241 free(apple_array);
242 }
243
244 T_DECL(libmalloc_hardened_heap_entitlements,
245 "hardened heap enablement via hardened process and hardened heap entitlements",
246 T_META_ASROOT(false))
247 {
248 uint64_t apple_array_val = 0;
249 size_t num_array_entries = 0;
250 char **apple_array;
251 bool found = false;
252
253 uint32_t mask_val = 1;
254 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_hardened_proc");
255 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_HEAP_KEY);
256 T_ASSERT_FALSE(found, "Didn't find " HARDENED_HEAP_KEY " in apple array");
257 free(apple_array);
258
259 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_hardened_heap_disable");
260 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_HEAP_KEY);
261 T_ASSERT_FALSE(found, "Didn't find " HARDENED_HEAP_KEY " in apple array");
262 free(apple_array);
263
264 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_hardened_heap");
265 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_HEAP_KEY);
266 T_ASSERT_TRUE(found, "Found " HARDENED_HEAP_KEY " in apple array");
267 T_ASSERT_EQ(apple_array_val, mask_val, "Bitmask value matches");
268 free(apple_array);
269
270 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_hardened_proc_all_subfeatures");
271 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_HEAP_KEY);
272 T_ASSERT_TRUE(found, "Found " HARDENED_HEAP_KEY " in apple array");
273 T_ASSERT_EQ(apple_array_val, mask_val, "Bitmask value matches");
274 free(apple_array);
275 }
276
277
278 T_DECL(libmalloc_hardened_binary_absent,
279 "hardened binary flags do not show up in apple array for normal third party processes",
280 T_META_ASROOT(false))
281 {
282 uint64_t new_val, apple_array_val = 0;
283 size_t num_array_entries = 0;
284 char **apple_array;
285 bool found = false;
286 apple_array = get_apple_array(&num_array_entries, NULL); // todo apple_array_3p?
287 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_RUNTIME_KEY);
288 T_ASSERT_TRUE(!found, "Did not find " HARDENED_RUNTIME_KEY " in apple array");
289 free(apple_array);
290 }
291
292 T_DECL(libmalloc_experiment,
293 "libmalloc experiment flags show up in apple array if we're doing an experiment",
294 T_META_ASROOT(false))
295 {
296 uint64_t new_val, apple_array_val = 0;
297 size_t num_array_entries = 0;
298 char **apple_array;
299 bool found = false;
300
301 if (running_as_root()) {
302 drop_priv();
303 }
304 new_val = (1ULL << 63) - 1;
305 set_libmalloc_experiment(new_val);
306
307 apple_array = get_apple_array(&num_array_entries, NULL);
308 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, LIBMALLOC_EXPERIMENT_FACTORS_KEY);
309 T_ASSERT_TRUE(found, "Found " LIBMALLOC_EXPERIMENT_FACTORS_KEY " in apple array");
310 T_ASSERT_EQ(apple_array_val, new_val, "Experiment value matches");
311 free(apple_array);
312 }
313
314 T_DECL(libmalloc_experiment_not_in_array,
315 "libmalloc experiment flags do not show up in apple array if we're not doing an experiment",
316 T_META_ASROOT(false))
317 {
318 size_t num_array_entries = 0;
319 char **apple_array;
320 bool found = false;
321
322 if (running_as_root()) {
323 drop_priv();
324 }
325 set_libmalloc_experiment(0);
326
327 apple_array = get_apple_array(&num_array_entries, NULL);
328 found = get_apple_array_key(apple_array, num_array_entries, NULL, LIBMALLOC_EXPERIMENT_FACTORS_KEY);
329 T_ASSERT_TRUE(!found, "Did not find " LIBMALLOC_EXPERIMENT_FACTORS_KEY " in apple array");
330 free(apple_array);
331 }
332 #endif /* ENTITLED */
333