]>
Commit | Line | Data |
---|---|---|
f427ee49 A |
1 | /* |
2 | * Copyright (c) 2020 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 <TargetConditionals.h> | |
25 | ||
26 | #if TARGET_OS_OSX && !defined(__i386__) | |
27 | /* | |
28 | * Support for shimming calls to open() to the SystemVersion plist on macOS for older | |
29 | * binaries. This code is only built into libsyscall_dynamic. | |
30 | */ | |
31 | ||
32 | #include <fcntl.h> | |
33 | #include <stdbool.h> | |
34 | #include <strings.h> | |
35 | #include <sys/errno.h> | |
36 | #include <sys/param.h> | |
37 | #include <unistd.h> | |
38 | ||
39 | #include "system-version-compat-support.h" | |
40 | ||
41 | #define PLAT_PREFIX_IOS "iOS" | |
42 | #define PLAT_PREFIX_MACOS "" | |
43 | ||
44 | #define COMPAT_SUFFIX_MACOS "Compat" | |
45 | #define COMPAT_SUFFIX_IOS "" | |
46 | ||
47 | #define SYSTEM_VERSION_PLIST_FILENAME "SystemVersion.plist" | |
48 | #define SYSTEM_VERSION_PLIST_PATH ("/System/Library/CoreServices/" SYSTEM_VERSION_PLIST_FILENAME) | |
49 | ||
50 | #define SYSTEM_VERSION_COMPAT_PLIST_FILENAME(platform_prefix, compat_suffix) (platform_prefix "SystemVersion" compat_suffix ".plist") | |
51 | ||
52 | #define SYSTEM_VERSION_PLIST_FILENAMELEN strlen(SYSTEM_VERSION_PLIST_FILENAME) | |
53 | ||
54 | #define SYSTEM_VERSION_COMPAT_PLIST_FILENAMELEN(platform_prefix, compat_suffix) strlen(SYSTEM_VERSION_COMPAT_PLIST_FILENAME(platform_prefix, compat_suffix)) | |
55 | ||
56 | #define SYSTEM_VERSION_PLIST_PATHLEN strlen(SYSTEM_VERSION_PLIST_PATH) | |
57 | ||
58 | extern system_version_compat_mode_t system_version_compat_mode; | |
59 | ||
60 | /* | |
61 | * This routine determines whether the path specified matches the path of the SystemVersion plist file | |
62 | * we are shimming accesses to. If the file name suffix matches, it's expected we'll call into the | |
63 | * version_compat_open_shim() routine below which will do a full comparison on the expanded path. | |
64 | * | |
65 | * Parameters: orig_path The path suffix that was provided to the open{at} call. | |
66 | * | |
67 | * Returns: true if the path suffix matches the SystemVersion plist path we're shimming | |
68 | * false otherwise | |
69 | */ | |
70 | __attribute__((visibility("hidden"))) | |
71 | bool | |
72 | _system_version_compat_check_path_suffix(const char *orig_path) | |
73 | { | |
74 | size_t path_str_len = strnlen(orig_path, MAXPATHLEN); | |
75 | /* | |
76 | * If the length of the filename we're opening is shorter than | |
77 | * SYSTEM_VERSION_PLIST_FILENAME, bail. | |
78 | */ | |
79 | if (path_str_len < SYSTEM_VERSION_PLIST_FILENAMELEN) { | |
80 | return false; | |
81 | } | |
82 | ||
83 | /* If the path we're accessing doesn't end in SYSTEM_VERSION_PLIST_FILENAME, bail. */ | |
84 | if (strncmp(&orig_path[path_str_len - SYSTEM_VERSION_PLIST_FILENAMELEN], SYSTEM_VERSION_PLIST_FILENAME, | |
85 | SYSTEM_VERSION_PLIST_FILENAMELEN) != 0) { | |
86 | return false; | |
87 | } | |
88 | ||
89 | /* If modifying the path specified would exceed MAXPATHLEN, bail */ | |
90 | if (path_str_len == MAXPATHLEN) { | |
91 | return false; | |
92 | } | |
93 | ||
94 | size_t compat_len = (system_version_compat_mode == SYSTEM_VERSION_COMPAT_MODE_IOS) ? SYSTEM_VERSION_COMPAT_PLIST_FILENAMELEN(PLAT_PREFIX_IOS, COMPAT_SUFFIX_IOS) : SYSTEM_VERSION_COMPAT_PLIST_FILENAMELEN(PLAT_PREFIX_MACOS, COMPAT_SUFFIX_MACOS); | |
95 | if ((compat_len - SYSTEM_VERSION_PLIST_FILENAMELEN) > (MAXPATHLEN - path_str_len - 1)) { | |
96 | return false; | |
97 | } | |
98 | ||
99 | /* Indicate that we should */ | |
100 | return true; | |
101 | } | |
102 | ||
103 | /* | |
104 | * This routine determines whether we are trying to open the SystemVersion plist at SYSTEM_VERSION_PLIST_PATH. | |
105 | * It's only used on targets that have the compatibility shim enabled (mainly older binaries). | |
106 | * | |
107 | * Note that this routine should * ABSOLUTELY NOT * be used as a general shim for accesses at all paths. We replace | |
108 | * what the developer generally expected to be one system call with multiple additional system calls. We're ok | |
109 | * with doing this here because we only do it for calls to open against files that match this very specific pattern | |
110 | * (named SystemVersion.plist), but doing so for all calls to open could result in various issues. Specifically it's | |
111 | * difficult to ensure the same cancellation semantics (around EINTR etc) that developers generally expect when replacing | |
112 | * a single system call with multiple. | |
113 | * | |
114 | * This routine should return with the same semantics as the general open system calls that it is shimming - specifically | |
115 | * it should leave errno and the return value matching what developers expect. | |
116 | * | |
117 | * It's expected that _version_compat_check_path_suffix() above was called prior to this call and returned true. | |
118 | * | |
119 | * We take the close, open and fcntl syscalls as parameters to make sure the variant we call matches the original call | |
120 | * to open{at}. | |
121 | * | |
122 | * Parameters: opened_fd The file descriptor that was opened in the original open{at} call | |
123 | * openat_fd The file descriptor passed to the original openat call (only used when use_openat is true) | |
124 | * orig_path The original path suffix passed to open{at} | |
125 | * oflag The original oflag passed to open{at} | |
126 | * mode The original mode passed to open{at} | |
127 | * close_syscall The appropriate syscall to use for closing file descriptors | |
128 | * open_syscall The syscall that should be used for a new call to open. | |
129 | * fctnl_syscall The appopriate syscall to use for fcntl. | |
130 | * | |
131 | * Returns: The original file descriptor if the open{at} access wasn't to SYSTEM_VERSION_PLIST_PATH | |
132 | * A new file descriptor (with the original closed) if the expanded path matches SYSTEM_VERSION_PLIST_PATH | |
133 | * The original file descriptor if the full path suffix does not match SYSTEM_VERSION_PLIST_PATH | |
134 | * -1 (with errno set to EINTR) if the new open or fcntl calls received EINTR (with all new fds closed) | |
135 | */ | |
136 | __attribute__((visibility("hidden"))) | |
137 | int | |
138 | _system_version_compat_open_shim(int opened_fd, int openat_fd, const char *orig_path, int oflag, mode_t mode, | |
139 | int (*close_syscall)(int), int (*open_syscall)(const char *, int, mode_t), | |
140 | int (*openat_syscall)(int, const char *, int, mode_t), | |
141 | int (*fcntl_syscall)(int, int, long)) | |
142 | { | |
143 | /* stash the errno from the original open{at} call */ | |
144 | int stashed_errno = errno; | |
145 | char new_path[MAXPATHLEN]; | |
146 | size_t path_str_len = strnlen(orig_path, sizeof(new_path)); | |
147 | ||
148 | /* Resolve the full path of the file we've opened */ | |
149 | if (fcntl_syscall(opened_fd, F_GETPATH, new_path)) { | |
150 | if (errno == EINTR) { | |
151 | /* If we got EINTR, we close the file that was opened and return -1 & EINTR */ | |
152 | close_syscall(opened_fd); | |
153 | errno = EINTR; | |
154 | return -1; | |
155 | } else { | |
156 | /* otherwise we return the original file descriptor that was requested */ | |
157 | errno = stashed_errno; | |
158 | return opened_fd; | |
159 | } | |
160 | } | |
161 | ||
162 | /* Check to see whether the path matches SYSTEM_VERSION_PLIST_PATH */ | |
163 | size_t newpathlen = strnlen(new_path, MAXPATHLEN); | |
164 | if (newpathlen != SYSTEM_VERSION_PLIST_PATHLEN) { | |
165 | errno = stashed_errno; | |
166 | return opened_fd; | |
167 | } | |
168 | ||
169 | if (strncmp(new_path, SYSTEM_VERSION_PLIST_PATH, SYSTEM_VERSION_PLIST_PATHLEN) != 0) { | |
170 | errno = stashed_errno; | |
171 | return opened_fd; | |
172 | } | |
173 | ||
174 | new_path[0] = '\0'; | |
175 | ||
176 | /* | |
177 | * It looks like we're trying to access the SystemVersion plist. Let's try to open | |
178 | * the compatibility plist and return that instead if it exists. | |
179 | */ | |
180 | size_t prefix_str_len = path_str_len - SYSTEM_VERSION_PLIST_FILENAMELEN; | |
181 | strlcpy(new_path, orig_path, (prefix_str_len + 1)); | |
182 | if (system_version_compat_mode == SYSTEM_VERSION_COMPAT_MODE_IOS) { | |
183 | strlcat(new_path, SYSTEM_VERSION_COMPAT_PLIST_FILENAME(PLAT_PREFIX_IOS, COMPAT_SUFFIX_IOS), MAXPATHLEN); | |
184 | } else { | |
185 | strlcat(new_path, SYSTEM_VERSION_COMPAT_PLIST_FILENAME(PLAT_PREFIX_MACOS, COMPAT_SUFFIX_MACOS), MAXPATHLEN); | |
186 | } | |
187 | ||
188 | int new_fd = -1; | |
189 | if (openat_syscall != NULL) { | |
190 | new_fd = openat_syscall(openat_fd, new_path, oflag, mode); | |
191 | } else { | |
192 | new_fd = open_syscall(new_path, oflag, mode); | |
193 | } | |
194 | if ((new_fd == -1) && (errno == ENOENT)) { | |
195 | /* The file doesn't exist, so return the original fd and errno. */ | |
196 | errno = stashed_errno; | |
197 | return opened_fd; | |
198 | } | |
199 | ||
200 | /* | |
201 | * Otherwise we close the first file we opened and populate errno | |
202 | * with errno from the call to open{at}. (Note this covers the EINTR | |
203 | * case and other failures). | |
204 | */ | |
205 | stashed_errno = errno; | |
206 | close_syscall(opened_fd); | |
207 | errno = stashed_errno; | |
208 | return new_fd; | |
209 | } | |
210 | ||
211 | #endif /* TARGET_OS_OSX && !defined(__i386__) */ |