2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
30 #include <sys/param.h>
31 #include <sys/mount.h>
32 #include <sys/ucred.h>
33 #include <dispatch/dispatch.h>
37 #define __APPLE_API_PRIVATE
38 #include <quarantine.h>
39 #undef __APPLE_API_PRIVATE
41 #include <security_utilities/cfutilities.h>
42 #include <security_utilities/unix++.h>
43 #include <security_utilities/logging.h>
44 #include <Security/SecStaticCode.h>
46 #include "SecTranslocateShared.hpp"
47 #include "SecTranslocateUtilities.hpp"
52 namespace SecTranslocate
{
56 /* String Constants for XPC dictionary passing */
57 /* XPC Function keys */
58 const char* kSecTranslocateXPCFuncCreate
= "create";
59 const char* kSecTranslocateXPCFuncCheckIn
= "check-in";
61 /* XPC message argument keys */
62 const char* kSecTranslocateXPCMessageFunction
= "function";
63 const char* kSecTranslocateXPCMessageOriginalPath
= "original";
64 const char* kSecTranslocateXPCMessageDestinationPath
= "dest";
65 const char* kSecTranslocateXPCMessagePid
= "pid";
67 /*XPC message reply keys */
68 const char* kSecTranslocateXPCReplyError
= "error";
69 const char* kSecTranslocateXPCReplySecurePath
= "result";
71 //Functions used only within this file
72 static void setMountPointQuarantineIfNecessary(const string
&mountPoint
, const string
&originalPath
);
73 static string
getMountpointFromAppPath(const string
&appPath
, const string
&originalPath
);
75 static vector
<struct statfs
> getMountTableSnapshot();
76 static string
mountExistsForUser(const string
&translationDirForUser
,
77 const TranslocationPath
&originalPath
,
78 const string
&destMount
);
79 static void validateMountpoint(const string
&mountpoint
, bool owned
=false);
80 static string
makeNewMountpoint(const string
&translationDir
);
81 static string
newAppPath (const string
&mountPoint
, const TranslocationPath
&originalPath
);
82 static void cleanupTranslocationDirForUser(const string
&userDir
);
83 static int removeMountPoint(const string
&mountpoint
, bool force
= false);
85 /* calculate whether a translocation should occur and where from */
86 TranslocationPath::TranslocationPath(string originalPath
)
89 /* To support testing of translocation the policy is as follows:
90 1. When the quarantine translocation sysctl is off, always translocate
91 if we aren't already on a translocated mount point.
92 2. When the quarantine translocation sysctl is on, use the quarantine
94 when asking if a path should run translocated need to:
95 check the current quarantine state of the path asked about
96 if it is already on a nullfs mount
98 else if it is unquarantined
101 if not QTN_FLAG_TRANSLOCATE or QTN_FLAG_DO_NOT_TRANSLOCATE
104 find the outermost acceptable code bundle
105 if not QTN_FLAG_TRANSLOCATE or QTN_FLAG_DO_NOT_TRANSLOCATE
110 See findOuterMostCodeBundleForFD for more info about what an acceptable outermost bundle is
111 in particular it should be noted that the outermost acceptable bundle for a quarantined inner
112 bundle can not be unquarantined. If the inner bundle is quarantined then any bundle containing it
113 must also have been quarantined.
116 ExtendedAutoFileDesc
fd(originalPath
);
119 realOriginalPath
= fd
.getRealPath();
121 /* don't translocate if it already is */
122 /* only consider translocation if the thing being asked about is marked for translocation */
123 /* Nullfs can't translocate other mount's roots so abort if its a mountpoint */
124 if(!fd
.isFileSystemType(NULLFS_FSTYPE
) && fd
.isQuarantined() && fd
.shouldTranslocate() && !fd
.isMountPoint())
126 ExtendedAutoFileDesc
&&outermost
= findOuterMostCodeBundleForFD(fd
);
128 should
= outermost
.isQuarantined() && outermost
.shouldTranslocate();
129 pathToTranslocate
= outermost
.getRealPath();
131 /* Calculate the path that will be needed to give the caller the path they asked for originally but in the translocated place */
134 vector
<string
> originalComponents
= splitPath(realOriginalPath
);
135 vector
<string
> toTranslocateComponents
= splitPath(pathToTranslocate
);
137 if (toTranslocateComponents
.size() == 0 ||
138 toTranslocateComponents
.size() > originalComponents
.size())
140 Syslog::error("SecTranslocate, TranslocationPath, path calculation failed:\n\toriginal: %s\n\tcalculated: %s",
141 realOriginalPath
.c_str(),
142 pathToTranslocate
.c_str());
143 UnixError::throwMe(EINVAL
);
146 componentNameToTranslocate
= toTranslocateComponents
.back();
148 for(size_t cnt
= 0; cnt
< originalComponents
.size(); cnt
++)
150 if (cnt
< toTranslocateComponents
.size())
152 if (toTranslocateComponents
[cnt
] != originalComponents
[cnt
])
154 Syslog::error("SecTranslocate, TranslocationPath, translocation path calculation failed:\n\toriginal: %s\n\tcalculated: %s",
155 realOriginalPath
.c_str(),
156 pathToTranslocate
.c_str());
157 UnixError::throwMe(EINVAL
);
163 want pathInsideTranslocationPoint to look like:
165 i.e. internal / but not at the front or back.
167 if(pathInsideTranslocationPoint
.empty())
169 pathInsideTranslocationPoint
= originalComponents
[cnt
];
173 pathInsideTranslocationPoint
+= "/" + originalComponents
[cnt
];
181 /* if we should translocate and a stored path inside the translocation point exists, then add it to the
182 passed in string. If no path inside is stored, then return the passed in string if translocation
183 should occur, and the original path for the TranslocationPath if translocation shouldn't occur */
184 string
TranslocationPath::getTranslocatedPathToOriginalPath(const string
&translocationPoint
) const
186 string seperator
= translocationPoint
.back() != '/' ? "/" : "";
190 if(!pathInsideTranslocationPoint
.empty())
192 return translocationPoint
+ seperator
+ pathInsideTranslocationPoint
;
196 return translocationPoint
;
201 //If we weren't supposed to translocate return the original path.
202 return realOriginalPath
;
206 /* Given an fd for a path find the outermost acceptable code bundle and return an fd for that.
207 an acceptable outermost bundle is quarantined, user approved, and a code bundle.
208 If nothing is found outside the path to the fd provided, then passed in fd or a copy there of is returned.*/
209 ExtendedAutoFileDesc
TranslocationPath::findOuterMostCodeBundleForFD(ExtendedAutoFileDesc
&fd
)
211 if( fd
.isMountPoint() || !fd
.isQuarantined())
215 vector
<string
> path
= splitPath(fd
.getRealPath());
216 size_t currentIndex
= path
.size() - 1;
217 size_t lastGoodIndex
= currentIndex
;
219 string pathToCheck
= joinPathUpTo(path
, currentIndex
);
221 Proposed algorithm (pseudo-code):
222 lastGood := path := canonicalized path to be launched
224 while path is not a mount point
225 if path is quarantined and not user-approved then exit loop # Gatekeeper has not cleared this code
226 if SecStaticCodeCreateWithPath(path) succeeds # used as an “is a code bundle” oracle
227 then lastGood := path
228 path := parent directory of path
233 ExtendedAutoFileDesc
currFd(pathToCheck
);
235 if (currFd
.isMountPoint() || !currFd
.isQuarantined() || !currFd
.isUserApproved())
240 SecStaticCodeRef staticCodeRef
= NULL
;
242 if( SecStaticCodeCreateWithPath(CFTempURL(currFd
.getRealPath()), kSecCSDefaultFlags
, &staticCodeRef
) == errSecSuccess
)
244 lastGoodIndex
= currentIndex
;
245 CFRelease(staticCodeRef
);
249 pathToCheck
= joinPathUpTo(path
, currentIndex
);
252 return ExtendedAutoFileDesc(joinPathUpTo(path
, lastGoodIndex
));
255 /* Given an fd to a translocated file, build the path to the original file
256 Throws if the fd isn't in a nullfs mount for the calling user. */
257 string
getOriginalPath(const ExtendedAutoFileDesc
& fd
, bool* isDir
)
259 if (!fd
.isFileSystemType(NULLFS_FSTYPE
) ||
261 !fd
.isInPrefixDir(fd
.getMountPoint()))
263 Syslog::error("SecTranslocate::getOriginalPath called with invalid params: fs_type = %s, isDir = %p, realPath = %s, mountpoint = %s",
264 fd
.getFsType().c_str(),
266 fd
.getRealPath().c_str(),
267 fd
.getMountPoint().c_str());
268 UnixError::throwMe(EINVAL
);
271 string translocationBaseDir
= translocationDirForUser();
273 if(!fd
.isInPrefixDir(translocationBaseDir
))
275 Syslog::error("SecTranslocate::getOriginal path called with path (%s) that doesn't belong to user (%d)",
276 fd
.getRealPath().c_str(),
278 UnixError::throwMe(EPERM
);
281 *isDir
= fd
.isA(S_IFDIR
);
283 vector
<string
> mountFromPath
= splitPath(fd
.getMountFromPath());
284 vector
<string
> mountPointPath
= splitPath(fd
.getMountPoint());
285 vector
<string
> translocatedRealPath
= splitPath(fd
.getRealPath());
287 if (mountPointPath
.size() > translocatedRealPath
.size())
289 Syslog::warning("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
290 UnixError::throwMe(EINVAL
);
293 string originalPath
= fd
.getMountFromPath();
297 for( i
= 0; i
<translocatedRealPath
.size(); i
++)
299 /* match the mount point directories to the real path directories */
300 if( i
< mountPointPath
.size())
302 if(translocatedRealPath
[i
] != mountPointPath
[i
])
304 Syslog::error("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
305 UnixError::throwMe(EINVAL
);
308 /* check for the d directory */
309 else if( i
== mountPointPath
.size())
311 if( translocatedRealPath
[i
] != "d")
313 Syslog::error("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
314 UnixError::throwMe(EINVAL
);
317 /* check for the app name */
318 else if( i
== mountPointPath
.size() + 1)
320 if( translocatedRealPath
[i
] != mountFromPath
.back())
322 Syslog::error("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
323 UnixError::throwMe(EINVAL
);
326 /* we are past the app name so add what ever is left */
329 originalPath
+="/"+translocatedRealPath
[i
];
333 if( i
== mountPointPath
.size() || i
== mountPointPath
.size() + 1)
335 //Asked for the original path of the mountpoint or /d/
336 Syslog::warning("SecTranslocate: asked for the original path of a virtual directory: %s", fd
.getRealPath().c_str());
337 UnixError::throwMe(ENOENT
);
340 /* Make sure what we built actually exists */
341 ExtendedAutoFileDesc
originalFD(originalPath
);
342 if(!originalFD
.pathIsAbsolute())
344 Syslog::error("SecTranslocate: Calculated original path contains symlinks:\n\tExpected: %s\n\tRequested: %s",
345 originalFD
.getRealPath().c_str(),
346 originalPath
.c_str());
347 UnixError::throwMe(EINVAL
);
353 /* Given a path that should be a translocation path, and the path to an app do the following:
354 1. Validate that the translocation path (appPath) is a valid translocation path
355 2. Validate that the translocation path (appPath) is valid for the app specified by originalPath
356 3. Calculate what the mountpoint path would be given the app path
358 static string
getMountpointFromAppPath(const string
&appPath
, const string
&originalPath
)
360 /* assume that appPath looks like:
361 /my/user/temp/dir/AppTranslocation/MY-UUID/d/foo.app
363 and assume original path looks like:
366 In this function we find and return /my/user/temp/dir/AppTranslocation/MY-UUID/
367 we also verify that the stuff after that in appPath was /d/foo.app if the last directory
368 in originalPath was /foo.app
372 vector
<string
> app
= splitPath(appPath
); // throws if empty or not absolute
373 vector
<string
> original
= splitPath(originalPath
); //throws if empty or not absolute
375 if (original
.size() == 0) // had to have at least one directory, can't null mount /
377 Syslog::error("SecTranslocate: invalid original path: %s", originalPath
.c_str());
378 UnixError::throwMe(EINVAL
);
381 if (app
.size() >= 3 && //the app path must have at least 3 directories, can't null mount onto /
382 app
.back() == original
.back()) //last directory of both match
385 if(app
.back() == "d") //last directory of app path is preceded by /d/
388 result
= joinPath(app
);
393 Syslog::error("SecTranslocate: invalid app path: %s", appPath
.c_str());
394 UnixError::throwMe(EINVAL
);
400 /* Read the mount table and return it in a vector */
401 static vector
<struct statfs
> getMountTableSnapshot()
403 vector
<struct statfs
> mntInfo
;
404 int fs_cnt_first
= 0;
405 int fs_cnt_second
= 0;
409 1. check the current mount table size
410 2. allocate double the required space
411 3. actually read the mount table
412 4. if the read actually filled up that double size try again once otherwise we are done
417 fs_cnt_first
= getfsstat(NULL
, 0 , MNT_WAIT
);
418 if(fs_cnt_first
<= 0)
420 Syslog::warning("SecTranslocate: error(%d) getting mount table info.", errno
);
421 UnixError::throwMe();
424 if( fs_cnt_first
== fs_cnt_second
)
426 /* this path only applies on a retry. If our second attempt to get the size is
427 the same as what we already read then break. */
431 mntInfo
.resize(fs_cnt_first
*2);
433 fs_cnt_second
= getfsstat(mntInfo
.data(), (int)(mntInfo
.size() * sizeof(struct statfs
)), MNT_WAIT
);
434 if (fs_cnt_second
<= 0)
436 Syslog::warning("SecTranslocate: error(%d) getting mount table info.", errno
);
437 UnixError::throwMe();
440 if( fs_cnt_second
== mntInfo
.size())
446 mntInfo
.resize(fs_cnt_second
); // trim the vector to what we actually need
453 Syslog::warning("SecTranslocate: mount table is growing very quickly");
459 /* Given the directory where app translocations go for this user, the path to the app to be translocated
460 and an optional destination mountpoint path. Check the mount table to see if a mount point already
461 user, for this app. If a destMountPoint is provided, make sure it is for this user, and that
462 exists for this the mountpoint found in the mount table is the same as the one requested */
463 static string
mountExistsForUser(const string
&translationDirForUser
, const TranslocationPath
&originalPath
, const string
&destMountPoint
)
465 string result
; // start empty
467 if(!destMountPoint
.empty())
469 /* Validate that destMountPoint path is well formed and for this user
470 well formed means it is === translationDirForUser/<1 directory>
472 vector
<string
> splitDestMount
= splitPath(destMountPoint
);
474 if(splitDestMount
.size() < 2) //translationDirForUser is never /
476 Syslog::warning("SecTranslocate: invalid destination mount point: %s",
477 destMountPoint
.c_str());
478 UnixError::throwMe(EINVAL
);
481 splitDestMount
.pop_back(); // knock off one directory
483 string destBaseDir
= joinPath(splitDestMount
)+"/"; //translationDirForUser has a / at the end
485 if (translationDirForUser
!= destBaseDir
)
487 Syslog::warning("SecTranslocate: invalid destination mount point for user\n\tExpected: %s\n\tRequested: %s",
488 translationDirForUser
.c_str(),
489 destBaseDir
.c_str());
490 /* requested destination isn't valid for the user */
491 UnixError::throwMe(EINVAL
);
495 vector
<struct statfs
> mntbuf
= getMountTableSnapshot();
497 /* Save the untranslocated inode number*/
498 ExtendedAutoFileDesc::UnixStat untranslocatedStat
;
500 if (stat(originalPath
.getPathToTranslocate().c_str(), &untranslocatedStat
))
503 Syslog::warning("SecTranslocate: failed to stat original path (%d): %s",
505 originalPath
.getPathToTranslocate().c_str());
506 UnixError::throwMe(err
);
509 for (auto &i
: mntbuf
)
511 string mountOnName
= i
.f_mntonname
;
512 size_t lastNonSlashPos
= mountOnName
.length() - 1; //start at the end of the string
514 /* find the last position of the last non slash character */
515 for(; lastNonSlashPos
!= 0 && mountOnName
[lastNonSlashPos
] == '/' ; lastNonSlashPos
--);
517 /* we want an exact match for originalPath and a prefix match for translationDirForUser
518 also make sure that this is a nullfs mount and that the mount point name is longer than the
519 translation directory with something other than / */
521 if (i
.f_mntfromname
== originalPath
.getPathToTranslocate() && //mount is for the requested path
522 strcmp(i
.f_fstypename
, NULLFS_FSTYPE
) == 0 && // mount is a nullfs mount
523 lastNonSlashPos
> translationDirForUser
.length()-1 && // no shenanigans, there must be more directory here than just the translation dir
524 strncmp(i
.f_mntonname
, translationDirForUser
.c_str(), translationDirForUser
.length()) == 0) //mount is inside the translocation dir
526 if(!destMountPoint
.empty())
528 if (mountOnName
!= destMountPoint
)
530 /* a mount exists for this path, but its not the one requested */
531 Syslog::warning("SecTranslocate: requested destination doesn't match existing\n\tExpected: %s\n\tRequested: %s",
533 destMountPoint
.c_str());
534 UnixError::throwMe(EEXIST
);
538 find the inode number for mountOnName+/d/appname
540 string pathToTranslocatedApp
= mountOnName
+"/d/"+originalPath
.getComponentNameToTranslocate();
542 ExtendedAutoFileDesc::UnixStat oldTranslocatedStat
;
544 if (stat(pathToTranslocatedApp
.c_str(), &oldTranslocatedStat
))
546 /* We should have access to this path and it should be real so complain if thats not true. */
548 Syslog::warning("SecTranslocate: expected app not inside mountpoint: %s (error: %d)", pathToTranslocatedApp
.c_str(), err
);
549 UnixError::throwMe(err
);
552 if(untranslocatedStat
.st_ino
!= oldTranslocatedStat
.st_ino
)
554 /* We have two Apps with the same name at the same path but different inodes. This means that the
555 translocated path is broken and should be removed */
556 destroyTranslocatedPathForUser(pathToTranslocatedApp
);
560 result
= mountOnName
;
568 /* Given what we think is a valid mountpoint, perform a sanity check, and clean up if we are wrong */
569 static void validateMountpoint(const string
&mountpoint
, bool owned
)
574 3. is not already a mountpoint
575 4. is an absolute path
578 bool isMount
= false;
582 /* first make sure this is a directory and that it is empty
583 (it could be dangerous to mount over a directory that contains something,
584 unfortunately this is still racy, and mount() is path based so we can't lock
585 down the directory until the mount succeeds (lock down is because of the entitlement
587 DIR* dir
= opendir(mountpoint
.c_str());
593 Syslog::warning("SecTranslocate: mountpoint is not a directory or doesn't exist: %s",
595 UnixError::throwMe(error
);
601 struct dirent dirbuf
;
604 while(((err
= readdir_r(dir
, &dirbuf
, &d
)) == 0) &&
607 /* skip . and .. but break if there is more than that */
620 Syslog::warning("SecTranslocate: error while checking that mountpoint is empty");
621 UnixError::throwMe(error
);
626 Syslog::warning("Sectranslocate: mountpoint is not empty: %s",
628 UnixError::throwMe(EBUSY
);
631 /* now check that the path is not a mountpoint */
632 ExtendedAutoFileDesc
fd(mountpoint
);
634 if(!fd
.pathIsAbsolute())
636 Syslog::warning("SecTranslocate: mountpoint isn't fully resolved\n\tExpected: %s\n\tActual: %s",
637 fd
.getRealPath().c_str(),
639 UnixError::throwMe(EINVAL
);
642 isMount
= fd
.isMountPoint();
646 Syslog::warning("SecTranslocate:Translocation failed, new mountpoint is already a mountpoint (%s)",
648 UnixError::throwMe(EINVAL
);
661 rmdir(mountpoint
.c_str());
663 /* Already logged the else case above */
667 Syslog::warning("SecTranslocate: unexpected file detected at mountpoint location (%s). Deleting.",
669 unlink(mountpoint
.c_str());
673 rethrow_exception(current_exception());
677 /* Create and validate the directory that we should mount at but don't create the mount yet */
678 static string
makeNewMountpoint(const string
&translationDir
)
680 AutoFileDesc
fd(getFDForDirectory(translationDir
));
682 string uuid
= makeUUID();
684 UnixError::check(mkdirat(fd
, uuid
.c_str(), 0500));
686 string mountpoint
= translationDir
+uuid
;
688 validateMountpoint(mountpoint
);
693 /* If the original path has mountpoint quarantine info, apply it to the new mountpoint*/
694 static void setMountPointQuarantineIfNecessary(const string
&mountPoint
, const string
&originalPath
)
696 struct statfs sfsbuf
;
699 UnixError::check(statfs(originalPath
.c_str(), &sfsbuf
));
700 qtn_file_t original_attr
= qtn_file_alloc();
702 if (original_attr
!= NULL
)
704 if (qtn_file_init_with_mount_point(original_attr
, sfsbuf
.f_mntonname
) == 0)
706 error
= qtn_file_apply_to_mount_point(original_attr
, mountPoint
.c_str());
708 qtn_file_free(original_attr
);
717 Syslog::warning("SecTranslocate: Failed to apply quarantine information\n\tMountpoint: %s\n\tOriginal Path: %s",
719 originalPath
.c_str());
720 UnixError::throwMe(error
);
724 /* Given the path to a new mountpoint and the original path to translocate, calculate the path
725 to the desired app in the new mountpoint, and sanity check that calculation */
726 static string
newAppPath (const string
&mountPoint
, const TranslocationPath
&originalPath
)
728 string midPath
= mountPoint
+"/d";
729 string outPath
= originalPath
.getTranslocatedPathToOriginalPath(midPath
+"/"+originalPath
.getComponentNameToTranslocate());
731 /* ExtendedAutoFileDesc will throw if one of these doesn't exist or isn't accessible */
732 ExtendedAutoFileDesc
mountFd(mountPoint
);
733 ExtendedAutoFileDesc
midFd(midPath
);
734 ExtendedAutoFileDesc
outFd(outPath
);
736 if(!outFd
.isFileSystemType(NULLFS_FSTYPE
) ||
737 !mountFd
.isFileSystemType(NULLFS_FSTYPE
) ||
738 !midFd
.isFileSystemType(NULLFS_FSTYPE
))
740 Syslog::warning("SecTranslocate::App exists at expected translocation path (%s) but isn't a nullfs mount (%s)",
742 outFd
.getFsType().c_str());
743 UnixError::throwMe(EINVAL
);
746 if(!outFd
.pathIsAbsolute() ||
747 !mountFd
.pathIsAbsolute() ||
748 !midFd
.pathIsAbsolute() )
750 Syslog::warning("SecTranslocate::App path isn't resolved\n\tGot: %s\n\tExpected: %s",
751 outFd
.getRealPath().c_str(),
753 UnixError::throwMe(EINVAL
);
756 fsid_t outFsid
= outFd
.getFsid();
757 fsid_t midFsid
= midFd
.getFsid();
758 fsid_t mountFsid
= mountFd
.getFsid();
760 /* different fsids mean that there is more than one volume between the expected mountpoint and the expected app path */
761 if (memcmp(&outFsid
, &midFsid
, sizeof(fsid_t
)) != 0 ||
762 memcmp(&outFsid
, &mountFsid
, sizeof(fsid_t
)) != 0)
764 Syslog::warning("SecTranslocate:: the fsid is not consistent between app, /d/ and mountpoint");
765 UnixError::throwMe(EINVAL
);
768 return outFd
.getRealPath();
771 /* Create an app translocation point given the original path and an optional destination path.
772 note the destination path can only be an outermost path (where the translocation would happen) and not a path to nested code
773 synchronize the process on the dispatch queue. */
774 string
translocatePathForUser(const TranslocationPath
&originalPath
, const string
&destPath
)
777 exception_ptr
exception(0);
783 const string
&toTranslocate
= originalPath
.getPathToTranslocate();
784 string baseDirForUser
= translocationDirForUser(); //throws
785 string destMountPoint
;
786 if(!destPath
.empty())
788 destMountPoint
= getMountpointFromAppPath(destPath
, toTranslocate
); //throws or returns a mountpoint
791 mountpoint
= mountExistsForUser(baseDirForUser
, originalPath
, destMountPoint
); //throws, detects invalid destMountPoint string
793 if (!mountpoint
.empty())
795 /* A mount point exists already so bail*/
796 newPath
= newAppPath(mountpoint
, originalPath
);
797 return newPath
; /* exit the block */
799 if (destMountPoint
.empty())
801 mountpoint
= makeNewMountpoint(baseDirForUser
); //throws
806 AutoFileDesc
fd(getFDForDirectory(destMountPoint
, &owned
)); //throws, makes the directory if it doesn't exist
808 validateMountpoint(destMountPoint
, owned
); //throws
809 mountpoint
= destMountPoint
;
812 UnixError::check(mount(NULLFS_FSTYPE
, mountpoint
.c_str(), MNT_RDONLY
, (void*)toTranslocate
.c_str()));
814 setMountPointQuarantineIfNecessary(mountpoint
, toTranslocate
); //throws
816 newPath
= newAppPath(mountpoint
, originalPath
); //throws
818 if (!destPath
.empty())
820 if (newPath
!= originalPath
.getTranslocatedPathToOriginalPath(destPath
))
822 Syslog::warning("SecTranslocate: created app translocation point did not equal requested app translocation point\n\texpected: %s\n\tcreated: %s",
825 /* the app at originalPath didn't match the one at destPath */
826 UnixError::throwMe(EINVAL
);
829 // log that we created a new mountpoint (we don't log when we are re-using)
830 Syslog::warning("SecTranslocateCreateSecureDirectoryForURL: created %s",
835 exception
= current_exception();
837 if (!mountpoint
.empty())
841 /* try to unmount/delete (best effort)*/
842 unmount(mountpoint
.c_str(), 0);
843 rmdir(mountpoint
.c_str());
848 /* rethrow outside the dispatch block */
851 rethrow_exception(exception
);
857 /* Loop through the directory in the specified user directory and delete any that aren't mountpoints */
858 static void cleanupTranslocationDirForUser(const string
&userDir
)
860 DIR* translocationDir
= opendir(userDir
.c_str());
862 if( translocationDir
)
866 struct dirent
* result
= NULL
;
868 while (readdir_r(translocationDir
, &de
, &result
) == 0 && result
)
870 if(result
->d_type
== DT_DIR
)
872 if (result
->d_name
[0] == '.')
874 if(result
->d_namlen
== 1 ||
875 (result
->d_namlen
== 2 &&
876 result
->d_name
[1] == '.'))
882 string nextDir
= userDir
+string(result
->d_name
);
883 if (0 == statfs(nextDir
.c_str(), &sfbuf
) &&
884 nextDir
== sfbuf
.f_mntonname
)
886 /* its a mount point so continue */
890 /* not a mountpoint so delete it */
891 if(unlinkat(dirfd(translocationDir
), result
->d_name
, AT_REMOVEDIR
))
893 Syslog::warning("SecTranslocate: failed to delete directory during cleanup (error %d)\n\tUser Dir: %s\n\tDir to delete: %s",
900 closedir(translocationDir
);
904 /* Unmount and delete a directory */
905 static int removeMountPoint(const string
&mountpoint
, bool force
)
909 if (0 == unmount(mountpoint
.c_str(), force
? MNT_FORCE
: 0) &&
910 0 == rmdir(mountpoint
.c_str()))
912 Syslog::warning("SecTranslocate: removed mountpoint: %s",
918 Syslog::warning("SecTranslocate: failed to unmount/remove mount point (errno: %d): %s",
919 error
, mountpoint
.c_str());
925 /* Destroy the specified translocated path, and clean up the user's translocation directory.
926 It is the caller's responsibility to synchronize the operation on the dispatch queue. */
927 bool destroyTranslocatedPathForUser(const string
&translocatedPath
)
932 1. verify the translocatedPath is for the user
933 2. verify it is a nullfs mountpoint (with app path)
936 5. loop through all the other directories in the app translation directory looking for directories not mounted on and delete them.
939 string baseDirForUser
= translocationDirForUser(); // throws
940 bool shouldUnmount
= false;
941 string translocatedMountpoint
;
943 { //Use a block to get rid of the file descriptor before we try to unmount.
944 ExtendedAutoFileDesc
fd(translocatedPath
);
945 translocatedMountpoint
= fd
.getMountPoint();
947 To support unmount when nested apps end, just make sure that the requested path is on a translocation
948 point for this user, not that they asked for a translocation point to be removed.
950 shouldUnmount
= fd
.isInPrefixDir(baseDirForUser
) && fd
.isFileSystemType(NULLFS_FSTYPE
);
955 error
= removeMountPoint(translocatedMountpoint
);
959 if (!result
&& !error
)
961 Syslog::warning("SecTranslocate: mountpoint does not belong to user(%d): %s",
963 translocatedPath
.c_str());
967 cleanupTranslocationDirForUser(baseDirForUser
);
971 UnixError::throwMe(error
);
977 /* Cleanup any translocation directories for this user that are either mounted from the
978 specified volume or from a volume that doesn't exist anymore. If an empty volumePath
979 is provided this has the effect of only cleaning up translocation points that point
980 to volumes that don't exist anymore.
982 It is the caller's responsibility to synchronize the operation on the dispatch queue.
984 bool destroyTranslocatedPathsForUserOnVolume(const string
&volumePath
)
986 bool cleanupError
= false;
987 string baseDirForUser
= translocationDirForUser();
988 vector
<struct statfs
> mountTable
= getMountTableSnapshot();
990 fsid_t unmountingFsid
;
991 int haveUnmountingFsid
= statfs(volumePath
.c_str(), &sb
);
992 int haveMntFromState
= 0;
994 memset(&unmountingFsid
, 0, sizeof(unmountingFsid
));
996 if(haveUnmountingFsid
== 0) {
997 unmountingFsid
= sb
.f_fsid
;
1000 for (auto &mnt
: mountTable
)
1003 we need to look at each translocation mount and check
1005 2. does its mntfromname still exist, if it doesn't unmount it
1006 3. if it does, is it the same as the volume we are cleaning up?, if so unmount it.
1008 if (strcmp(mnt
.f_fstypename
, NULLFS_FSTYPE
) == 0 &&
1009 strncmp(mnt
.f_mntonname
, baseDirForUser
.c_str(), baseDirForUser
.length()) == 0)
1011 haveMntFromState
= statfs(mnt
.f_mntfromname
, &sb
);
1013 if (haveMntFromState
!= 0)
1015 // In this case we are trying to unmount a translocation point that points to nothing. Force it.
1016 // Not forcing it currently hangs in UBC cleanup.
1017 (void)removeMountPoint(mnt
.f_mntonname
, true);
1019 else if (haveUnmountingFsid
== 0)
1021 fsid_t toCheckFsid
= sb
.f_fsid
;
1022 if( memcmp(&unmountingFsid
, &toCheckFsid
, sizeof(fsid_t
)) == 0)
1024 if(removeMountPoint(mnt
.f_mntonname
) != 0)
1026 cleanupError
= true;
1033 return !cleanupError
;
1036 /* This is intended to be used periodically to clean up translocation points that aren't used anymore */
1037 void tryToDestroyUnusedTranslocationMounts()
1039 vector
<struct statfs
> mountTable
= getMountTableSnapshot();
1040 string baseDirForUser
= translocationDirForUser();
1042 for (auto &mnt
: mountTable
)
1044 if (strcmp(mnt
.f_fstypename
, NULLFS_FSTYPE
) == 0 &&
1045 strncmp(mnt
.f_mntonname
, baseDirForUser
.c_str(), baseDirForUser
.length()) == 0)
1047 ExtendedAutoFileDesc
volumeToCheck(mnt
.f_mntfromname
, O_RDONLY
, FileDesc::modeMissingOk
);
1049 // Try to destroy the mount point. If the mirroed volume (volumeToCheck) isn't open then force it.
1050 // Not forcing it currently hangs in UBC cleanup.
1051 (void)removeMountPoint(mnt
.f_mntonname
, !volumeToCheck
.isOpen());
1056 } //namespace SecTranslocate
1057 }// namespace Security