Skip to content

Commit

Permalink
win32: Enforce loading of plugins from a trusted directory
Browse files Browse the repository at this point in the history
Currently, there's a risk associated with allowing plugins to be loaded
from any location. This update ensures plugins are only loaded from a
trusted directory, which is either:

    - HKLM\SOFTWARE\OpenVPN\plugin_dir (or if the key is missing,
    then HKLM\SOFTWARE\OpenVPN, which is installation directory)

    - System directory

Loading from UNC paths is disallowed.

Note: This change affects only Windows environments.

CVE: 2024-27903

Change-Id: I154a4aaad9242c9253a64312a14c5fd2ea95f40d
Reported-by: Vladimir Tokarev <[email protected]>
Signed-off-by: Lev Stipakov <[email protected]>
Acked-by: Selva Nair <[email protected]>
Message-Id: <[email protected]>
URL: https://www.mail-archive.com/[email protected]/msg28416.html
Signed-off-by: Gert Doering <[email protected]>
(cherry picked from commit aaea545)
  • Loading branch information
lstipakov authored and cron2 committed Mar 19, 2024
1 parent ff06f4c commit 05d321e
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 15 deletions.
18 changes: 15 additions & 3 deletions src/openvpn/plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,23 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)

#else /* ifndef _WIN32 */

rel = !platform_absolute_pathname(p->so_pathname);
p->module = LoadLibraryW(wide_string(p->so_pathname, &gc));
WCHAR *wpath = wide_string(p->so_pathname, &gc);
WCHAR normalized_plugin_path[MAX_PATH] = {0};
/* Normalize the plugin path, converting any relative paths to absolute paths. */
if (!GetFullPathNameW(wpath, MAX_PATH, normalized_plugin_path, NULL))
{
msg(M_ERR, "PLUGIN_INIT: could not load plugin DLL: %ls. Failed to normalize plugin path.", wpath);
}

if (!plugin_in_trusted_dir(normalized_plugin_path))
{
msg(M_FATAL, "PLUGIN_INIT: could not load plugin DLL: %ls. The DLL is not in a trusted directory.", normalized_plugin_path);
}

p->module = LoadLibraryW(normalized_plugin_path);
if (!p->module)
{
msg(M_ERR, "PLUGIN_INIT: could not load plugin DLL: %s", p->so_pathname);
msg(M_ERR, "PLUGIN_INIT: could not load plugin DLL: %ls", normalized_plugin_path);
}

#define PLUGIN_SYM(var, name, flags) dll_resolve_symbol(p->module, (void *)&p->var, name, p->so_pathname, flags)
Expand Down
77 changes: 65 additions & 12 deletions src/openvpn/win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -1525,27 +1525,24 @@ openvpn_swprintf(wchar_t *const str, const size_t size, const wchar_t *const for
return (len >= 0 && len < size);
}

static BOOL
get_install_path(WCHAR *path, DWORD size)
bool
get_openvpn_reg_value(const WCHAR *key, WCHAR *value, DWORD size)
{
WCHAR reg_path[256];
HKEY key;
BOOL res = FALSE;
HKEY hkey;
openvpn_swprintf(reg_path, _countof(reg_path), L"SOFTWARE\\" PACKAGE_NAME);

LONG status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &key);
LONG status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &hkey);
if (status != ERROR_SUCCESS)
{
return res;
return false;
}

/* The default value of REG_KEY is the install path */
status = RegGetValueW(key, NULL, NULL, RRF_RT_REG_SZ, NULL, (LPBYTE)path, &size);
res = status == ERROR_SUCCESS;
status = RegGetValueW(hkey, NULL, key, RRF_RT_REG_SZ, NULL, (LPBYTE)value, &size);

RegCloseKey(key);
RegCloseKey(hkey);

return res;
return status == ERROR_SUCCESS;
}

static void
Expand All @@ -1554,7 +1551,7 @@ set_openssl_env_vars()
const WCHAR *ssl_fallback_dir = L"C:\\Windows\\System32";

WCHAR install_path[MAX_PATH] = { 0 };
if (!get_install_path(install_path, _countof(install_path)))
if (!get_openvpn_reg_value(NULL, install_path, _countof(install_path)))
{
/* if we cannot find installation path from the registry,
* use Windows directory as a fallback
Expand Down Expand Up @@ -1633,4 +1630,60 @@ win32_sleep(const int n)
}
}
}

bool
plugin_in_trusted_dir(const WCHAR *plugin_path)
{
/* UNC paths are not allowed */
if (wcsncmp(plugin_path, L"\\\\", 2) == 0)
{
msg(M_WARN, "UNC paths for plugins are not allowed.");
return false;
}

WCHAR plugin_dir[MAX_PATH] = { 0 };

/* Attempt to retrieve the trusted plugin directory path from the registry,
* using installation path as a fallback */
if (!get_openvpn_reg_value(L"plugin_dir", plugin_dir, _countof(plugin_dir))
&& !get_openvpn_reg_value(NULL, plugin_dir, _countof(plugin_dir)))
{
msg(M_WARN, "Installation path could not be determined.");
}

/* Get the system directory */
WCHAR system_dir[MAX_PATH] = { 0 };
if (GetSystemDirectoryW(system_dir, _countof(system_dir)) == 0)
{
msg(M_NONFATAL | M_ERRNO, "Failed to get system directory.");
}

if ((wcslen(plugin_dir) == 0) && (wcslen(system_dir) == 0))
{
return false;
}

WCHAR normalized_plugin_dir[MAX_PATH] = { 0 };

/* Normalize the plugin dir */
if (wcslen(plugin_dir) > 0)
{
if (!GetFullPathNameW(plugin_dir, MAX_PATH, normalized_plugin_dir, NULL))
{
msg(M_NONFATAL | M_ERRNO, "Failed to normalize plugin dir.");
return false;
}
}

/* Check if the plugin path resides within the plugin/install directory */
if ((wcslen(normalized_plugin_dir) > 0) && (wcsnicmp(normalized_plugin_dir,
plugin_path, wcslen(normalized_plugin_dir)) == 0))
{
return true;
}

/* Fallback to the system directory */
return wcsnicmp(system_dir, plugin_path, wcslen(system_dir)) == 0;
}

#endif /* ifdef _WIN32 */
27 changes: 27 additions & 0 deletions src/openvpn/win32.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,5 +333,32 @@ openvpn_swprintf(wchar_t *const str, const size_t size, const wchar_t *const for
/* Sleep that can be interrupted by signals and exit event */
void win32_sleep(const int n);

/**
* @brief Fetches a registry value for OpenVPN registry key.
*
* @param key Registry value name to fetch.
* @param value Buffer to store the fetched string value.
* @param size Size of `value` buffer in bytes.
* @return `true` if successful, `false` otherwise.
*/
bool
get_openvpn_reg_value(const WCHAR *key, WCHAR *value, DWORD size);

/**
* @brief Checks if a plugin is located in a trusted directory.
*
* Verifies the plugin's path against a trusted directory, which is:
*
* - "plugin_dir" registry value or installation path, if the registry key is missing
* - system directory
*
* UNC paths are explicitly disallowed.
*
* @param plugin_path Normalized path to the plugin.
* @return \c true if the plugin is in a trusted directory and not a UNC path; \c false otherwise.
*/
bool
plugin_in_trusted_dir(const WCHAR *plugin_path);

#endif /* ifndef OPENVPN_WIN32_H */
#endif /* ifdef _WIN32 */

0 comments on commit 05d321e

Please sign in to comment.