]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_translocate/lib/SecTranslocate.cpp
Security-59754.41.1.tar.gz
[apple/security.git] / OSX / libsecurity_translocate / lib / SecTranslocate.cpp
1 /*
2 * Copyright (c) 2016 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 <stdio.h>
25 #include <sys/param.h>
26 #include <sys/mount.h>
27
28 #include <string>
29 #include <vector>
30
31 #include <security_utilities/cfutilities.h>
32 #include <security_utilities/unix++.h>
33 #include <security_utilities/logging.h>
34
35 #include "SecTranslocate.h"
36 #include "SecTranslocateShared.hpp"
37 #include "SecTranslocateInterface.hpp"
38 #include "SecTranslocateUtilities.hpp"
39
40
41 /* Strategy:
42
43 This library exists to create and destroy app translocation points. To ensure that a given
44 process using this library is only making or destroying one mountpoint at a time, the two
45 interface functions are sychronized on a dispatch queue.
46
47 **** App Translocation Strategy w/o a destination path ****
48 (This functionality is implemented in SecTranslocateShared.hpp/cpp)
49
50 To create a translocation mountpoint, if no destination path is provided, first the app
51 path to be translocated is realpathed to ensure it exists and there are no symlink's in
52 the path we work with. Then the calling user's _CS_DARWIN_USER_TEMP_DIR is found. This
53 is used to calculate the user's AppTranslocation directory. The formula is:
54 User's App Translocation Directory = realpath(confstr(_CS_DARWIN_USER_TEMP_DIR))+/AppTranslocation/
55
56 Then the mount table is checked to see whether or not a translocation point already exists
57 for this user for the app being requested. The rule for an already existing mount is that
58 there must exist a mountpoint in the user's app translocation directory that is mounted
59 from realpath of the requested app.
60
61 If a mount exists already for this user, then the path to the app in that mountpoint is
62 calculated and sanity checked.
63
64 The rules to create the app path inside the mountpoint are:
65
66 original app path = /some/path/<app name>
67 new app path = realpath(confstr(_CS_DARWIN_USER_TEMP_DIR))+/AppTranslocation/<UUID>/d/<app name>
68
69 The sanity check for the new app path is that:
70 1. new app path exists
71 2. new app path is in a nullfs mount
72 3. new app path is already completely resolved.
73
74 If the sanity checks pass for the new app path, then that path is returned to the caller.
75
76 If no translocation mount point exists already per the mount table then an AppTranslocation
77 directory is created within the temp dir if it doesn't already exist. After that a UUID is
78 generated, that UUID is used as the name of a new directory in the AppTranslocation directory.
79 Once the new directory has been created and sanity checked, mount is called to create the
80 translocation between the original path and the new directory. Then the new path to the app
81 within the mountpoint is calculated and sanity checked.
82
83 The sanity check rules for the mountpoint before the mount are:
84 1. Something exists at the expected path
85 2. That something is a directory
86 3. That something is not already a mountpoint
87 4. The expected path is fully resolved
88
89 The sanity check for the new app path is listed above (for the mountpoint exists case).
90
91 **** App Translocation strategy w/ a destination path ****
92 (This functionality is implemented in SecTranslocateShared.hpp/cpp)
93
94 If a destination path is provided, a sequence similar to that described above is followed
95 with the following modifications.
96
97 The destination path is expected to be of the same form as new app path. This expectation
98 is verified.
99
100 First we verify that the destination path ends with /d/<app name> and that the <app name>
101 component of the destination path matches the <app name> of the original app app path
102 requested. If not, an error occurs. Everything before the /d/ is treated becomes the
103 requested destination mount point.
104
105 After the user's app translocation directory is calculated, we ensure that the requested
106 destination mount point is prefixed by the translocation directory, and contains only one
107 path component after the user's app translocation path, otherwise an error occurs.
108
109 When we check the mount table, we make sure that if the a translocation of the app already
110 exists for the user, then the translocation path must exactly match the requested
111 destination path, otherwise an error occurs.
112
113 If no mountpoint exists for the app, then we attempt to create the requested directory within
114 the user's app translocation directory. This becomes the mount point, and the mount point
115 sanity checks listed above are applied.
116
117 If the requested destination mountpoint is successfully created, the flow continues as above
118 to create the mount and verify the requested path within the mountpoint. The only extra step
119 here is that at the end, the requested app path, must exactly equal the created app path.
120
121 **** App Translocation error cleanup ****
122 (This functionality is implemented in SecTranslocateShared.hpp/cpp)
123
124 The error cleanup strategy for translocation creation is to try to destroy any directories
125 or mount points in the user's translocation directory that were created before an error
126 was detected. This means tracking whether we created a directory, or it already existed when
127 a caller asked for it. Clean up is considered best effort.
128
129 **** Deleting an App Translocation point ****
130 (This functionality is implemented in SecTranslocateShared.hpp/cpp)
131
132 To destroy an app translocation point, the first thing we do is calculate the user's app
133 translocation directory to ensure that the requested path is actually within that directory.
134 We also verify that it is in fact a nullfs mount point. If it is, then we attempt to unmount and
135 remove the translocation point.
136
137 Regardless of whether or not the requested path is a translocation point, we opportunistically
138 attempt to cleanup the app translocation directory. Clean up means, looping through all the
139 directories currently in the user's app translocation directory and checking whether or not
140 they are a mount point. If a directory inside the user's app translocation directory is not
141 a mountpoint, then we attempt to delete it.
142
143 **** Quarantine considerations ****
144 (This functionality is implemented in SecTranslocateShared.hpp/cpp and SecTranslocateUtilities.hpp/cpp)
145
146 If the original app path includes files with quarantine extended attributes, then those extended
147 attributes will be readable through the created app translocation mountpoint. nullfs does not
148 support removing or setting extended attributes on its vnodes. Changes to the quarantine
149 attributes at the original path will be reflected in the app translocation mountpoint without
150 creating a new mount point.
151
152 If the original app path is inside a quarantined mountpoint (such as a quarantined dmg), then
153 that the quarantine information for that mountpoint is read from the original app path's
154 mountpoint and applied to the created app translocation mountpoint.
155
156 **** Concurrency considerations ****
157 This library treats the kernel as the source of truth for the status of the file system.
158 Unfortunately it has no way to lock the state of the file system and mount table while
159 it is operating. Because of this, there are two potential areas that have race windows.
160
161 First, if any other system entity (thread within the same process, or other process
162 within the system) is adding or removing entries from the mount table while
163 SecTranslocateCreateSecureDirectoryForURL is executing, then there is the possibility that
164 an incorrect decision will be made about the current existence of a mount point for a user
165 for an app. This is because getfsstat gets a snapshot of the mount table state rather than a
166 locked window into the kernel and because we make two seperate calls to getfsstat, one to get
167 the number of mountpoints, and a second to actually read the mountpoint data. If more than
168 one process is using this library for the same user, then both processes could attempt to
169 create a translocation for the same app, and this could result in more than one translocation
170 for that app for the user. This shouldn't effect the user other than using additional
171 system resources. We attempt to mitigate this by allocating double the required memory from
172 the first call and then trying the process again (once) if the initial memory was filled.
173
174 Second, if more than one process is using this library simultaneously and one process calls
175 SecTranslocateDeleteSecureDirectory for a user and the other calls
176 SecTranslocateCreateSecureDirectoryForURL for that same user, then the call to
177 SecTranslocateDeleteSecureDirectory may cause SecTranslocateCreateSecureDirectoryForURL to
178 fail. This will occur if the loop checking for unmounted directories in the user's app
179 translocation directory deletes a newly created directory before the mount call finishes. This
180 race condition will probably result in a failed app launch. A second attempt to launch the app
181 will probably succeed.
182
183 Concurrency is now split between SecTranslocateClient.hpp/cpp, SecTranslocateServer.hpp/cpp,
184 SecTranslocateDANotification.hpp/cpp, SecTranslocateLSNotification.hpp/cpp, and
185 SecTranslocateXPCServer.hpp/cpp. Each of these represent different ways of entering translocation
186 functionality.
187
188 **** Logging Strategy ****
189 Use warning logging for interesting conditions (e.g. translocation point created or destroyed).
190 Use error logging for non-fatal failures. Use critical logging for fatal failures.
191 */
192
193 /* Make a CFError from an POSIX error code */
194 static CFErrorRef SecTranslocateMakePosixError(CFIndex errorCode)
195 {
196 return CFErrorCreate(NULL, kCFErrorDomainPOSIX, errorCode, NULL);
197 }
198
199 /* must be called before any other function in this SPI if the process is intended to be the server */
200 Boolean SecTranslocateStartListening(CFErrorRef* __nullable error)
201 {
202 Boolean result = false;
203 CFIndex errorCode = 0;
204 try
205 {
206 /* ask getTranslocator for the server */
207 result = Security::SecTranslocate::getTranslocator(true) != NULL;
208 }
209 catch (Security::UnixError err)
210 {
211 errorCode = err.unixError();
212 }
213 catch(...)
214 {
215 Syslog::critical("SecTranslocate: uncaught exception during server initialization");
216 errorCode = EINVAL;
217 }
218
219 if (error && errorCode)
220 {
221 *error = SecTranslocateMakePosixError(errorCode);
222 }
223
224 return result;
225 }
226
227 /* placeholder api for now to allow for future options at startup */
228 Boolean SecTranslocateStartListeningWithOptions(CFDictionaryRef __unused options, CFErrorRef * __nullable outError)
229 {
230 return SecTranslocateStartListening(outError);
231 }
232
233 /* Register that a (translocated) pid has launched */
234 void SecTranslocateAppLaunchCheckin(pid_t pid)
235 {
236 try
237 {
238 Security::SecTranslocate::getTranslocator()->appLaunchCheckin(pid);
239 }
240 catch (...)
241 {
242 Syslog::error("SecTranslocate: error in SecTranslocateAppLaunchCheckin");
243 }
244 }
245
246 /* Create an app translocation point given the original path and an optional destination path. */
247 CFURLRef __nullable SecTranslocateCreateSecureDirectoryForURL (CFURLRef pathToTranslocate,
248 CFURLRef __nullable destinationPath,
249 CFErrorRef* __nullable error)
250 {
251 CFURLRef result = NULL;
252 CFIndex errorCode = 0;
253
254 try
255 {
256 string sourcePath = cfString(pathToTranslocate); // returns an absolute path
257
258 Security::SecTranslocate::TranslocationPath toTranslocatePath(sourcePath, Security::SecTranslocate::TranslocationOptions::Default);
259
260 if(!toTranslocatePath.shouldTranslocate())
261 {
262 /* We shouldn't translocate so, just retain so that the return value can be treated as a copy */
263 CFRetain(pathToTranslocate);
264 return pathToTranslocate;
265 }
266
267 /* We need to translocate so keep going */
268 string destPath;
269
270 if(destinationPath)
271 {
272 destPath = cfString(destinationPath); //returns an absolute path
273 }
274
275 string out_path = Security::SecTranslocate::getTranslocator()->translocatePathForUser(toTranslocatePath, destPath);
276
277 if(!out_path.empty())
278 {
279 result = makeCFURL(out_path, true);
280 }
281 else
282 {
283 Syslog::error("SecTranslocateCreateSecureDirectoryForURL: No mountpoint and no prior exception. Shouldn't be here");
284 UnixError::throwMe(EINVAL);
285 }
286
287 }
288 catch (Security::UnixError err)
289 {
290 errorCode = err.unixError();
291 }
292 catch(...)
293 {
294 Syslog::critical("SecTranslocate: uncaught exception during mountpoint creation");
295 errorCode = EACCES;
296 }
297
298 if (error && errorCode)
299 {
300 *error = SecTranslocateMakePosixError(errorCode);
301 }
302 return result;
303 }
304
305 /* Destroy the specified translocated path, and clean up the user's translocation directory. */
306 Boolean SecTranslocateDeleteSecureDirectory(CFURLRef translocatedPath, CFErrorRef* __nullable error)
307 {
308 bool result = false;
309 int errorCode = 0;
310
311 if(translocatedPath == NULL)
312 {
313 errorCode = EINVAL;
314 goto end;
315 }
316
317 try
318 {
319 string pathToDestroy = cfString(translocatedPath);
320 result = Security::SecTranslocate::getTranslocator()->destroyTranslocatedPathForUser(pathToDestroy);
321 }
322 catch (Security::UnixError err)
323 {
324 errorCode = err.unixError();
325 }
326 catch(...)
327 {
328 Syslog::critical("SecTranslocate: uncaught exception during mountpoint deletion");
329 errorCode = EACCES;
330 }
331 end:
332 if (error && errorCode)
333 {
334 *error = SecTranslocateMakePosixError(errorCode);
335 }
336
337 return result;
338 }
339
340 CFURLRef __nullable SecTranslocateCreateGeneric (CFURLRef pathToTranslocate,
341 CFURLRef destinationPath,
342 CFErrorRef* __nullable error)
343 {
344 CFURLRef result = NULL;
345 CFIndex errorCode = 0;
346
347 try
348 {
349 string sourcePath = cfString(pathToTranslocate);
350 Security::SecTranslocate::GenericTranslocationPath path{sourcePath, Security::SecTranslocate::TranslocationOptions::Unveil};
351
352 string dpath = cfString(destinationPath);
353 string out_path = Security::SecTranslocate::getTranslocator()->translocatePathForUser(path, dpath);
354
355 if(!out_path.empty())
356 {
357 result = makeCFURL(out_path, true);
358 }
359 else
360 {
361 Syslog::error("SecTranslocateCreateGeneric: No mountpoint and no prior exception. Shouldn't be here");
362 UnixError::throwMe(EINVAL);
363 }
364
365 }
366 catch (Security::UnixError err)
367 {
368 errorCode = err.unixError();
369 }
370 catch(...)
371 {
372 Syslog::critical("SecTranslocateCreateGeneric: uncaught exception during mountpoint creation");
373 errorCode = EACCES;
374 }
375
376 if (error && errorCode)
377 {
378 *error = SecTranslocateMakePosixError(errorCode);
379 }
380 return result;
381 }
382
383 /* Decide whether we need to translocate */
384 Boolean SecTranslocateURLShouldRunTranslocated(CFURLRef path, bool* shouldTranslocate, CFErrorRef* __nullable error)
385 {
386 bool result = false;
387 int errorCode = 0;
388
389 if(path == NULL || shouldTranslocate == NULL)
390 {
391 errorCode = EINVAL;
392 goto end;
393 }
394
395 try
396 {
397 string pathToCheck = cfString(path);
398 Security::SecTranslocate::TranslocationPath tPath(pathToCheck, Security::SecTranslocate::TranslocationOptions::Default);
399 *shouldTranslocate = tPath.shouldTranslocate();
400 result = true;
401 }
402 catch (Security::UnixError err)
403 {
404 errorCode = err.unixError();
405 }
406 catch(...)
407 {
408 Syslog::critical("SecTranslocate: uncaught exception during policy check");
409 errorCode = EACCES;
410 }
411
412 end:
413 if (error && errorCode)
414 {
415 *error = SecTranslocateMakePosixError(errorCode);
416 }
417
418 return result;
419 }
420
421 /* Answer whether or not the passed in URL is a nullfs URL. This just checks nullfs rather than
422 nullfs + in the user's translocation path to allow callers like LaunchServices to apply special
423 handling to nullfs mounts regardless of the calling user (i.e. root lsd can identify all translocated
424 mount points for all users).
425 */
426 Boolean SecTranslocateIsTranslocatedURL(CFURLRef path, bool* isTranslocated, CFErrorRef* __nullable error)
427 {
428 bool result = false;
429 int errorCode = 0;
430
431 if(path == NULL || isTranslocated == NULL)
432 {
433 if(error)
434 {
435 *error = SecTranslocateMakePosixError(EINVAL);
436 }
437 return result;
438 }
439
440 *isTranslocated = false;
441
442 try
443 {
444 string cpp_path = cfString(path);
445 /* "/" i.e. the root volume, cannot be translocated (or mounted on by other file system after boot)
446 so don't bother to make system calls if "/" is what is being asked about.
447 This is an optimization to help LaunchServices which expects to use SecTranslocateIsTranslocatedURL
448 on every App Launch.
449 */
450 if (cpp_path != "/")
451 {
452 /* to avoid AppSandbox violations, use a path based check here.
453 We only look for nullfs file type anyway. */
454 struct statfs sfb;
455 if (statfs(cpp_path.c_str(), &sfb) == 0)
456 {
457 *isTranslocated = (strcmp(sfb.f_fstypename, NULLFS_FSTYPE) == 0);
458 result = true;
459 }
460 else
461 {
462 errorCode = errno;
463 Syslog::error("SecTranslocate: can not access %s, error: %s", cpp_path.c_str(), strerror(errorCode));
464 }
465 }
466 else
467 {
468 result = true;
469 }
470 }
471 catch (Security::UnixError err)
472 {
473 errorCode = err.unixError();
474 }
475 catch(...)
476 {
477 Syslog::critical("SecTranslocate: uncaught exception during policy check");
478 errorCode = EACCES;
479 }
480
481 if (error && errorCode)
482 {
483 *error = SecTranslocateMakePosixError(errorCode);
484 }
485
486 return result;
487 }
488
489 /* Find the original path for translocation mounts belonging to the calling user.
490 if the url isn't on a nullfs volume then returned a retained copy of the passed in url.
491 if the url is on a nullfs volume but that volume doesn't belong to the user, or another
492 error occurs then null is returned */
493 CFURLRef __nullable SecTranslocateCreateOriginalPathForURL(CFURLRef translocatedPath, CFErrorRef* __nullable error)
494 {
495 CFURLRef result = NULL;
496 int errorCode = 0;
497
498 if(translocatedPath == NULL)
499 {
500 errorCode = EINVAL;
501 goto end;
502 }
503 try
504 {
505 string path = cfString(translocatedPath);
506 Security::SecTranslocate::ExtendedAutoFileDesc fd(path);
507
508 if(fd.isFileSystemType(NULLFS_FSTYPE))
509 {
510 bool isDir = false;
511 string out_path = Security::SecTranslocate::getOriginalPath(fd, &isDir);
512 if(!out_path.empty())
513 {
514 result = makeCFURL(out_path, isDir);
515 }
516 else
517 {
518 Syslog::error("SecTranslocateCreateOriginalPath: No original and no prior exception. Shouldn't be here");
519 UnixError::throwMe(EINVAL);
520 }
521 }
522 else
523 {
524 result = translocatedPath;
525 CFRetain(result);
526 }
527 }
528 catch (Security::UnixError err)
529 {
530 errorCode = err.unixError();
531 }
532 catch(...)
533 {
534 Syslog::critical("SecTranslocate: uncaught exception during policy check");
535 errorCode = EACCES;
536 }
537 end:
538 if (error && errorCode)
539 {
540 *error = SecTranslocateMakePosixError(errorCode);
541 }
542 return result;
543 }