]>
Commit | Line | Data |
---|---|---|
f427ee49 A |
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 | ||
13 | T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true), | |
14 | T_META_ASROOT(true)); | |
15 | ||
16 | struct 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 | ||
26 | static void printHexDump(void* buffer, size_t size); | |
27 | ||
28 | typedef struct procargs *procargs_t; | |
29 | ||
30 | #define TEST_ENVIRONMENT_VARIABLE "TESTENVVARIABLE" | |
31 | #define TEST_ENVIRONMENT_VARIABLE_VALUE "TESTENVVARIABLE_VALUE" | |
32 | ||
33 | ||
34 | static size_t argmax; | |
35 | ||
36 | static 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 | ||
137 | static 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 | ||
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++) { | |
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 | ||
187 | static 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 | ||
196 | T_HELPER_DECL(child_helper, "Child process helper") | |
197 | { | |
198 | while (true) { | |
199 | wait(NULL); | |
200 | } | |
201 | } | |
202 | ||
203 | static pid_t | |
204 | launch_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 | ||
243 | T_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 | } |