1#include <Foundation/Foundation.h> 2#include <darwintest.h> 3#include <darwintest_utils.h> 4#include <mach-o/dyld.h> 5#include <System/sys/codesign.h> 6#include <unistd.h> 7#include <stdlib.h> 8#include <signal.h> 9#include <sys/types.h> 10#include <sys/sysctl.h> 11 12 13T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true), 14 T_META_ASROOT(true)); 15 16struct procargs { 17 int argc; 18 size_t preflightSize; 19 NSString *executablePath; 20 NSArray *components; 21 NSString *legacyExecutablePath; 22 void *rawBuffer; 23 size_t rawBufferSize; 24}; 25 26static void printHexDump(void* buffer, size_t size); 27 28typedef struct procargs *procargs_t; 29 30#define TEST_ENVIRONMENT_VARIABLE "TESTENVVARIABLE" 31#define TEST_ENVIRONMENT_VARIABLE_VALUE "TESTENVVARIABLE_VALUE" 32 33 34static size_t argmax; 35 36static procargs_t getProcArgs(int type, pid_t pid, size_t allocSize) 37{ 38 int sysctlArgs[3] = {CTL_KERN, type, pid}; 39 int argc; 40 NSMutableArray *components = [NSMutableArray array]; 41 procargs_t args = (procargs_t) malloc(sizeof(struct procargs)); 42 size_t currentLen = 0; 43 bool legacyPathPresent = false; 44 NSString *current = nil; 45 NSString *legacyExecutablePath = nil; 46 NSString *executablePath = nil; 47 size_t bufferSize; 48 size_t preflightSize = 0; 49 const char *name = type == KERN_PROCARGS ? "KERN_PROCARGS" : "KERN_PROCARGS2"; 50 const char *cursor; 51 void *buffer; 52 53 T_LOG("Get proc args for pid %d, allocSize %lu with %s", pid, allocSize, name); 54 55 56 T_ASSERT_TRUE(type == KERN_PROCARGS || type == KERN_PROCARGS2, "type is valid"); 57 58 /* Determine how much memory to allocate. If allocSize is 0 we will use the size 59 * we get from the sysctl for our buffer. */ 60 T_ASSERT_POSIX_SUCCESS(sysctl(sysctlArgs, 3, NULL, &preflightSize, NULL, 0), "sysctl %s", name); 61 T_LOG("procargs data should be %lu bytes", preflightSize); 62 63 if (allocSize == 0) { 64 allocSize = preflightSize; 65 } 66 67 buffer = malloc(allocSize); 68 T_QUIET; T_ASSERT_NOTNULL(buffer, "malloc buffer of size %lu", allocSize); 69 bufferSize = allocSize; 70 71 T_ASSERT_POSIX_SUCCESS(sysctl(sysctlArgs, 3, buffer, &bufferSize, NULL, 0), "sysctl %s", name); 72 T_ASSERT_LE(bufferSize, allocSize, "returned buffer size should be less than allocated size"); 73 T_LOG("sysctl wrote %lu bytes", bufferSize); 74 if (allocSize >= bufferSize) { 75 /* Allocated buffer is larger than what kernel wrote, so it should match preflightSize */ 76 T_ASSERT_EQ(bufferSize, preflightSize, "buffer size should be the same as preflight size"); 77 } 78 79 printHexDump(buffer, bufferSize); 80 81 if (type == KERN_PROCARGS2) { 82 argc = *(int *)buffer; 83 cursor = (const char *)buffer + sizeof(int); 84 } else { 85 /* Without KERN_PROCARGS2, we can't tell where argv ends and environ begins. 86 * Set argc to -1 to indicate this */ 87 argc = -1; 88 cursor = buffer; 89 } 90 91 while ((uintptr_t)cursor < (uintptr_t)buffer + bufferSize) { 92 /* Ensure alignment and check if the uint16_t at cursor is the magic value */ 93 if (!((uintptr_t)cursor & (sizeof(uint16_t) - 1)) && 94 (uintptr_t)buffer + bufferSize - (uintptr_t)cursor > sizeof(uint16_t)) { 95 /* Silence -Wcast-align by casting to const void * */ 96 uint16_t value = *(const uint16_t *)(const void *)cursor; 97 if (value == 0xBFFF) { 98 /* Magic value that specifies the end of the argument/environ section */ 99 cursor += sizeof(uint16_t) + sizeof(uint32_t); 100 legacyPathPresent = true; 101 break; 102 } 103 } 104 currentLen = strnlen(cursor, bufferSize - ((uintptr_t)cursor - (uintptr_t)buffer)); 105 current = [[NSString alloc] initWithBytes:cursor length:currentLen encoding:NSUTF8StringEncoding]; 106 T_QUIET; T_ASSERT_NOTNULL(current, "allocated string"); 107 cursor += currentLen + 1; 108 109 if (executablePath == nil) { 110 executablePath = current; 111 [executablePath retain]; 112 while (*cursor == 0) { 113 cursor++; 114 } 115 } else { 116 [components addObject:current]; 117 } 118 [current release]; 119 } 120 if (legacyPathPresent) { 121 T_ASSERT_EQ(type, KERN_PROCARGS, "Legacy executable path should only be present for KERN_PROCARGS"); 122 currentLen = strnlen(cursor, bufferSize - ((uintptr_t)cursor - (uintptr_t)buffer)); 123 current = [[NSString alloc] initWithBytes:cursor length:currentLen encoding:NSUTF8StringEncoding]; 124 T_QUIET; T_ASSERT_NOTNULL(current, "allocated string"); 125 legacyExecutablePath = current; 126 } 127 args->argc = argc; 128 args->executablePath = executablePath; 129 args->components = components; 130 args->legacyExecutablePath = legacyExecutablePath; 131 args->preflightSize = preflightSize; 132 args->rawBuffer = buffer; 133 args->rawBufferSize = bufferSize; 134 return args; 135} 136 137static void printProcArgs(procargs_t procargs) { 138 if (procargs->argc == -1) { 139 T_LOG("No argument count"); 140 } else { 141 T_LOG("Argc is %d", procargs->argc); 142 } 143 T_LOG("Executable path: %s (length %lu)", [procargs->executablePath UTF8String], [procargs->executablePath length]); 144 for (size_t i = 0; i < [procargs->components count]; i++) { 145 NSString *component = [procargs->components objectAtIndex:i]; 146 const char *str = [component UTF8String]; 147 size_t len = [component length]; 148 if (procargs->argc != -1) { 149 T_LOG("%s %zu: %s (length %lu)", i >= (size_t)procargs->argc ? "Env var" : "Argument", i, str, len); 150 } else { 151 T_LOG("Component %zu: %s (length %lu)", i, str, len); 152 } 153 } 154 if (procargs->legacyExecutablePath) { 155 T_LOG("Contains legacy executable path: %s (length %lu)", [procargs->legacyExecutablePath UTF8String], [procargs->legacyExecutablePath length]); 156 } 157 printHexDump(procargs->rawBuffer, procargs->rawBufferSize); 158} 159 160static void printHexDump(void* buffer, size_t size) { 161 #define ROW_LENGTH 24 162 T_LOG("Buffer %p, size %zu", buffer, size); 163 for (size_t row = 0; row < size; row += ROW_LENGTH) { 164 NSMutableString *line = [[NSMutableString alloc] initWithCapacity:0]; 165 NSMutableString *text = [[NSMutableString alloc] initWithCapacity:0]; 166 [line appendFormat:@" %04zx ", row]; 167 for (size_t col = row; col < row + ROW_LENGTH; col++) { 168 if (col < size) { 169 char c = ((char *)buffer)[col]; 170 [line appendFormat:@"%02x ", c]; 171 if (isprint(c)) { 172 [text appendFormat:@"%c", c]; 173 } else { 174 [text appendString:@"."]; 175 } 176 } else { 177 [line appendString:@" "]; 178 } 179 } 180 [line appendFormat:@" %@", text]; 181 T_LOG("%s", [line UTF8String]); 182 [text release]; 183 [line release]; 184 } 185} 186 187static void deallocProcArgs(procargs_t procargs) 188{ 189 [procargs->components release]; 190 [procargs->executablePath release]; 191 [procargs->legacyExecutablePath release]; 192 free(procargs->rawBuffer); 193 free(procargs); 194} 195 196T_HELPER_DECL(child_helper, "Child process helper") 197{ 198 while (true) { 199 wait(NULL); 200 } 201} 202 203static pid_t 204launch_child_process(NSArray *args, bool cs_restrict) 205{ 206 pid_t pid; 207 char path[PATH_MAX]; 208 uint32_t path_size = sizeof(path); 209 uint32_t csopsStatus = 0; 210 const char** dt_args; 211 size_t dt_args_count; 212 213 T_ASSERT_POSIX_SUCCESS(_NSGetExecutablePath(path, &path_size), "get executable path"); 214 215 /* We need to add 4 arguments to the beginning and NULL at the end */ 216 dt_args_count = [args count] + 5; 217 dt_args = malloc(sizeof(char *) * dt_args_count); 218 dt_args[0] = path; 219 dt_args[1] = "-n"; 220 dt_args[2] = "child_helper"; 221 dt_args[3] = "--"; 222 for (size_t i = 0; i < [args count]; i++) { 223 NSString *arg = [args objectAtIndex:i]; 224 dt_args[i + 4] = [arg UTF8String]; 225 } 226 dt_args[[args count] + 4] = NULL; 227 228 T_LOG("Launching %s", path); 229 T_LOG("Arguments: "); 230 for (size_t i = 0; i < dt_args_count; i++) { 231 T_LOG(" %s", dt_args[i] ? dt_args[i] : "(null)"); 232 } 233 T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&pid, (char **)dt_args, false, NULL, NULL), "launched helper"); 234 free(dt_args); 235 236 if (cs_restrict) { 237 csopsStatus |= CS_RESTRICT; 238 T_ASSERT_POSIX_SUCCESS(csops(pid, CS_OPS_SET_STATUS, &csopsStatus, sizeof(csopsStatus)), "set CS_RESTRICT"); 239 } 240 return pid; 241} 242 243T_DECL(test_sysctl_kern_procargs_25397314, "Test kern.procargs and kern.procargs2 sysctls") 244{ 245 procargs_t procargs; 246 size_t argsize = sizeof(argmax); 247 NSString *testArgument1 = @"test argument 1"; 248 bool containsTestArgument1 = false; 249 NSString *testArgument2 = @"test argument 2"; 250 bool containsTestArgument2 = false; 251 NSString *testEnvironmentVariable = @TEST_ENVIRONMENT_VARIABLE; 252 bool containsTestEnvironmentVariable = false; 253 bool containsPathEnvironmentVariable = false; 254 int development = 0; 255 size_t development_size = sizeof(development); 256 uint32_t csopsStatus = 0; 257 258 259 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.development", &development, &development_size, NULL, 0), "sysctl kern.development"); 260 261 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.argmax", &argmax, &argsize, NULL, 0), "sysctl kern.argmax"); 262 procargs = getProcArgs(KERN_PROCARGS2, getpid(), argmax); 263 T_ASSERT_NOTNULL(procargs->executablePath, "executable path should be non-null"); 264 T_ASSERT_GT([procargs->executablePath length], 0, "executable path should not be empty"); 265 printProcArgs(procargs); 266 deallocProcArgs(procargs); 267 268 procargs = getProcArgs(KERN_PROCARGS2, getpid(), 0); 269 T_ASSERT_NOTNULL(procargs->executablePath, "executable path should be non-null"); 270 T_ASSERT_GT([procargs->executablePath length], 0, "executable path should not be empty"); 271 printProcArgs(procargs); 272 deallocProcArgs(procargs); 273 274 setenv(TEST_ENVIRONMENT_VARIABLE, TEST_ENVIRONMENT_VARIABLE_VALUE, true); 275 276 pid_t child = launch_child_process(@[testArgument1, testArgument2], false); 277 procargs = getProcArgs(KERN_PROCARGS2, child, argmax); 278 T_ASSERT_NOTNULL(procargs->executablePath, "executable path should be non-null"); 279 T_ASSERT_GT([procargs->executablePath length], 0, "executable path should not be empty"); 280 printProcArgs(procargs); 281 282 for (NSString *component in procargs->components) { 283 if ([component isEqualToString:testArgument1]) { 284 containsTestArgument1 = true; 285 } 286 if ([component isEqualToString:testArgument2]) { 287 containsTestArgument2 = true; 288 } 289 if ([component containsString:testEnvironmentVariable]) { 290 containsTestEnvironmentVariable = true; 291 } 292 } 293 deallocProcArgs(procargs); 294 kill(child, SIGKILL); 295 T_ASSERT_TRUE(containsTestArgument1, "Found test argument 1"); 296 T_ASSERT_TRUE(containsTestArgument2, "Found test argument 2"); 297 T_ASSERT_TRUE(containsTestEnvironmentVariable, "Found test environment variable"); 298 299 if (development) { 300 T_LOG("Skipping test on DEVELOPMENT || DEBUG kernel"); 301 } else { 302 containsTestArgument1 = false; 303 containsTestArgument2 = false; 304 containsTestEnvironmentVariable = false; 305 306 child = launch_child_process(@[testArgument1, testArgument2], true); 307 procargs = getProcArgs(KERN_PROCARGS2, child, argmax); 308 T_ASSERT_NOTNULL(procargs->executablePath, "executable path should be non-null"); 309 T_ASSERT_GT([procargs->executablePath length], 0, "executable path should not be empty"); 310 printProcArgs(procargs); 311 for (NSString *component in procargs->components) { 312 if ([component isEqualToString:testArgument1]) { 313 containsTestArgument1 = true; 314 } 315 if ([component isEqualToString:testArgument2]) { 316 containsTestArgument2 = true; 317 } 318 if ([component containsString:testEnvironmentVariable]) { 319 containsTestEnvironmentVariable = true; 320 } 321 } 322 deallocProcArgs(procargs); 323 kill(child, SIGKILL); 324 T_ASSERT_TRUE(containsTestArgument1, "Found test argument 1"); 325 T_ASSERT_TRUE(containsTestArgument2, "Found test argument 2"); 326 T_ASSERT_FALSE(containsTestEnvironmentVariable, "No test environment variable"); 327 328 329 csopsStatus |= CS_RESTRICT; 330 T_ASSERT_POSIX_SUCCESS(csops(getpid(), CS_OPS_SET_STATUS, &csopsStatus, sizeof(csopsStatus)), "set CS_RESTRICT on self"); 331 procargs = getProcArgs(KERN_PROCARGS2, getpid(), argmax); 332 T_ASSERT_NOTNULL(procargs->executablePath, "executable path should be non-null"); 333 T_ASSERT_GT([procargs->executablePath length], 0, "executable path should not be empty"); 334 printProcArgs(procargs); 335 for (NSString *component in procargs->components) { 336 if ([component containsString:@"PATH"]) { 337 containsPathEnvironmentVariable = true; 338 } 339 } 340 deallocProcArgs(procargs); 341 T_ASSERT_TRUE(containsPathEnvironmentVariable, "Found $PATH environment variable"); 342 } 343} 344