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>
10 #include <sys/sysctl.h>
13 T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true),
19 NSString *executablePath;
21 NSString *legacyExecutablePath;
26 static void printHexDump(void* buffer, size_t size);
28 typedef struct procargs *procargs_t;
30 #define TEST_ENVIRONMENT_VARIABLE "TESTENVVARIABLE"
31 #define TEST_ENVIRONMENT_VARIABLE_VALUE "TESTENVVARIABLE_VALUE"
36 static procargs_t getProcArgs(int type, pid_t pid, size_t allocSize)
38 int sysctlArgs[3] = {CTL_KERN, type, pid};
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;
48 size_t preflightSize = 0;
49 const char *name = type == KERN_PROCARGS ? "KERN_PROCARGS" : "KERN_PROCARGS2";
53 T_LOG("Get proc args for pid %d, allocSize %lu with %s", pid, allocSize, name);
56 T_ASSERT_TRUE(type == KERN_PROCARGS || type == KERN_PROCARGS2, "type is valid");
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);
64 allocSize = preflightSize;
67 buffer = malloc(allocSize);
68 T_QUIET; T_ASSERT_NOTNULL(buffer, "malloc buffer of size %lu", allocSize);
69 bufferSize = allocSize;
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");
79 printHexDump(buffer, bufferSize);
81 if (type == KERN_PROCARGS2) {
82 argc = *(int *)buffer;
83 cursor = (const char *)buffer + sizeof(int);
85 /* Without KERN_PROCARGS2, we can't tell where argv ends and environ begins.
86 * Set argc to -1 to indicate this */
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;
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;
109 if (executablePath == nil) {
110 executablePath = current;
111 [executablePath retain];
112 while (*cursor == 0) {
116 [components addObject:current];
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;
128 args->executablePath = executablePath;
129 args->components = components;
130 args->legacyExecutablePath = legacyExecutablePath;
131 args->preflightSize = preflightSize;
132 args->rawBuffer = buffer;
133 args->rawBufferSize = bufferSize;
137 static void printProcArgs(procargs_t procargs) {
138 if (procargs->argc == -1) {
139 T_LOG("No argument count");
141 T_LOG("Argc is %d", procargs->argc);
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);
151 T_LOG("Component %zu: %s (length %lu)", i, str, len);
154 if (procargs->legacyExecutablePath) {
155 T_LOG("Contains legacy executable path: %s (length %lu)", [procargs->legacyExecutablePath UTF8String], [procargs->legacyExecutablePath length]);
157 printHexDump(procargs->rawBuffer, procargs->rawBufferSize);
160 static 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++) {
169 char c = ((char *)buffer)[col];
170 [line appendFormat:@"%02x ", c];
172 [text appendFormat:@"%c", c];
174 [text appendString:@"."];
177 [line appendString:@" "];
180 [line appendFormat:@" %@", text];
181 T_LOG("%s", [line UTF8String]);
187 static void deallocProcArgs(procargs_t procargs)
189 [procargs->components release];
190 [procargs->executablePath release];
191 [procargs->legacyExecutablePath release];
192 free(procargs->rawBuffer);
196 T_HELPER_DECL(child_helper, "Child process helper")
204 launch_child_process(NSArray *args, bool cs_restrict)
208 uint32_t path_size = sizeof(path);
209 uint32_t csopsStatus = 0;
210 const char** dt_args;
211 size_t dt_args_count;
213 T_ASSERT_POSIX_SUCCESS(_NSGetExecutablePath(path, &path_size), "get executable path");
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);
220 dt_args[2] = "child_helper";
222 for (size_t i = 0; i < [args count]; i++) {
223 NSString *arg = [args objectAtIndex:i];
224 dt_args[i + 4] = [arg UTF8String];
226 dt_args[[args count] + 4] = NULL;
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)");
233 T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&pid, (char **)dt_args, false, NULL, NULL), "launched helper");
237 csopsStatus |= CS_RESTRICT;
238 T_ASSERT_POSIX_SUCCESS(csops(pid, CS_OPS_SET_STATUS, &csopsStatus, sizeof(csopsStatus)), "set CS_RESTRICT");
243 T_DECL(test_sysctl_kern_procargs_25397314, "Test kern.procargs and kern.procargs2 sysctls")
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;
255 size_t development_size = sizeof(development);
256 uint32_t csopsStatus = 0;
259 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.development", &development, &development_size, NULL, 0), "sysctl kern.development");
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);
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);
274 setenv(TEST_ENVIRONMENT_VARIABLE, TEST_ENVIRONMENT_VARIABLE_VALUE, true);
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);
282 for (NSString *component in procargs->components) {
283 if ([component isEqualToString:testArgument1]) {
284 containsTestArgument1 = true;
286 if ([component isEqualToString:testArgument2]) {
287 containsTestArgument2 = true;
289 if ([component containsString:testEnvironmentVariable]) {
290 containsTestEnvironmentVariable = true;
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");
300 T_LOG("Skipping test on DEVELOPMENT || DEBUG kernel");
302 containsTestArgument1 = false;
303 containsTestArgument2 = false;
304 containsTestEnvironmentVariable = false;
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;
315 if ([component isEqualToString:testArgument2]) {
316 containsTestArgument2 = true;
318 if ([component containsString:testEnvironmentVariable]) {
319 containsTestEnvironmentVariable = true;
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");
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;
340 deallocProcArgs(procargs);
341 T_ASSERT_TRUE(containsPathEnvironmentVariable, "Found $PATH environment variable");