return tmpdir;
}
+bool
+win_path_in_dir(const WCHAR *path, const WCHAR *dir)
+{
+ size_t dir_len = wcslen(dir);
+ /* dir_len <= 1 guards the dir[dir_len - 1] access below and rejects a
+ * degenerate single-character directory (a normalized absolute path is
+ * always longer). */
+ if (dir_len <= 1 || wcsnicmp(dir, path, dir_len) != 0)
+ {
+ return false;
+ }
+
+ /* A plain prefix match is not sufficient: if dir is "C:\foo" then
+ * "C:\foo_evil\bar.dll" shares the prefix but is not inside "C:\foo".
+ * Require that the matched prefix ends on a path separator, i.e. either
+ * dir already ends with a separator or the character following the prefix
+ * in path is one. */
+ if (dir[dir_len - 1] == L'\\' || dir[dir_len - 1] == L'/')
+ {
+ return true;
+ }
+
+ WCHAR next = path[dir_len];
+ return next == L'\\' || next == L'/';
+}
+
#endif /* _WIN32 */
/* Find temporary directory */
const char *win_get_tempdir(void);
+/**
+ * Check whether @p path resides within directory @p dir.
+ *
+ * Unlike a plain string prefix match, this requires the match to end on a
+ * path-component boundary, so that "C:\\foo_evil\\bar" is not considered to be
+ * inside "C:\\foo". Both arguments are expected to be normalized absolute
+ * paths. The comparison is case-insensitive.
+ *
+ * @return true if @p path is inside @p dir, false otherwise.
+ */
+bool win_path_in_dir(const WCHAR *path, const WCHAR *dir);
+
#endif /* OPENVPN_WIN32_UTIL_H */
#endif /* ifdef _WIN32 */
}
/* 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))
+ if (win_path_in_dir(plugin_path, normalized_plugin_dir))
{
return true;
}
/* Fallback to the system directory */
- return wcsnicmp(system_dir, plugin_path, wcslen(system_dir)) == 0;
+ return win_path_in_dir(plugin_path, system_dir);
}
bool
#include "test_common.h"
#include "list.h"
#include "mock_msg.h"
+#ifdef _WIN32
+#include "win32-util.h"
+#endif
static void
test_compat_lzo_string(void **state)
mock_set_debug_level(saved_log_level);
}
-const struct CMUnitTest misc_tests[] = { cmocka_unit_test(test_compat_lzo_string),
- cmocka_unit_test(test_auth_fail_temp_no_flags),
- cmocka_unit_test(test_auth_fail_temp_flags),
- cmocka_unit_test(test_auth_fail_temp_flags_msg),
- cmocka_unit_test(test_list),
- cmocka_unit_test(test_atoi_variants) };
+#ifdef _WIN32
+static void
+test_win_path_in_dir(void **state)
+{
+ /* plugin/install dir without trailing separator */
+ assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L"C:\\openvpn_plugins"));
+
+ /* the bug being fixed: a sibling dir sharing the prefix must NOT match */
+ assert_false(win_path_in_dir(L"C:\\openvpn_plugins_evil\\foo.dll", L"C:\\openvpn_plugins"));
+
+ /* trusted dir with trailing separator */
+ assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L"C:\\openvpn_plugins\\"));
+ assert_false(win_path_in_dir(L"C:\\openvpn_plugins_evil\\foo.dll", L"C:\\openvpn_plugins\\"));
+
+ /* forward slash separator in the candidate path is accepted */
+ assert_true(win_path_in_dir(L"C:\\openvpn_plugins/foo.dll", L"C:\\openvpn_plugins"));
+
+ /* comparison is case-insensitive */
+ assert_true(win_path_in_dir(L"c:\\OPENVPN_PLUGINS\\foo.dll", L"C:\\openvpn_plugins"));
+
+ /* the directory itself (no trailing component) is not "in" the directory */
+ assert_false(win_path_in_dir(L"C:\\openvpn_plugins", L"C:\\openvpn_plugins"));
+
+ /* nested subdirectories are still inside */
+ assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\sub\\foo.dll", L"C:\\openvpn_plugins"));
+
+ /* an empty trusted dir never matches */
+ assert_false(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L""));
+}
+#endif /* _WIN32 */
+
+const struct CMUnitTest misc_tests[] = {
+#ifdef _WIN32
+ cmocka_unit_test(test_win_path_in_dir),
+#endif
+ cmocka_unit_test(test_compat_lzo_string),
+ cmocka_unit_test(test_auth_fail_temp_no_flags),
+ cmocka_unit_test(test_auth_fail_temp_flags),
+ cmocka_unit_test(test_auth_fail_temp_flags_msg),
+ cmocka_unit_test(test_list),
+ cmocka_unit_test(test_atoi_variants)
+};
int
main(void)