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