From patchwork Mon Nov 17 17:28:59 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 4612 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:6d04:b0:7b1:439f:bdf with SMTP id e4csp3403027may; Mon, 17 Nov 2025 09:39:03 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCXK0ja3wz/Ca+dYlb2e7xNjS+nnvviH7eEIQ6zScw8CSweEKfc4+BWIoLkxXOmv6qtHveE+WOPpeoY=@openvpn.net X-Google-Smtp-Source: AGHT+IHzhvvl/yyX39cvjwMNQBL9siJGUHhlUAHQnYsxBvAgOc34pZMNDoIRvor74lRZcnj3v5iM X-Received: by 2002:a05:6830:448c:b0:7c7:69c8:2cb with SMTP id 46e09a7af769-7c769c80f29mr2735989a34.24.1763401143062; Mon, 17 Nov 2025 09:39:03 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1763401143; cv=none; d=google.com; s=arc-20240605; b=hVGsZMIr+EHy71zZH7OJtJ/l2ZCVgunJ+Rxpw2sPdD5vutFqgaM9CJELvqTOuGtiHI BvSHWyq/E/rDwjES6VO4WRQQQZch2Rb5CWg6MOfOqZ8U8eG0rjLZIcCuhVdrw52Urcot oKd/ZxigYPOeSVumCa4wYJvf1z4RxTqDCgfHWCVus9fWL5XmkSX9whTjmqHnKKMdffJH DKYumS40IzS1WMaj2nk+qT4xfUq0hGF7ZA1SHgoB/Qy4J0H6FC6+k21xP205VUVIicYs 0uqnQW3ByxuhiwpT67V8uMh2wcZPraZWAIpLOUdhoYKSy8YhMmhrSrICjaFqlRHTkA/S Ge5g== 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=no/GEUXLKKaJiDTnZ7msPsdb08tWpKFEcxWMfXqaMPE=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=QfnOBKnfsAmzIQBQMmptW3PgnqANTJJLRwx6puQ7qfREaTh3UczbCFa6XpspS08VnI za/x8iLseUGgQlYjuZ3fRQlSH8UXzqrRlHK9diK9/E2j9F1H7AeUFiU+V37s43Lt3EQo /6lkPjQU93VGK4y+ZE7yiZm87hdno48CkHGV7s1C6WDABZ1DKoyFYp/wp5u4dAq8OwST vZiHZRVbyzNsb0YW5t5/ggGzNhG55hc6eT3jJ76nztj8NK4OJLYcFFMIlH5A/MG/QVnv x3U5aFLq+LHvz6K4Ll28RJqoj4Lf91n+e5XgepeeOK2iECOzerpXLVEMeXvXGKSuEbSe u5dw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="mPTG/wkO"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=iMOFbhqd; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=SuoLdMvv; 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-7c758e96685si1693511a34.166.2025.11.17.09.39.02 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 17 Nov 2025 09:39:02 -0800 (PST) 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="mPTG/wkO"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=iMOFbhqd; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=SuoLdMvv; 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=no/GEUXLKKaJiDTnZ7msPsdb08tWpKFEcxWMfXqaMPE=; b=mPTG/wkOWwHEZNAvntZm8j8pvV gECagPgcVPZSpzC5YGPDwb97AqWYWqtimQB0DtqeXi+d7MWWlzpjqhSc4oUDRqtEWWX8MKayTh24C VQXVhosLtUvTTgmYJcOFHAppEjXbvf0o82KmGhjO+ZmIhEH/Ky62g2wMK+LBK52l80EA=; 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 1vL3Bs-0003JW-7B; Mon, 17 Nov 2025 17:39:00 +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 1vL3Bq-0003JQ-Se for openvpn-devel@lists.sourceforge.net; Mon, 17 Nov 2025 17:38:58 +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=OjKp9HiGayUsnxgXRLUGsOSGa7zegj8JcvdBcb3OShw=; b=iMOFbhqdyY+0L25ukqFn3ID1eK YGQwlOpepSi3pDdxJ6JvBN9O8mB2relmjdcQqHf0xsm/qQE4ejF2IEctjxPpuxaaJVQcc4UnbkXj3 uJWLUIsZITOJqR4G3VzdSkbKPrOzzwXl+H+JoEo/OP71Trn2J27tAj76JswMNBOXvfsE=; 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=OjKp9HiGayUsnxgXRLUGsOSGa7zegj8JcvdBcb3OShw=; b=SuoLdMvva8hgu30Z+E/LOVpzV0 UnJN6zlZUhWyyJQLQzLqqY40ni8FEK9l7/TXfl3Uo3isKGZneYpOGFln1OUU+sGnO3Q8R/c85Po8A cjuHOLI9shFlOsWLsmdAh/08OofnvAtOCBDCMeulBBKXJpjot3Ny2YsZsFQAzerrCTPY=; Received: from chekov.greenie.muc.de ([193.149.48.178]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1vL3Bq-0007yP-0o for openvpn-devel@lists.sourceforge.net; Mon, 17 Nov 2025 17:38:58 +0000 Received: from chekov.greenie.muc.de (localhost [IPv6:0:0:0:0:0:0:0:1]) by chekov.greenie.muc.de (8.18.1/8.18.1) with ESMTPS id 5AHHckle010433 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO) for ; Mon, 17 Nov 2025 18:38:46 +0100 (CET) (envelope-from gert@chekov.greenie.muc.de) Received: (from gert@localhost) by chekov.greenie.muc.de (8.18.1/8.18.1/Submit) id 5AHHckHR010432 for openvpn-devel@lists.sourceforge.net; Mon, 17 Nov 2025 18:38:46 +0100 (CET) (envelope-from gert) From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Mon, 17 Nov 2025 18:28:59 +0100 Message-ID: <20251117173843.10091-3-gert@greenie.muc.de> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251117173843.10091-1-gert@greenie.muc.de> References: <20251117173843.10091-1-gert@greenie.muc.de> MIME-Version: 1.0 X-Spam-Score: 0.0 (/) 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: Arne Schwabe This is a stupid mistake but causes all hmac cookies to be accepted, thus breaking source IP address validation. As a consequence, TLS sessions can be openend and state can be consumed in the server f [...] Content analysis details: (0.0 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- X-Headers-End: 1vL3Bq-0007yP-0o Subject: [Openvpn-devel] [PATCH 2/2] Fix memcmp check for the hmac verification in the 3way handshake being inverted 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?1849060116992125605?= X-GMAIL-MSGID: =?utf-8?q?1849060116992125605?= From: Arne Schwabe This is a stupid mistake but causes all hmac cookies to be accepted, thus breaking source IP address validation. As a consequence, TLS sessions can be openend and state can be consumed in the server from IP addresses that did not initiate an initial connection. While at it, fix check to only allow [t-2;t] timeslots, disallowing HMACs coming in from a future timeslot. Github: OpenVPN/openvpn-private-issues#56 CVE: 2025-13086 Reported-By: Joshua Rogers Found-by: ZeroPath (https://zeropath.com/) Reported-By: stefan@srlabs.de Change-Id: I9cbe2bf535575b47ddd7f34e985c5c1c6953a6fc Signed-off-by: Arne Schwabe Acked-by: Max Fillinger --- src/openvpn/ssl_pkt.c | 7 ++-- tests/unit_tests/openvpn/test_pkt.c | 58 ++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c index f216e88b..85a55b15 100644 --- a/src/openvpn/ssl_pkt.c +++ b/src/openvpn/ssl_pkt.c @@ -548,13 +548,14 @@ check_session_hmac_and_pkt_id(struct tls_pre_decrypt_state *state, } - /* check adjacent timestamps too */ - for (int offset = -2; offset <= 1; offset++) + /* check adjacent timestamps too, the handwindow is split in 2 for the + * offset, so we check the current timeslot and the two before that */ + for (int offset = -2; offset <= 0; offset++) { struct session_id expected_id = calculate_session_id_hmac(state->peer_session_id, from, hmac, handwindow, offset); - if (memcmp_constant_time(&expected_id, &state->server_session_id, SID_SIZE)) + if (memcmp_constant_time(&expected_id, &state->server_session_id, SID_SIZE) == 0) { return true; } diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c index 1423d469..c2aff8c6 100644 --- a/tests/unit_tests/openvpn/test_pkt.c +++ b/tests/unit_tests/openvpn/test_pkt.c @@ -406,6 +406,8 @@ test_verify_hmac_tls_auth(void **ut_state) hmac_ctx_t *hmac = session_id_hmac_init(); struct link_socket_actual from = { 0 }; + from.dest.addr.sa.sa_family = AF_INET; + from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304); struct tls_auth_standalone tas = { 0 }; struct tls_pre_decrypt_state state = { 0 }; @@ -433,10 +435,12 @@ test_verify_hmac_tls_auth(void **ut_state) static void test_verify_hmac_none(void **ut_state) { + now = 1000; hmac_ctx_t *hmac = session_id_hmac_init(); struct link_socket_actual from = { 0 }; from.dest.addr.sa.sa_family = AF_INET; + from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304); struct tls_auth_standalone tas = { 0 }; struct tls_pre_decrypt_state state = { 0 }; @@ -451,9 +455,61 @@ test_verify_hmac_none(void **ut_state) verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); assert_int_equal(verdict, VERDICT_VALID_ACK_V1); + /* This packet has a random hmac, so it should fail to validate */ bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true); + assert_false(valid); + + struct session_id client_id = { { 0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79, 0xc8 } }; + assert_memory_equal(&client_id, &state.peer_session_id, sizeof(struct session_id)); + + struct session_id expected_id = calculate_session_id_hmac(client_id, &from.dest, hmac, 30, 0); + + free_tls_pre_decrypt_state(&state); + buf_reset_len(&buf); + + /* Write the packet again into the buffer but this time, replacing the peer packet + * id with the expected one */ + buf_write(&buf, client_ack_none_random_id, sizeof(client_ack_none_random_id) - 8); + buf_write(&buf, expected_id.id, 8); + + verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); + assert_int_equal(verdict, VERDICT_VALID_ACK_V1); + valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true); + assert_true(valid); + /* Our handwindow is 30 so the slices are half of that, so they are + * (975,990), (990, 1005), (1005, 1020), (1020, 1035), (1035, 1050) + * So setting time to the two future ones should work + */ + now = 980; + assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + now = 1040; + assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + now = 1002; + assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + now = 1022; + assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + now = 1010; + assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + + /* Changing the IP address should make this invalid */ + from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020305); + assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + + /* Change to the correct one again */ + from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304); + assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + + /* Modify the peer id, should now fail hmac verification */ + buf_inc_len(&buf, -4); + buf_write_u32(&buf, 0x12345678); + + free_tls_pre_decrypt_state(&state); + verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); + assert_int_equal(verdict, VERDICT_VALID_ACK_V1); + assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); + free_tls_pre_decrypt_state(&state); free_buf(&buf); hmac_ctx_cleanup(hmac); @@ -696,12 +752,12 @@ main(void) openvpn_unit_test_setup(); const struct CMUnitTest tests[] = { + cmocka_unit_test(test_verify_hmac_none), cmocka_unit_test(test_tls_decrypt_lite_none), cmocka_unit_test(test_tls_decrypt_lite_auth), cmocka_unit_test(test_tls_decrypt_lite_crypt), cmocka_unit_test(test_parse_ack), cmocka_unit_test(test_calc_session_id_hmac_static), - cmocka_unit_test(test_verify_hmac_none), cmocka_unit_test(test_verify_hmac_tls_auth), cmocka_unit_test(test_verify_hmac_none_out_of_range_ack), cmocka_unit_test(test_generate_reset_packet_plain),