xref: /xnu-11417.121.6/libsyscall/wrappers/spawn/posix_spawn_filtering.c (revision a1e26a70f38d1d7daa7b49b258e2f8538ad81650)
1 /*
2  * Copyright (c) 2021 Apple Inc. All rights reserved.
3  *
4  * @APPLE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. Please obtain a copy of the License at
10  * http://www.opensource.apple.com/apsl/ and read it before using this
11  * file.
12  *
13  * The Original Code and all software distributed under the License are
14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18  * Please see the License for the specific language governing rights and
19  * limitations under the License.
20  *
21  * @APPLE_LICENSE_HEADER_END@
22  */
23 
24 #include <spawn_filtering_private.h>
25 
26 #if POSIX_SPAWN_FILTERING_ENABLED
27 
28 #include <spawn.h>
29 #include <spawn_private.h>
30 #include <sys/spawn_internal.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <TargetConditionals.h>
38 
39 extern void __posix_spawnattr_init(struct _posix_spawnattr *psattrp);
40 
41 /*
42  * Actual syscall wrappers.
43  */
44 extern int __posix_spawn(pid_t * __restrict, const char * __restrict,
45     struct _posix_spawn_args_desc *, char *const argv[__restrict],
46     char *const envp[__restrict]);
47 extern int __execve(const char *fname, char * const *argp, char * const *envp);
48 extern int __open_nocancel(const char *path, int oflag, mode_t mode);
49 extern ssize_t __read_nocancel(int, void *, size_t);
50 extern int __close_nocancel(int fd);
51 
52 static const char *
_simple_getenv(char * const * envp,const char * var)53 _simple_getenv(char * const *envp, const char *var)
54 {
55 	size_t var_len = strlen(var);
56 
57 	for (char * const *p = envp; p && *p; p++) {
58 		size_t p_len = strlen(*p);
59 
60 		if (p_len >= var_len && memcmp(*p, var, var_len) == 0 &&
61 		    (*p)[var_len] == '=') {
62 			return &(*p)[var_len + 1];
63 		}
64 	}
65 
66 	return NULL;
67 }
68 
69 /*
70  * Check that file exists and is accessible.
71  * access() does not have a cancellation point, so it's already nocancel.
72  */
73 static bool
can_access(const char * path)74 can_access(const char *path)
75 {
76 	int saveerrno = errno;
77 
78 	if (access(path, R_OK) != 0) {
79 		errno = saveerrno;
80 		return false;
81 	}
82 	return true;
83 }
84 
85 /*
86  * Read filtering rules from /usr/local/share/posix_spawn_filtering_rules, and
87  * if the target being launched matches, apply changes to the posix_spawn
88  * request. Example contents of the file:
89  *
90  * binary_name:Calculator
91  * binary_name:ld
92  * path_start:/opt/bin/
93  * add_env:DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
94  * binpref:x86_64
95  * alt_rosetta:1
96  *
97  * In this case, if we're launching either Calculator or ld, or anything in
98  * /opt/bin (arbitrarily deep), DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
99  * will be added to the environment of the target, it will be launched with
100  * x86_64 binpref, and alternative rosetta runtime.
101  *
102  * Unrecognized lines are silently skipped. All lines must be 1023 characters
103  * or shorter.
104  *
105  * We need to be careful in this codepath (and in all called functions) because
106  * we can be called as part of execve() and that's required to be
107  * async-signal-safe by POSIX. We're also replacing one syscall with multiple,
108  * so we need to watch out to preserve cancellation/EINTR semantics, and avoid
109  * changing errno.
110  */
111 static bool
evaluate_rules(const char * rules_file_path,const char * fname,char ** envs,size_t envs_capacity,char * env_storage,size_t env_storage_capacity,cpu_type_t * type,cpu_subtype_t * subtype,uint32_t * psa_options,uint8_t * sec_flags)112 evaluate_rules(const char *rules_file_path, const char *fname, char **envs,
113     size_t envs_capacity, char *env_storage, size_t env_storage_capacity,
114     cpu_type_t *type, cpu_subtype_t *subtype, uint32_t *psa_options, uint8_t *sec_flags)
115 {
116 	int saveerrno = errno;
117 	int fd = -1;
118 
119 	/*
120 	 * Preflight check on rules_file_path to avoid triggering sandbox reports in
121 	 * case the process doesn't have access. We don't care about TOCTOU here.
122 	 */
123 	if (!can_access(rules_file_path)) {
124 		return false;
125 	}
126 
127 	while (1) {
128 		fd = __open_nocancel(rules_file_path, O_RDONLY | O_CLOEXEC, 0);
129 		if (fd >= 0) {
130 			break;
131 		}
132 		if (errno == EINTR) {
133 			continue;
134 		}
135 		errno = saveerrno;
136 		return false;
137 	}
138 
139 	const char *fname_basename = fname;
140 	const char *slash_pos;
141 	while ((slash_pos = strchr(fname_basename, '/')) != NULL) {
142 		fname_basename = slash_pos + 1;
143 	}
144 
145 	bool fname_matches = false;
146 
147 	char read_buffer[1024];
148 	size_t bytes = 0;
149 	while (1) {
150 		if (sizeof(read_buffer) - bytes <= 0) {
151 			break;
152 		}
153 
154 		bzero(read_buffer + bytes, sizeof(read_buffer) - bytes);
155 		size_t read_result = __read_nocancel(fd,
156 		    read_buffer + bytes, sizeof(read_buffer) - bytes);
157 
158 		if (read_result == 0) {
159 			break;
160 		} else if (read_result < 0) {
161 			if (errno == EINTR) {
162 				continue;
163 			} else {
164 				break;
165 			}
166 		}
167 		bytes += read_result;
168 
169 		while (bytes > 0) {
170 			char *newline_pos = memchr(read_buffer, '\n', bytes);
171 			if (newline_pos == NULL) {
172 				break;
173 			}
174 
175 			char *line = read_buffer;
176 			size_t line_length = newline_pos - read_buffer;
177 			*newline_pos = '\0';
178 
179 			/* 'line' now has a NUL-terminated string of 1023 chars max */
180 			if (memcmp(line, "binary_name:", strlen("binary_name:")) == 0) {
181 				char *binary_name = line + strlen("binary_name:");
182 				if (strcmp(fname_basename, binary_name) == 0) {
183 					fname_matches = true;
184 				}
185 			} else if (memcmp(line, "path_start:", strlen("path_start:")) == 0) {
186 				char *path_start = line + strlen("path_start:");
187 				if (strncmp(fname, path_start, strlen(path_start)) == 0) {
188 					fname_matches = true;
189 				}
190 			} else if (memcmp(line, "add_env:", strlen("add_env:")) == 0) {
191 				char *add_env = line + strlen("add_env:");
192 				size_t env_size = strlen(add_env) + 1;
193 				if (env_storage_capacity >= env_size && envs_capacity > 0) {
194 					memcpy(env_storage, add_env, env_size);
195 					envs[0] = env_storage;
196 
197 					envs += 1;
198 					envs_capacity -= 1;
199 					env_storage += env_size;
200 					env_storage_capacity -= env_size;
201 				}
202 			} else if (memcmp(line, "binpref:", strlen("binpref:")) == 0) {
203 				char *binpref = line + strlen("binpref:");
204 				if (strcmp(binpref, "x86_64") == 0) {
205 					*type = CPU_TYPE_X86_64;
206 					*subtype = CPU_SUBTYPE_ANY;
207 				}
208 			} else if (memcmp(line, "alt_rosetta:", strlen("alt_rosetta:")) == 0) {
209 				char *alt_rosetta = line + strlen("alt_rosetta:");
210 				if (strcmp(alt_rosetta, "1") == 0) {
211 					*psa_options |= PSA_OPTION_ALT_ROSETTA;
212 				}
213 			} else if (memcmp(line, "has_sec_transition:", strlen("has_sec_transition:")) == 0) {
214 				char *enable_sec_transitions = line + strlen("has_sec_transition:");
215 				if (strcmp(enable_sec_transitions, "1") == 0) {
216 					*sec_flags |= POSIX_SPAWN_SECFLAG_EXPLICIT_ENABLE;
217 				}
218 			} else if (memcmp(line, "sec_transition_inherit:", strlen("sec_transition_inherit:")) == 0) {
219 				char *enable_sec_transitions = line + strlen("sec_transition_inherit:");
220 				if (strcmp(enable_sec_transitions, "1") == 0) {
221 					*sec_flags |= POSIX_SPAWN_SECFLAG_EXPLICIT_ENABLE_INHERIT;
222 				}
223 			}
224 
225 			memmove(read_buffer, newline_pos + 1, sizeof(read_buffer) - line_length);
226 			bytes -= line_length + 1;
227 		}
228 	}
229 
230 	__close_nocancel(fd);
231 	errno = saveerrno;
232 	return fname_matches;
233 }
234 
235 /*
236  * Apply posix_spawn filtering rules, and invoke a possibly modified posix_spawn
237  * call. Returns true if the posix_spawn was handled/invoked (and populates the
238  * 'ret' outparam in that case), false if the filter does not apply and the
239  * caller should proceed to call posix_spawn/exec normally.
240  *
241  * We need to be careful in this codepath (and in all called functions) because
242  * we can be called as part of execve() and that's required to be
243  * async-signal-safe by POSIX. We're also replacing one syscall with multiple,
244  * so we need to watch out to preserve cancellation/EINTR semantics, and avoid
245  * changing errno.
246  */
247 __attribute__((visibility("hidden")))
248 bool
_posix_spawn_with_filter(pid_t * pid,const char * fname,char * const * argp,char * const * envp,struct _posix_spawn_args_desc * adp,int * ret)249 _posix_spawn_with_filter(pid_t *pid, const char *fname, char * const *argp,
250     char * const *envp, struct _posix_spawn_args_desc *adp, int *ret)
251 {
252 	/*
253 	 * For testing, the path to the rules file can be overridden with an env var.
254 	 * It's hard to get access to 'environ' or '_NSGetEnviron' here so instead
255 	 * peek into the envp arg of posix_spawn/exec, even though we should really
256 	 * inspect the parent's env instead. For testing only purposes, it's fine.
257 	 */
258 	const char *rules_file_path =
259 	    _simple_getenv(envp, "POSIX_SPAWN_FILTERING_RULES_PATH");
260 
261 #if TARGET_OS_IPHONE
262 	/*
263 	 * Use `/var/mobile` path (writable by iOS apps) if it exists.
264 	 * We don't care about TOCTOU here.
265 	 */
266 	if (!rules_file_path) {
267 		const char *path =
268 		    "/private/var/mobile/Library/posix_spawn_filtering_rules";
269 		if (can_access(path)) {
270 			rules_file_path = path;
271 		}
272 	}
273 #endif
274 
275 	/*
276 	 * Try the default rule file location (on root filesystem).
277 	 */
278 	if (!rules_file_path) {
279 		rules_file_path = "/usr/local/share/posix_spawn_filtering_rules";
280 	}
281 
282 	/*
283 	 * Stack-allocated storage for extra env vars to add to the posix_spawn call.
284 	 * 16 env vars, and 1024 bytes total should be enough for everyone.
285 	 */
286   #define MAX_EXTRA_ENVS 16
287   #define MAX_ENV_STORAGE_SIZE 1024
288 	char env_storage[MAX_ENV_STORAGE_SIZE];
289 	bzero(env_storage, sizeof(env_storage));
290 	char *envs_to_add[MAX_EXTRA_ENVS];
291 	bzero(envs_to_add, sizeof(envs_to_add));
292 	cpu_type_t cputype_binpref = 0;
293 	cpu_subtype_t cpusubtype_binpref = 0;
294 	uint32_t psa_options = 0;
295 	uint8_t sec_flags = 0;
296 	bool should_apply_rules = evaluate_rules(rules_file_path, fname,
297 	    envs_to_add, sizeof(envs_to_add) / sizeof(envs_to_add[0]),
298 	    env_storage, sizeof(env_storage),
299 	    &cputype_binpref, &cpusubtype_binpref,
300 	    &psa_options, &sec_flags);
301 
302 	if (!should_apply_rules) {
303 		return false;
304 	}
305 
306 	/*
307 	 * Create stack-allocated private copies of args_desc and spawnattr_t structs
308 	 * that we can modify.
309 	 */
310 	struct _posix_spawn_args_desc new_ad;
311 	bzero(&new_ad, sizeof(new_ad));
312 	struct _posix_spawnattr new_attr;
313 	__posix_spawnattr_init(&new_attr);
314 	if (adp != NULL) {
315 		memcpy(&new_ad, adp, sizeof(new_ad));
316 	}
317 	if (new_ad.attrp != NULL) {
318 		memcpy(&new_attr, new_ad.attrp, sizeof(new_attr));
319 	}
320 	new_ad.attrp = &new_attr;
321 
322 	/*
323 	 * Now 'new_ad' and 'new_attr' are always non-NULL and okay to be modified.
324 	 */
325 	if (cputype_binpref != 0) {
326 		for (int i = 0; i < NBINPREFS; i++) {
327 			new_attr.psa_binprefs[i] = 0;
328 			new_attr.psa_subcpuprefs[i] = CPU_SUBTYPE_ANY;
329 		}
330 		new_attr.psa_binprefs[0] = cputype_binpref;
331 		new_attr.psa_subcpuprefs[0] = cpusubtype_binpref;
332 	}
333 
334 	if (psa_options != 0) {
335 		new_attr.psa_options |= psa_options;
336 	}
337 	if (sec_flags != 0) {
338 		new_attr.psa_sec_flags |= sec_flags;
339 	}
340 
341 	/*
342 	 * Count old envs.
343 	 */
344 	size_t envp_count = 0;
345 	char *const *ep = envp;
346 	while (*ep++) {
347 		envp_count += 1;
348 	}
349 
350 	/*
351 	 * Count envs to add.
352 	 */
353 	size_t envs_to_add_count = 0;
354 	ep = envs_to_add;
355 	while (envs_to_add_count < MAX_EXTRA_ENVS && *ep++) {
356 		envs_to_add_count += 1;
357 	}
358 
359 	/*
360 	 * Make enough room for old and new envs plus NULL at the end.
361 	 */
362 	char *new_envp[envs_to_add_count + envp_count + 1];
363 
364 	/*
365 	 * Prepend the new envs so that they get picked up by Libc's getenv and common
366 	 * simple_getenv implementations. It's technically undefined what happens if
367 	 * a name occurs multiple times, but the common implementations pick the first
368 	 * entry.
369 	 */
370 	bzero(&new_envp[0], sizeof(new_envp));
371 	memcpy(&new_envp[0], &envs_to_add[0], envs_to_add_count * sizeof(void *));
372 	memcpy(&new_envp[envs_to_add_count], envp, envp_count * sizeof(void *));
373 
374 	*ret = __posix_spawn(pid, fname, &new_ad, argp, new_envp);
375 	return true;
376 }
377 
378 __attribute__((visibility("hidden")))
379 int
_execve_with_filter(const char * fname,char * const * argp,char * const * envp)380 _execve_with_filter(const char *fname, char * const *argp, char * const *envp)
381 {
382 	int ret = 0;
383 
384 	/*
385 	 * Rewrite the execve() call into a posix_spawn(SETEXEC) call. We need to be
386 	 * careful in this codepath (and in all called functions) because execve is
387 	 * required to be async-signal-safe by POSIX.
388 	 */
389 	struct _posix_spawn_args_desc ad;
390 	bzero(&ad, sizeof(ad));
391 
392 	struct _posix_spawnattr attr;
393 	__posix_spawnattr_init(&attr);
394 	attr.psa_flags |= POSIX_SPAWN_SETEXEC;
395 
396 	ad.attrp = &attr;
397 	ad.attr_size = sizeof(struct _posix_spawnattr);
398 
399 	if (_posix_spawn_with_filter(NULL, fname, argp, envp, &ad, &ret)) {
400 		if (ret == 0) {
401 			return 0;
402 		} else {
403 			errno = ret;
404 			return -1;
405 		}
406 	}
407 
408 	ret = __execve(fname, argp, envp);
409 	return ret;
410 }
411 
412 #endif /* POSIX_SPAWN_FILTERING_ENABLED */
413