xref: /xnu-8796.101.5/tests/test_sysctl_kern_procargs_25397314.m (revision aca3beaa3dfbd42498b42c5e5ce20a938e6554e5)
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