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..c0d77c4bf5 --- /dev/null +++ b/package/openvpn/0001-avoid-unbounded-reliable-tls-timeout.patch @@ -0,0 +1,162 @@ +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 +[Titouan: + - Fix merge conflicts with openvpn release/2.6 branch + - Add atoi_constrained() without logging, copied from + https://github.com/OpenVPN/openvpn/blob/57b1f870a76dbe94dbf8dc6a4d22d1a225a42578/src/openvpn/options_util.c#L166-L195 +] +Signed-off-by: Titouan Christophe +--- + src/openvpn/options.c | 29 ++++++++++++++++++++++++++++- + src/openvpn/reliable.c | 13 ++++++++++++- + src/openvpn/reliable.h | 39 ++++++++++++++++++++++++++------------- + 3 files changed, 66 insertions(+), 15 deletions(-) + +diff --git a/src/openvpn/options.c b/src/openvpn/options.c +index 9344f996..e11f1ad2 100644 +--- a/src/openvpn/options.c ++++ b/src/openvpn/options.c +@@ -4966,6 +4966,26 @@ positive_atoi(const char *str) + return i < 0 ? 0 : i; + } + ++static bool ++atoi_constrained(const char *str, int *value, int min, int max) ++{ ++ ASSERT(min < max); ++ ++ char *endptr; ++ long long i = strtoll(str, &endptr, 10); ++ if (i < INT_MIN || *endptr != '\0' || i > INT_MAX) ++ { ++ return false; ++ } ++ if (i < min || i > max) ++ { ++ return false; ++ } ++ ++ *value = (int)i; ++ return true; ++} ++ + #ifdef _WIN32 /* This function is only used when compiling on Windows */ + static unsigned int + atou(const char *str) +@@ -9179,7 +9199,14 @@ add_option(struct options *options, + else if (streq(p[0], "tls-timeout") && p[1] && !p[2]) + { + VERIFY_PERMISSION(OPT_P_TLS_PARMS); +- options->tls_timeout = positive_atoi(p[1]); ++ /* 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, ++ 1, RELIABLE_MAX_INITIAL_TIMEOUT)) ++ { ++ 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 c2b94390..babe4c84 100644 +--- a/src/openvpn/reliable.c ++++ b/src/openvpn/reliable.c +@@ -683,11 +683,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 766c07d7..08e0fd75 100644 +--- a/src/openvpn/reliable.h ++++ b/src/openvpn/reliable.h +@@ -40,19 +40,32 @@ + /** @addtogroup reliable + * @{ */ + +- +-#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 +-- +2.55.0 + 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..ad03ee6547 --- /dev/null +++ b/package/openvpn/0002-ignore-acks-for-packets-that-cannot-be-outstanding.patch @@ -0,0 +1,214 @@ +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 +[Titouan: fix merge conflicts with openvpn release/2.6 branch] +Signed-off-by: Titouan Christophe +--- + src/openvpn/reliable.c | 42 ++++++++++++++++--- + src/openvpn/reliable.h | 17 ++++++-- + tests/unit_tests/openvpn/test_packet_id.c | 50 ++++++++++++++++++++++- + 3 files changed, 100 insertions(+), 9 deletions(-) + +diff --git a/src/openvpn/reliable.c b/src/openvpn/reliable.c +index babe4c84..1ecabb1a 100644 +--- a/src/openvpn/reliable.c ++++ b/src/openvpn/reliable.c +@@ -404,15 +404,36 @@ 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) + { +- int i, j; +- for (i = 0; i < ack->len; ++i) ++ 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]; +- for (j = 0; j < rel->size; ++j) ++ ++ ++ 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]; + if (e->active && e->packet_id == pid) +@@ -432,16 +453,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 08e0fd75..a76e75ea 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]; + }; + +@@ -191,6 +191,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 ff3f7886..a27bd6d4 100644 +--- a/tests/unit_tests/openvpn/test_packet_id.c ++++ b/tests/unit_tests/openvpn/test_packet_id.c +@@ -271,6 +271,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) + { +@@ -295,7 +342,8 @@ main(void) + test_packet_id_write_setup, + 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) + + }; + +-- +2.55.0 + 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