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