1/*
2 * Copyright (c) 2021-2022 The Khronos Group Inc.
3 * Copyright (c) 2021-2022 Valve Corporation
4 * Copyright (c) 2021-2022 LunarG, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and/or associated documentation files (the "Materials"), to
8 * deal in the Materials without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Materials, and to permit persons to whom the Materials are
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice(s) and this permission notice shall be included in
14 * all copies or substantial portions of the Materials.
15 *
16 * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 *
20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
23 * USE OR OTHER DEALINGS IN THE MATERIALS.
24 *
25 * Author: Charles Giessen <charles@lunarg.com>
26 */
27
28#include "shim.h"
29
30#include <algorithm>
31
32#if defined(__APPLE__)
33#include <CoreFoundation/CoreFoundation.h>
34#endif
35
36PlatformShim platform_shim;
37extern "C" {
38#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__)
39PlatformShim* get_platform_shim(std::vector<fs::FolderManager>* folders) {
40    platform_shim = PlatformShim(folders);
41    return &platform_shim;
42}
43#elif defined(__APPLE__)
44FRAMEWORK_EXPORT PlatformShim* get_platform_shim(std::vector<fs::FolderManager>* folders) {
45    platform_shim = PlatformShim(folders);
46    return &platform_shim;
47}
48#endif
49
50// Necessary for MacOS function shimming
51#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__)
52#define OPENDIR_FUNC_NAME opendir
53#define READDIR_FUNC_NAME readdir
54#define CLOSEDIR_FUNC_NAME closedir
55#define ACCESS_FUNC_NAME access
56#define FOPEN_FUNC_NAME fopen
57#define DLOPEN_FUNC_NAME dlopen
58#define GETEUID_FUNC_NAME geteuid
59#define GETEGID_FUNC_NAME getegid
60#if defined(HAVE_SECURE_GETENV)
61#define SECURE_GETENV_FUNC_NAME secure_getenv
62#endif
63#if defined(HAVE___SECURE_GETENV)
64#define __SECURE_GETENV_FUNC_NAME __secure_getenv
65#endif
66#elif defined(__APPLE__)
67#define OPENDIR_FUNC_NAME my_opendir
68#define READDIR_FUNC_NAME my_readdir
69#define CLOSEDIR_FUNC_NAME my_closedir
70#define ACCESS_FUNC_NAME my_access
71#define FOPEN_FUNC_NAME my_fopen
72#define DLOPEN_FUNC_NAME my_dlopen
73#define GETEUID_FUNC_NAME my_geteuid
74#define GETEGID_FUNC_NAME my_getegid
75#if !defined(TARGET_OS_IPHONE)
76#if defined(HAVE_SECURE_GETENV)
77#define SECURE_GETENV_FUNC_NAME my_secure_getenv
78#endif
79#if defined(HAVE___SECURE_GETENV)
80#define __SECURE_GETENV_FUNC_NAME my__secure_getenv
81#endif
82#endif
83#endif
84
85using PFN_OPENDIR = DIR* (*)(const char* path_name);
86using PFN_READDIR = struct dirent* (*)(DIR* dir_stream);
87using PFN_CLOSEDIR = int (*)(DIR* dir_stream);
88using PFN_ACCESS = int (*)(const char* pathname, int mode);
89using PFN_FOPEN = FILE* (*)(const char* filename, const char* mode);
90using PFN_DLOPEN = void* (*)(const char* in_filename, int flags);
91using PFN_GETEUID = uid_t (*)(void);
92using PFN_GETEGID = gid_t (*)(void);
93#if defined(HAVE_SECURE_GETENV) || defined(HAVE___SECURE_GETENV)
94using PFN_SEC_GETENV = char* (*)(const char* name);
95#endif
96
97#if defined(__APPLE__)
98#define real_opendir opendir
99#define real_readdir readdir
100#define real_closedir closedir
101#define real_access access
102#define real_fopen fopen
103#define real_dlopen dlopen
104#define real_geteuid geteuid
105#define real_getegid getegid
106#if defined(HAVE_SECURE_GETENV)
107#define real_secure_getenv secure_getenv
108#endif
109#if defined(HAVE___SECURE_GETENV)
110#define real__secure_getenv __secure_getenv
111#endif
112#else
113PFN_OPENDIR real_opendir = nullptr;
114PFN_READDIR real_readdir = nullptr;
115PFN_CLOSEDIR real_closedir = nullptr;
116PFN_ACCESS real_access = nullptr;
117PFN_FOPEN real_fopen = nullptr;
118PFN_DLOPEN real_dlopen = nullptr;
119PFN_GETEUID real_geteuid = nullptr;
120PFN_GETEGID real_getegid = nullptr;
121#if defined(HAVE_SECURE_GETENV)
122PFN_SEC_GETENV real_secure_getenv = nullptr;
123#endif
124#if defined(HAVE___SECURE_GETENV)
125PFN_SEC_GETENV real__secure_getenv = nullptr;
126#endif
127#endif
128
129FRAMEWORK_EXPORT DIR* OPENDIR_FUNC_NAME(const char* path_name) {
130#if !defined(__APPLE__)
131    if (!real_opendir) real_opendir = (PFN_OPENDIR)dlsym(RTLD_NEXT, "opendir");
132#endif
133    if (platform_shim.is_during_destruction) {
134        return real_opendir(path_name);
135    }
136    DIR* dir;
137    if (platform_shim.is_fake_path(path_name)) {
138        auto real_path_name = platform_shim.get_real_path_from_fake_path(fs::path(path_name));
139        dir = real_opendir(real_path_name.c_str());
140        platform_shim.dir_entries.push_back(DirEntry{dir, std::string(path_name), {}, 0, true});
141    } else if (platform_shim.is_known_path(path_name)) {
142        dir = real_opendir(path_name);
143        platform_shim.dir_entries.push_back(DirEntry{dir, std::string(path_name), {}, 0, false});
144    } else {
145        dir = real_opendir(path_name);
146    }
147
148    return dir;
149}
150
151FRAMEWORK_EXPORT struct dirent* READDIR_FUNC_NAME(DIR* dir_stream) {
152#if !defined(__APPLE__)
153    if (!real_readdir) real_readdir = (PFN_READDIR)dlsym(RTLD_NEXT, "readdir");
154#endif
155    if (platform_shim.is_during_destruction) {
156        return real_readdir(dir_stream);
157    }
158    auto it = std::find_if(platform_shim.dir_entries.begin(), platform_shim.dir_entries.end(),
159                           [dir_stream](DirEntry const& entry) { return entry.directory == dir_stream; });
160
161    if (it == platform_shim.dir_entries.end()) {
162        return real_readdir(dir_stream);
163    }
164    // Folder was found but this is the first file to be read from it
165    if (it->current_index == 0) {
166        std::vector<struct dirent*> folder_contents;
167        std::vector<std::string> dirent_filenames;
168        while (true) {
169            struct dirent* dir_entry = real_readdir(dir_stream);
170            if (NULL == dir_entry) {
171                break;
172            }
173            folder_contents.push_back(dir_entry);
174            dirent_filenames.push_back(&dir_entry->d_name[0]);
175        }
176        auto real_path = it->folder_path;
177        if (it->is_fake_path) {
178            real_path = platform_shim.redirection_map.at(it->folder_path).str();
179        }
180        auto filenames = get_folder_contents(platform_shim.folders, real_path);
181
182        // Add the dirent structures in the order they appear in the FolderManager
183        // Ignore anything which wasn't in the FolderManager
184        for (auto const& file : filenames) {
185            for (size_t i = 0; i < dirent_filenames.size(); i++) {
186                if (file == dirent_filenames.at(i)) {
187                    it->contents.push_back(folder_contents.at(i));
188                    break;
189                }
190            }
191        }
192    }
193    if (it->current_index >= it->contents.size()) return nullptr;
194    return it->contents.at(it->current_index++);
195}
196
197FRAMEWORK_EXPORT int CLOSEDIR_FUNC_NAME(DIR* dir_stream) {
198#if !defined(__APPLE__)
199    if (!real_closedir) real_closedir = (PFN_CLOSEDIR)dlsym(RTLD_NEXT, "closedir");
200#endif
201    if (platform_shim.is_during_destruction) {
202        return real_closedir(dir_stream);
203    }
204    auto it = std::find_if(platform_shim.dir_entries.begin(), platform_shim.dir_entries.end(),
205                           [dir_stream](DirEntry const& entry) { return entry.directory == dir_stream; });
206
207    if (it != platform_shim.dir_entries.end()) {
208        platform_shim.dir_entries.erase(it);
209    }
210
211    return real_closedir(dir_stream);
212}
213
214FRAMEWORK_EXPORT int ACCESS_FUNC_NAME(const char* in_pathname, int mode) {
215#if !defined(__APPLE__)
216    if (!real_access) real_access = (PFN_ACCESS)dlsym(RTLD_NEXT, "access");
217#endif
218    fs::path path{in_pathname};
219    if (!path.has_parent_path()) {
220        return real_access(in_pathname, mode);
221    }
222
223    if (platform_shim.is_fake_path(path.parent_path())) {
224        fs::path real_path = platform_shim.get_real_path_from_fake_path(path.parent_path());
225        real_path /= path.filename();
226        return real_access(real_path.c_str(), mode);
227    }
228    return real_access(in_pathname, mode);
229}
230
231FRAMEWORK_EXPORT FILE* FOPEN_FUNC_NAME(const char* in_filename, const char* mode) {
232#if !defined(__APPLE__)
233    if (!real_fopen) real_fopen = (PFN_FOPEN)dlsym(RTLD_NEXT, "fopen");
234#endif
235    fs::path path{in_filename};
236    if (!path.has_parent_path()) {
237        return real_fopen(in_filename, mode);
238    }
239
240    FILE* f_ptr;
241    if (platform_shim.is_fake_path(path.parent_path())) {
242        auto real_path = platform_shim.get_real_path_from_fake_path(path.parent_path()) / path.filename();
243        f_ptr = real_fopen(real_path.c_str(), mode);
244    } else {
245        f_ptr = real_fopen(in_filename, mode);
246    }
247
248    return f_ptr;
249}
250
251FRAMEWORK_EXPORT void* DLOPEN_FUNC_NAME(const char* in_filename, int flags) {
252#if !defined(__APPLE__)
253    if (!real_dlopen) real_dlopen = (PFN_DLOPEN)dlsym(RTLD_NEXT, "dlopen");
254#endif
255
256    if (platform_shim.is_dlopen_redirect_name(in_filename)) {
257        return real_dlopen(platform_shim.dlopen_redirection_map[in_filename].c_str(), flags);
258    }
259    return real_dlopen(in_filename, flags);
260}
261
262FRAMEWORK_EXPORT uid_t GETEUID_FUNC_NAME(void) {
263#if !defined(__APPLE__)
264    if (!real_geteuid) real_geteuid = (PFN_GETEUID)dlsym(RTLD_NEXT, "geteuid");
265#endif
266    if (platform_shim.use_fake_elevation) {
267        // Root on linux is 0, so force pretending like we're root
268        return 0;
269    } else {
270        return real_geteuid();
271    }
272}
273
274FRAMEWORK_EXPORT gid_t GETEGID_FUNC_NAME(void) {
275#if !defined(__APPLE__)
276    if (!real_getegid) real_getegid = (PFN_GETEGID)dlsym(RTLD_NEXT, "getegid");
277#endif
278    if (platform_shim.use_fake_elevation) {
279        // Root on linux is 0, so force pretending like we're root
280        return 0;
281    } else {
282        return real_getegid();
283    }
284}
285
286#if !defined(TARGET_OS_IPHONE)
287#if defined(HAVE_SECURE_GETENV)
288FRAMEWORK_EXPORT char* SECURE_GETENV_FUNC_NAME(const char* name) {
289#if !defined(__APPLE__)
290    if (!real_secure_getenv) real_secure_getenv = (PFN_SEC_GETENV)dlsym(RTLD_NEXT, "secure_getenv");
291#endif
292    if (platform_shim.use_fake_elevation) {
293        return NULL;
294    } else {
295        return real_secure_getenv(name);
296    }
297}
298#endif
299#if defined(HAVE___SECURE_GETENV)
300FRAMEWORK_EXPORT char* __SECURE_GETENV_FUNC_NAME(const char* name) {
301#if !defined(__APPLE__)
302    if (!real__secure_getenv) real__secure_getenv = (PFN_SEC_GETENV)dlsym(RTLD_NEXT, "__secure_getenv");
303#endif
304
305    if (platform_shim.use_fake_elevation) {
306        return NULL;
307    } else {
308        return real__secure_getenv(name);
309    }
310}
311#endif
312#endif
313#if defined(__APPLE__)
314FRAMEWORK_EXPORT CFBundleRef my_CFBundleGetMainBundle() {
315    static CFBundleRef global_bundle{};
316    return reinterpret_cast<CFBundleRef>(&global_bundle);
317}
318FRAMEWORK_EXPORT CFURLRef my_CFBundleCopyResourcesDirectoryURL([[maybe_unused]] CFBundleRef bundle) {
319    static CFURLRef global_url{};
320    return reinterpret_cast<CFURLRef>(&global_url);
321}
322FRAMEWORK_EXPORT Boolean my_CFURLGetFileSystemRepresentation([[maybe_unused]] CFURLRef url,
323                                                             [[maybe_unused]] Boolean resolveAgainstBase, UInt8* buffer,
324                                                             CFIndex maxBufLen) {
325    if (!platform_shim.bundle_contents.empty()) {
326        CFIndex copy_len = (CFIndex)platform_shim.bundle_contents.size();
327        if (copy_len > maxBufLen) {
328            copy_len = maxBufLen;
329        }
330        strncpy(reinterpret_cast<char*>(buffer), platform_shim.bundle_contents.c_str(), copy_len);
331        return TRUE;
332    }
333    return FALSE;
334}
335#endif
336
337/* Shiming functions on apple is limited by the linker prefering to not use functions in the
338 * executable in loaded dylibs. By adding an interposer, we redirect the linker to use our
339 * version of the function over the real one, thus shimming the system function.
340 */
341#if defined(__APPLE__)
342#define MACOS_ATTRIB __attribute__((section("__DATA,__interpose")))
343#define VOIDP_CAST(_func) reinterpret_cast<const void*>(&_func)
344
345struct Interposer {
346    const void* shim_function;
347    const void* underlying_function;
348};
349
350__attribute__((used)) static Interposer _interpose_opendir MACOS_ATTRIB = {VOIDP_CAST(my_opendir), VOIDP_CAST(opendir)};
351// don't intercept readdir as it crashes when using ASAN with macOS
352// __attribute__((used)) static Interposer _interpose_readdir MACOS_ATTRIB = {VOIDP_CAST(my_readdir), VOIDP_CAST(readdir)};
353__attribute__((used)) static Interposer _interpose_closedir MACOS_ATTRIB = {VOIDP_CAST(my_closedir), VOIDP_CAST(closedir)};
354__attribute__((used)) static Interposer _interpose_access MACOS_ATTRIB = {VOIDP_CAST(my_access), VOIDP_CAST(access)};
355__attribute__((used)) static Interposer _interpose_fopen MACOS_ATTRIB = {VOIDP_CAST(my_fopen), VOIDP_CAST(fopen)};
356__attribute__((used)) static Interposer _interpose_dlopen MACOS_ATTRIB = {VOIDP_CAST(my_dlopen), VOIDP_CAST(dlopen)};
357__attribute__((used)) static Interposer _interpose_euid MACOS_ATTRIB = {VOIDP_CAST(my_geteuid), VOIDP_CAST(geteuid)};
358__attribute__((used)) static Interposer _interpose_egid MACOS_ATTRIB = {VOIDP_CAST(my_getegid), VOIDP_CAST(getegid)};
359#if !defined(TARGET_OS_IPHONE)
360#if defined(HAVE_SECURE_GETENV)
361__attribute__((used)) static Interposer _interpose_secure_getenv MACOS_ATTRIB = {VOIDP_CAST(my_secure_getenv),
362                                                                                 VOIDP_CAST(secure_getenv)};
363#endif
364#if defined(HAVE___SECURE_GETENV)
365__attribute__((used)) static Interposer _interpose__secure_getenv MACOS_ATTRIB = {VOIDP_CAST(my__secure_getenv),
366                                                                                  VOIDP_CAST(__secure_getenv)};
367#endif
368#endif
369__attribute__((used)) static Interposer _interpose_CFBundleGetMainBundle MACOS_ATTRIB = {VOIDP_CAST(my_CFBundleGetMainBundle),
370                                                                                         VOIDP_CAST(CFBundleGetMainBundle)};
371__attribute__((used)) static Interposer _interpose_CFBundleCopyResourcesDirectoryURL MACOS_ATTRIB = {
372    VOIDP_CAST(my_CFBundleCopyResourcesDirectoryURL), VOIDP_CAST(CFBundleCopyResourcesDirectoryURL)};
373__attribute__((used)) static Interposer _interpose_CFURLGetFileSystemRepresentation MACOS_ATTRIB = {
374    VOIDP_CAST(my_CFURLGetFileSystemRepresentation), VOIDP_CAST(CFURLGetFileSystemRepresentation)};
375
376#endif
377}  // extern "C"
378