1/*
2 *
3 * Copyright (c) 2014-2023 The Khronos Group Inc.
4 * Copyright (c) 2014-2023 Valve Corporation
5 * Copyright (c) 2014-2023 LunarG, Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *     http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * Author: Jon Ashburn <jon@lunarg.com>
20 * Author: Courtney Goeltzenleuchter <courtney@LunarG.com>
21 * Author: Chia-I Wu <olvaffe@gmail.com>
22 * Author: Chia-I Wu <olv@lunarg.com>
23 * Author: Mark Lobodzinski <mark@LunarG.com>
24 * Author: Lenny Komow <lenny@lunarg.com>
25 * Author: Charles Giessen <charles@lunarg.com>
26 *
27 */
28
29#include "loader_environment.h"
30
31#include "allocation.h"
32#include "loader.h"
33#include "log.h"
34
35#include <ctype.h>
36#include "param/sys_param.h"
37
38// Environment variables
39#if COMMON_UNIX_PLATFORMS
40
41bool is_high_integrity() { return geteuid() != getuid() || getegid() != getgid(); }
42
43char *loader_getenv(const char *name, const struct loader_instance *inst) {
44    if (NULL == name) return NULL;
45#if defined(__OHOS__)
46    CachedHandle g_Handle = CachedParameterCreate(name, "");
47    int changed = 0;
48    const char *res = CachedParameterGetChanged(g_Handle, &changed);
49    loader_log(inst, VULKAN_LOADER_DEBUG_BIT | VULKAN_LOADER_INFO_BIT, 0, "loader_getenv name:%s, res:%s", name, res);
50    if (res == NULL || res[0] == '\0') {
51        return NULL;
52    }
53    return (char *)res;
54#else
55    // No allocation of memory necessary for Linux, but we should at least touch
56    // the inst pointer to get rid of compiler warnings.
57    (void)inst;
58    return getenv(name);
59#endif
60}
61
62char *loader_secure_getenv(const char *name, const struct loader_instance *inst) {
63#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
64    // Apple does not appear to have a secure getenv implementation.
65    // The main difference between secure getenv and getenv is that secure getenv
66    // returns NULL if the process is being run with elevated privileges by a normal user.
67    // The idea is to prevent the reading of malicious environment variables by a process
68    // that can do damage.
69    // This algorithm is derived from glibc code that sets an internal
70    // variable (__libc_enable_secure) if the process is running under setuid or setgid.
71    return is_high_integrity() ? NULL : loader_getenv(name, inst);
72#elif defined(__Fuchsia__)
73    return loader_getenv(name, inst);
74#else
75    // Linux
76    char *out;
77#if defined(HAVE_SECURE_GETENV) && !defined(LOADER_USE_UNSAFE_FILE_SEARCH)
78    (void)inst;
79    out = secure_getenv(name);
80#elif defined(HAVE___SECURE_GETENV) && !defined(LOADER_USE_UNSAFE_FILE_SEARCH)
81    (void)inst;
82    out = __secure_getenv(name);
83#else
84    out = loader_getenv(name, inst);
85#if !defined(LOADER_USE_UNSAFE_FILE_SEARCH)
86    loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, "Loader is using non-secure environment variable lookup for %s", name);
87#endif
88#endif
89    return out;
90#endif
91}
92
93void loader_free_getenv(char *val, const struct loader_instance *inst) {
94    // No freeing of memory necessary for Linux, but we should at least touch
95    // the val and inst pointers to get rid of compiler warnings.
96    (void)val;
97    (void)inst;
98}
99
100#elif defined(WIN32)
101
102bool is_high_integrity() {
103    HANDLE process_token;
104    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_QUERY_SOURCE, &process_token)) {
105        // Maximum possible size of SID_AND_ATTRIBUTES is maximum size of a SID + size of attributes DWORD.
106        uint8_t mandatory_label_buffer[SECURITY_MAX_SID_SIZE + sizeof(DWORD)];
107        DWORD buffer_size;
108        if (GetTokenInformation(process_token, TokenIntegrityLevel, mandatory_label_buffer, sizeof(mandatory_label_buffer),
109                                &buffer_size) != 0) {
110            const TOKEN_MANDATORY_LABEL *mandatory_label = (const TOKEN_MANDATORY_LABEL *)mandatory_label_buffer;
111            const DWORD sub_authority_count = *GetSidSubAuthorityCount(mandatory_label->Label.Sid);
112            const DWORD integrity_level = *GetSidSubAuthority(mandatory_label->Label.Sid, sub_authority_count - 1);
113
114            CloseHandle(process_token);
115            return integrity_level >= SECURITY_MANDATORY_HIGH_RID;
116        }
117
118        CloseHandle(process_token);
119    }
120
121    return false;
122}
123
124char *loader_getenv(const char *name, const struct loader_instance *inst) {
125    int name_utf16_size = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);
126    if (name_utf16_size <= 0) {
127        return NULL;
128    }
129    wchar_t *name_utf16 = (wchar_t *)loader_stack_alloc(name_utf16_size * sizeof(wchar_t));
130    if (MultiByteToWideChar(CP_UTF8, 0, name, -1, name_utf16, name_utf16_size) != name_utf16_size) {
131        return NULL;
132    }
133
134    DWORD val_size = GetEnvironmentVariableW(name_utf16, NULL, 0);
135    // val_size DOES include the null terminator, so for any set variable
136    // will always be at least 1. If it's 0, the variable wasn't set.
137    if (val_size == 0) {
138        return NULL;
139    }
140
141    wchar_t *val = (wchar_t *)loader_stack_alloc(val_size * sizeof(wchar_t));
142    if (GetEnvironmentVariableW(name_utf16, val, val_size) != val_size - 1) {
143        return NULL;
144    }
145
146    int val_utf8_size = WideCharToMultiByte(CP_UTF8, 0, val, -1, NULL, 0, NULL, NULL);
147    if (val_utf8_size <= 0) {
148        return NULL;
149    }
150    char *val_utf8 = (char *)loader_instance_heap_alloc(inst, val_utf8_size * sizeof(char), VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
151    if (val_utf8 == NULL) {
152        return NULL;
153    }
154    if (WideCharToMultiByte(CP_UTF8, 0, val, -1, val_utf8, val_utf8_size, NULL, NULL) != val_utf8_size) {
155        loader_instance_heap_free(inst, val_utf8);
156        return NULL;
157    }
158    return val_utf8;
159}
160
161char *loader_secure_getenv(const char *name, const struct loader_instance *inst) {
162    if (NULL == name) return NULL;
163#if !defined(LOADER_USE_UNSAFE_FILE_SEARCH)
164    if (is_high_integrity()) {
165        loader_log(inst, VULKAN_LOADER_INFO_BIT, 0,
166                   "Loader is running with elevated permissions. Environment variable %s will be ignored", name);
167        return NULL;
168    }
169#endif
170
171    return loader_getenv(name, inst);
172}
173
174void loader_free_getenv(char *val, const struct loader_instance *inst) { loader_instance_heap_free(inst, (void *)val); }
175
176#else
177
178#warning \
179    "This platform does not support environment variables! If this is not intended, please implement the stubs functions loader_getenv and loader_free_getenv"
180
181char *loader_getenv(const char *name, const struct loader_instance *inst) {
182    // stub func
183    (void)inst;
184    (void)name;
185    return NULL;
186}
187void loader_free_getenv(char *val, const struct loader_instance *inst) {
188    // stub func
189    (void)val;
190    (void)inst;
191}
192
193#endif
194
195// Determine the type of filter string based on the contents of it.
196// This will properly check against:
197//  - substrings "*string*"
198//  - prefixes "string*"
199//  - suffixes "*string"
200//  - full string names "string"
201// It will also return the correct start and finish to remove any star '*' characters for the actual string compare
202void determine_filter_type(const char *filter_string, enum loader_filter_string_type *filter_type, const char **new_start,
203                           size_t *new_length) {
204    size_t filter_length = strlen(filter_string);
205    bool star_begin = false;
206    bool star_end = false;
207    if ('~' == filter_string[0]) {
208        // One of the special identifiers like: ~all~, ~implicit~, or ~explicit~
209        *filter_type = FILTER_STRING_SPECIAL;
210        *new_start = filter_string;
211        *new_length = filter_length;
212    } else {
213        if ('*' == filter_string[0]) {
214            // Only the * means everything
215            if (filter_length == 1) {
216                *filter_type = FILTER_STRING_SPECIAL;
217                *new_start = filter_string;
218                *new_length = filter_length;
219            } else {
220                star_begin = true;
221            }
222        }
223        if ('*' == filter_string[filter_length - 1]) {
224            // Not really valid, but just catch this case so if someone accidentally types "**" it will also mean everything
225            if (filter_length == 2) {
226                *filter_type = FILTER_STRING_SPECIAL;
227                *new_start = filter_string;
228                *new_length = filter_length;
229            } else {
230                star_end = true;
231            }
232        }
233        if (star_begin && star_end) {
234            *filter_type = FILTER_STRING_SUBSTRING;
235            *new_start = &filter_string[1];
236            *new_length = filter_length - 2;
237        } else if (star_begin) {
238            *new_start = &filter_string[1];
239            *new_length = filter_length - 1;
240            *filter_type = FILTER_STRING_SUFFIX;
241        } else if (star_end) {
242            *filter_type = FILTER_STRING_PREFIX;
243            *new_start = filter_string;
244            *new_length = filter_length - 1;
245        } else {
246            *filter_type = FILTER_STRING_FULLNAME;
247            *new_start = filter_string;
248            *new_length = filter_length;
249        }
250    }
251}
252
253// Parse the provided filter string provided by the envrionment variable into the appropriate filter
254// struct variable.
255VkResult parse_generic_filter_environment_var(const struct loader_instance *inst, const char *env_var_name,
256                                              struct loader_envvar_filter *filter_struct) {
257    VkResult result = VK_SUCCESS;
258    memset(filter_struct, 0, sizeof(struct loader_envvar_filter));
259    char *parsing_string = NULL;
260    char *env_var_value = loader_secure_getenv(env_var_name, inst);
261    if (NULL == env_var_value) {
262        return result;
263    }
264    const size_t env_var_len = strlen(env_var_value);
265    if (env_var_len == 0) {
266        goto out;
267    }
268    // Allocate a separate string since scan_for_next_comma modifies the original string
269    parsing_string = loader_instance_heap_calloc(inst, env_var_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
270    if (NULL == parsing_string) {
271        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0,
272                   "parse_generic_filter_environment_var: Failed to allocate space for parsing env var \'%s\'", env_var_name);
273        result = VK_ERROR_OUT_OF_HOST_MEMORY;
274        goto out;
275    }
276
277    for (uint32_t iii = 0; iii < env_var_len; ++iii) {
278        parsing_string[iii] = (char)tolower(env_var_value[iii]);
279    }
280    parsing_string[env_var_len] = '\0';
281
282    char *context = NULL;
283    char *token = thread_safe_strtok(parsing_string, ",", &context);
284    while (NULL != token) {
285        enum loader_filter_string_type cur_filter_type;
286        const char *actual_start;
287        size_t actual_len;
288        determine_filter_type(token, &cur_filter_type, &actual_start, &actual_len);
289        if (actual_len > VK_MAX_EXTENSION_NAME_SIZE) {
290            loader_strncpy(filter_struct->filters[filter_struct->count].value, VK_MAX_EXTENSION_NAME_SIZE, actual_start,
291                           VK_MAX_EXTENSION_NAME_SIZE);
292        } else {
293            loader_strncpy(filter_struct->filters[filter_struct->count].value, VK_MAX_EXTENSION_NAME_SIZE, actual_start,
294                           actual_len);
295        }
296        filter_struct->filters[filter_struct->count].length = actual_len;
297        filter_struct->filters[filter_struct->count++].type = cur_filter_type;
298        if (filter_struct->count >= MAX_ADDITIONAL_FILTERS) {
299            break;
300        }
301        token = thread_safe_strtok(NULL, ",", &context);
302    }
303
304out:
305
306    loader_instance_heap_free(inst, parsing_string);
307    loader_free_getenv(env_var_value, inst);
308    return result;
309}
310
311// Parse the disable layer string.  The layer disable has some special behavior because we allow it to disable
312// all layers (either with "~all~", "*", or "**"), all implicit layers (with "~implicit~"), and all explicit layers
313// (with "~explicit~"), in addition to the other layer filtering behavior.
314VkResult parse_layers_disable_filter_environment_var(const struct loader_instance *inst,
315                                                     struct loader_envvar_disable_layers_filter *disable_struct) {
316    VkResult result = VK_SUCCESS;
317    memset(disable_struct, 0, sizeof(struct loader_envvar_disable_layers_filter));
318    char *parsing_string = NULL;
319    char *env_var_value = loader_secure_getenv(VK_LAYERS_DISABLE_ENV_VAR, inst);
320    if (NULL == env_var_value) {
321        goto out;
322    }
323    const size_t env_var_len = strlen(env_var_value);
324    if (env_var_len == 0) {
325        goto out;
326    }
327    // Allocate a separate string since scan_for_next_comma modifies the original string
328    parsing_string = loader_instance_heap_calloc(inst, env_var_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
329    if (NULL == parsing_string) {
330        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0,
331                   "parse_layers_disable_filter_environment_var: Failed to allocate space for parsing env var "
332                   "\'VK_LAYERS_DISABLE_ENV_VAR\'");
333        result = VK_ERROR_OUT_OF_HOST_MEMORY;
334        goto out;
335    }
336
337    for (uint32_t iii = 0; iii < env_var_len; ++iii) {
338        parsing_string[iii] = (char)tolower(env_var_value[iii]);
339    }
340    parsing_string[env_var_len] = '\0';
341
342    char *context = NULL;
343    char *token = thread_safe_strtok(parsing_string, ",", &context);
344    while (NULL != token) {
345        uint32_t cur_count = disable_struct->additional_filters.count;
346        enum loader_filter_string_type cur_filter_type;
347        const char *actual_start;
348        size_t actual_len;
349        determine_filter_type(token, &cur_filter_type, &actual_start, &actual_len);
350        if (cur_filter_type == FILTER_STRING_SPECIAL) {
351            if (!strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_1, token) || !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_2, token) ||
352                !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_3, token)) {
353                disable_struct->disable_all = true;
354            } else if (!strcmp(VK_LOADER_DISABLE_IMPLICIT_LAYERS_VAR, token)) {
355                disable_struct->disable_all_implicit = true;
356            } else if (!strcmp(VK_LOADER_DISABLE_EXPLICIT_LAYERS_VAR, token)) {
357                disable_struct->disable_all_explicit = true;
358            }
359        } else {
360            if (actual_len > VK_MAX_EXTENSION_NAME_SIZE) {
361                loader_strncpy(disable_struct->additional_filters.filters[cur_count].value, VK_MAX_EXTENSION_NAME_SIZE,
362                               actual_start, VK_MAX_EXTENSION_NAME_SIZE);
363            } else {
364                loader_strncpy(disable_struct->additional_filters.filters[cur_count].value, VK_MAX_EXTENSION_NAME_SIZE,
365                               actual_start, actual_len);
366            }
367            disable_struct->additional_filters.filters[cur_count].length = actual_len;
368            disable_struct->additional_filters.filters[cur_count].type = cur_filter_type;
369            disable_struct->additional_filters.count++;
370            if (disable_struct->additional_filters.count >= MAX_ADDITIONAL_FILTERS) {
371                break;
372            }
373        }
374        token = thread_safe_strtok(NULL, ",", &context);
375    }
376out:
377    loader_instance_heap_free(inst, parsing_string);
378    loader_free_getenv(env_var_value, inst);
379    return result;
380}
381
382// Parses the filter environment variables to determine if we have any special behavior
383VkResult parse_layer_environment_var_filters(const struct loader_instance *inst, struct loader_envvar_all_filters *layer_filters) {
384    VkResult res = parse_generic_filter_environment_var(inst, VK_LAYERS_ENABLE_ENV_VAR, &layer_filters->enable_filter);
385    if (VK_SUCCESS != res) {
386        return res;
387    }
388    res = parse_layers_disable_filter_environment_var(inst, &layer_filters->disable_filter);
389    if (VK_SUCCESS != res) {
390        return res;
391    }
392    res = parse_generic_filter_environment_var(inst, VK_LAYERS_ALLOW_ENV_VAR, &layer_filters->allow_filter);
393    if (VK_SUCCESS != res) {
394        return res;
395    }
396    return res;
397}
398
399// Check to see if the provided layer name matches any of the filter strings.
400// This will properly check against:
401//  - substrings "*string*"
402//  - prefixes "string*"
403//  - suffixes "*string"
404//  - full string names "string"
405bool check_name_matches_filter_environment_var(const char *name, const struct loader_envvar_filter *filter_struct) {
406    bool ret_value = false;
407    const size_t name_len = strlen(name);
408    char lower_name[VK_MAX_EXTENSION_NAME_SIZE];
409    for (uint32_t iii = 0; iii < name_len; ++iii) {
410        lower_name[iii] = (char)tolower(name[iii]);
411    }
412    lower_name[name_len] = '\0';
413    for (uint32_t filt = 0; filt < filter_struct->count; ++filt) {
414        // Check if the filter name is longer (this is with all special characters removed), and if it is
415        // continue since it can't match.
416        if (filter_struct->filters[filt].length > name_len) {
417            continue;
418        }
419        switch (filter_struct->filters[filt].type) {
420            case FILTER_STRING_SPECIAL:
421                if (!strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_1, filter_struct->filters[filt].value) ||
422                    !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_2, filter_struct->filters[filt].value) ||
423                    !strcmp(VK_LOADER_DISABLE_ALL_LAYERS_VAR_3, filter_struct->filters[filt].value)) {
424                    ret_value = true;
425                }
426                break;
427
428            case FILTER_STRING_SUBSTRING:
429                if (NULL != strstr(lower_name, filter_struct->filters[filt].value)) {
430                    ret_value = true;
431                }
432                break;
433
434            case FILTER_STRING_SUFFIX:
435                if (0 == strncmp(lower_name + name_len - filter_struct->filters[filt].length, filter_struct->filters[filt].value,
436                                 filter_struct->filters[filt].length)) {
437                    ret_value = true;
438                }
439                break;
440
441            case FILTER_STRING_PREFIX:
442                if (0 == strncmp(lower_name, filter_struct->filters[filt].value, filter_struct->filters[filt].length)) {
443                    ret_value = true;
444                }
445                break;
446
447            case FILTER_STRING_FULLNAME:
448                if (0 == strncmp(lower_name, filter_struct->filters[filt].value, name_len)) {
449                    ret_value = true;
450                }
451                break;
452        }
453        if (ret_value) {
454            break;
455        }
456    }
457    return ret_value;
458}
459
460// Get the layer name(s) from the env_name environment variable. If layer is found in
461// search_list then add it to layer_list.  But only add it to layer_list if type_flags matches.
462VkResult loader_add_environment_layers(struct loader_instance *inst, const enum layer_type_flags type_flags,
463                                       const struct loader_envvar_all_filters *filters,
464                                       struct loader_pointer_layer_list *target_list,
465                                       struct loader_pointer_layer_list *expanded_target_list,
466                                       const struct loader_layer_list *source_list) {
467    VkResult res = VK_SUCCESS;
468    char *layer_env = loader_getenv(ENABLED_LAYERS_ENV, inst);
469
470    // If the layer environment variable is present (i.e. VK_INSTANCE_LAYERS), we will always add it to the layer list.
471    if (layer_env != NULL) {
472        size_t layer_env_len = strlen(layer_env) + 1;
473        char *name = loader_stack_alloc(layer_env_len);
474        if (name != NULL) {
475            loader_strncpy(name, layer_env_len, layer_env, layer_env_len);
476
477            loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, "env var \'%s\' defined and adding layers \"%s\"",
478                       ENABLED_LAYERS_ENV, name);
479
480            // First look for the old-fashion layers forced on with VK_INSTANCE_LAYERS
481            while (name && *name) {
482                char *next = loader_get_next_path(name);
483
484                if (strlen(name) > 0) {
485                    bool found = false;
486                    for (uint32_t i = 0; i < source_list->count; i++) {
487                        struct loader_layer_properties *source_prop = &source_list->list[i];
488
489                        if (0 == strcmp(name, source_prop->info.layerName)) {
490                            found = true;
491                            // Only add it if it doesn't already appear in the layer list
492                            if (!loader_find_layer_name_in_list(source_prop->info.layerName, target_list)) {
493                                if (0 == (source_prop->type_flags & VK_LAYER_TYPE_FLAG_META_LAYER)) {
494                                    res = loader_add_layer_properties_to_list(inst, target_list, source_prop);
495                                    if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
496                                    res = loader_add_layer_properties_to_list(inst, expanded_target_list, source_prop);
497                                    if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
498                                } else {
499                                    res = loader_add_meta_layer(inst, filters, source_prop, target_list, expanded_target_list,
500                                                                source_list, NULL);
501                                    if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
502                                }
503                                break;
504                            }
505                        }
506                    }
507                    if (!found) {
508                        loader_log(inst, VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_LAYER_BIT, 0,
509                                   "Layer \"%s\" was not found but was requested by env var VK_INSTANCE_LAYERS!", name);
510                    }
511                }
512                name = next;
513            }
514        }
515    }
516
517    // Loop through all the layers and check the enable/disable filters
518    for (uint32_t i = 0; i < source_list->count; i++) {
519        struct loader_layer_properties *source_prop = &source_list->list[i];
520
521        // If it doesn't match the type, or the name isn't what we're looking for, just continue
522        if ((source_prop->type_flags & type_flags) != type_flags) {
523            continue;
524        }
525
526        // We found a layer we're interested in, but has it been disabled...
527        bool adding = true;
528        bool is_implicit = (0 == (source_prop->type_flags & VK_LAYER_TYPE_FLAG_EXPLICIT_LAYER));
529        bool disabled_by_type =
530            (is_implicit) ? (filters->disable_filter.disable_all_implicit) : (filters->disable_filter.disable_all_explicit);
531        if ((filters->disable_filter.disable_all || disabled_by_type ||
532             check_name_matches_filter_environment_var(source_prop->info.layerName, &filters->disable_filter.additional_filters)) &&
533            !check_name_matches_filter_environment_var(source_prop->info.layerName, &filters->allow_filter)) {
534            loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0,
535                       "Layer \"%s\" ignored because it has been disabled by env var \'%s\'", source_prop->info.layerName,
536                       VK_LAYERS_DISABLE_ENV_VAR);
537            adding = false;
538        }
539
540        // If we are supposed to filter through all layers, we need to compare the layer name against the filter.
541        // This can override the disable above, so we want to do it second.
542        // Also make sure the layer isn't already in the output_list, skip adding it if it is.
543        if (check_name_matches_filter_environment_var(source_prop->info.layerName, &filters->enable_filter) &&
544            !loader_find_layer_name_in_list(source_prop->info.layerName, target_list)) {
545            adding = true;
546            // Only way is_substring is true is if there are enable variables.  If that's the case, and we're past the
547            // above, we should indicate that it was forced on in this way.
548            loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0,
549                       "Layer \"%s\" forced enabled due to env var \'%s\'", source_prop->info.layerName, VK_LAYERS_ENABLE_ENV_VAR);
550        } else {
551            adding = false;
552        }
553
554        if (!adding) {
555            continue;
556        }
557
558        // If not a meta-layer, simply add it.
559        if (0 == (source_prop->type_flags & VK_LAYER_TYPE_FLAG_META_LAYER)) {
560            res = loader_add_layer_properties_to_list(inst, target_list, source_prop);
561            if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
562            res = loader_add_layer_properties_to_list(inst, expanded_target_list, source_prop);
563            if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
564        } else {
565            res = loader_add_meta_layer(inst, filters, source_prop, target_list, expanded_target_list, source_list, NULL);
566            if (res == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
567        }
568    }
569
570out:
571
572    if (layer_env != NULL) {
573        loader_free_getenv(layer_env, inst);
574    }
575
576    return res;
577}
578