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 +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) +CVE: 2026-84732 +Change-Id: Id8ac9c48a8f751b0df95c6436ad3fbff3c4ae4a6 +Signed-off-by: Arne Schwabe +Signed-off-by: Razvan Cojocaru +Acked-by: MaxF + +--- +Upstream: https://github.com/OpenVPN/openvpn/commit/207ac5f47e46565881dcec385020ebdcb682caa4 +CVE: CVE-2026-84732 +Signed-off-by: Titouan Christophe +--- + 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 +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) +CVE: 2026-84732 +Change-Id: I944478767b52a1b9bf6a65373ce0544c69cb1012 +Signed-off-by: Arne Schwabe +Signed-off-by: Razvan Cojocaru +Acked-by: MaxF + +--- +Upstream: https://github.com/OpenVPN/openvpn/commit/d988ef4508e63b28c8e3efcb9f62eb8c836907fe +CVE: CVE-2026-84732 +Signed-off-by: Titouan Christophe +--- + 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