[Buildroot] [PATCH for 2025.02.x] package/openvpn: add patches for CVE-2026-84732
Titouan Christophe
titouan.christophe at mind.be
Mon Sep 7 16:03:00 UTC 2026
This fix has been released in OpenVPN 2.7.7, but is not available yet for
OpenVPN 2.6 series (which is included in Buildroot 2025.02.x)
(alternative to commit 25b8142ef7b9e09ed7d273571ca61aa92888a39c)
Signed-off-by: Titouan Christophe <titouan.christophe at mind.be>
---
...avoid-unbounded-reliable-tls-timeout.patch | 129 +++++++++++
...r-packets-that-cannot-be-outstanding.patch | 206 ++++++++++++++++++
package/openvpn/openvpn.mk | 4 +
3 files changed, 339 insertions(+)
create mode 100644 package/openvpn/0001-avoid-unbounded-reliable-tls-timeout.patch
create mode 100644 package/openvpn/0002-ignore-acks-for-packets-that-cannot-be-outstanding.patch
diff --git a/package/openvpn/0001-avoid-unbounded-reliable-tls-timeout.patch b/package/openvpn/0001-avoid-unbounded-reliable-tls-timeout.patch
new file mode 100644
index 0000000000..c559ffcb4d
--- /dev/null
+++ b/package/openvpn/0001-avoid-unbounded-reliable-tls-timeout.patch
@@ -0,0 +1,129 @@
+From: Arne Schwabe <arne at rfc2549.org>
+Date: Tue, 1 Sep 2026 13:35:09 +0200
+Subject: Avoid unbounded reliable TLS timeout
+
+Avoid an unbounded TLS retransmit timeout, which could potentially
+trigger an integer overflow.
+
+The maximum initial timeout is defined in reliable.h and used to
+constrain --tls-timeout, so that the relation between the option range
+and RELIABLE_MAX_TIMEOUT_SHIFT is explicit and checked at compile time.
+
+Github: OpenVPN/openvpn-private-issues#161
+Reported-By: Mark Bregman (Fox-IT) <mark.bregman at fox-it.com>
+CVE: 2026-84732
+Change-Id: Id8ac9c48a8f751b0df95c6436ad3fbff3c4ae4a6
+Signed-off-by: Arne Schwabe <arne at rfc2549.org>
+Signed-off-by: Razvan Cojocaru <razvanc at mailbox.org>
+Acked-by: MaxF <max at max-fillinger.net>
+
+---
+Upstream: https://github.com/OpenVPN/openvpn/commit/207ac5f47e46565881dcec385020ebdcb682caa4
+CVE: CVE-2026-84732
+Signed-off-by: Titouan Christophe <titouan.christophe at mind.be>
+---
+ src/openvpn/options.c | 9 ++++++++-
+ src/openvpn/reliable.c | 13 ++++++++++++-
+ src/openvpn/reliable.h | 41 ++++++++++++++++++++++++++---------------
+ 3 files changed, 46 insertions(+), 17 deletions(-)
+
+diff --git a/src/openvpn/options.c b/src/openvpn/options.c
+index 5f3f4e96028..7a649de8822 100644
+--- a/src/openvpn/options.c
++++ b/src/openvpn/options.c
+@@ -7548,7 +7548,14 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
+ else if (streq(p[0], "tls-timeout") && p[1] && !p[2])
+ {
+ VERIFY_PERMISSION(OPT_P_TLS_PARMS);
+- options->tls_timeout = positive_atoi(p[1], msglevel);
++ /* Constrain the timeout to not have problems with
++ * RELIABLE_MAX_TIMEOUT_SHIFT creating an overflow. 65k seconds
++ * timeout is already way too much anyway */
++ if (!atoi_constrained(p[1], &options->tls_timeout, "tls-timeout", 1,
++ RELIABLE_MAX_INITIAL_TIMEOUT, msglevel))
++ {
++ goto err;
++ }
+ }
+ else if (streq(p[0], "reneg-bytes") && p[1] && !p[2])
+ {
+diff --git a/src/openvpn/reliable.c b/src/openvpn/reliable.c
+index 690e50d6d95..230d6aca47e 100644
+--- a/src/openvpn/reliable.c
++++ b/src/openvpn/reliable.c
+@@ -655,11 +655,22 @@ reliable_send(struct reliable *rel, int *opcode)
+ }
+ }
+ }
++
+ if (best)
+ {
++ /* The initial timeout is bounded by RELIABLE_MAX_INITIAL_TIMEOUT, so
++ * shifting it cannot overflow. */
++ static_assert(RELIABLE_MAX_INITIAL_TIMEOUT <= (INT_MAX >> RELIABLE_MAX_TIMEOUT_SHIFT),
++ "initial reliable timeout overflows when shifted");
++ const interval_t max_timeout = rel->initial_timeout << RELIABLE_MAX_TIMEOUT_SHIFT;
++
+ /* exponential backoff */
+ best->next_try = local_now + best->timeout;
+- best->timeout *= 2;
++ if (best->timeout < max_timeout)
++ {
++ best->timeout *= 2;
++ }
++
+ best->n_acks = 0;
+ *opcode = best->opcode;
+ dmsg(D_REL_DEBUG, "ACK reliable_send ID " packet_id_format " (size=%d to=%d)",
+diff --git a/src/openvpn/reliable.h b/src/openvpn/reliable.h
+index a5ed75cf0d0..af95bbc17a1 100644
+--- a/src/openvpn/reliable.h
++++ b/src/openvpn/reliable.h
+@@ -40,21 +40,32 @@
+ * @{ */
+
+
+-#define RELIABLE_ACK_SIZE \
+- 8 /**< The maximum number of packet IDs \
+- * waiting to be acknowledged which can \
+- * be stored in one \c reliable_ack \
+- * structure. */
+-
+-#define RELIABLE_CAPACITY \
+- 12 /**< The maximum number of packets that \
+- * the reliability layer for one VPN \
+- * tunnel in one direction can store. */
+-
+-#define N_ACK_RETRANSMIT \
+- 3 /**< We retry sending a packet early if \
+- * this many later packets have been \
+- * ACKed. */
++#define RELIABLE_ACK_SIZE 8
++/**< The maximum number of packet IDs
++ * waiting to be acknowledged which can
++ * be stored in one \c reliable_ack
++ * structure. */
++
++#define RELIABLE_CAPACITY 12
++/**< The maximum number of packets that
++ * the reliability layer for one VPN
++ * tunnel in one direction can store. */
++
++#define N_ACK_RETRANSMIT 3
++/**< We retry sending a packet early if
++ * this many later packets have been
++ * ACKed. */
++
++#define RELIABLE_MAX_TIMEOUT_SHIFT 6
++/**< Maximum shift or doubling in exponential backoff
++ * we allow. This is a safeguard against an unbounded
++ * exponential backoff. With the default timeout of 2s this
++ * equals 128s */
++
++#define RELIABLE_MAX_INITIAL_TIMEOUT (1 << 16)
++/**< Maximum initial timeout (--tls-timeout) we accept.
++ * Bounded so that shifting it by RELIABLE_MAX_TIMEOUT_SHIFT
++ * cannot overflow an int. */
+
+ /**
+ * The acknowledgment structure in which packet IDs are stored for later
diff --git a/package/openvpn/0002-ignore-acks-for-packets-that-cannot-be-outstanding.patch b/package/openvpn/0002-ignore-acks-for-packets-that-cannot-be-outstanding.patch
new file mode 100644
index 0000000000..0b97582965
--- /dev/null
+++ b/package/openvpn/0002-ignore-acks-for-packets-that-cannot-be-outstanding.patch
@@ -0,0 +1,206 @@
+From: Arne Schwabe <arne at rfc2549.org>
+Date: Tue, 1 Sep 2026 13:35:09 +0200
+Subject: Ignore acks for packets that cannot be outstanding
+
+This ignores acks for pids outside the send window, i.e. for packets
+that have either not been sent out yet or already left the window.
+Nothing outside that window can be outstanding, so such acks can only
+be used to manipulate the state of the send buffer.
+
+Comparing the pid of an outstanding packet against the acked pid needs
+to be wraparound aware as well. Otherwise a peer can ack a pid from the
+lower half of the id space, which passes the window check above, and
+still increment n_acks on every outstanding packet, forcing an early
+retransmit after N_ACK_RETRANSMIT such acks.
+
+Github: OpenVPN/openvpn-private-issues#161
+Reported-By: Mark Bregman (Fox-IT) <mark.bregman at fox-it.com>
+CVE: 2026-84732
+Change-Id: I944478767b52a1b9bf6a65373ce0544c69cb1012
+Signed-off-by: Arne Schwabe <arne at rfc2549.org>
+Signed-off-by: Razvan Cojocaru <razvanc at mailbox.org>
+Acked-by: MaxF <max at max-fillinger.net>
+
+---
+Upstream: https://github.com/OpenVPN/openvpn/commit/d988ef4508e63b28c8e3efcb9f62eb8c836907fe
+CVE: CVE-2026-84732
+Signed-off-by: Titouan Christophe <titouan.christophe at mind.be>
+---
+ src/openvpn/reliable.c | 37 ++++++++++++++++-
+ src/openvpn/reliable.h | 17 ++++++--
+ tests/unit_tests/openvpn/test_packet_id.c | 50 ++++++++++++++++++++++-
+ 3 files changed, 98 insertions(+), 6 deletions(-)
+
+diff --git a/src/openvpn/reliable.c b/src/openvpn/reliable.c
+index 230d6aca47e..ced438e481d 100644
+--- a/src/openvpn/reliable.c
++++ b/src/openvpn/reliable.c
+@@ -390,13 +390,35 @@ reliable_empty(const struct reliable *rel)
+ return true;
+ }
+
++int
++validate_packet_id_window(struct reliable *rel, packet_id_type pid)
++{
++ return reliable_pid_min(pid, rel->packet_id)
++ && reliable_pid_min(subtract_pid(rel->packet_id, RELIABLE_CAPACITY), pid);
++}
++
+ /* del acknowledged items from send buf */
+ void
+ reliable_send_purge(struct reliable *rel, const struct reliable_ack *ack)
+ {
++ unsigned int out_of_window = 0;
++ packet_id_type first_out_of_window = 0;
++
+ for (int i = 0; i < ack->len; ++i)
+ {
+ packet_id_type pid = ack->packet_id[i];
++
++
++ if (!validate_packet_id_window(rel, pid))
++ {
++ if (out_of_window == 0)
++ {
++ first_out_of_window = pid;
++ }
++ out_of_window++;
++ continue;
++ }
++
+ for (int j = 0; j < rel->size; ++j)
+ {
+ struct reliable_entry *e = &rel->array[j];
+@@ -417,16 +439,27 @@ reliable_send_purge(struct reliable *rel, const struct reliable_ack *ack)
+ #endif
+ e->active = false;
+ }
+- else if (e->active && e->packet_id < pid)
++
++ if (e->active && reliable_pid_min(e->packet_id, pid))
+ {
+ /* We have received an ACK for a packet with a higher PID. Either
+ * we have received ACKs out of or order or the packet has been
+ * lost. We count the number of ACKs to determine if we should
+- * resend it early. */
++ * resend it early. The comparison needs to be wraparound aware,
++ * otherwise a peer can inflate n_acks with an ACK for a pid from
++ * the lower half of the id space and force a retransmit. */
+ e->n_acks++;
+ }
+ }
+ }
++
++ if (out_of_window > 0)
++ {
++ (void)first_out_of_window; /* dmsg might not generate code */
++ dmsg(D_REL_LOW, "ACK contained %u ids outside the send window, "
++ "first was " packet_id_format,
++ out_of_window, (packet_id_print_type)first_out_of_window);
++ }
+ }
+
+ #ifdef ENABLE_DEBUG
+diff --git a/src/openvpn/reliable.h b/src/openvpn/reliable.h
+index af95bbc17a1..a85f2e97807 100644
+--- a/src/openvpn/reliable.h
++++ b/src/openvpn/reliable.h
+@@ -105,9 +105,9 @@ struct reliable
+ {
+ int size;
+ interval_t initial_timeout;
+- packet_id_type packet_id;
+- int offset; /**< Offset of the bufs in the reliable_entry array */
+- bool hold; /* don't xmit until reliable_schedule_now is called */
++ packet_id_type packet_id; /**< Packet ID for the next packet to be sent out. */
++ int offset; /**< Offset of the bufs in the reliable_entry array */
++ bool hold; /* don't xmit until reliable_schedule_now is called */
+ struct reliable_entry array[RELIABLE_CAPACITY];
+ };
+
+@@ -189,6 +189,17 @@ reliable_ack_empty(struct reliable_ack *ack)
+ return !ack->len;
+ }
+
++/**
++ * check that pid is inside the window of possible outstanding packets
++ * of size RELIABLE_CAPACITY, ie inside the range
++ * [rel->packet_id - RELIABLE_CAPACITY, rel->packet_id).
++ *
++ * rel->packet is the *next* packet id to be sent out, so it is not
++ * included in the valid range.
++ */
++int
++validate_packet_id_window(struct reliable *rel, packet_id_type pid);
++
+ /**
+ * Returns the number of packets that need to be acked.
+ *
+diff --git a/tests/unit_tests/openvpn/test_packet_id.c b/tests/unit_tests/openvpn/test_packet_id.c
+index 5dfd319ab9a..a5d50de4ae7 100644
+--- a/tests/unit_tests/openvpn/test_packet_id.c
++++ b/tests/unit_tests/openvpn/test_packet_id.c
+@@ -325,6 +325,53 @@ test_copy_acks_to_lru(void **state)
+ assert_memory_equal(mru_ack.packet_id, expected_ack.packet_id, sizeof(expected_ack.packet_id));
+ }
+
++static void
++test_packet_id_window(void **state)
++{
++ struct reliable rel = { 0 };
++ rel.packet_id = 1;
++
++ assert_true(validate_packet_id_window(&rel, 0));
++
++ /* packet id 1 is outside the window as it is the *next* packet id */
++ assert_false(validate_packet_id_window(&rel, 1));
++
++ /* wrapped around packet id, "-2" */
++ assert_true(validate_packet_id_window(&rel, 0xFFFFFFFD));
++
++ /* wrapped around packet id, "-10" */
++ assert_true(validate_packet_id_window(&rel, 0xFFFFFFF6));
++
++ /* wrapped around packet id, "-11" */
++ assert_false(validate_packet_id_window(&rel, 0xFFFFFFF5));
++ assert_false(validate_packet_id_window(&rel, 0x80000000));
++
++ rel.packet_id = 0x80000000;
++
++ /* near the signed/usigned integer area */
++ assert_false(validate_packet_id_window(&rel, 0x80000001));
++ assert_true(validate_packet_id_window(&rel, 0x7fffffff));
++ assert_true(validate_packet_id_window(&rel, 0x7ffffff5));
++ assert_false(validate_packet_id_window(&rel, 0x7ffffff4));
++
++ rel.packet_id = 0xFFFFFFFD;
++ assert_false(validate_packet_id_window(&rel, 0xFFFFFFFD));
++ assert_false(validate_packet_id_window(&rel, 0));
++ assert_false(validate_packet_id_window(&rel, 1));
++ assert_false(validate_packet_id_window(&rel, 0xFFFFFFFE));
++ assert_false(validate_packet_id_window(&rel, 0xFFFFFFFF));
++ assert_true(validate_packet_id_window(&rel, 0xFFFFFFF3));
++ assert_true(validate_packet_id_window(&rel, 0xFFFFFFF2));
++ assert_false(validate_packet_id_window(&rel, 0xFFFFFFF1));
++
++ rel.packet_id = 500;
++ assert_false(validate_packet_id_window(&rel, 501));
++ assert_true(validate_packet_id_window(&rel, 497));
++ assert_true(validate_packet_id_window(&rel, 500 - (RELIABLE_CAPACITY - 1)));
++ assert_false(validate_packet_id_window(&rel, 500 - RELIABLE_CAPACITY));
++}
++
++
+ int
+ main(void)
+ {
+@@ -346,7 +393,8 @@ main(void)
+ test_packet_id_write_teardown),
+
+ cmocka_unit_test(test_get_num_output_sequenced_available),
+- cmocka_unit_test(test_copy_acks_to_lru)
++ cmocka_unit_test(test_copy_acks_to_lru),
++ cmocka_unit_test(test_packet_id_window)
+
+ };
+
diff --git a/package/openvpn/openvpn.mk b/package/openvpn/openvpn.mk
index 2b24c1524d..e6c36ef904 100644
--- a/package/openvpn/openvpn.mk
+++ b/package/openvpn/openvpn.mk
@@ -16,6 +16,10 @@ OPENVPN_CONF_OPTS = \
$(if $(BR2_STATIC_LIBS),--disable-plugins)
OPENVPN_CONF_ENV = NETSTAT=/bin/netstat
+# 0001-avoid-unbounded-reliable-tls-timeout.patch
+# 0002-ignore-acks-for-packets-that-cannot-be-outstanding.patch
+OPENVPN_IGNORE_CVES += CVE-2026-84732
+
ifeq ($(BR2_PACKAGE_LIBNL)$(BR2_TOOLCHAIN_HEADERS_AT_LEAST_4_16),yy)
OPENVPN_CONF_OPTS += --enable-dco
OPENVPN_DEPENDENCIES += libnl
--
2.55.0
More information about the buildroot
mailing list