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
, const string
&originalPath
, const string
&destMount
);
77 static void validateMountpoint(const string
&mountpoint
, bool owned
=false);
78 static string
makeNewMountpoint(const string
&translationDir
);
79 static string
newAppPath (const string
&mountPoint
, const TranslocationPath
&originalPath
);
80 static void cleanupTranslocationDirForUser(const string
&userDir
);
81 static int removeMountPoint(const string
&mountpoint
, bool force
= false);
83 /* calculate whether a translocation should occur and where from */
84 TranslocationPath::TranslocationPath(string originalPath
)
87 /* To support testing of translocation the policy is as follows:
88 1. When the quarantine translocation sysctl is off, always translocate
89 if we aren't already on a translocated mount point.
90 2. When the quarantine translocation sysctl is on, use the quarantine
92 when asking if a path should run translocated need to:
93 check the current quarantine state of the path asked about
94 if it is already on a nullfs mount
96 else if it is unquarantined
99 if not QTN_FLAG_TRANSLOCATE or QTN_FLAG_DO_NOT_TRANSLOCATE
102 find the outermost acceptable code bundle
103 if not QTN_FLAG_TRANSLOCATE or QTN_FLAG_DO_NOT_TRANSLOCATE
108 See findOuterMostCodeBundleForFD for more info about what an acceptable outermost bundle is
109 in particular it should be noted that the outermost acceptable bundle for a quarantined inner
110 bundle can not be unquarantined. If the inner bundle is quarantined then any bundle containing it
111 must also have been quarantined.
114 ExtendedAutoFileDesc
fd(originalPath
);
117 realOriginalPath
= fd
.getRealPath();
119 /* don't translocate if it already is */
120 /* only consider translocation if the thing being asked about is marked for translocation */
121 if(!fd
.isFileSystemType(NULLFS_FSTYPE
) && fd
.isQuarantined() && fd
.shouldTranslocate())
123 ExtendedAutoFileDesc
&&outermost
= findOuterMostCodeBundleForFD(fd
);
125 should
= outermost
.isQuarantined() && outermost
.shouldTranslocate();
126 pathToTranslocate
= outermost
.getRealPath();
128 /* Calculate the path that will be needed to give the caller the path they asked for originally but in the translocated place */
131 vector
<string
> originalComponents
= splitPath(realOriginalPath
);
132 vector
<string
> toTranslocateComponents
= splitPath(pathToTranslocate
);
134 if (toTranslocateComponents
.size() == 0 ||
135 toTranslocateComponents
.size() > originalComponents
.size())
137 Syslog::error("SecTranslocate, TranslocationPath, path calculation failed:\n\toriginal: %s\n\tcalculated: %s",
138 realOriginalPath
.c_str(),
139 pathToTranslocate
.c_str());
140 UnixError::throwMe(EINVAL
);
143 for(size_t cnt
= 0; cnt
< originalComponents
.size(); cnt
++)
145 if (cnt
< toTranslocateComponents
.size())
147 if (toTranslocateComponents
[cnt
] != originalComponents
[cnt
])
149 Syslog::error("SecTranslocate, TranslocationPath, translocation path calculation failed:\n\toriginal: %s\n\tcalculated: %s",
150 realOriginalPath
.c_str(),
151 pathToTranslocate
.c_str());
152 UnixError::throwMe(EINVAL
);
158 want pathInsideTranslocationPoint to look like:
160 i.e. internal / but not at the front or back.
162 if(pathInsideTranslocationPoint
.empty())
164 pathInsideTranslocationPoint
= originalComponents
[cnt
];
168 pathInsideTranslocationPoint
+= "/" + originalComponents
[cnt
];
176 /* if we should translocate and a stored path inside the translocation point exists, then add it to the
177 passed in string. If no path inside is stored, then return the passed in string if translocation
178 should occur, and the original path for the TranslocationPath if translocation shouldn't occur */
179 string
TranslocationPath::getTranslocatedPathToOriginalPath(const string
&translocationPoint
) const
181 string seperator
= translocationPoint
.back() != '/' ? "/" : "";
185 if(!pathInsideTranslocationPoint
.empty())
187 return translocationPoint
+ seperator
+ pathInsideTranslocationPoint
;
191 return translocationPoint
;
196 //If we weren't supposed to translocate return the original path.
197 return realOriginalPath
;
201 /* Given an fd for a path find the outermost acceptable code bundle and return an fd for that.
202 an acceptable outermost bundle is quarantined, user approved, and a code bundle.
203 If nothing is found outside the path to the fd provided, then passed in fd or a copy there of is returned.*/
204 ExtendedAutoFileDesc
TranslocationPath::findOuterMostCodeBundleForFD(ExtendedAutoFileDesc
&fd
)
206 if( fd
.isMountPoint() || !fd
.isQuarantined())
210 vector
<string
> path
= splitPath(fd
.getRealPath());
211 size_t currentIndex
= path
.size() - 1;
212 size_t lastGoodIndex
= currentIndex
;
214 string pathToCheck
= joinPathUpTo(path
, currentIndex
);
216 Proposed algorithm (pseudo-code):
217 lastGood := path := canonicalized path to be launched
219 while path is not a mount point
220 if path is quarantined and not user-approved then exit loop # Gatekeeper has not cleared this code
221 if SecStaticCodeCreateWithPath(path) succeeds # used as an “is a code bundle” oracle
222 then lastGood := path
223 path := parent directory of path
228 ExtendedAutoFileDesc
currFd(pathToCheck
);
230 if (currFd
.isMountPoint() || !currFd
.isQuarantined() || !currFd
.isUserApproved())
235 SecStaticCodeRef staticCodeRef
= NULL
;
237 if( SecStaticCodeCreateWithPath(CFTempURL(currFd
.getRealPath()), kSecCSDefaultFlags
, &staticCodeRef
) == errSecSuccess
)
239 lastGoodIndex
= currentIndex
;
240 CFRelease(staticCodeRef
);
244 pathToCheck
= joinPathUpTo(path
, currentIndex
);
247 return ExtendedAutoFileDesc(joinPathUpTo(path
, lastGoodIndex
));
250 /* Given an fd to a translocated file, build the path to the original file
251 Throws if the fd isn't in a nullfs mount for the calling user. */
252 string
getOriginalPath(const ExtendedAutoFileDesc
& fd
, bool* isDir
)
254 if (!fd
.isFileSystemType(NULLFS_FSTYPE
) ||
256 !fd
.isInPrefixDir(fd
.getMountPoint()))
258 Syslog::error("SecTranslocate::getOriginalPath called with invalid params: fs_type = %s, isDir = %p, realPath = %s, mountpoint = %s",
259 fd
.getFsType().c_str(),
261 fd
.getRealPath().c_str(),
262 fd
.getMountPoint().c_str());
263 UnixError::throwMe(EINVAL
);
266 string translocationBaseDir
= translocationDirForUser();
268 if(!fd
.isInPrefixDir(translocationBaseDir
))
270 Syslog::error("SecTranslocate::getOriginal path called with path (%s) that doesn't belong to user (%d)",
271 fd
.getRealPath().c_str(),
273 UnixError::throwMe(EPERM
);
276 *isDir
= fd
.isA(S_IFDIR
);
278 vector
<string
> mountFromPath
= splitPath(fd
.getMountFromPath());
279 vector
<string
> mountPointPath
= splitPath(fd
.getMountPoint());
280 vector
<string
> translocatedRealPath
= splitPath(fd
.getRealPath());
282 if (mountPointPath
.size() > translocatedRealPath
.size())
284 Syslog::warning("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
285 UnixError::throwMe(EINVAL
);
288 string originalPath
= fd
.getMountFromPath();
292 for( i
= 0; i
<translocatedRealPath
.size(); i
++)
294 /* match the mount point directories to the real path directories */
295 if( i
< mountPointPath
.size())
297 if(translocatedRealPath
[i
] != mountPointPath
[i
])
299 Syslog::error("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
300 UnixError::throwMe(EINVAL
);
303 /* check for the d directory */
304 else if( i
== mountPointPath
.size())
306 if( translocatedRealPath
[i
] != "d")
308 Syslog::error("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
309 UnixError::throwMe(EINVAL
);
312 /* check for the app name */
313 else if( i
== mountPointPath
.size() + 1)
315 if( translocatedRealPath
[i
] != mountFromPath
.back())
317 Syslog::error("SecTranslocate: invalid translocated path %s", fd
.getRealPath().c_str());
318 UnixError::throwMe(EINVAL
);
321 /* we are past the app name so add what ever is left */
324 originalPath
+="/"+translocatedRealPath
[i
];
328 if( i
== mountPointPath
.size() || i
== mountPointPath
.size() + 1)
330 //Asked for the original path of the mountpoint or /d/
331 Syslog::warning("SecTranslocate: asked for the original path of a virtual directory: %s", fd
.getRealPath().c_str());
332 UnixError::throwMe(ENOENT
);
335 /* Make sure what we built actually exists */
336 ExtendedAutoFileDesc
originalFD(originalPath
);
337 if(!originalFD
.pathIsAbsolute())
339 Syslog::error("SecTranslocate: Calculated original path contains symlinks:\n\tExpected: %s\n\tRequested: %s",
340 originalFD
.getRealPath().c_str(),
341 originalPath
.c_str());
342 UnixError::throwMe(EINVAL
);
348 /* Given a path that should be a translocation path, and the path to an app do the following:
349 1. Validate that the translocation path (appPath) is a valid translocation path
350 2. Validate that the translocation path (appPath) is valid for the app specified by originalPath
351 3. Calculate what the mountpoint path would be given the app path
353 static string
getMountpointFromAppPath(const string
&appPath
, const string
&originalPath
)
355 /* assume that appPath looks like:
356 /my/user/temp/dir/AppTranslocation/MY-UUID/d/foo.app
358 and assume original path looks like:
361 In this function we find and return /my/user/temp/dir/AppTranslocation/MY-UUID/
362 we also verify that the stuff after that in appPath was /d/foo.app if the last directory
363 in originalPath was /foo.app
367 vector
<string
> app
= splitPath(appPath
); // throws if empty or not absolute
368 vector
<string
> original
= splitPath(originalPath
); //throws if empty or not absolute
370 if (original
.size() == 0) // had to have at least one directory, can't null mount /
372 Syslog::error("SecTranslocate: invalid original path: %s", originalPath
.c_str());
373 UnixError::throwMe(EINVAL
);
376 if (app
.size() >= 3 && //the app path must have at least 3 directories, can't null mount onto /
377 app
.back() == original
.back()) //last directory of both match
380 if(app
.back() == "d") //last directory of app path is preceded by /d/
383 result
= joinPath(app
);
388 Syslog::error("SecTranslocate: invalid app path: %s", appPath
.c_str());
389 UnixError::throwMe(EINVAL
);
395 /* Read the mount table and return it in a vector */
396 static vector
<struct statfs
> getMountTableSnapshot()
398 vector
<struct statfs
> mntInfo
;
399 int fs_cnt_first
= 0;
400 int fs_cnt_second
= 0;
404 1. check the current mount table size
405 2. allocate double the required space
406 3. actually read the mount table
407 4. if the read actually filled up that double size try again once otherwise we are done
412 fs_cnt_first
= getfsstat(NULL
, 0 , MNT_WAIT
);
413 if(fs_cnt_first
<= 0)
415 Syslog::warning("SecTranslocate: error(%d) getting mount table info.", errno
);
416 UnixError::throwMe();
419 if( fs_cnt_first
== fs_cnt_second
)
421 /* this path only applies on a retry. If our second attempt to get the size is
422 the same as what we already read then break. */
426 mntInfo
.resize(fs_cnt_first
*2);
428 fs_cnt_second
= getfsstat(mntInfo
.data(), (int)(mntInfo
.size() * sizeof(struct statfs
)), MNT_WAIT
);
429 if (fs_cnt_second
<= 0)
431 Syslog::warning("SecTranslocate: error(%d) getting mount table info.", errno
);
432 UnixError::throwMe();
435 if( fs_cnt_second
== mntInfo
.size())
441 mntInfo
.resize(fs_cnt_second
); // trim the vector to what we actually need
448 Syslog::warning("SecTranslocate: mount table is growing very quickly");
454 /* Given the directory where app translocations go for this user, the path to the app to be translocated
455 and an optional destination mountpoint path. Check the mount table to see if a mount point already
456 user, for this app. If a destMountPoint is provided, make sure it is for this user, and that
457 exists for this the mountpoint found in the mount table is the same as the one requested */
458 static string
mountExistsForUser(const string
&translationDirForUser
, const string
&originalPath
, const string
&destMountPoint
)
460 string result
; // start empty
462 if(!destMountPoint
.empty())
464 /* Validate that destMountPoint path is well formed and for this user
465 well formed means it is === translationDirForUser/<1 directory>
467 vector
<string
> splitDestMount
= splitPath(destMountPoint
);
469 if(splitDestMount
.size() < 2) //translationDirForUser is never /
471 Syslog::warning("SecTranslocate: invalid destination mount point: %s",
472 destMountPoint
.c_str());
473 UnixError::throwMe(EINVAL
);
476 splitDestMount
.pop_back(); // knock off one directory
478 string destBaseDir
= joinPath(splitDestMount
)+"/"; //translationDirForUser has a / at the end
480 if (translationDirForUser
!= destBaseDir
)
482 Syslog::warning("SecTranslocate: invalid destination mount point for user\n\tExpected: %s\n\tRequested: %s",
483 translationDirForUser
.c_str(),
484 destBaseDir
.c_str());
485 /* requested destination isn't valid for the user */
486 UnixError::throwMe(EINVAL
);
490 vector
<struct statfs
> mntbuf
= getMountTableSnapshot();
492 for (auto &i
: mntbuf
)
494 string mountOnName
= i
.f_mntonname
;
495 size_t lastNonSlashPos
= mountOnName
.length() - 1; //start at the end of the string
497 /* find the last position of the last non slash character */
498 for(; lastNonSlashPos
!= 0 && mountOnName
[lastNonSlashPos
] == '/' ; lastNonSlashPos
--);
500 /* we want an exact match for originalPath and a prefix match for translationDirForUser
501 also make sure that this is a nullfs mount and that the mount point name is longer than the
502 translation directory with something other than / */
504 if (i
.f_mntfromname
== originalPath
&& //mount is for the requested path
505 strcmp(i
.f_fstypename
, NULLFS_FSTYPE
) == 0 && // mount is a nullfs mount
506 lastNonSlashPos
> translationDirForUser
.length()-1 && // no shenanigans, there must be more directory here than just the translation dir
507 strncmp(i
.f_mntonname
, translationDirForUser
.c_str(), translationDirForUser
.length()) == 0) //mount is inside the translocation dir
509 if(!destMountPoint
.empty())
511 if (mountOnName
!= destMountPoint
)
513 /* a mount exists for this path, but its not the one requested */
514 Syslog::warning("SecTranslocate: requested destination doesn't match existing\n\tExpected: %s\n\tRequested: %s",
516 destMountPoint
.c_str());
517 UnixError::throwMe(EEXIST
);
520 result
= mountOnName
;
528 /* Given what we think is a valid mountpoint, perform a sanity check, and clean up if we are wrong */
529 static void validateMountpoint(const string
&mountpoint
, bool owned
)
534 3. is not already a mountpoint
535 4. is an absolute path
538 bool isMount
= false;
542 /* first make sure this is a directory and that it is empty
543 (it could be dangerous to mount over a directory that contains something,
544 unfortunately this is still racy, and mount() is path based so we can't lock
545 down the directory until the mount succeeds (lock down is because of the entitlement
547 DIR* dir
= opendir(mountpoint
.c_str());
553 Syslog::warning("SecTranslocate: mountpoint is not a directory or doesn't exist: %s",
555 UnixError::throwMe(error
);
561 struct dirent dirbuf
;
564 while(((err
= readdir_r(dir
, &dirbuf
, &d
)) == 0) &&
567 /* skip . and .. but break if there is more than that */
580 Syslog::warning("SecTranslocate: error while checking that mountpoint is empty");
581 UnixError::throwMe(error
);
586 Syslog::warning("Sectranslocate: mountpoint is not empty: %s",
588 UnixError::throwMe(EBUSY
);
591 /* now check that the path is not a mountpoint */
592 ExtendedAutoFileDesc
fd(mountpoint
);
594 if(!fd
.pathIsAbsolute())
596 Syslog::warning("SecTranslocate: mountpoint isn't fully resolved\n\tExpected: %s\n\tActual: %s",
597 fd
.getRealPath().c_str(),
599 UnixError::throwMe(EINVAL
);
602 isMount
= fd
.isMountPoint();
606 Syslog::warning("SecTranslocate:Translocation failed, new mountpoint is already a mountpoint (%s)",
608 UnixError::throwMe(EINVAL
);
621 rmdir(mountpoint
.c_str());
623 /* Already logged the else case above */
627 Syslog::warning("SecTranslocate: unexpected file detected at mountpoint location (%s). Deleting.",
629 unlink(mountpoint
.c_str());
633 rethrow_exception(current_exception());
637 /* Create and validate the directory that we should mount at but don't create the mount yet */
638 static string
makeNewMountpoint(const string
&translationDir
)
640 AutoFileDesc
fd(getFDForDirectory(translationDir
));
642 string uuid
= makeUUID();
644 UnixError::check(mkdirat(fd
, uuid
.c_str(), 0500));
646 string mountpoint
= translationDir
+uuid
;
648 validateMountpoint(mountpoint
);
653 /* If the original path has mountpoint quarantine info, apply it to the new mountpoint*/
654 static void setMountPointQuarantineIfNecessary(const string
&mountPoint
, const string
&originalPath
)
656 struct statfs sfsbuf
;
659 UnixError::check(statfs(originalPath
.c_str(), &sfsbuf
));
660 qtn_file_t original_attr
= qtn_file_alloc();
662 if (original_attr
!= NULL
)
664 if (qtn_file_init_with_mount_point(original_attr
, sfsbuf
.f_mntonname
) == 0)
666 error
= qtn_file_apply_to_mount_point(original_attr
, mountPoint
.c_str());
668 qtn_file_free(original_attr
);
677 Syslog::warning("SecTranslocate: Failed to apply quarantine information\n\tMountpoint: %s\n\tOriginal Path: %s",
679 originalPath
.c_str());
680 UnixError::throwMe(error
);
684 /* Given the path to a new mountpoint and the original path to translocate, calculate the path
685 to the desired app in the new mountpoint, and sanity check that calculation */
686 static string
newAppPath (const string
&mountPoint
, const TranslocationPath
&originalPath
)
688 vector
<string
> original
= splitPath(originalPath
.getPathToTranslocate());
690 if (original
.size() == 0)
692 Syslog::error("SecTranslocate: Invalid originalPath: %s", originalPath
.getPathToTranslocate().c_str());
693 UnixError::throwMe(EINVAL
);
696 string midPath
= mountPoint
+"/d";
697 string outPath
= originalPath
.getTranslocatedPathToOriginalPath(midPath
+"/"+original
.back());
699 /* ExtendedAutoFileDesc will throw if one of these doesn't exist or isn't accessible */
700 ExtendedAutoFileDesc
mountFd(mountPoint
);
701 ExtendedAutoFileDesc
midFd(midPath
);
702 ExtendedAutoFileDesc
outFd(outPath
);
704 if(!outFd
.isFileSystemType(NULLFS_FSTYPE
) ||
705 !mountFd
.isFileSystemType(NULLFS_FSTYPE
) ||
706 !midFd
.isFileSystemType(NULLFS_FSTYPE
))
708 Syslog::warning("SecTranslocate::App exists at expected translocation path (%s) but isn't a nullfs mount (%s)",
710 outFd
.getFsType().c_str());
711 UnixError::throwMe(EINVAL
);
714 if(!outFd
.pathIsAbsolute() ||
715 !mountFd
.pathIsAbsolute() ||
716 !midFd
.pathIsAbsolute() )
718 Syslog::warning("SecTranslocate::App path isn't resolved\n\tGot: %s\n\tExpected: %s",
719 outFd
.getRealPath().c_str(),
721 UnixError::throwMe(EINVAL
);
724 fsid_t outFsid
= outFd
.getFsid();
725 fsid_t midFsid
= midFd
.getFsid();
726 fsid_t mountFsid
= mountFd
.getFsid();
728 /* different fsids mean that there is more than one volume between the expected mountpoint and the expected app path */
729 if (memcmp(&outFsid
, &midFsid
, sizeof(fsid_t
)) != 0 ||
730 memcmp(&outFsid
, &mountFsid
, sizeof(fsid_t
)) != 0)
732 Syslog::warning("SecTranslocate:: the fsid is not consistent between app, /d/ and mountpoint");
733 UnixError::throwMe(EINVAL
);
736 return outFd
.getRealPath();
739 /* Create an app translocation point given the original path and an optional destination path.
740 note the destination path can only be an outermost path (where the translocation would happen) and not a path to nested code
741 synchronize the process on the dispatch queue. */
742 string
translocatePathForUser(const TranslocationPath
&originalPath
, const string
&destPath
)
745 exception_ptr
exception(0);
751 const string
&toTranslocate
= originalPath
.getPathToTranslocate();
752 string baseDirForUser
= translocationDirForUser(); //throws
753 string destMountPoint
;
754 if(!destPath
.empty())
756 destMountPoint
= getMountpointFromAppPath(destPath
, toTranslocate
); //throws or returns a mountpoint
759 mountpoint
= mountExistsForUser(baseDirForUser
, toTranslocate
, destMountPoint
); //throws, detects invalid destMountPoint string
761 if (!mountpoint
.empty())
763 /* A mount point exists already so bail*/
764 newPath
= newAppPath(mountpoint
, originalPath
);
765 return newPath
; /* exit the block */
767 if (destMountPoint
.empty())
769 mountpoint
= makeNewMountpoint(baseDirForUser
); //throws
774 AutoFileDesc
fd(getFDForDirectory(destMountPoint
, &owned
)); //throws, makes the directory if it doesn't exist
776 validateMountpoint(destMountPoint
, owned
); //throws
777 mountpoint
= destMountPoint
;
780 UnixError::check(mount(NULLFS_FSTYPE
, mountpoint
.c_str(), MNT_RDONLY
, (void*)toTranslocate
.c_str()));
782 setMountPointQuarantineIfNecessary(mountpoint
, toTranslocate
); //throws
784 newPath
= newAppPath(mountpoint
, originalPath
); //throws
786 if (!destPath
.empty())
788 if (newPath
!= originalPath
.getTranslocatedPathToOriginalPath(destPath
))
790 Syslog::warning("SecTranslocate: created app translocation point did not equal requested app translocation point\n\texpected: %s\n\tcreated: %s",
793 /* the app at originalPath didn't match the one at destPath */
794 UnixError::throwMe(EINVAL
);
797 // log that we created a new mountpoint (we don't log when we are re-using)
798 Syslog::warning("SecTranslocateCreateSecureDirectoryForURL: created %s",
803 exception
= current_exception();
805 if (!mountpoint
.empty())
809 /* try to unmount/delete (best effort)*/
810 unmount(mountpoint
.c_str(), 0);
811 rmdir(mountpoint
.c_str());
816 /* rethrow outside the dispatch block */
819 rethrow_exception(exception
);
825 /* Loop through the directory in the specified user directory and delete any that aren't mountpoints */
826 static void cleanupTranslocationDirForUser(const string
&userDir
)
828 DIR* translocationDir
= opendir(userDir
.c_str());
830 if( translocationDir
)
834 struct dirent
* result
= NULL
;
836 while (readdir_r(translocationDir
, &de
, &result
) == 0 && result
)
838 if(result
->d_type
== DT_DIR
)
840 if (result
->d_name
[0] == '.')
842 if(result
->d_namlen
== 1 ||
843 (result
->d_namlen
== 2 &&
844 result
->d_name
[1] == '.'))
850 string nextDir
= userDir
+string(result
->d_name
);
851 if (0 == statfs(nextDir
.c_str(), &sfbuf
) &&
852 nextDir
== sfbuf
.f_mntonname
)
854 /* its a mount point so continue */
858 /* not a mountpoint so delete it */
859 if(unlinkat(dirfd(translocationDir
), result
->d_name
, AT_REMOVEDIR
))
861 Syslog::warning("SecTranslocate: failed to delete directory during cleanup (error %d)\n\tUser Dir: %s\n\tDir to delete: %s",
868 closedir(translocationDir
);
872 /* Unmount and delete a directory */
873 static int removeMountPoint(const string
&mountpoint
, bool force
)
877 if (0 == unmount(mountpoint
.c_str(), force
? MNT_FORCE
: 0) &&
878 0 == rmdir(mountpoint
.c_str()))
880 Syslog::warning("SecTranslocate: removed mountpoint: %s",
886 Syslog::warning("SecTranslocate: failed to unmount/remove mount point (errno: %d): %s",
887 error
, mountpoint
.c_str());
893 /* Destroy the specified translocated path, and clean up the user's translocation directory.
894 It is the caller's responsibility to synchronize the operation on the dispatch queue. */
895 bool destroyTranslocatedPathForUser(const string
&translocatedPath
)
900 1. verify the translocatedPath is for the user
901 2. verify it is a nullfs mountpoint (with app path)
904 5. loop through all the other directories in the app translation directory looking for directories not mounted on and delete them.
907 string baseDirForUser
= translocationDirForUser(); // throws
908 bool shouldUnmount
= false;
909 string translocatedMountpoint
;
911 { //Use a block to get rid of the file descriptor before we try to unmount.
912 ExtendedAutoFileDesc
fd(translocatedPath
);
913 translocatedMountpoint
= fd
.getMountPoint();
915 To support unmount when nested apps end, just make sure that the requested path is on a translocation
916 point for this user, not that they asked for a translocation point to be removed.
918 shouldUnmount
= fd
.isInPrefixDir(baseDirForUser
) && fd
.isFileSystemType(NULLFS_FSTYPE
);
923 error
= removeMountPoint(translocatedMountpoint
);
927 if (!result
&& !error
)
929 Syslog::warning("SecTranslocate: mountpoint does not belong to user(%d): %s",
931 translocatedPath
.c_str());
935 cleanupTranslocationDirForUser(baseDirForUser
);
939 UnixError::throwMe(error
);
945 /* Cleanup any translocation directories for this user that are either mounted from the
946 specified volume or from a volume that doesn't exist anymore. If an empty volumePath
947 is provided this has the effect of only cleaning up translocation points that point
948 to volumes that don't exist anymore.
950 It is the caller's responsibility to synchronize the operation on the dispatch queue.
952 bool destroyTranslocatedPathsForUserOnVolume(const string
&volumePath
)
954 bool cleanupError
= false;
955 string baseDirForUser
= translocationDirForUser();
956 vector
<struct statfs
> mountTable
= getMountTableSnapshot();
957 fsid_t unmountingFsid
;
959 /* passing in an empty volume here will fail to open */
960 ExtendedAutoFileDesc
volume(volumePath
, O_RDONLY
, FileDesc::modeMissingOk
);
964 unmountingFsid
= volume
.getFsid();
967 for (auto &mnt
: mountTable
)
970 we need to look at each translocation mount and check
972 2. does its mntfromname still exist, if it doesn't unmount it
973 3. if it does, is it the same as the volume we are cleaning up?, if so unmount it.
975 if (strcmp(mnt
.f_fstypename
, NULLFS_FSTYPE
) == 0 &&
976 strncmp(mnt
.f_mntonname
, baseDirForUser
.c_str(), baseDirForUser
.length()) == 0)
978 ExtendedAutoFileDesc
volumeToCheck(mnt
.f_mntfromname
, O_RDONLY
, FileDesc::modeMissingOk
);
980 if (!volumeToCheck
.isOpen())
982 // In this case we are trying to unmount a translocation point that points to nothing. Force it.
983 // Not forcing it currently hangs in UBC cleanup.
984 (void)removeMountPoint(mnt
.f_mntonname
, true);
986 else if (volume
.isOpen())
988 fsid_t toCheckFsid
= volumeToCheck
.getFsid();
989 if( memcmp(&unmountingFsid
, &toCheckFsid
, sizeof(fsid_t
)) == 0)
991 if(removeMountPoint(mnt
.f_mntonname
) != 0)
1000 return !cleanupError
;
1002 /* This is intended to be used periodically to clean up translocation points that aren't used anymore */
1003 void tryToDestroyUnusedTranslocationMounts()
1005 vector
<struct statfs
> mountTable
= getMountTableSnapshot();
1006 string baseDirForUser
= translocationDirForUser();
1008 for (auto &mnt
: mountTable
)
1010 if (strcmp(mnt
.f_fstypename
, NULLFS_FSTYPE
) == 0 &&
1011 strncmp(mnt
.f_mntonname
, baseDirForUser
.c_str(), baseDirForUser
.length()) == 0)
1013 ExtendedAutoFileDesc
volumeToCheck(mnt
.f_mntfromname
, O_RDONLY
, FileDesc::modeMissingOk
);
1015 // Try to destroy the mount point. If the mirroed volume (volumeToCheck) isn't open then force it.
1016 // Not forcing it currently hangs in UBC cleanup.
1017 (void)removeMountPoint(mnt
.f_mntonname
, !volumeToCheck
.isOpen());
1022 } //namespace SecTranslocate
1023 }// namespace Security