/* * * Copyright (c) 2014-2022 The Khronos Group Inc. * Copyright (c) 2014-2022 Valve Corporation * Copyright (c) 2014-2022 LunarG, 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. * * Author: Jon Ashburn * Author: Courtney Goeltzenleuchter * Author: Chia-I Wu * Author: Chia-I Wu * Author: Mark Lobodzinski * Author: Lenny Komow * Author: Charles Giessen * */ #include "loader_environment.h" #include "allocation.h" #include "loader.h" #include "log.h" #include // Environment variables #if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__) || defined(__QNXNTO__) || defined(__FreeBSD__) || \ defined(__OpenBSD__) bool is_high_integrity() { return geteuid() != getuid() || getegid() != getgid(); } char *loader_getenv(const char *name, const struct loader_instance *inst) { // No allocation of memory necessary for Linux, but we should at least touch // the inst pointer to get rid of compiler warnings. (void)inst; return getenv(name); } char *loader_secure_getenv(const char *name, const struct loader_instance *inst) { #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) // 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 is_high_integrity() ? NULL : loader_getenv(name, inst); #elif defined(__Fuchsia__) return loader_getenv(name, inst); #else // Linux char *out; #if defined(HAVE_SECURE_GETENV) && !defined(USE_UNSAFE_FILE_SEARCH) (void)inst; out = secure_getenv(name); #elif defined(HAVE___SECURE_GETENV) && !defined(USE_UNSAFE_FILE_SEARCH) (void)inst; out = __secure_getenv(name); #else out = loader_getenv(name, inst); #if !defined(USE_UNSAFE_FILE_SEARCH) loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, "Loader is using non-secure environment variable lookup for %s", name); #endif #endif return out; #endif } void loader_free_getenv(char *val, const struct loader_instance *inst) { // No freeing of memory necessary for Linux, but we should at least touch // the val and inst pointers to get rid of compiler warnings. (void)val; (void)inst; } #elif defined(WIN32) bool is_high_integrity() { HANDLE process_token; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_QUERY_SOURCE, &process_token)) { // Maximum possible size of SID_AND_ATTRIBUTES is maximum size of a SID + size of attributes DWORD. uint8_t mandatory_label_buffer[SECURITY_MAX_SID_SIZE + sizeof(DWORD)]; DWORD buffer_size; if (GetTokenInformation(process_token, TokenIntegrityLevel, mandatory_label_buffer, sizeof(mandatory_label_buffer), &buffer_size) != 0) { const TOKEN_MANDATORY_LABEL *mandatory_label = (const TOKEN_MANDATORY_LABEL *)mandatory_label_buffer; const DWORD sub_authority_count = *GetSidSubAuthorityCount(mandatory_label->Label.Sid); const DWORD integrity_level = *GetSidSubAuthority(mandatory_label->Label.Sid, sub_authority_count - 1); CloseHandle(process_token); return integrity_level >= SECURITY_MANDATORY_HIGH_RID; } CloseHandle(process_token); } return false; } char *loader_getenv(const char *name, const struct loader_instance *inst) { int name_utf16_size = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0); if (name_utf16_size <= 0) { return NULL; } wchar_t *name_utf16 = (wchar_t *)loader_stack_alloc(name_utf16_size * sizeof(wchar_t)); if (MultiByteToWideChar(CP_UTF8, 0, name, -1, name_utf16, name_utf16_size) != name_utf16_size) { return NULL; } DWORD val_size = GetEnvironmentVariableW(name_utf16, NULL, 0); // val_size 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 (val_size == 0) { return NULL; } wchar_t *val = (wchar_t *)loader_stack_alloc(val_size * sizeof(wchar_t)); if (GetEnvironmentVariableW(name_utf16, val, val_size) != val_size - 1) { return NULL; } int val_utf8_size = WideCharToMultiByte(CP_UTF8, 0, val, -1, NULL, 0, NULL, NULL); if (val_utf8_size <= 0) { return NULL; } char *val_utf8 = (char *)loader_instance_heap_alloc(inst, val_utf8_size * sizeof(char), VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); if (val_utf8 == NULL) { return NULL; } if (WideCharToMultiByte(CP_UTF8, 0, val, -1, val_utf8, val_utf8_size, NULL, NULL) != val_utf8_size) { loader_instance_heap_free(inst, val_utf8); return NULL; } return val_utf8; } char *loader_secure_getenv(const char *name, const struct loader_instance *inst) { #if !defined(USE_UNSAFE_FILE_SEARCH) if (is_high_integrity()) { loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, "Loader is running with elevated permissions. Environment variable %s will be ignored", name); return NULL; } #endif return loader_getenv(name, inst); } void loader_free_getenv(char *val, const struct loader_instance *inst) { loader_instance_heap_free(inst, (void *)val); } #else char *loader_getenv(const char *name, const struct loader_instance *inst) { // stub func (void)inst; (void)name; return NULL; } void loader_free_getenv(char *val, const struct loader_instance *inst) { // stub func (void)val; (void)inst; } #endif // Determine the type of filter string based on the contents of it. // This will properly check against: // - substrings "*string*" // - prefixes "string*" // - suffixes "*string" // - full string names "string" // It will also return the correct start and finish to remove any star '*' characters for the actual string compare void determine_filter_type(const char *filter_string, enum loader_filter_string_type *filter_type, const char **new_start, size_t *new_length) { size_t filter_length = strlen(filter_string); bool star_begin = false; bool star_end = false; if ('~' == filter_string[0]) { // One of the special identifiers like: ~all~, ~implicit~, or ~explicit~ *filter_type = FILTER_STRING_SPECIAL; *new_start = filter_string; *new_length = filter_length; } else { if ('*' == filter_string[0]) { // Only the * means everything if (filter_length == 1) { *filter_type = FILTER_STRING_SPECIAL; *new_start = filter_string; *new_length = filter_length; } else { star_begin = true; } } if ('*' == filter_string[filter_length - 1]) { // Not really valid, but just catch this case so if someone accidentally types "**" it will also mean everything if (filter_length == 2) { *filter_type = FILTER_STRING_SPECIAL; *new_start = filter_string; *new_length = filter_length; } else { star_end = true; } } if (star_begin && star_end) { *filter_type = FILTER_STRING_SUBSTRING; *new_start = &filter_string[1]; *new_length = filter_length - 2; } else if (star_begin) { *new_start = &filter_string[1]; *new_length = filter_length - 1; *filter_type = FILTER_STRING_SUFFIX; } else if (star_end) { *filter_type = FILTER_STRING_PREFIX; *new_start = filter_string; *new_length = filter_length - 1; } else { *filter_type = FILTER_STRING_FULLNAME; *new_start = filter_string; *new_length = filter_length; } } } // Parse the provided filter string provided by the envrionment variable into the appropriate filter // struct variable. VkResult parse_generic_filter_environment_var(const struct loader_instance *inst, const char *env_var_name, struct loader_envvar_filter *filter_struct) { VkResult result = VK_SUCCESS; memset(filter_struct, 0, sizeof(struct loader_envvar_filter)); char *env_var_value = loader_secure_getenv(env_var_name, inst); if (NULL == env_var_value) { return result; } if (strlen(env_var_value) > 0) { const size_t env_var_len = strlen(env_var_value); // Allocate a separate string since strtok modifies the original string char *parsing_string = loader_instance_heap_calloc(inst, env_var_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); if (NULL != parsing_string) { const char tokenizer[3] = ","; for (uint32_t iii = 0; iii < env_var_len; ++iii) { parsing_string[iii] = (char)tolower(env_var_value[iii]); } parsing_string[env_var_len] = '\0'; char *token = strtok(parsing_string, tokenizer); while (NULL != token) { enum loader_filter_string_type cur_filter_type; const char *actual_start; size_t actual_len; determine_filter_type(token, &cur_filter_type, &actual_start, &actual_len); if (actual_len > VK_MAX_EXTENSION_NAME_SIZE) { strncpy(filter_struct->filters[filter_struct->count].value, actual_start, VK_MAX_EXTENSION_NAME_SIZE); } else { strncpy(filter_struct->filters[filter_struct->count].value, actual_start, actual_len); } filter_struct->filters[filter_struct->count].length = actual_len; filter_struct->filters[filter_struct->count++].type = cur_filter_type; if (filter_struct->count >= MAX_ADDITIONAL_FILTERS) { break; } token = strtok(NULL, tokenizer); } loader_instance_heap_free(inst, parsing_string); } else { loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "parse_generic_filter_environment_var: Failed to allocate space for parsing env var \'%s\'", env_var_name); result = VK_ERROR_OUT_OF_HOST_MEMORY; } } loader_free_getenv(env_var_value, inst); return result; } // Parse the disable layer string. The layer disable has some special behavior because we allow it to disable // all layers (either with "~all~", "*", or "**"), all implicit layers (with "~implicit~"), and all explicit layers // (with "~explicit~"), in addition to the other layer filtering behavior. VkResult parse_layers_disable_filter_environment_var(const struct loader_instance *inst, struct loader_envvar_disable_layers_filter *disable_struct) { VkResult result = VK_SUCCESS; memset(disable_struct, 0, sizeof(struct loader_envvar_disable_layers_filter)); char *env_var_value = loader_secure_getenv(VK_LAYERS_DISABLE_ENV_VAR, inst); if (NULL == env_var_value) { goto out; } if (strlen(env_var_value) > 0) { const size_t env_var_len = strlen(env_var_value); // Allocate a separate string since strtok modifies the original string char *parsing_string = loader_instance_heap_calloc(inst, env_var_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); if (NULL != parsing_string) { const char tokenizer[3] = ","; for (uint32_t iii = 0; iii < env_var_len; ++iii) { parsing_string[iii] = (char)tolower(env_var_value[iii]); } parsing_string[env_var_len] = '\0'; char *token = strtok(parsing_string, tokenizer); while (NULL != token) { uint32_t cur_count = disable_struct->additional_filters.count; enum loader_filter_string_type cur_filter_type; const char *actual_start; size_t actual_len; determine_filter_type(token, &cur_filter_type, &actual_start, &actual_len); if (cur_filter_type == FILTER_STRING_SPECIAL) { if (!strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_1, token) || !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_2, token) || !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_3, token)) { disable_struct->disable_all = true; } else if (!strcmp(VK_LOADER_DISABLE_IMPLICIT_LAYERS_VAR, token)) { disable_struct->disable_all_implicit = true; } else if (!strcmp(VK_LOADER_DISABLE_EXPLICIT_LAYERS_VAR, token)) { disable_struct->disable_all_explicit = true; } } else { if (actual_len > VK_MAX_EXTENSION_NAME_SIZE) { strncpy(disable_struct->additional_filters.filters[cur_count].value, actual_start, VK_MAX_EXTENSION_NAME_SIZE); } else { strncpy(disable_struct->additional_filters.filters[cur_count].value, actual_start, actual_len); } disable_struct->additional_filters.filters[cur_count].length = actual_len; disable_struct->additional_filters.filters[cur_count].type = cur_filter_type; disable_struct->additional_filters.count++; if (disable_struct->additional_filters.count >= MAX_ADDITIONAL_FILTERS) { break; } } token = strtok(NULL, tokenizer); } loader_instance_heap_free(inst, parsing_string); } else { loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "parse_layers_disable_filter_environment_var: Failed to allocate space for parsing env var " "\'VK_LAYERS_DISABLE_ENV_VAR\'"); result = VK_ERROR_OUT_OF_HOST_MEMORY; } } loader_free_getenv(env_var_value, inst); out: return result; } // Check to see if the provided layer name matches any of the filter strings. // This will properly check against: // - substrings "*string*" // - prefixes "string*" // - suffixes "*string" // - full string names "string" bool check_name_matches_filter_environment_var(const struct loader_instance *inst, const char *name, const struct loader_envvar_filter *filter_struct) { bool ret_value = false; const size_t name_len = strlen(name); char lower_name[VK_MAX_EXTENSION_NAME_SIZE]; for (uint32_t iii = 0; iii < name_len; ++iii) { lower_name[iii] = (char)tolower(name[iii]); } lower_name[name_len] = '\0'; for (uint32_t filt = 0; filt < filter_struct->count; ++filt) { // Check if the filter name is longer (this is with all special characters removed), and if it is // continue since it can't match. if (filter_struct->filters[filt].length > name_len) { continue; } switch (filter_struct->filters[filt].type) { case FILTER_STRING_SPECIAL: if (!strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_1, filter_struct->filters[filt].value) || !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_2, filter_struct->filters[filt].value) || !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_3, filter_struct->filters[filt].value)) { ret_value = true; } break; case FILTER_STRING_SUBSTRING: if (NULL != strstr(lower_name, filter_struct->filters[filt].value)) { ret_value = true; } break; case FILTER_STRING_SUFFIX: if (0 == strncmp(lower_name + name_len - filter_struct->filters[filt].length, filter_struct->filters[filt].value, filter_struct->filters[filt].length)) { ret_value = true; } break; case FILTER_STRING_PREFIX: if (0 == strncmp(lower_name, filter_struct->filters[filt].value, filter_struct->filters[filt].length)) { ret_value = true; } break; case FILTER_STRING_FULLNAME: if (0 == strncmp(lower_name, filter_struct->filters[filt].value, name_len)) { ret_value = true; } break; } if (ret_value) { break; } } return ret_value; } // Get the layer name(s) from the env_name environment variable. If layer is found in // search_list then add it to layer_list. But only add it to layer_list if type_flags matches. VkResult loader_add_environment_layers(struct loader_instance *inst, const enum layer_type_flags type_flags, const char *env_name, const struct loader_envvar_filter *enable_filter, const struct loader_envvar_disable_layers_filter *disable_filter, struct loader_layer_list *target_list, struct loader_layer_list *expanded_target_list, const struct loader_layer_list *source_list) { VkResult res = VK_SUCCESS; char *next, *name; char *layer_env = loader_getenv(env_name, inst); char **vk_inst_layers = NULL; uint32_t vk_inst_layer_count = 0; uint32_t separator_count = 0; // If the layer environment variable is present (i.e. VK_INSTANCE_LAYERS), we will always add it to the layer list. if (layer_env != NULL) { name = loader_stack_alloc(strlen(layer_env) + 1); if (name != NULL) { separator_count = 1; for (uint32_t c = 0; c < strlen(layer_env); ++c) { if (layer_env[c] == PATH_SEPARATOR) { separator_count++; } } vk_inst_layers = loader_instance_heap_calloc(inst, (separator_count * sizeof(char *)), VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); if (vk_inst_layers == NULL) { res = VK_ERROR_OUT_OF_HOST_MEMORY; goto out; } for (uint32_t cur_layer = 0; cur_layer < separator_count; ++cur_layer) { vk_inst_layers[cur_layer] = loader_instance_heap_calloc(inst, VK_MAX_EXTENSION_NAME_SIZE, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); if (vk_inst_layers[cur_layer] == NULL) { res = VK_ERROR_OUT_OF_HOST_MEMORY; goto out; } } strcpy(name, layer_env); loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, "env var \'%s\' defined and adding layers \"%s\"", env_name, name); // First look for the old-fashion layers forced on with VK_INSTANCE_LAYERS while (name && *name) { next = loader_get_next_path(name); if (strlen(name) > 0) { strncpy(vk_inst_layers[vk_inst_layer_count++], name, VK_MAX_EXTENSION_NAME_SIZE); } name = next; } } } // Loop through all the layers and check the enable/disable filters as well as the VK_INSTANCE_LAYERS value. for (uint32_t i = 0; i < source_list->count; i++) { struct loader_layer_properties *source_prop = &source_list->list[i]; // If it doesn't match the type, or the name isn't what we're looking for, just continue if ((source_prop->type_flags & type_flags) != type_flags) { continue; } // We found a layer we're interested in, but has it been disabled... bool adding = true; bool is_implicit = (0 == (source_prop->type_flags & VK_LAYER_TYPE_FLAG_EXPLICIT_LAYER)); bool disabled_by_type = (is_implicit) ? (NULL != disable_filter && disable_filter->disable_all_implicit) : (NULL != disable_filter && disable_filter->disable_all_explicit); if (NULL != disable_filter && (disable_filter->disable_all || disabled_by_type || check_name_matches_filter_environment_var(inst, source_prop->info.layerName, &disable_filter->additional_filters))) { loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, "Layer \"%s\" ignored because it has been disabled by env var \'%s\'", source_prop->info.layerName, VK_LAYERS_DISABLE_ENV_VAR); adding = false; } // If we are supposed to filter through all layers, we need to compare the layer name against the filter. // This can override the disable above, so we want to do it second. if (check_name_matches_filter_environment_var(inst, source_prop->info.layerName, enable_filter)) { adding = true; // Only way is_substring is true is if there are enable variables. If that's the case, and we're past the // above, we should indicate that it was forced on in this way. loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, "Layer \"%s\" forced enabled due to env var \'%s\'", source_prop->info.layerName, VK_LAYERS_ENABLE_ENV_VAR); } else { adding = false; // If it's not in the enable filter, check the environment variable if it exists if (vk_inst_layer_count > 0) { for (uint32_t cur_layer = 0; cur_layer < vk_inst_layer_count; ++cur_layer) { if (!strcmp(vk_inst_layers[cur_layer], source_prop->info.layerName)) { adding = true; break; } } } } if (!adding) { continue; } // If not a meta-layer, simply add it. if (0 == (source_prop->type_flags & VK_LAYER_TYPE_FLAG_META_LAYER)) { res = loader_add_layer_properties_to_list(inst, target_list, 1, source_prop); if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out; res = loader_add_layer_properties_to_list(inst, expanded_target_list, 1, source_prop); if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out; } else { res = loader_add_meta_layer(inst, enable_filter, disable_filter, source_prop, target_list, expanded_target_list, source_list, NULL); if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out; } } out: if (NULL != vk_inst_layers) { for (uint32_t cur_layer = 0; cur_layer < separator_count; ++cur_layer) { loader_instance_heap_free(inst, vk_inst_layers[cur_layer]); } loader_instance_heap_free(inst, vk_inst_layers); } if (layer_env != NULL) { loader_free_getenv(layer_env, inst); } return res; }