From patchwork Wed Sep 24 20:15:56 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 4451 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:c08a:b0:72f:f16c:e055 with SMTP id jr10csp1793241mab; Wed, 24 Sep 2025 13:16:30 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXhEMFB6xeSq7ohV46coIt9pPp6IhXb62rY+NcEVSa6K8k0x/CV8R8PYS8+S/R7E4ZdvQHDl58L/8M=@openvpn.net X-Google-Smtp-Source: AGHT+IH1q5fJpce20nW0LrEc5lNCYwTCxk/lWYIa61ljdY1++A07fHGd/HGdHqnxbQ5hQWmQ7kmi X-Received: by 2002:a05:6808:1815:b0:43d:2bbe:bfba with SMTP id 5614622812f47-43f4cf313a2mr688453b6e.23.1758744989946; Wed, 24 Sep 2025 13:16:29 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1758744989; cv=none; d=google.com; s=arc-20240605; b=KZ12fBebwd5xTfL45FmyjyOuF8Dolbg+iOUdknnUTosPTcOsAYpC7xJ+AOpMy9IVB8 w9+KQo2yORnGxbTfNPYjNZz+OZMtXgnXbmIXf5dPv7aH+19wSbT81d8NmkfR0qBoJtDa 87dujLhPoKQuuWMUc8yTyJ7wlEI2nFV+NbsLWkHVFq6whD2EZQDmEzYQ58uvLtbr9WvH Xdiz8HaoZlInjlsiSs4ncRezK/VDxFjlTDxz+jl8YZ1hqWiFVX35l/5TM5ZJ9rW6G3eO 4BVXn7R6j5Tp65mhYh+7Tn3IfdHHSpgbmUT1VygE0EEaqyLc0jc1l9ilkXp3jsLU+DDY 6TaA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature; bh=lBOuQwVwocD2HBdJzCIUAT93+Yul7ozKkbA5+oH+24E=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=MxWs9RM9PkLMPprbrLg+xlgCLddmd+Dch2wTq+4HCMFMwicwhzR026YrtGze3lhe6p VtpLUjbWQ8l0f1x3lQPW5lYussJxrnjkg3p34aOmklhezco9NO4eVI1hRpe4565qW86m 5yBP/HPxShlPgbx/y/uy6ZnYgH/eUjhoP6Aq2dXmBNXogNl+BDqW++fqzpIHHDSOICPL 8QAypGiFimyJAY+PLCiVVBed2KYyluku7rlGlQWoTBg2xafjqww+sJqBTVJ0tMF9reEx UqigS9+i8EVaY5SSOygkk34MI78ggKsK0NjEMr9hSxfzxvHFlHbHllTlxK2YxpgNkiAS kwRA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=gimWMXT+; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=DzX4D2db; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=U5nVdxnp; 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=NONE dis=NONE) header.from=muc.de Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 46e09a7af769-78daaa90201si1993324a34.306.2025.09.24.13.16.29 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 24 Sep 2025 13:16:29 -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=pass header.i=@lists.sourceforge.net header.s=beta header.b=gimWMXT+; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=DzX4D2db; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=U5nVdxnp; 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=NONE dis=NONE) header.from=muc.de DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=lBOuQwVwocD2HBdJzCIUAT93+Yul7ozKkbA5+oH+24E=; b=gimWMXT+uX1kRekqyibTM/u4XA uVsr/QBuZkOMdcyuLxPm8GNGELxfVl10AOmRmRDcKgm6sTcjonV6JnJ/6k9DYUosiUuy+Q+Rvz/nJ 8oTBa/LFBGoNu3cGj9hZX+sxr1NwhputcOQubINkcgjwvuXLm/s+G/zSKOJDbQ10hoOY=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1v1Vud-0006ba-Na; Wed, 24 Sep 2025 20:16:27 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1v1Vua-0006bL-Uh for openvpn-devel@lists.sourceforge.net; Wed, 24 Sep 2025 20:16:24 +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:References: In-Reply-To:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=iRop0ioOaIw6OfdkDvBwuRurqUG6xck8Lj8htY8pPaQ=; b=DzX4D2dbo8xBOUl8L7QxSk2V2N 4381JBYI6iPBw8zFM/uPe2DX4+5PNg9BoHMPfMurf4X5f6M+b4gLyTlXTpFjaiy3vPm1lNibDPxe7 hLF/NwL9VQvIyhH9wcRfd8QdydM0leTnXpVaF5CEDtMfT+dx6H4m5uJC8yMZa8E8n11E=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=iRop0ioOaIw6OfdkDvBwuRurqUG6xck8Lj8htY8pPaQ=; b=U5nVdxnpBhESwsQxrNb8RfGJ8X 81BgIGicLVlzPlsVvvcc9sfPA1sjX09gU457sFpwSycSYsy7vgjpVPu4SYNkc/nXvoaWQUb/xzXst 85clqEu/++nWx9sDegFhZCX7KA1UBqUFGB22m/zsS51rBSEMHuFvZT/i/yuVsaPsEDjM=; Received: from [193.149.48.134] (helo=blue.greenie.muc.de) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1v1VuQ-00067D-B2 for openvpn-devel@lists.sourceforge.net; Wed, 24 Sep 2025 20:16:16 +0000 Received: from blue.greenie.muc.de (localhost [127.0.0.1]) by blue.greenie.muc.de (8.18.1/8.18.1) with ESMTP id 58OKG234025322 for ; Wed, 24 Sep 2025 22:16:02 +0200 Received: (from gert@localhost) by blue.greenie.muc.de (8.18.1/8.18.1/Submit) id 58OKG2Ht025321 for openvpn-devel@lists.sourceforge.net; Wed, 24 Sep 2025 22:16:02 +0200 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Wed, 24 Sep 2025 22:15:56 +0200 Message-ID: <20250924201601.25304-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.49.1 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: 1.3 (+) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", 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: From: Lev Stipakov This adds validation of following DNS options: --dns search-domains --dns server N resolve-domains --dns server N sni Content analysis details: (1.3 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1v1VuQ-00067D-B2 Subject: [Openvpn-devel] [PATCH v1] Validate DNS parameters 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1844177786351378236?= X-GMAIL-MSGID: =?utf-8?q?1844177786351378236?= From: Lev Stipakov This adds validation of following DNS options: --dns search-domains --dns server N resolve-domains --dns server N sni --dhcp-option DOMAIN --dhcp-option ADAPTER_DOMAIN_SUFFIX --dhcp-option DOMAIN-SEARCH On Linux (and similar platforms), those options are written to a tmp file, which is later sourced by a script running as root. Since options are controlled by the server, it is possible for a malicious server to execute script injection attack by pushing something like --dns search-domains x;id in which case "id" command will be executed as a root. On Windows, the value of DOMAIN/ADAPTER_DOMAIN_SUFFIX is passed to a powershell script. A malicious server could push: --dhcp-option DOMAIN a';Restart-Computer' and if openvpn is not using DHCP (this is the default, with dco-win driver) and and running without interactive service, that powershell command will be executed. Validation is performed in a way that value only contains following symbols: [A-Za-z0-9.-_\x80-\0xff] Reported-By: Stanislav Fort CVE: 2025-10680 Change-Id: I09209ccd785cc368b2fcf467a3d211fbd41005c6 Signed-off-by: Lev Stipakov Acked-by: Gert Doering Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1213 --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1213 This mail reflects revision 1 of this Change. Acked-by according to Gerrit (reflected above): Gert Doering diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 217f897..1247f11 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -61,6 +61,7 @@ dco_win.c dco_win.h \ dhcp.c dhcp.h \ dns.c dns.h \ + domain_helper.h \ env_set.c env_set.h \ errlevel.h \ error.c error.h \ diff --git a/src/openvpn/dns.c b/src/openvpn/dns.c index 2a9e60b..d2ff670 100644 --- a/src/openvpn/dns.c +++ b/src/openvpn/dns.c @@ -30,6 +30,7 @@ #include "socket_util.h" #include "options.h" #include "run_command.h" +#include "domain_helper.h" #ifdef _WIN32 #include "win32.h" @@ -143,7 +144,7 @@ return true; } -void +bool dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc) { /* Fast forward to the end of the list */ @@ -155,11 +156,19 @@ /* Append all domains to the end of the list */ while (*domains) { + char *domain = *domains++; + if (!validate_domain(domain)) + { + return false; + } + ALLOC_OBJ_CLEAR_GC(*entry, struct dns_domain, gc); struct dns_domain *new = *entry; - new->name = *domains++; + new->name = domain; entry = &new->next; } + + return true; } bool diff --git a/src/openvpn/dns.h b/src/openvpn/dns.h index 6cd98f2..3dd7847 100644 --- a/src/openvpn/dns.h +++ b/src/openvpn/dns.h @@ -141,13 +141,14 @@ struct dns_server *dns_server_get(struct dns_server **entry, long priority, struct gc_arena *gc); /** - * Appends DNS domain parameters to a linked list. + * Appends safe DNS domain parameters to a linked list. * * @param entry Address of the first list entry pointer * @param domains Address of the first domain parameter * @param gc The gc the new list items should be allocated in + * @return True if domains were appended and don't contain invalid characters */ -void dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc); +bool dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc); /** * Parses a string IPv4 or IPv6 address and optional colon separated port, diff --git a/src/openvpn/domain_helper.h b/src/openvpn/domain_helper.h new file mode 100644 index 0000000..f1ecf86 --- /dev/null +++ b/src/openvpn/domain_helper.h @@ -0,0 +1,45 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2025 Lev Stipakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +static inline bool +is_allowed_domain_ascii(unsigned char c) +{ + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '.' || c == '-' || c == '_' || c >= 0x80; +} + +static inline bool +validate_domain(const char *domain) +{ + for (const char *ch = domain; *ch; ++ch) + { + if (!is_allowed_domain_ascii((unsigned char)*ch)) + { + return false; + } + } + + return true; +} diff --git a/src/openvpn/options.c b/src/openvpn/options.c index f801743..f35738d 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -61,6 +61,7 @@ #include "dco.h" #include "options_util.h" #include "tun_afunix.h" +#include "domain_helper.h" #include @@ -5877,8 +5878,12 @@ { if (streq(p[1], "search-domains") && p[2]) { - dns_domain_list_append(&options->dns_options.search_domains, &p[2], - &options->dns_options.gc); + if (!dns_domain_list_append(&options->dns_options.search_domains, &p[2], + &options->dns_options.gc)) + { + msg(msglevel, "--dns %s contain invalid characters", p[1]); + return false; + } } else if (streq(p[1], "server") && p[2] && p[3] && p[4]) { @@ -5906,7 +5911,11 @@ } else if (streq(p[3], "resolve-domains")) { - dns_domain_list_append(&server->domains, &p[4], &options->dns_options.gc); + if (!dns_domain_list_append(&server->domains, &p[4], &options->dns_options.gc)) + { + msg(msglevel, "--dns server %ld: %s contain invalid characters", priority, p[3]); + return false; + } } else if (streq(p[3], "dnssec") && !p[5]) { @@ -5950,6 +5959,11 @@ } else if (streq(p[3], "sni") && !p[5]) { + if (!validate_domain(p[4])) + { + msg(msglevel, "--dns server %ld: %s contains invalid characters", priority, p[3]); + return false; + } server->sni = p[4]; } else @@ -8551,11 +8565,23 @@ if ((streq(p[1], "DOMAIN") || streq(p[1], "ADAPTER_DOMAIN_SUFFIX")) && p[2] && !p[3]) { + if (!validate_domain(p[2])) + { + msg(msglevel, "--dhcp-option %s contains invalid characters", p[1]); + goto err; + } + dhcp->domain = p[2]; dhcp_optional = true; } else if (streq(p[1], "DOMAIN-SEARCH") && p[2] && !p[3]) { + if (!validate_domain(p[2])) + { + msg(msglevel, "--dhcp-option %s contains invalid characters", p[1]); + goto err; + } + if (dhcp->domain_search_list_len < N_SEARCH_LIST_LEN) { dhcp->domain_search_list[dhcp->domain_search_list_len++] = p[2];