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 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 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 ** 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 183 /* 184 * Get the value of the key in the apple array. 185 * Returns true iff the key is present. 186 */ 187 static bool 188 get_apple_array_key(char **apple_array, size_t num_array_entries, uint64_t *factors, const char *key) 189 { 190 bool found = false; 191 for (size_t i = 0; i < num_array_entries; i++) { 192 char *str = apple_array[i]; 193 if (strstr(str, key)) { 194 found = true; 195 if (factors != NULL) { 196 str = strchr(str, '='); 197 T_ASSERT_NOTNULL(str, "skip over ="); 198 ++str; 199 *factors = strtoull_l(str, NULL, 16, NULL); 200 } 201 break; 202 } 203 } 204 return found; 205 } 206 207 /* libmalloc relies on these values not changing. If they change, 208 * you need to update the values in that project as well */ 209 __options_decl(HR_flags_t, uint32_t, { 210 BrowserHostEntitlementMask = 0x01, 211 BrowserGPUEntitlementMask = 0x02, 212 BrowserNetworkEntitlementMask = 0x04, 213 BrowserWebContentEntitlementMask = 0x08, 214 }); 215 216 T_DECL(libmalloc_hardened_binary_present, 217 "hardened binary flags show up in apple array", 218 T_META_ASROOT(false)) 219 { 220 uint64_t apple_array_val = 0; 221 size_t num_array_entries = 0; 222 char **apple_array; 223 bool found = false; 224 225 /* These are the entitlements on the HR1 binary */ 226 uint32_t mask_val = BrowserHostEntitlementMask | BrowserGPUEntitlementMask | BrowserWebContentEntitlementMask; 227 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_HR1"); 228 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_RUNTIME_KEY); 229 T_ASSERT_TRUE(found, "Found " HARDENED_RUNTIME_KEY " in apple array"); 230 T_ASSERT_EQ(apple_array_val, mask_val, "Bitmask value matches"); 231 232 /* These are the entitlements on the HR2 binary */ 233 mask_val = BrowserGPUEntitlementMask | BrowserNetworkEntitlementMask; 234 apple_array = get_apple_array(&num_array_entries, "tools/print_apple_array_HR2"); 235 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_RUNTIME_KEY); 236 T_ASSERT_TRUE(found, "Found " HARDENED_RUNTIME_KEY " in apple array"); 237 T_ASSERT_EQ(apple_array_val, mask_val, "Bitmask value matches"); 238 free(apple_array); 239 } 240 241 242 T_DECL(libmalloc_hardened_binary_absent, 243 "hardened binary flags do not show up in apple array for normal third party processes", 244 T_META_ASROOT(false)) 245 { 246 uint64_t new_val, apple_array_val = 0; 247 size_t num_array_entries = 0; 248 char **apple_array; 249 bool found = false; 250 apple_array = get_apple_array(&num_array_entries, NULL); // todo apple_array_3p? 251 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, HARDENED_RUNTIME_KEY); 252 T_ASSERT_TRUE(!found, "Did not find " HARDENED_RUNTIME_KEY " in apple array"); 253 free(apple_array); 254 } 255 256 T_DECL(libmalloc_experiment, 257 "libmalloc experiment flags show up in apple array if we're doing an experiment", 258 T_META_ASROOT(false)) 259 { 260 uint64_t new_val, apple_array_val = 0; 261 size_t num_array_entries = 0; 262 char **apple_array; 263 bool found = false; 264 265 if (running_as_root()) { 266 drop_priv(); 267 } 268 new_val = (1ULL << 63) - 1; 269 set_libmalloc_experiment(new_val); 270 271 apple_array = get_apple_array(&num_array_entries, NULL); 272 found = get_apple_array_key(apple_array, num_array_entries, &apple_array_val, LIBMALLOC_EXPERIMENT_FACTORS_KEY); 273 T_ASSERT_TRUE(found, "Found " LIBMALLOC_EXPERIMENT_FACTORS_KEY " in apple array"); 274 T_ASSERT_EQ(apple_array_val, new_val, "Experiment value matches"); 275 free(apple_array); 276 } 277 278 T_DECL(libmalloc_experiment_not_in_array, 279 "libmalloc experiment flags do not show up in apple array if we're not doing an experiment", 280 T_META_ASROOT(false)) 281 { 282 size_t num_array_entries = 0; 283 char **apple_array; 284 bool found = false; 285 286 if (running_as_root()) { 287 drop_priv(); 288 } 289 set_libmalloc_experiment(0); 290 291 apple_array = get_apple_array(&num_array_entries, NULL); 292 found = get_apple_array_key(apple_array, num_array_entries, NULL, LIBMALLOC_EXPERIMENT_FACTORS_KEY); 293 T_ASSERT_TRUE(!found, "Did not find " LIBMALLOC_EXPERIMENT_FACTORS_KEY " in apple array"); 294 free(apple_array); 295 } 296 #endif /* ENTITLED */ 297