dyld-655.1.1.tar.gz
[apple/dyld.git] / dyld3 / ClosureFileSystemPhysical.cpp
1 /*
2 * Copyright (c) 2017 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include "ClosureFileSystemPhysical.h"
25
26 #include <fcntl.h>
27 #include <stdlib.h>
28 #include <sandbox.h>
29 #include <sandbox/private.h>
30 #include <unistd.h>
31 #include <sys/errno.h>
32 #include <sys/mman.h>
33 #include <sys/stat.h>
34
35 using dyld3::closure::FileSystemPhysical;
36
37 bool FileSystemPhysical::getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const {
38 bool success = false;
39 int fd = ::open(possiblePath, O_RDONLY);
40 if ( fd != -1 ) {
41 success = fcntl(fd, F_GETPATH, realPath) == 0;
42 ::close(fd);
43 }
44 if (success)
45 return success;
46 realpath(possiblePath, realPath);
47 int realpathErrno = errno;
48 // If realpath() resolves to a path which does not exist on disk, errno is set to ENOENT
49 return (realpathErrno == ENOENT) || (realpathErrno == 0);
50 }
51
52 static bool sandboxBlocked(const char* path, const char* kind)
53 {
54 #if TARGET_IPHONE_SIMULATOR
55 // sandbox calls not yet supported in dyld_sim
56 return false;
57 #else
58 sandbox_filter_type filter = (sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT);
59 return ( sandbox_check(getpid(), kind, filter, path) > 0 );
60 #endif
61 }
62
63 static bool sandboxBlockedMmap(const char* path)
64 {
65 return sandboxBlocked(path, "file-map-executable");
66 }
67
68 static bool sandboxBlockedOpen(const char* path)
69 {
70 return sandboxBlocked(path, "file-read-data");
71 }
72
73 static bool sandboxBlockedStat(const char* path)
74 {
75 return sandboxBlocked(path, "file-read-metadata");
76 }
77
78 // Returns true on success. If an error occurs the given callback will be called with the reason.
79 // On success, info is filled with info about the loaded file. If the path supplied includes a symlink,
80 // the supplier realerPath is filled in with the real path of the file, otherwise it is set to the empty string.
81 bool FileSystemPhysical::loadFile(const char* path, LoadedFileInfo& info, char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const {
82 // open file
83 const char* originalPath = path;
84 char altPath[PATH_MAX];
85 int fd = -1;
86 if ( _fileSystemPrefix != nullptr ) {
87 strlcpy(altPath, _fileSystemPrefix, PATH_MAX);
88 strlcat(altPath, path, PATH_MAX);
89 fd = ::open(altPath, O_RDONLY, 0);
90 if ( fd != -1 )
91 path = altPath;
92 }
93 if ( fd == -1 ) {
94 fd = ::open(path, O_RDONLY, 0);
95 if ( fd == -1 ) {
96 int openErrno = errno;
97 if ( (openErrno == EPERM) && sandboxBlockedOpen(path) )
98 error("file system sandbox blocked open(\"%s\", O_RDONLY)", path);
99 else if ( (openErrno != ENOENT) && (openErrno != ENOTDIR) )
100 error("open(\"%s\", O_RDONLY) failed with errno=%d", path, openErrno);
101 return false;
102 }
103 }
104
105 // Get the realpath of the file if it is a symlink
106 if ( fcntl(fd, F_GETPATH, realerPath) == 0 ) {
107 // Don't set the realpath if it is just the same as the regular path
108 if ( strcmp(originalPath, realerPath) == 0 )
109 realerPath[0] = '\0';
110 } else {
111 error("Could not get real path for \"%s\"\n", path);
112 ::close(fd);
113 return false;
114 }
115
116 // get file info
117 struct stat statBuf;
118 #if TARGET_IPHONE_SIMULATOR
119 if ( ::stat(path, &statBuf) != 0 ) {
120 #else
121 if ( ::fstat(fd, &statBuf) != 0 ) {
122 #endif
123 int statErr = errno;
124 if ( (statErr == EPERM) && sandboxBlockedStat(path) )
125 error("file system sandbox blocked stat(\"%s\")", path);
126 else
127 error("stat(\"%s\") failed with errno=%d", path, errno);
128 ::close(fd);
129 return false;
130 }
131
132 // only regular files can be loaded
133 if ( !S_ISREG(statBuf.st_mode) ) {
134 error("not a file for %s", path);
135 ::close(fd);
136 return false;
137 }
138
139 // mach-o files must be at list one page in size
140 if ( statBuf.st_size < 4096 ) {
141 error("file too short %s", path);
142 ::close(fd);
143 return false;
144 }
145
146 info.fileContent = nullptr;
147 info.fileContentLen = statBuf.st_size;
148 info.sliceOffset = 0;
149 info.sliceLen = statBuf.st_size;
150 info.inode = statBuf.st_ino;
151 info.mtime = statBuf.st_mtime;
152 info.path = originalPath;
153
154 // mmap() whole file
155 void* wholeFile = ::mmap(nullptr, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE|MAP_RESILIENT_CODESIGN, fd, 0);
156 if ( wholeFile == MAP_FAILED ) {
157 int mmapErr = errno;
158 if ( mmapErr == EPERM ) {
159 if ( sandboxBlockedMmap(path) )
160 error("file system sandbox blocked mmap() of '%s'", path);
161 else
162 error("code signing blocked mmap() of '%s'", path);
163 }
164 else {
165 error("mmap() failed with errno=%d for %s", errno, path);
166 }
167 ::close(fd);
168 return false;
169 }
170 info.fileContent = wholeFile;
171
172 // Set unmap as the unload method.
173 info.unload = [](const LoadedFileInfo& info) {
174 ::munmap((void*)info.fileContent, (size_t)info.fileContentLen);
175 };
176
177 ::close(fd);
178 return true;
179 }
180
181 void FileSystemPhysical::unloadFile(const LoadedFileInfo& info) const {
182 if (info.unload)
183 info.unload(info);
184 }
185
186 void FileSystemPhysical::unloadPartialFile(LoadedFileInfo& info, uint64_t keepStartOffset, uint64_t keepLength) const {
187 // Unmap from 0..keepStartOffset and (keepStartOffset+keepLength)..info.fileContentLen
188 if (keepStartOffset)
189 ::munmap((void*)info.fileContent, (size_t)keepStartOffset);
190 if ((keepStartOffset + keepLength) != info.fileContentLen) {
191 // Round up to page alignment
192 keepLength = (keepLength + PAGE_SIZE - 1) & (-PAGE_SIZE);
193 ::munmap((void*)((char*)info.fileContent + keepStartOffset + keepLength), (size_t)(info.fileContentLen - (keepStartOffset + keepLength)));
194 }
195 info.fileContent = (const void*)((char*)info.fileContent + keepStartOffset);
196 info.fileContentLen = keepLength;
197 }
198
199 bool FileSystemPhysical::fileExists(const char* path, uint64_t* inode, uint64_t* mtime, bool* issetuid) const {
200 struct stat statBuf;
201 if ( _fileSystemPrefix != nullptr ) {
202 char altPath[PATH_MAX];
203 strlcpy(altPath, _fileSystemPrefix, PATH_MAX);
204 strlcat(altPath, path, PATH_MAX);
205 if ( ::stat(altPath, &statBuf) == 0 ) {
206 if (inode)
207 *inode = statBuf.st_ino;
208 if (mtime)
209 *mtime = statBuf.st_mtime;
210 if (issetuid)
211 *issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID));
212 return true;
213 }
214 }
215 if ( ::stat(path, &statBuf) != 0 )
216 return false;
217 if (inode)
218 *inode = statBuf.st_ino;
219 if (mtime)
220 *mtime = statBuf.st_mtime;
221 if (issetuid)
222 *issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID));
223 return true;
224 }