dyld-832.7.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 <TargetConditionals.h>
27
28 #include <string.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <sys/errno.h>
33 #include <sys/mman.h>
34 #include <sys/stat.h>
35 #include <mach/mach.h>
36 #if !TARGET_OS_SIMULATOR && !TARGET_OS_DRIVERKIT
37 #include <sandbox.h>
38 #include <sandbox/private.h>
39 #endif
40 #include <TargetConditionals.h>
41 #include "MachOFile.h"
42 #include "MachOAnalyzer.h"
43
44 using dyld3::closure::FileSystemPhysical;
45
46 bool FileSystemPhysical::getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const {
47 __block bool success = false;
48 // first pass: open file and ask kernel for canonical path
49 forEachPath(possiblePath, ^(const char* aPath, unsigned prefixLen, bool& stop) {
50 int fd = dyld3::open(aPath, O_RDONLY, 0);
51 if ( fd != -1 ) {
52 char tempPath[MAXPATHLEN];
53 success = (fcntl(fd, F_GETPATH, tempPath) == 0);
54 ::close(fd);
55 if ( success ) {
56 // if prefix was used, remove it
57 strcpy(realPath, &tempPath[prefixLen]);
58 }
59 stop = true;
60 }
61 });
62 if (success)
63 return success;
64
65 // second pass: file does not exist but may be a symlink to a non-existent file
66 // This is only for use on-device on platforms where dylibs are removed
67 if ( _overlayPath == nullptr && _rootPath == nullptr ) {
68 realpath(possiblePath, realPath);
69 int realpathErrno = errno;
70 // If realpath() resolves to a path which does not exist on disk, errno is set to ENOENT
71 success = (realpathErrno == ENOENT) || (realpathErrno == 0);
72 }
73 return success;
74 }
75
76 static bool sandboxBlocked(const char* path, const char* kind)
77 {
78 #if TARGET_OS_SIMULATOR || TARGET_OS_DRIVERKIT
79 // sandbox calls not yet supported in dyld_sim
80 return false;
81 #else
82 sandbox_filter_type filter = (sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT);
83 return ( sandbox_check(getpid(), kind, filter, path) > 0 );
84 #endif
85 }
86
87 static bool sandboxBlockedMmap(const char* path)
88 {
89 return sandboxBlocked(path, "file-map-executable");
90 }
91
92 static bool sandboxBlockedOpen(const char* path)
93 {
94 return sandboxBlocked(path, "file-read-data");
95 }
96
97 static bool sandboxBlockedStat(const char* path)
98 {
99 return sandboxBlocked(path, "file-read-metadata");
100 }
101
102 void FileSystemPhysical::forEachPath(const char* path, void (^handler)(const char* fullPath, unsigned prefixLen, bool& stop)) const
103 {
104 bool stop = false;
105 char altPath[PATH_MAX];
106 if ( _overlayPath != nullptr ) {
107 strlcpy(altPath, _overlayPath, PATH_MAX);
108 strlcat(altPath, path, PATH_MAX);
109 handler(altPath, (unsigned)strlen(_overlayPath), stop);
110 if ( stop )
111 return;
112 }
113 if ( _rootPath != nullptr ) {
114 strlcpy(altPath, _rootPath, PATH_MAX);
115 if ( path[0] != '/' )
116 strlcat(altPath, "/", PATH_MAX);
117 strlcat(altPath, path, PATH_MAX);
118 handler(altPath, (unsigned)strlen(_rootPath), stop);
119 if ( stop )
120 return;
121 }
122 else {
123 handler(path, 0, stop);
124 }
125 }
126
127 static bool isFileRelativePath(const char* path)
128 {
129 if ( path[0] == '/' )
130 return false;
131 if ( path[0] != '.' )
132 return true;
133 if ( path[1] == '/' )
134 return true;
135 if ( (path[1] == '.') && (path[2] == '/') )
136 return true;
137 return false;
138 }
139
140 // Returns true on success. If an error occurs the given callback will be called with the reason.
141 // On success, info is filled with info about the loaded file. If the path supplied includes a symlink,
142 // the supplier realerPath is filled in with the real path of the file, otherwise it is set to the empty string.
143 bool FileSystemPhysical::loadFile(const char* path, LoadedFileInfo& info, char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const {
144 if ( !_allowRelativePaths && isFileRelativePath(path) ) {
145 error("relative file paths not allowed '%s'", path);
146 return false;
147 }
148 // open file
149 __block int fd;
150 __block struct stat statBuf;
151 forEachPath(path, ^(const char* aPath, unsigned prefixLen, bool& stop) {
152 fd = dyld3::open(aPath, O_RDONLY, 0);
153 if ( fd == -1 ) {
154 int openErrno = errno;
155 if ( (openErrno == EPERM) && sandboxBlockedOpen(path) )
156 error("file system sandbox blocked open(\"%s\", O_RDONLY)", path);
157 else if ( (openErrno != ENOENT) && (openErrno != ENOTDIR) )
158 error("open(\"%s\", O_RDONLY) failed with errno=%d", path, openErrno);
159 }
160 else {
161 // get file info
162 #if TARGET_OS_SIMULATOR
163 if ( ::stat(aPath, &statBuf) != 0 ) {
164 #else
165 if ( ::fstat(fd, &statBuf) != 0 ) {
166 #endif
167 int statErr = errno;
168 if ( (statErr == EPERM) && sandboxBlockedStat(path) )
169 error("file system sandbox blocked stat(\"%s\")", path);
170 else
171 error("stat(\"%s\") failed with errno=%d", path, errno);
172 ::close(fd);
173 fd = -1;
174 }
175 else {
176 // Get the realpath of the file if it is a symlink
177 char tempPath[MAXPATHLEN];
178 if ( fcntl(fd, F_GETPATH, tempPath) == 0 ) {
179 const char* realPathWithin = &tempPath[prefixLen];
180 // Don't set the realpath if it is just the same as the regular path
181 if ( strcmp(path, realPathWithin) == 0 ) {
182 // zero out realerPath if path is fine as-is
183 // <rdar://45018392> don't trash input 'path' if realerPath is same buffer as path
184 if ( realerPath != path )
185 realerPath[0] = '\0';
186 }
187 else
188 strcpy(realerPath, realPathWithin);
189 stop = true;
190 }
191 else {
192 error("Could not get real path for \"%s\"\n", path);
193 ::close(fd);
194 fd = -1;
195 }
196 }
197 }
198 });
199 if ( fd == -1 )
200 return false;
201
202 // only regular files can be loaded
203 if ( !S_ISREG(statBuf.st_mode) ) {
204 error("not a file for %s", path);
205 ::close(fd);
206 return false;
207 }
208
209 // mach-o files must be at list one page in size
210 if ( statBuf.st_size < 4096 ) {
211 error("file too short %s", path);
212 ::close(fd);
213 return false;
214 }
215
216 info.fileContent = nullptr;
217 info.fileContentLen = statBuf.st_size;
218 info.sliceOffset = 0;
219 info.sliceLen = statBuf.st_size;
220 info.isOSBinary = false;
221 info.inode = statBuf.st_ino;
222 info.mtime = statBuf.st_mtime;
223 info.path = path;
224
225 // mmap() whole file
226 void* wholeFile = ::mmap(nullptr, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE|MAP_RESILIENT_CODESIGN, fd, 0);
227 if ( wholeFile == MAP_FAILED ) {
228 int mmapErr = errno;
229 if ( mmapErr == EPERM ) {
230 if ( sandboxBlockedMmap(path) )
231 error("file system sandbox blocked mmap() of '%s'", path);
232 else
233 error("code signing blocked mmap() of '%s'", path);
234 }
235 else {
236 error("mmap() failed with errno=%d for %s", errno, path);
237 }
238 ::close(fd);
239 return false;
240 }
241 info.fileContent = wholeFile;
242
243 // if this is an arm64e mach-o or a fat file with an arm64e slice we need to record if it is an OS binary
244 #if TARGET_OS_OSX && __arm64e__
245 const MachOAnalyzer* ma = (MachOAnalyzer*)wholeFile;
246 if ( ma->hasMachOMagic() ) {
247 if ( (ma->cputype == CPU_TYPE_ARM64) && ((ma->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E) ) {
248 if ( ma->isOSBinary(fd, 0, info.fileContentLen) )
249 info.isOSBinary = true;
250 }
251 }
252 else if ( const FatFile* fat = FatFile::isFatFile(wholeFile) ) {
253 Diagnostics diag;
254 fat->forEachSlice(diag, info.fileContentLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) {
255 if ( (sliceCpuType == CPU_TYPE_ARM64) && ((sliceCpuSubType & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E) ) {
256 uint64_t sliceOffset = (uint8_t*)sliceStart-(uint8_t*)wholeFile;
257 const MachOAnalyzer* sliceMA = (MachOAnalyzer*)((uint8_t*)wholeFile + sliceOffset);
258 if ( sliceMA->isOSBinary(fd, sliceOffset, sliceSize) )
259 info.isOSBinary = true;
260 }
261 });
262 }
263 #endif
264 // Set unmap as the unload method.
265 info.unload = [](const LoadedFileInfo& info) {
266 ::munmap((void*)info.fileContent, (size_t)info.fileContentLen);
267 };
268
269 ::close(fd);
270 return true;
271 }
272
273 void FileSystemPhysical::unloadFile(const LoadedFileInfo& info) const {
274 if (info.unload)
275 info.unload(info);
276 }
277
278 void FileSystemPhysical::unloadPartialFile(LoadedFileInfo& info, uint64_t keepStartOffset, uint64_t keepLength) const {
279 // Unmap from 0..keepStartOffset and (keepStartOffset+keepLength)..info.fileContentLen
280 if (keepStartOffset)
281 ::munmap((void*)info.fileContent, (size_t)trunc_page(keepStartOffset));
282 if ((keepStartOffset + keepLength) != info.fileContentLen) {
283 uintptr_t start = round_page((uintptr_t)info.fileContent + keepStartOffset + keepLength);
284 uintptr_t end = (uintptr_t)info.fileContent + info.fileContentLen;
285 ::munmap((void*)start, end - start);
286 }
287 info.fileContent = (const void*)((char*)info.fileContent + keepStartOffset);
288 info.fileContentLen = keepLength;
289 }
290
291 bool FileSystemPhysical::fileExists(const char* path, uint64_t* inode, uint64_t* mtime,
292 bool* issetuid, bool* inodesMatchRuntime) const {
293 __block bool result = false;
294 forEachPath(path, ^(const char* aPath, unsigned prefixLen, bool& stop) {
295 struct stat statBuf;
296 if ( dyld3::stat(aPath, &statBuf) == 0 ) {
297 if (inode)
298 *inode = statBuf.st_ino;
299 if (mtime)
300 *mtime = statBuf.st_mtime;
301 if (issetuid)
302 *issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID));
303 if (inodesMatchRuntime)
304 *inodesMatchRuntime = true;
305 stop = true;
306 result = true;
307 }
308 });
309 return result;
310 }