diff --git a/CMakeLists.txt b/CMakeLists.txt index b3f8c0b3..768d3002 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,15 +33,28 @@ find_package (Threads REQUIRED) # advance. Use it with discretion. option (BUILD_SHARED_LIBS "Build shared libs" ON) +include(CheckFunctionExists) +check_function_exists(secure_getenv HAVE_SECURE_GETENV) +check_function_exists(__secure_getenv HAVE___SECURE_GETENV) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/loader/icd_cmake_config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/icd_cmake_config.h) + set (OPENCL_ICD_LOADER_SOURCES loader/icd.c - loader/icd_dispatch.c) + loader/icd.h + loader/icd_dispatch.c + loader/icd_dispatch.h + loader/icd_envvars.h + loader/icd_platform.h) if (WIN32) list (APPEND OPENCL_ICD_LOADER_SOURCES loader/windows/icd_windows.c - loader/windows/icd_windows_hkr.c loader/windows/icd_windows_dxgk.c + loader/windows/icd_windows_dxgk.h + loader/windows/icd_windows_envvars.c + loader/windows/icd_windows_hkr.c + loader/windows/icd_windows_hkr.h loader/windows/OpenCL.def loader/windows/OpenCL.rc) # Only add the DXSDK include directory if the environment variable is @@ -53,6 +66,7 @@ if (WIN32) else () list (APPEND OPENCL_ICD_LOADER_SOURCES loader/linux/icd_linux.c + loader/linux/icd_linux_envvars.c loader/linux/icd_exports.map) endif () @@ -108,7 +122,7 @@ endif () include_directories (${OPENCL_ICD_LOADER_HEADERS_DIR}) add_definitions (-DCL_TARGET_OPENCL_VERSION=220) -target_include_directories (OpenCL PRIVATE loader) +target_include_directories (OpenCL PRIVATE ${CMAKE_CURRENT_BINARY_DIR} loader) target_link_libraries (OpenCL ${CMAKE_DL_LIBS}) include (CTest) diff --git a/README.md b/README.md index 27c1de71..ee8499a6 100644 --- a/README.md +++ b/README.md @@ -115,4 +115,13 @@ Please create a GitHub issue to report an issue or ask questions. ## Contributing Contributions to the OpenCL ICD Loader are welcomed and encouraged. -You will be prompted with a one-time "click-through" CLA dialog as part of submitting your pull request or other contribution to GitHub. \ No newline at end of file +You will be prompted with a one-time "click-through" CLA dialog as part of submitting your pull request or other contribution to GitHub. + +## Table of Debug Environment Variables + +The following debug environment variables are available for use with the OpenCL ICD loader: + +| Environment Variable | Behavior | Example Format | +|:---------------------------------:|---------------------|----------------------| +| OCL_ICD_FILENAMES | Specifies a list of additional ICDs to load. The ICDs will be enumerated first, before any ICDs discovered via default mechanisms. | `export OCL_ICD_FILENAMES=libVendorA.so:libVendorB.so`

`set OCL_ICD_FILENAMES=vendor_a.dll;vendor_b.dll` | +| OCL_ICD_VENDORS | On Linux and Android, specifies a directory to scan for ICDs to enumerate in place of the default `/etc/OpenCL/vendors'. | `export OCL_ICD_VENDORS=/my/local/icd/search/path` | diff --git a/loader/icd.c b/loader/icd.c index 8e142c48..ea8306ab 100644 --- a/loader/icd.c +++ b/loader/icd.c @@ -18,6 +18,7 @@ #include "icd.h" #include "icd_dispatch.h" +#include "icd_envvars.h" #include #include @@ -188,6 +189,46 @@ void khrIcdVendorAdd(const char *libraryName) } } +// Get next file or dirname given a string list or registry key path. +// Note: the input string may be modified! +static char *loader_get_next_path(char *path) { + size_t len; + char *next; + + if (path == NULL) return NULL; + next = strchr(path, PATH_SEPARATOR); + if (next == NULL) { + len = strlen(path); + next = path + len; + } else { + *next = '\0'; + next++; + } + + return next; +} + +void khrIcdVendorsEnumerateEnv(void) +{ + char* icdFilenames = khrIcd_secure_getenv("OCL_ICD_FILENAMES"); + char* cur_file = NULL; + char* next_file = NULL; + if (icdFilenames) + { + KHR_ICD_TRACE("Found OCL_ICD_FILENAMES environment variable.\n"); + + next_file = icdFilenames; + while (NULL != next_file && *next_file != '\0') { + cur_file = next_file; + next_file = loader_get_next_path(cur_file); + + khrIcdVendorAdd(cur_file); + } + + khrIcd_free_getenv(icdFilenames); + } +} + void khrIcdContextPropertiesGetPlatform(const cl_context_properties *properties, cl_platform_id *outPlatform) { if (properties == NULL && khrIcdVendors != NULL) diff --git a/loader/icd.h b/loader/icd.h index a1b69692..34751e95 100644 --- a/loader/icd.h +++ b/loader/icd.h @@ -19,6 +19,8 @@ #ifndef _ICD_H_ #define _ICD_H_ +#include "icd_platform.h" + #ifndef CL_USE_DEPRECATED_OPENCL_1_0_APIS #define CL_USE_DEPRECATED_OPENCL_1_0_APIS #endif @@ -34,10 +36,6 @@ #include #include -#ifdef _WIN32 -#include -#endif - /* * type definitions */ @@ -101,6 +99,9 @@ void khrIcdInitialize(void); // n.b, this call is OS-specific void khrIcdOsVendorsEnumerateOnce(void); +// read vendors from environment variables +void khrIcdVendorsEnumerateEnv(void); + // add a vendor's implementation to the list of libraries void khrIcdVendorAdd(const char *libraryName); diff --git a/loader/icd_cmake_config.h.in b/loader/icd_cmake_config.h.in new file mode 100644 index 00000000..3bbc4612 --- /dev/null +++ b/loader/icd_cmake_config.h.in @@ -0,0 +1,2 @@ +#cmakedefine HAVE_SECURE_GETENV +#cmakedefine HAVE___SECURE_GETENV diff --git a/loader/icd_envvars.h b/loader/icd_envvars.h new file mode 100644 index 00000000..0d34d3d6 --- /dev/null +++ b/loader/icd_envvars.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016-2019 The Khronos Group Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * OpenCL is a trademark of Apple Inc. used under license by Khronos. + */ + +#ifndef _ICD_ENVVARS_H_ +#define _ICD_ENVVARS_H_ + +char *khrIcd_getenv(const char *name); +char *khrIcd_secure_getenv(const char *name); +void khrIcd_free_getenv(char *val); + +#endif diff --git a/loader/icd_platform.h b/loader/icd_platform.h new file mode 100644 index 00000000..b16d0dbe --- /dev/null +++ b/loader/icd_platform.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016-2019 The Khronos Group Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * OpenCL is a trademark of Apple Inc. used under license by Khronos. + */ + +#ifndef _ICD_PLATFORM_H_ +#define _ICD_PLATFORM_H_ + +#if defined(__linux__) || defined(__APPLE__) + +#define PATH_SEPARATOR ':' +#define DIRECTORY_SYMBOL '/' +#ifdef __ANDROID__ +#define ICD_VENDOR_PATH "/system/vendor/Khronos/OpenCL/vendors/"; +#else +#define ICD_VENDOR_PATH "/etc/OpenCL/vendors/"; +#endif // ANDROID + +#elif defined(_WIN32) + +#define PATH_SEPARATOR ';' +#define DIRECTORY_SYMBOL '\\' + +#endif + +#endif diff --git a/loader/linux/icd_linux.c b/loader/linux/icd_linux.c index f6bb7b67..f6ae7902 100644 --- a/loader/linux/icd_linux.c +++ b/loader/linux/icd_linux.c @@ -17,6 +17,8 @@ */ #include "icd.h" +#include "icd_envvars.h" + #include #include #include @@ -38,102 +40,106 @@ void khrIcdOsVendorsEnumerate(void) { DIR *dir = NULL; struct dirent *dirEntry = NULL; -#ifdef __ANDROID__ - char *vendorPath = "/system/vendor/Khronos/OpenCL/vendors/"; -#else - char *vendorPath = "/etc/OpenCL/vendors/"; -#endif // ANDROID + char* vendorPath = ICD_VENDOR_PATH; + char* envPath = NULL; + + khrIcdVendorsEnumerateEnv(); + + envPath = khrIcd_secure_getenv("OCL_ICD_VENDORS"); + if (NULL != envPath) + { + vendorPath = envPath; + } - // open the directory dir = opendir(vendorPath); if (NULL == dir) { - KHR_ICD_TRACE("Failed to open path %s\n", vendorPath); - goto Cleanup; + KHR_ICD_TRACE("Failed to open path %s, continuing\n", vendorPath); } - - // attempt to load all files in the directory - for (dirEntry = readdir(dir); dirEntry; dirEntry = readdir(dir) ) + else { - switch(dirEntry->d_type) + // attempt to load all files in the directory + for (dirEntry = readdir(dir); dirEntry; dirEntry = readdir(dir) ) { - case DT_UNKNOWN: - case DT_REG: - case DT_LNK: + switch(dirEntry->d_type) { - const char* extension = ".icd"; - FILE *fin = NULL; - char* fileName = NULL; - char* buffer = NULL; - long bufferSize = 0; - - // make sure the file name ends in .icd - if (strlen(extension) > strlen(dirEntry->d_name) ) - { - break; - } - if (strcmp(dirEntry->d_name + strlen(dirEntry->d_name) - strlen(extension), extension) ) - { - break; - } - - // allocate space for the full path of the vendor library name - fileName = malloc(strlen(dirEntry->d_name) + strlen(vendorPath) + 1); - if (!fileName) - { - KHR_ICD_TRACE("Failed allocate space for ICD file path\n"); - break; - } - sprintf(fileName, "%s%s", vendorPath, dirEntry->d_name); - - // open the file and read its contents - fin = fopen(fileName, "r"); - if (!fin) + case DT_UNKNOWN: + case DT_REG: + case DT_LNK: { - free(fileName); - break; - } - fseek(fin, 0, SEEK_END); - bufferSize = ftell(fin); + const char* extension = ".icd"; + FILE *fin = NULL; + char* fileName = NULL; + char* buffer = NULL; + long bufferSize = 0; + + // make sure the file name ends in .icd + if (strlen(extension) > strlen(dirEntry->d_name) ) + { + break; + } + if (strcmp(dirEntry->d_name + strlen(dirEntry->d_name) - strlen(extension), extension) ) + { + break; + } + + // allocate space for the full path of the vendor library name + fileName = malloc(strlen(dirEntry->d_name) + strlen(vendorPath) + 1); + if (!fileName) + { + KHR_ICD_TRACE("Failed allocate space for ICD file path\n"); + break; + } + sprintf(fileName, "%s%s", vendorPath, dirEntry->d_name); + + // open the file and read its contents + fin = fopen(fileName, "r"); + if (!fin) + { + free(fileName); + break; + } + fseek(fin, 0, SEEK_END); + bufferSize = ftell(fin); + + buffer = malloc(bufferSize+1); + if (!buffer) + { + free(fileName); + fclose(fin); + break; + } + memset(buffer, 0, bufferSize+1); + fseek(fin, 0, SEEK_SET); + if (bufferSize != (long)fread(buffer, 1, bufferSize, fin) ) + { + free(fileName); + free(buffer); + fclose(fin); + break; + } + // ignore a newline at the end of the file + if (buffer[bufferSize-1] == '\n') buffer[bufferSize-1] = '\0'; + + // load the string read from the file + khrIcdVendorAdd(buffer); - buffer = malloc(bufferSize+1); - if (!buffer) - { - free(fileName); - fclose(fin); - break; - } - memset(buffer, 0, bufferSize+1); - fseek(fin, 0, SEEK_SET); - if (bufferSize != (long)fread(buffer, 1, bufferSize, fin) ) - { free(fileName); free(buffer); fclose(fin); - break; } - // ignore a newline at the end of the file - if (buffer[bufferSize-1] == '\n') buffer[bufferSize-1] = '\0'; - - // load the string read from the file - khrIcdVendorAdd(buffer); - - free(fileName); - free(buffer); - fclose(fin); + break; + default: + break; } - break; - default: - break; } - } -Cleanup: + closedir(dir); + } - // free resources and exit - if (dir) + if (NULL != envPath) { - closedir(dir); + khrIcd_free_getenv(envPath); } } diff --git a/loader/linux/icd_linux_envvars.c b/loader/linux/icd_linux_envvars.c new file mode 100644 index 00000000..4af5a082 --- /dev/null +++ b/loader/linux/icd_linux_envvars.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2019 The Khronos Group Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * OpenCL is a trademark of Apple Inc. used under license by Khronos. + */ + +// for secure_getenv(): +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "icd_cmake_config.h" + +#include + +char *khrIcd_getenv(const char *name) { + // No allocation of memory necessary for Linux. + return getenv(name); +} + +char *khrIcd_secure_getenv(const char *name) { +#if defined(__APPLE__) + // Apple does not appear to have a secure getenv implementation. + // The main difference between secure getenv and getenv is that secure getenv + // returns NULL if the process is being run with elevated privileges by a normal user. + // The idea is to prevent the reading of malicious environment variables by a process + // that can do damage. + // This algorithm is derived from glibc code that sets an internal + // variable (__libc_enable_secure) if the process is running under setuid or setgid. + return geteuid() != getuid() || getegid() != getgid() ? NULL : khrIcd_getenv(name); +#else +// Linux +#ifdef HAVE_SECURE_GETENV + return secure_getenv(name); +#elif defined(HAVE___SECURE_GETENV) + return __secure_getenv(name); +#else +#pragma message( \ + "Warning: Falling back to non-secure getenv for environmental lookups! Consider" \ + " updating to a different libc.") + return khrIcd_getenv(name); +#endif +#endif +} + +void khrIcd_free_getenv(char *val) { + // No freeing of memory necessary for Linux, but we should at least touch + // val to get rid of compiler warnings. + (void)val; +} diff --git a/loader/windows/icd_windows.c b/loader/windows/icd_windows.c index ec86184b..9c878175 100644 --- a/loader/windows/icd_windows.c +++ b/loader/windows/icd_windows.c @@ -40,6 +40,8 @@ BOOL CALLBACK khrIcdOsVendorsEnumerate(PINIT_ONCE InitOnce, PVOID Parameter, PVO HKEY platformsKey = NULL; DWORD dwIndex; + khrIcdVendorsEnumerateEnv(); + if (!khrIcdOsVendorsEnumerateDXGK()) { KHR_ICD_TRACE("Failed to load via DXGK interface on RS4, continuing\n"); @@ -59,59 +61,60 @@ BOOL CALLBACK khrIcdOsVendorsEnumerate(PINIT_ONCE InitOnce, PVOID Parameter, PVO if (ERROR_SUCCESS != result) { KHR_ICD_TRACE("Failed to open platforms key %s, continuing\n", platformsName); - return TRUE; } - - // for each value - for (dwIndex = 0;; ++dwIndex) + else { - char cszLibraryName[1024] = {0}; - DWORD dwLibraryNameSize = sizeof(cszLibraryName); - DWORD dwLibraryNameType = 0; - DWORD dwValue = 0; - DWORD dwValueSize = sizeof(dwValue); - - // read the value name - KHR_ICD_TRACE("Reading value %d...\n", dwIndex); - result = RegEnumValueA( - platformsKey, - dwIndex, - cszLibraryName, - &dwLibraryNameSize, - NULL, - &dwLibraryNameType, - (LPBYTE)&dwValue, - &dwValueSize); - // if RegEnumKeyEx fails, we are done with the enumeration - if (ERROR_SUCCESS != result) + // for each value + for (dwIndex = 0;; ++dwIndex) { - KHR_ICD_TRACE("Failed to read value %d, done reading key.\n", dwIndex); - break; - } - KHR_ICD_TRACE("Value %s found...\n", cszLibraryName); + char cszLibraryName[1024] = {0}; + DWORD dwLibraryNameSize = sizeof(cszLibraryName); + DWORD dwLibraryNameType = 0; + DWORD dwValue = 0; + DWORD dwValueSize = sizeof(dwValue); + + // read the value name + KHR_ICD_TRACE("Reading value %d...\n", dwIndex); + result = RegEnumValueA( + platformsKey, + dwIndex, + cszLibraryName, + &dwLibraryNameSize, + NULL, + &dwLibraryNameType, + (LPBYTE)&dwValue, + &dwValueSize); + // if RegEnumKeyEx fails, we are done with the enumeration + if (ERROR_SUCCESS != result) + { + KHR_ICD_TRACE("Failed to read value %d, done reading key.\n", dwIndex); + break; + } + KHR_ICD_TRACE("Value %s found...\n", cszLibraryName); - // Require that the value be a DWORD and equal zero - if (REG_DWORD != dwLibraryNameType) - { - KHR_ICD_TRACE("Value not a DWORD, skipping\n"); - continue; + // Require that the value be a DWORD and equal zero + if (REG_DWORD != dwLibraryNameType) + { + KHR_ICD_TRACE("Value not a DWORD, skipping\n"); + continue; + } + if (dwValue) + { + KHR_ICD_TRACE("Value not zero, skipping\n"); + continue; + } + + // add the library + khrIcdVendorAdd(cszLibraryName); } - if (dwValue) + + result = RegCloseKey(platformsKey); + if (ERROR_SUCCESS != result) { - KHR_ICD_TRACE("Value not zero, skipping\n"); - continue; + KHR_ICD_TRACE("Failed to close platforms key %s, ignoring\n", platformsName); } - - // add the library - khrIcdVendorAdd(cszLibraryName); } - result = RegCloseKey(platformsKey); - if (ERROR_SUCCESS != result) - { - KHR_ICD_TRACE("Failed to close platforms key %s, ignoring\n", platformsName); - } - return TRUE; } diff --git a/loader/windows/icd_windows_envvars.c b/loader/windows/icd_windows_envvars.c new file mode 100644 index 00000000..223604e8 --- /dev/null +++ b/loader/windows/icd_windows_envvars.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016-2019 The Khronos Group Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * OpenCL is a trademark of Apple Inc. used under license by Khronos. + */ + +#include + +char *khrIcd_getenv(const char *name) { + char *retVal; + DWORD valSize; + + valSize = GetEnvironmentVariableA(name, NULL, 0); + + // valSize DOES include the null terminator, so for any set variable + // will always be at least 1. If it's 0, the variable wasn't set. + if (valSize == 0) return NULL; + + // Allocate the space necessary for the registry entry + retVal = (char *)malloc(valSize); + + if (NULL != retVal) { + GetEnvironmentVariableA(name, retVal, valSize); + } + + return retVal; +} + +char *khrIcd_secure_getenv(const char *name) { + return khrIcd_getenv(name); +} + +void khrIcd_free_getenv(char *val) { + free((void *)val); +} diff --git a/test/driver_stub/driver_stub.def b/test/driver_stub/driver_stub.def index 5b238e8e..1ec27601 100644 --- a/test/driver_stub/driver_stub.def +++ b/test/driver_stub/driver_stub.def @@ -1,3 +1,2 @@ EXPORTS clGetExtensionFunctionAddress -clIcdGetPlatformIDsKHR