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)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)
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 *psa_options |= PSA_OPTION_USE_SEC_TRANSITION_SHIMS;
217 }
218 }
219
220 memmove(read_buffer, newline_pos + 1, sizeof(read_buffer) - line_length);
221 bytes -= line_length + 1;
222 }
223 }
224
225 __close_nocancel(fd);
226 errno = saveerrno;
227 return fname_matches;
228 }
229
230 /*
231 * Apply posix_spawn filtering rules, and invoke a possibly modified posix_spawn
232 * call. Returns true if the posix_spawn was handled/invoked (and populates the
233 * 'ret' outparam in that case), false if the filter does not apply and the
234 * caller should proceed to call posix_spawn/exec normally.
235 *
236 * We need to be careful in this codepath (and in all called functions) because
237 * we can be called as part of execve() and that's required to be
238 * async-signal-safe by POSIX. We're also replacing one syscall with multiple,
239 * so we need to watch out to preserve cancellation/EINTR semantics, and avoid
240 * changing errno.
241 */
242 __attribute__((visibility("hidden")))
243 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)244 _posix_spawn_with_filter(pid_t *pid, const char *fname, char * const *argp,
245 char * const *envp, struct _posix_spawn_args_desc *adp, int *ret)
246 {
247 /*
248 * For testing, the path to the rules file can be overridden with an env var.
249 * It's hard to get access to 'environ' or '_NSGetEnviron' here so instead
250 * peek into the envp arg of posix_spawn/exec, even though we should really
251 * inspect the parent's env instead. For testing only purposes, it's fine.
252 */
253 const char *rules_file_path =
254 _simple_getenv(envp, "POSIX_SPAWN_FILTERING_RULES_PATH");
255
256 #if TARGET_OS_IPHONE
257 /*
258 * Use `/var/mobile` path (writable by iOS apps) if it exists.
259 * We don't care about TOCTOU here.
260 */
261 if (!rules_file_path) {
262 const char *path =
263 "/private/var/mobile/Library/posix_spawn_filtering_rules";
264 if (can_access(path)) {
265 rules_file_path = path;
266 }
267 }
268 #endif
269
270 /*
271 * Try the default rule file location (on root filesystem).
272 */
273 if (!rules_file_path) {
274 rules_file_path = "/usr/local/share/posix_spawn_filtering_rules";
275 }
276
277 /*
278 * Stack-allocated storage for extra env vars to add to the posix_spawn call.
279 * 16 env vars, and 1024 bytes total should be enough for everyone.
280 */
281 #define MAX_EXTRA_ENVS 16
282 #define MAX_ENV_STORAGE_SIZE 1024
283 char env_storage[MAX_ENV_STORAGE_SIZE];
284 bzero(env_storage, sizeof(env_storage));
285 char *envs_to_add[MAX_EXTRA_ENVS];
286 bzero(envs_to_add, sizeof(envs_to_add));
287 cpu_type_t cputype_binpref = 0;
288 cpu_subtype_t cpusubtype_binpref = 0;
289 uint32_t psa_options = 0;
290 bool should_apply_rules = evaluate_rules(rules_file_path, fname,
291 envs_to_add, sizeof(envs_to_add) / sizeof(envs_to_add[0]),
292 env_storage, sizeof(env_storage),
293 &cputype_binpref, &cpusubtype_binpref,
294 &psa_options);
295
296 if (!should_apply_rules) {
297 return false;
298 }
299
300 /*
301 * Create stack-allocated private copies of args_desc and spawnattr_t structs
302 * that we can modify.
303 */
304 struct _posix_spawn_args_desc new_ad;
305 bzero(&new_ad, sizeof(new_ad));
306 struct _posix_spawnattr new_attr;
307 __posix_spawnattr_init(&new_attr);
308 if (adp != NULL) {
309 memcpy(&new_ad, adp, sizeof(new_ad));
310 }
311 if (new_ad.attrp != NULL) {
312 memcpy(&new_attr, new_ad.attrp, sizeof(new_attr));
313 }
314 new_ad.attrp = &new_attr;
315
316 /*
317 * Now 'new_ad' and 'new_attr' are always non-NULL and okay to be modified.
318 */
319 if (cputype_binpref != 0) {
320 for (int i = 0; i < NBINPREFS; i++) {
321 new_attr.psa_binprefs[i] = 0;
322 new_attr.psa_subcpuprefs[i] = CPU_SUBTYPE_ANY;
323 }
324 new_attr.psa_binprefs[0] = cputype_binpref;
325 new_attr.psa_subcpuprefs[0] = cpusubtype_binpref;
326 }
327
328 if (psa_options != 0) {
329 new_attr.psa_options |= psa_options;
330 }
331
332 /*
333 * Count old envs.
334 */
335 size_t envp_count = 0;
336 char *const *ep = envp;
337 while (*ep++) {
338 envp_count += 1;
339 }
340
341 /*
342 * Count envs to add.
343 */
344 size_t envs_to_add_count = 0;
345 ep = envs_to_add;
346 while (envs_to_add_count < MAX_EXTRA_ENVS && *ep++) {
347 envs_to_add_count += 1;
348 }
349
350 /*
351 * Make enough room for old and new envs plus NULL at the end.
352 */
353 char *new_envp[envs_to_add_count + envp_count + 1];
354
355 /*
356 * Prepend the new envs so that they get picked up by Libc's getenv and common
357 * simple_getenv implementations. It's technically undefined what happens if
358 * a name occurs multiple times, but the common implementations pick the first
359 * entry.
360 */
361 bzero(&new_envp[0], sizeof(new_envp));
362 memcpy(&new_envp[0], &envs_to_add[0], envs_to_add_count * sizeof(void *));
363 memcpy(&new_envp[envs_to_add_count], envp, envp_count * sizeof(void *));
364
365 *ret = __posix_spawn(pid, fname, &new_ad, argp, new_envp);
366 return true;
367 }
368
369 __attribute__((visibility("hidden")))
370 int
_execve_with_filter(const char * fname,char * const * argp,char * const * envp)371 _execve_with_filter(const char *fname, char * const *argp, char * const *envp)
372 {
373 int ret = 0;
374
375 /*
376 * Rewrite the execve() call into a posix_spawn(SETEXEC) call. We need to be
377 * careful in this codepath (and in all called functions) because execve is
378 * required to be async-signal-safe by POSIX.
379 */
380 struct _posix_spawn_args_desc ad;
381 bzero(&ad, sizeof(ad));
382
383 struct _posix_spawnattr attr;
384 __posix_spawnattr_init(&attr);
385 attr.psa_flags |= POSIX_SPAWN_SETEXEC;
386
387 ad.attrp = &attr;
388 ad.attr_size = sizeof(struct _posix_spawnattr);
389
390 if (_posix_spawn_with_filter(NULL, fname, argp, envp, &ad, &ret)) {
391 if (ret == 0) {
392 return 0;
393 } else {
394 errno = ret;
395 return -1;
396 }
397 }
398
399 ret = __execve(fname, argp, envp);
400 return ret;
401 }
402
403 #endif /* POSIX_SPAWN_FILTERING_ENABLED */
404