From patchwork Tue Mar 19 13:53:45 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lev Stipakov X-Patchwork-Id: 3654 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:f20a:b0:55c:c090:46f0 with SMTP id sk10csp1911041mab; Tue, 19 Mar 2024 06:55:59 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXA5/DMSWCVt2b8gi4vMLcW4oXKGVwKigqd/5ekTT6yzT/XVg/bHRQZvLFP/+YHZJqfqYxZg9kkeWZdVX4zsvhICaTlZyI= X-Google-Smtp-Source: AGHT+IH4AfGvpDK2L97m4zJOsUacA7hYajJOcygvY+lTz5khuzOMy1QZduQhnvtYkrW9r8TowoGy X-Received: by 2002:a05:6a21:3392:b0:1a3:6b56:d504 with SMTP id yy18-20020a056a21339200b001a36b56d504mr2271024pzb.5.1710856559091; Tue, 19 Mar 2024 06:55:59 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1710856559; cv=none; d=google.com; s=arc-20160816; b=ItMu5U4UEzVkswHEb5kzepS9lYPaVE1he7HdXtrL4Z4wS9uoFzfPWRX3S8wq5uLLVV Mf5x2zqjIIq11sGkE/6swk2cwRbQ0RO6WI6vP4KZBM61lBd5L7KFpdQNNVyoVLcFwYgr j/JCmMTz5xw3ybbFnHgtMBsrdisFtIyMhFxNJdcIfU1aOonVBPK0hY5A0ae1jbeMzlGE b74GxrZXEmLxaQ/YlmTLgqHKxZv8z+fOV9Gb0PI+DsHcH2RSZw6D0Q4utZUdaBmeZ5iO 5WmGkOIPiSbvk91lqkPuuZ4qI3jD+Pxk00Kv0o0JmWXQwAr3Z3q3MEzJ3gCZQl2Laxdq oSJA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:content-transfer-encoding:cc:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:message-id:date:to:from:dkim-signature:dkim-signature :dkim-signature; bh=o5eG6qrMHFAV8ePWLyB1fftCwvXOyyBJRbzMbW0F0nY=; fh=8p5ATZZN/nEIGSHZpGVXtZecpqjT7+rrnR1opigjIqw=; b=tPWutGSv7oZ+FBqejPD/K+uVi0YfDo5atTZsnYSFw76MHuSnf5/sWl2HR/AiqWINh9 0VX3oFl6C5/l5aYTmD//mzxPHHVxXKSyNtdOdbarC1UONFiGwXuiwa1feITMKY5zRR+Q FxBfYHbxOfgIQkgJxg/C6uM6BwyFAGfMexw3YijerXaImm7+Jrx/hFbQU8F2LYYiS6lh Ry95krTpT7AC+Zhd0h9hZf+2DKrFgvP7R8h/12Tb24KlEhs/u7tYDwoZ4GrvPUZsiuHT ncyVsR0NMTZkSGd00Pljnzj+xxDqtzOuF/s5QjN3AeM7MvoJNt/UgvSKnYjElODhGp+/ BKmg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=RUzBurWZ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Iw7nBKVY; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=TbdtyHLn; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id s66-20020a632c45000000b005dc4f9ceccdsi10331136pgs.609.2024.03.19.06.55.58 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 19 Mar 2024 06:55:59 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=RUzBurWZ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Iw7nBKVY; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=TbdtyHLn; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1rmZw7-0007IN-0j; Tue, 19 Mar 2024 13:55:27 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1rmZw5-0007IH-Eg for openvpn-devel@lists.sourceforge.net; Tue, 19 Mar 2024 13:55:25 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=2oQanM1Q4JI5evZL2Ju+VAVlO984FhiacliN7hWV0+I=; b=RUzBurWZUEvrRlzKeAcFIK5Qeq wbzIjT9fasICQph6Oa2z+x8L93ACl9rPB7cytR/Q0H8GugU+v6UGVy5QBwHeW6RhqYplTtLrEMXxc WIextrwx8kxpZPa0Be+3boExwXEeM8YtMJ129nZTGAUIADdx/8OikY0u0Kc6xEmnfpuE=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject:Cc:To:From :Sender:Reply-To:Content-Type:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To: References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post: List-Owner:List-Archive; bh=2oQanM1Q4JI5evZL2Ju+VAVlO984FhiacliN7hWV0+I=; b=I w7nBKVYfK3sLY1HFLIFNkOa5JxRe2sGkxx9Is83dqPSVR9z0brW1Fm+dlpURu9bcf4XfXeySBaHXF lntUnD5+pd5/RmGRb1Qw45XZtFZNHnx+s9uLWhdTvKmXpQtyIU87aH5Y3UgLaCI9yN2oYO4aGG2Dd kLKqxcG6VLjQnqI4=; Received: from mail-pl1-f173.google.com ([209.85.214.173]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.95) id 1rmZvu-0000DV-6p for openvpn-devel@lists.sourceforge.net; Tue, 19 Mar 2024 13:55:25 +0000 Received: by mail-pl1-f173.google.com with SMTP id d9443c01a7336-1dca3951ad9so36520915ad.3 for ; Tue, 19 Mar 2024 06:55:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1710856511; x=1711461311; darn=lists.sourceforge.net; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=2oQanM1Q4JI5evZL2Ju+VAVlO984FhiacliN7hWV0+I=; b=TbdtyHLn4dPBmRf1ZeRtLWdUENCtgY3jjjwxHV3hWULomOvVXws3YUtIYF76PVpIwU CVgupMuCZ0JLzRMKfOGZ8wr+0WH/I4fa2uvzx8hb5Uai+iqnTKRRB9TfcIeijLHXtPHK VKnj37WNwIdAfzTkZzj9WTAvPVYNt6OQBmv2hiYyydELcTDdTVPZrcvo9mBcSLAI2P0J yg/hDrEFZQYJYxLovdOHaGrtfNK19Z4gXbCY/W1VtnqTW6EenevBhgEInzN7Zo6wAo7J PGGppRt3al6LSQIkFD3dvjWxfEAuD8gZkpI2HWsSu0oeGXhdOILwLkkxjTwmjZZcoMvy nbUw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1710856511; x=1711461311; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=2oQanM1Q4JI5evZL2Ju+VAVlO984FhiacliN7hWV0+I=; b=eriWsncKs4XXQVsC9UpUWQk2PUC2oLhsFjvVJ/mMmc1DaPUwCCfOLvpY7ICajP3ge1 aMnebmnZoh0v9EqYYKQYzLU84rccxToOxvceRAHV0RPHCoN8OPDWfiCjZXHn/pq9oKv0 yK4JfJkZgDxAF4mk+XXWs/TB8B5NyGXMhHeCiydjk7tzzvXjPKUVPUNODv2KkFmdyAbU D8C/lqQvBX769F/ClvKVeT0Oc7Kng5pjxEIzcs8Otl8TGySdXiF+ZL8kWczjfdR0LSvW kVh7hQDGoxsbw3RUQg7hxF9TGin0mtaN58MGFMD0ZZpegRsc7mgNgMnJUPdUqgfQDOWU P3Zg== X-Gm-Message-State: AOJu0YxMJ4wN2GK4aCaRU30yajmv/1OGh3n468NX/oSS7Lkj6BMYdDy+ PGobVmiLGEX6HCwYCIteiRg8IWoVnbjtpQaE8timiuVKwnUgDolmsG/ECFC7gE8= X-Received: by 2002:a17:902:e5cb:b0:1e0:15c3:521b with SMTP id u11-20020a170902e5cb00b001e015c3521bmr3647467plf.45.1710856510671; Tue, 19 Mar 2024 06:55:10 -0700 (PDT) Received: from localhost.localdomain ([2a00:1d50:3:0:21d0:d153:5fa3:f06b]) by smtp.gmail.com with ESMTPSA id q13-20020a170902dacd00b001db616fa11dsm11367325plx.238.2024.03.19.06.55.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 Mar 2024 06:55:10 -0700 (PDT) From: Lev Stipakov X-Google-Original-From: Lev Stipakov To: openvpn-devel@lists.sourceforge.net Date: Tue, 19 Mar 2024 15:53:45 +0200 Message-ID: <20240319135355.1279-2-lev@openvpn.net> X-Mailer: git-send-email 2.42.0.windows.2 MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-1.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: 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) Content analysis details: (-0.2 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider [lstipakov[at]gmail.com] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [209.85.214.173 listed in list.dnswl.org] 0.0 RCVD_IN_MSPIKE_H3 RBL: Good reputation (+3) [209.85.214.173 listed in wl.mailspike.net] -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 0.0 RCVD_IN_MSPIKE_WL Mailspike good senders -0.0 T_SCC_BODY_TEXT_LINE No description available. X-Headers-End: 1rmZvu-0000DV-6p Subject: [Openvpn-devel] [PATCH] win32: Enforce loading of plugins from a trusted directory X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Lev Stipakov , Vladimir Tokarev Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1793963127263118471?= X-GMAIL-MSGID: =?utf-8?q?1793963127263118471?= 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 Signed-off-by: Lev Stipakov Acked-by: Selva Nair --- src/openvpn/plugin.c | 18 +++++++++-- src/openvpn/win32.c | 77 +++++++++++++++++++++++++++++++++++++------- src/openvpn/win32.h | 27 ++++++++++++++++ 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/openvpn/plugin.c b/src/openvpn/plugin.c index b4d4a986..3d62eced 100644 --- a/src/openvpn/plugin.c +++ b/src/openvpn/plugin.c @@ -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) diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c index 6b7ba5e4..7ed32811 100644 --- a/src/openvpn/win32.c +++ b/src/openvpn/win32.c @@ -1497,27 +1497,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 @@ -1526,7 +1523,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 @@ -1605,4 +1602,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 */ diff --git a/src/openvpn/win32.h b/src/openvpn/win32.h index aa8513b2..2f147c09 100644 --- a/src/openvpn/win32.h +++ b/src/openvpn/win32.h @@ -330,5 +330,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 */