Update XDG paths

Issue #245 was created because we were not properly using the XDG
paths.
This change updates the path search order to be more correct.
While an argument could be made that the loader really should only
be looking in the DATA folders, we already are looking in the
CONFIG folders.  Therefore, just correct the order and also make
sure the proper _HOME variable for CONFIG and DATA is searched
first (but only in non-superuser mode).
This commit is contained in:
Mark Young 2021-08-13 17:26:04 -06:00
parent 140738f852
commit 4c8bf4cc69
4 changed files with 238 additions and 128 deletions

View File

@ -922,30 +922,39 @@ information on this.
On Linux, the Vulkan loader will scan the files in the following Linux
directories:
/usr/local/etc/vulkan/explicit_layer.d
/usr/local/etc/vulkan/implicit_layer.d
/usr/local/share/vulkan/explicit_layer.d
/usr/local/share/vulkan/implicit_layer.d
/etc/vulkan/explicit_layer.d
/etc/vulkan/implicit_layer.d
/usr/share/vulkan/explicit_layer.d
/usr/share/vulkan/implicit_layer.d
$HOME/.local/share/vulkan/explicit_layer.d
$HOME/.local/share/vulkan/implicit_layer.d
```
$XDG_CONFIG_HOME (or if not defined $HOME/.config)
$XDG_CONFIG_DIRS
$SYSCONFDIR (or if not defined /etc)
$EXTRASYSCONFDIR (or ir not defined /etc)
$XDG_DATA_HOME (or if not defined $HOME/.local/share)
$XDG_DATA_DIRS
```
Of course, there are some things you have to know about the above folders:
1. The "/usr/local/*" directories can be configured to be other directories at
build time.
2. $HOME is the current home directory of the application's user id; this path
will be ignored for suid programs.
3. The "/usr/local/etc/vulkan/\*\_layer.d" and
"/usr/local/share/vulkan/\*\_layer.d" directories are for layers that are
installed from locally-built sources.
4. The "/usr/share/vulkan/\*\_layer.d" directories are for layers that are
installed from Linux-distribution-provided packages.
5. The locations in `$HOME` will only be searched if an application does not have
root access. This is done to ensure that an application with root access does not
run layers that did not need root access to install.
The "XDG_..._HOME" default directories can be configured to be other directories
at build time.
To each of these paths will be added two suffixes.
First, the suffix **/vulkan** is added so the loader will only look for
information in a "vulkan" sub-folder.
Second, a suffix for the type of layer being searched for is used.
This second suffix will be **/explicit_layer.d** when searching for explicit
layers, and **/implicit_layer.d** when searching for implicit layers.
Duplicate resulting folders will be ignored.
An example combined search path for explicit layers may look like this:
```
/usr/local/etc/vulkan/explicit_layer.d
/usr/local/share/vulkan/explicit_layer.d
/etc/vulkan/explicit_layer.d
/usr/share/vulkan/explicit_layer.d
$HOME/.local/share/vulkan/explicit_layer.d
```
<b>IMPORTANT NOTE!</b> Any path using $HOME is ignored for all suid programs for
security reasons.
As on Windows, if VK\_LAYER\_PATH is defined, then the
loader will instead look at the paths defined by that variable instead of using
@ -958,25 +967,42 @@ information on this.
On macOS, the Vulkan loader will scan the files in the following directories:
<bundle>/Contents/Resources/vulkan/explicit_layer.d
<bundle>/Contents/Resources/vulkan/implicit_layer.d
/etc/vulkan/explicit_layer.d
/etc/vulkan/implicit_layer.d
/usr/local/share/vulkan/explicit_layer.d
/usr/local/share/vulkan/implicit_layer.d
/usr/share/vulkan/explicit_layer.d
/usr/share/vulkan/implicit_layer.d
$HOME/.local/share/vulkan/explicit_layer.d
$HOME/.local/share/vulkan/implicit_layer.d
```
<bundle>/Contents/Resources
$XDG_CONFIG_HOME (or if not defined $HOME/.config)
$XDG_CONFIG_DIRS
$SYSCONFDIR (or if not defined /etc)
$EXTRASYSCONFDIR (or ir not defined /etc)
$XDG_DATA_HOME (or if not defined $HOME/.local/share)
$XDG_DATA_DIRS
```
1. &lt;bundle&gt; is the directory containing a bundled application. It is scanned first.
2. The "/usr/local/\*" directories can be configured to be other directories at
build time.
3. $HOME is the current home directory of the application's user id; this path
will be ignored for suid programs.
4. The locations in `$HOME` will only be searched if an application does not have
root access. This is done to ensure that an application with root access does not
run layers that did not need root access to install.
**bundle**; is the directory containing a bundled application.
It is scanned first.
The "XDG_..._HOME" default directories can be configured to be other directories
at build time.
To each of these paths will be added two suffixes.
First, the suffix **/vulkan** is added so the loader will only look for
information in a "vulkan" sub-folder.
Second, a suffix for the type of layer being searched for is used.
This second suffix will be **/explicit_layer.d** when searching for explicit
layers, and **/implicit_layer.d** when searching for implicit layers.
Duplicate resulting folders will be ignored.
An example combined search path for implicit layers may look like this:
```
<bundle>/Contents/Resources/vulkan/implicit_layer.d
/etc/vulkan/implicit_layer.d
/usr/local/share/vulkan/implicit_layer.d
/usr/share/vulkan/implicit_layer.d
$HOME/.local/share/vulkan/implicit_layer.d
```
<b>IMPORTANT NOTE!</b> Any path using $HOME is ignored for all suid programs for
security reasons.
As on Windows, if VK\_LAYER\_PATH is defined, then the
loader will instead look at the paths defined by that variable instead of using
@ -2191,6 +2217,27 @@ details.
In order to find installed ICDs, the Vulkan loader will scan the files
in the following Linux directories:
```
$XDG_CONFIG_HOME (or if not defined $HOME/.config)
$XDG_CONFIG_DIRS
$SYSCONFDIR (or if not defined /etc)
$EXTRASYSCONFDIR (or ir not defined /etc)
$XDG_DATA_HOME (or if not defined $HOME/.local/share)
$XDG_DATA_DIRS
```
The "XDG_..._HOME" default directories can be configured to be other directories
at build time.
To each of these paths will be added a suffix to narrow down the search and
prevent attempted loading of invalid content.
First, the string "/vulkan" is appended so the loader will only look for
information in a **/vulkan** sub-folder.
Second, the string **/icd.d** is appended to indicate the loader is specifically
looking for only ICDs.
An example combined search path for ICDs may look like this:
```
/usr/local/etc/vulkan/icd.d
/usr/local/share/vulkan/icd.d
@ -2199,18 +2246,8 @@ in the following Linux directories:
$HOME/.local/share/vulkan/icd.d
```
The "/usr/local/*" directories can be configured to be other directories at
build time.
The typical usage of the directories is indicated in the table below.
| Location | Details |
|-------------------|------------------------|
| $HOME/.local/share/vulkan/icd.d | $HOME is the current home directory of the application's user id; this path will be ignored for suid programs |
| "/usr/local/etc/vulkan/icd.d" | Directory for locally built ICDs |
| "/usr/local/share/vulkan/icd.d" | Directory for locally built ICDs |
| "/etc/vulkan/icd.d" | Location of ICDs installed from non-Linux-distribution-provided packages |
| "/usr/share/vulkan/icd.d" | Location of ICDs installed from Linux-distribution-provided packages |
<b>IMPORTANT NOTE!</b> Any path using $HOME is ignored for all suid programs for
security reasons.
The Vulkan loader will open each manifest file found to obtain the name or
pathname of an ICD shared library (".so") file.
@ -2224,15 +2261,17 @@ In order to find installed ICDs, the Vulkan loader will scan the files
in the following directories:
```
<bundle>/Contents/Resources/vulkan/icd.d
/etc/vulkan/icd.d
/usr/local/share/vulkan/icd.d
/usr/share/vulkan/icd.d
$HOME/.local/share/vulkan/icd.d
<bundle>/Contents/Resources
$XDG_CONFIG_HOME (or if not defined $HOME/.config)
$XDG_CONFIG_DIRS
$SYSCONFDIR (or if not defined /etc)
$EXTRASYSCONFDIR (or ir not defined /etc)
$XDG_DATA_HOME (or if not defined $HOME/.local/share)
$XDG_DATA_DIRS
```
The "/usr/local/*" directories can be configured to be other directories at
build time.
The "XDG_..._HOME" default directories can be configured to be other directories
at build time.
The typical usage of the directories is indicated in the table below.

View File

@ -3904,48 +3904,76 @@ static VkResult ReadDataFilesInSearchPaths(const struct loader_instance *inst, e
size_t rel_size = 0;
bool use_first_found_manifest = false;
#ifndef _WIN32
bool xdgconfig_alloc = true;
bool xdgdata_alloc = true;
bool xdg_config_home_secenv_alloc = true;
bool xdg_config_dirs_secenv_alloc = true;
bool xdg_data_home_secenv_alloc = true;
bool xdg_data_dirs_secenv_alloc = true;
#endif
#ifndef _WIN32
// Determine how much space is needed to generate the full search path
// for the current manifest files.
char *xdgconfdirs = loader_secure_getenv("XDG_CONFIG_DIRS", inst);
char *xdgdatadirs = loader_secure_getenv("XDG_DATA_DIRS", inst);
char *xdgdatahome = loader_secure_getenv("XDG_DATA_HOME", inst);
char *home = NULL;
char *home_root = NULL;
if (xdgconfdirs == NULL) {
xdgconfig_alloc = false;
char *xdg_config_home = loader_secure_getenv("XDG_CONFIG_HOME", inst);
if (NULL == xdg_config_home) {
xdg_config_home_secenv_alloc = false;
}
if (xdgdatadirs == NULL) {
xdgdata_alloc = false;
char *xdg_config_dirs = loader_secure_getenv("XDG_CONFIG_DIRS", inst);
if (NULL == xdg_config_dirs) {
xdg_config_dirs_secenv_alloc = false;
}
#if !defined(__Fuchsia__) && !defined(__QNXNTO__)
if (xdgconfdirs == NULL || xdgconfdirs[0] == '\0') {
xdgconfdirs = FALLBACK_CONFIG_DIRS;
}
if (xdgdatadirs == NULL || xdgdatadirs[0] == '\0') {
xdgdatadirs = FALLBACK_DATA_DIRS;
if (NULL == xdg_config_dirs || '\0' == xdg_config_dirs[0]) {
xdg_config_dirs = FALLBACK_CONFIG_DIRS;
}
#endif
char *xdg_data_home = loader_secure_getenv("XDG_DATA_HOME", inst);
if (NULL == xdg_data_home) {
xdg_data_home_secenv_alloc = false;
}
char *xdg_data_dirs = loader_secure_getenv("XDG_DATA_DIRS", inst);
if (NULL == xdg_data_dirs) {
xdg_data_dirs_secenv_alloc = false;
}
#if !defined(__Fuchsia__) && !defined(__QNXNTO__)
if (NULL == xdg_data_dirs || '\0' == xdg_data_dirs[0]) {
xdg_data_dirs = FALLBACK_DATA_DIRS;
}
#endif
char *home = NULL;
char *default_data_home = NULL;
char *default_config_home = NULL;
// Only use HOME if XDG_DATA_HOME is not present on the system
if (NULL == xdgdatahome) {
home = loader_secure_getenv("HOME", inst);
if (home != NULL) {
home_root = loader_instance_heap_alloc(inst, strlen(home) + 14, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
if (home_root == NULL) {
home = loader_secure_getenv("HOME", inst);
if (home != NULL) {
if (NULL == xdg_config_home || '\0' == xdg_config_home[0]) {
const char config_suffix[] = "/.config";
default_config_home =
loader_instance_heap_alloc(inst, strlen(home) + strlen(config_suffix) + 1, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
if (default_config_home == NULL) {
vk_result = VK_ERROR_OUT_OF_HOST_MEMORY;
goto out;
}
strcpy(home_root, home);
strcat(home_root, "/.local/share");
strcpy(default_config_home, home);
strcat(default_config_home, config_suffix);
}
if (NULL == xdg_data_home || '\0' == xdg_data_home[0]) {
const char data_suffix[] = "/.local/share";
default_data_home =
loader_instance_heap_alloc(inst, strlen(home) + strlen(data_suffix) + 1, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
if (default_data_home == NULL) {
vk_result = VK_ERROR_OUT_OF_HOST_MEMORY;
goto out;
}
strcpy(default_data_home, home);
strcat(default_data_home, data_suffix);
}
}
#endif
#endif // !_WIN32
if (path_override != NULL) {
override_path = path_override;
@ -3988,19 +4016,29 @@ static VkResult ReadDataFilesInSearchPaths(const struct loader_instance *inst, e
search_path_size += MAXPATHLEN;
#endif
#ifndef _WIN32
search_path_size += DetermineDataFilePathSize(xdgconfdirs, rel_size);
search_path_size += DetermineDataFilePathSize(xdgdatadirs, rel_size);
// Only add the home folders if not ICD filenames or superuser
if (is_directory_list && !IsHighIntegrity()) {
if (NULL != default_config_home) {
search_path_size += DetermineDataFilePathSize(default_config_home, rel_size);
} else {
search_path_size += DetermineDataFilePathSize(xdg_config_home, rel_size);
}
}
search_path_size += DetermineDataFilePathSize(xdg_config_dirs, rel_size);
search_path_size += DetermineDataFilePathSize(SYSCONFDIR, rel_size);
#if defined(EXTRASYSCONFDIR)
search_path_size += DetermineDataFilePathSize(EXTRASYSCONFDIR, rel_size);
#endif
if (is_directory_list) {
if (!IsHighIntegrity()) {
search_path_size += DetermineDataFilePathSize(xdgdatahome, rel_size);
search_path_size += DetermineDataFilePathSize(home_root, rel_size);
// Only add the home folders if not ICD filenames or superuser
if (is_directory_list && !IsHighIntegrity()) {
if (NULL != default_data_home) {
search_path_size += DetermineDataFilePathSize(default_data_home, rel_size);
} else {
search_path_size += DetermineDataFilePathSize(xdg_data_home, rel_size);
}
}
#endif
search_path_size += DetermineDataFilePathSize(xdg_data_dirs, rel_size);
#endif // !_WIN32
}
}
@ -4042,17 +4080,30 @@ static VkResult ReadDataFilesInSearchPaths(const struct loader_instance *inst, e
CFRelease(ref);
}
}
#endif
CopyDataFilePath(xdgconfdirs, relative_location, rel_size, &cur_path_ptr);
#endif // __APPLE__
// Only add the home folders if not ICD filenames or superuser
if (is_directory_list && !IsHighIntegrity()) {
if (NULL != default_config_home) {
CopyDataFilePath(default_config_home, relative_location, rel_size, &cur_path_ptr);
} else {
CopyDataFilePath(xdg_config_home, relative_location, rel_size, &cur_path_ptr);
}
}
CopyDataFilePath(xdg_config_dirs, relative_location, rel_size, &cur_path_ptr);
CopyDataFilePath(SYSCONFDIR, relative_location, rel_size, &cur_path_ptr);
#if defined(EXTRASYSCONFDIR)
CopyDataFilePath(EXTRASYSCONFDIR, relative_location, rel_size, &cur_path_ptr);
#endif
CopyDataFilePath(xdgdatadirs, relative_location, rel_size, &cur_path_ptr);
if (is_directory_list) {
CopyDataFilePath(xdgdatahome, relative_location, rel_size, &cur_path_ptr);
CopyDataFilePath(home_root, relative_location, rel_size, &cur_path_ptr);
// Only add the home folders if not ICD filenames or superuser
if (is_directory_list && !IsHighIntegrity()) {
if (NULL != default_data_home) {
CopyDataFilePath(default_data_home, relative_location, rel_size, &cur_path_ptr);
} else {
CopyDataFilePath(xdg_data_home, relative_location, rel_size, &cur_path_ptr);
}
}
CopyDataFilePath(xdg_data_dirs, relative_location, rel_size, &cur_path_ptr);
}
// Remove the last path separator
@ -4060,7 +4111,7 @@ static VkResult ReadDataFilesInSearchPaths(const struct loader_instance *inst, e
assert(cur_path_ptr - search_path < (ptrdiff_t)search_path_size);
*cur_path_ptr = '\0';
#endif
#endif // !_WIN32
}
// Remove duplicate paths, or it would result in duplicate extensions, duplicate devices, etc.
@ -4150,20 +4201,29 @@ out:
loader_free_getenv(override_env, inst);
}
#ifndef _WIN32
if (xdgconfig_alloc) {
loader_free_getenv(xdgconfdirs, inst);
if (xdg_config_home_secenv_alloc) {
loader_free_getenv(xdg_config_home, inst);
}
if (xdgdata_alloc) {
loader_free_getenv(xdgdatadirs, inst);
if (xdg_config_dirs_secenv_alloc) {
loader_free_getenv(xdg_config_dirs, inst);
}
if (NULL != xdgdatahome) {
loader_free_getenv(xdgdatahome, inst);
if (xdg_data_home_secenv_alloc) {
loader_free_getenv(xdg_data_home, inst);
}
if (xdg_data_dirs_secenv_alloc) {
loader_free_getenv(xdg_data_dirs, inst);
}
if (NULL != xdg_data_home) {
loader_free_getenv(xdg_data_home, inst);
}
if (NULL != home) {
loader_free_getenv(home, inst);
}
if (NULL != home_root) {
loader_instance_heap_free(inst, home_root);
if (NULL != default_data_home) {
loader_instance_heap_free(inst, default_data_home);
}
if (NULL != default_config_home) {
loader_instance_heap_free(inst, default_config_home);
}
#endif

View File

@ -456,37 +456,44 @@ inline void PlatformShim::add_manifest(ManifestCategory category, fs::path const
inline void PlatformShim::redirect_category(fs::path const& new_path, ManifestCategory category) {
std::vector<std::string> xdg_paths;
std::string xdg_data_dirs_var = get_env_var("XDG_DATA_DIRS");
if (xdg_data_dirs_var.size() == 0) {
xdg_data_dirs_var = FALLBACK_CONFIG_DIRS;
std::string home = get_env_var("HOME");
if (home.size() != 0) {
std::string xdg_config_home_path = fs::path(home).c_str();
xdg_config_home_path += "/.config";
xdg_paths.push_back(xdg_config_home_path);
}
auto data_dirs_paths = parse_env_var_list(xdg_data_dirs_var);
xdg_paths.insert(xdg_paths.begin(), data_dirs_paths.begin(), data_dirs_paths.end());
std::string xdg_config_dirs_var = get_env_var("XDG_CONFIG_DIRS");
if (xdg_config_dirs_var.size() == 0) {
xdg_config_dirs_var = FALLBACK_DATA_DIRS;
xdg_config_dirs_var = FALLBACK_CONFIG_DIRS;
}
auto config_dirs_paths = parse_env_var_list(xdg_config_dirs_var);
xdg_paths.insert(xdg_paths.begin(), config_dirs_paths.begin(), config_dirs_paths.end());
xdg_paths.insert(xdg_paths.end(), config_dirs_paths.begin(), config_dirs_paths.end());
add(fs::path(SYSCONFDIR) / "vulkan" / category_path_name(category), new_path);
xdg_paths.push_back(fs::path(SYSCONFDIR).c_str());
#if defined(EXTRASYSCONFDIR)
// EXTRASYSCONFDIR default is /etc, if SYSCONFDIR wasn't defined, it will have /etc put
// as its default. Don't want to double add it
if (!string_eq(SYSCONFDIR, EXTRASYSCONFDIR)) {
add(fs::path(EXTRASYSCONFDIR) / "vulkan" / category_path_name(category), new_path);
xdg_paths.push_back(fs::path(EXTRASYSCONFDIR).c_str());
}
#endif
if (home.size() != 0) {
std::string xdg_data_home_path = fs::path(home).c_str();
xdg_data_home_path += "/.local/share";
xdg_paths.push_back(xdg_data_home_path);
}
std::string xdg_data_dirs_var = get_env_var("XDG_DATA_DIRS");
if (xdg_data_dirs_var.size() == 0) {
xdg_data_dirs_var = FALLBACK_DATA_DIRS;
}
auto data_dirs_paths = parse_env_var_list(xdg_data_dirs_var);
xdg_paths.insert(xdg_paths.end(), data_dirs_paths.begin(), data_dirs_paths.end());
for (auto& path : xdg_paths) {
add(fs::path(path) / "vulkan" / category_path_name(category), new_path);
}
std::string home = get_env_var("HOME");
if (home.size() != 0) {
add(fs::path(home) / ".local/share/vulkan" / category_path_name(category), new_path);
}
}
inline void PlatformShim::set_path(ManifestCategory category, fs::path const& path) {

View File

@ -16,20 +16,23 @@ RunEnvironmentVariablePathsTest()
# Set vars to include some "challenging" paths and run the test.
output=$(VK_LOADER_DEBUG=all \
XDG_CONFIG_DIRS=":/tmp/goober:::::/tmp/goober2/:/tmp/goober3/with spaces:::" \
XDG_DATA_DIRS="::::/tmp/goober4:::::/tmp/goober5:/tmp/goober6/with spaces::::/tmp/goober7:" \
XDG_CONFIG_DIRS=":/tmp/goober:::::/tmp/goober2/::::" \
XDG_DATA_DIRS="::::/tmp/goober3:/tmp/goober4/with spaces:::" \
VK_LAYER_PATH=${vk_layer_path} \
GTEST_FILTER=CreateInstance.LayerPresent \
./vk_loader_validation_tests 2>&1)
# Here is a path we expect to find. The loader constructs these from the XDG* env vars.
right_path="/tmp/goober/vulkan/icd.d:/tmp/goober2/vulkan/icd.d:/tmp/goober3/with spaces/vulkan/icd.d"
right_path="$HOME/.config/vulkan/icd.d:/tmp/goober/vulkan/icd.d:/tmp/goober2/vulkan/icd.d"
# There are other paths that come from SYSCONFIG settings established at build time.
# So we can't really guess at what those are here.
right_path+=".*"
# Also expect to find these, since we added them.
right_path+="/tmp/goober4/vulkan/icd.d:/tmp/goober5/vulkan/icd.d:/tmp/goober6/with spaces/vulkan/icd.d:/tmp/goober7/vulkan/icd.d"
echo "$output" | grep -q "$right_path"
right_path+="$HOME/.local/share/vulkan/icd.d:/tmp/goober3/vulkan/icd.d:/tmp/goober4/with spaces/vulkan/icd.d"
# Find just the line we're interested in
manifest_path_output=`grep "Searching the following paths for manifest files.*icd.d" <<< $output`
echo "$manifest_path_output" | grep -q "$right_path"
ec=$?
if [ $ec -eq 1 ]
then
@ -38,7 +41,8 @@ RunEnvironmentVariablePathsTest()
fi
# Change the string to implicit layers.
right_path=${right_path//icd.d/implicit_layer.d}
echo "$output" | grep -q "$right_path"
manifest_path_output=`grep "Searching the following paths for manifest files.*implicit_layer.d" <<< $output`
echo "$manifest_path_output" | grep -q "$right_path"
ec=$?
if [ $ec -eq 1 ]
then
@ -48,7 +52,7 @@ RunEnvironmentVariablePathsTest()
# The loader cleans up this path to remove the empty paths, so we need to clean up the right path, too
right_path="${vk_layer_path//:::::/:}"
right_path="${right_path//::::/:}"
echo "$output" | grep -q "$right_path"
echo "$manifest_path_output" | grep -q "$right_path"
ec=$?
if [ $ec -eq 1 ]
then