new file mode 100644
@@ -0,0 +1,399 @@
+From 6d5668341de9d91f0e4e9d71e36f4141fdb9a528 Mon Sep 17 00:00:00 2001
+From: Bart Van Assche <bvanassche@acm.org>
+Date: Mon, 14 Sep 2026 10:19:57 -0700
+Subject: [PATCH] snmpd: SMUX: Make accepted peer connection handshake
+ non-blocking
+
+When accepting a new SMUX connection, smux_accept() previously executed
+a synchronous, blocking recvfrom() in the main daemon loop to read the
+initial OpenPDU before authenticating the peer. If an unauthenticated
+client connected and sent no data, the entire snmpd process blocked,
+preventing all SNMP request processing (CVE-2026-89147 / Issue #1134).
+
+Make the SMUX connection acceptance and authentication asynchronous:
+- Set accepted SMUX peer sockets to non-blocking mode immediately upon
+ accept() in smux_accept() and return the socket to the select loop
+ without performing any synchronous read.
+- In smux_process(), check whether the peer is authenticated. If not yet
+ authenticated, read and parse the OpenPDU non-blockingly when the
+ socket becomes readable, authenticating the peer and processing any
+ subsequent PDUs in the buffer.
+- Register a 5-second one-shot alarm on accepted connections to close
+ and clean up unauthenticated sockets that fail to complete the
+ handshake in time.
+- In smux_peer_cleanup(), only decrement npeers if the connection was
+ an authenticated peer.
+
+Fixes: https://github.com/net-snmp/net-snmp/issues/1134
+Fixes: ee0daa3b7bcd ("- (smux.c, smux.h): Patch from Nick Amato: - completely re-written smux modules.")
+
+CVE: CVE-2026-89147
+Upstream-Status: Backport [https://github.com/net-snmp/net-snmp/commit/ad843e83da1c229847ae7884dc08311fad680389]
+
+Backport Changes:
+- Retain int sockets, -1 errors, and the older accept() check.
+- Use %d because 5.9.5.2 lacks NETSNMP_FMT_SKT.
+- Omit netsnmp_set_tcp_nodelay(), absent in 5.9.5.2.
+- Include snmpSocketBaseDomain.h for the nonblocking helper.
+
+(cherry picked from commit ad843e83da1c229847ae7884dc08311fad680389)
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ agent/mibgroup/smux/smux.c | 245 +++++++++++++++++++++----------------
+ 1 file changed, 141 insertions(+), 104 deletions(-)
+
+diff --git a/agent/mibgroup/smux/smux.c b/agent/mibgroup/smux/smux.c
+index 078c4c6fa..5778f3590 100644
+--- a/agent/mibgroup/smux/smux.c
++++ b/agent/mibgroup/smux/smux.c
+@@ -64,6 +64,7 @@
+ #include <net-snmp/net-snmp-includes.h>
+ #include <net-snmp/agent/net-snmp-agent-includes.h>
+ #include <net-snmp/library/tools.h>
++#include <net-snmp/library/snmpSocketBaseDomain.h>
+
+ #include "smux.h"
+ #include "snmpd.h"
+@@ -92,6 +93,8 @@ static void smux_send_close(int, int);
+ static void smux_list_detach(smux_reg **, smux_reg *);
+ static void smux_replace_active(smux_reg *, smux_reg *);
+ static void smux_peer_cleanup(int);
++static int smux_peer_is_authenticated(int);
++static void smux_auth_timeout_cb(unsigned int, void *);
+ static int smux_auth_peer(oid *, size_t, char *, int);
+ static int smux_build(u_char, long, oid *,
+ size_t *, u_char, u_char *, size_t, u_char *,
+@@ -623,138 +626,175 @@ var_smux_write(int action,
+ }
+
+
++static int
++smux_peer_is_authenticated(int sd)
++{
++ int i;
++
++ for (i = 0; i < nauths; i++)
++ if (Auths[i]->sa_active_fd == sd)
++ return 1;
++
++ return 0;
++}
++
++static void
++smux_auth_timeout_cb(unsigned int clientreg, void *clientarg)
++{
++ int fd = (int)(uintptr_t)clientarg;
++ int i, found = 0;
++
++ for (i = 0; i < smux_snmp_select_list_get_length(); i++) {
++ if (smux_snmp_select_list_get_SD_from_List(i) == fd) {
++ found = 1;
++ break;
++ }
++ }
++ if (found && !smux_peer_is_authenticated(fd)) {
++ DEBUGMSGTL(("smux",
++ "[smux_auth_timeout] closing idle unauthenticated fd %d\n", fd));
++ close(fd);
++ smux_snmp_select_list_del(fd);
++ }
++}
++
+ int
+ smux_accept(int sd)
+ {
+- u_char data[SMUXMAXPKTSIZE], *ptr, type;
+ struct sockaddr_in in_socket;
+- struct timeval tv;
+- int fail, fd;
++ int fd;
+ socklen_t alen;
+- int length;
+- size_t len;
+
+ alen = sizeof(struct sockaddr_in);
+- /*
+- * this may be too high
+- */
+- tv.tv_sec = 5;
+- tv.tv_usec = 0;
+
+ /*
+ * connection request
+ */
+ DEBUGMSGTL(("smux", "[smux_accept] Calling accept()\n"));
+ errno = 0;
+- if ((fd = (int) accept(sd, (struct sockaddr *) &in_socket, &alen)) < 0) {
++ fd = accept(sd, (struct sockaddr *) &in_socket, &alen);
++ if (fd < 0) {
+ snmp_log_perror("[smux_accept] accept failed");
+ return -1;
+- } else {
+- DEBUGMSGTL(("smux", "[smux_accept] accepted fd %d from %s:%d\n",
+- fd, inet_ntoa(in_socket.sin_addr),
+- ntohs(in_socket.sin_port)));
+- if (npeers + 1 == SMUXMAXPEERS) {
+- snmp_log(LOG_ERR,
+- "[smux_accept] denied peer on fd %d, limit %d reached",
+- fd, SMUXMAXPEERS);
+- close(fd);
+- return -1;
+- }
++ }
++
++ netsnmp_set_non_blocking_mode(fd, TRUE);
++ DEBUGMSGTL(("smux", "[smux_accept] accepted fd %d from %s:%d\n",
++ fd, inet_ntoa(in_socket.sin_addr),
++ ntohs(in_socket.sin_port)));
++ if (npeers + 1 == SMUXMAXPEERS) {
++ snmp_log(LOG_ERR,
++ "[smux_accept] denied peer on fd %d, limit %d reached",
++ fd, SMUXMAXPEERS);
++ close(fd);
++ return -1;
++ }
++
++ snmp_alarm_register(5, 0, smux_auth_timeout_cb, (void *)(intptr_t)fd);
++
++ return fd;
++}
++
++int
++smux_process(int sock)
++{
++ int length, tmp_length;
++ u_char data[SMUXMAXPKTSIZE];
++ u_char type, *ptr;
++ size_t packet_len, len;
++ int fail;
+
++ if (!smux_peer_is_authenticated(sock)) {
+ /*
+- * now block for an OpenPDU
++ * Unauthenticated peer: read OpenPDU non-blockingly.
+ */
+- do
+- {
+- length = recvfrom(fd, (char *) data, SMUXMAXPKTSIZE, 0, NULL, NULL);
+- }
+- while((length == -1) && ((errno == EINTR) || (errno == EAGAIN)));
++ do {
++ length = recvfrom(sock, (char *) data, SMUXMAXPKTSIZE, 0, NULL, NULL);
++ } while (length == -1 && errno == EINTR);
+
+- if (length <= 0) {
++ if (length < 0) {
++ if (errno == EAGAIN ||
++ (EAGAIN != EWOULDBLOCK && errno == EWOULDBLOCK))
++ return 0;
+ DEBUGMSGTL(("smux",
+- "[smux_accept] peer on fd %d died or timed out\n",
+- fd));
+- close(fd);
++ "[smux_process] recvfrom failed on unauthenticated fd %d\n", sock));
++ close(sock);
++ return -1;
++ }
++ if (length == 0) {
++ DEBUGMSGTL(("smux",
++ "[smux_process] peer on fd %d closed connection before auth\n", sock));
++ close(sock);
+ return -1;
+ }
++
+ /*
+- * try to authorize him
++ * Try to authorize peer.
+ */
+ ptr = data;
+ len = length;
+ if ((ptr = asn_parse_header(ptr, &len, &type)) == NULL) {
+- smux_send_close(fd, SMUXC_PACKETFORMAT);
+- close(fd);
+- DEBUGMSGTL(("smux", "[smux_accept] peer on %d sent bad open", fd));
++ smux_send_close(sock, SMUXC_PACKETFORMAT);
++ close(sock);
++ DEBUGMSGTL(("smux",
++ "[smux_process] peer on %d sent bad open\n",
++ sock));
+ return -1;
+ } else if (type != (u_char) SMUX_OPEN) {
+- smux_send_close(fd, SMUXC_PROTOCOLERROR);
+- close(fd);
++ smux_send_close(sock, SMUXC_PROTOCOLERROR);
++ close(sock);
+ DEBUGMSGTL(("smux",
+- "[smux_accept] peer on %d did not send open: (%d)\n",
+- fd, type));
++ "[smux_process] peer on %d did not send open: (%d)\n",
++ sock, type));
+ return -1;
+ }
+- ptr = smux_open_process(fd, ptr, &len, &fail);
++ ptr = smux_open_process(sock, ptr, &len, &fail);
+ if (fail) {
+- smux_send_close(fd, SMUXC_AUTHENTICATIONFAILURE);
+- close(fd);
++ smux_send_close(sock, SMUXC_AUTHENTICATIONFAILURE);
++ close(sock);
+ DEBUGMSGTL(("smux",
+- "[smux_accept] peer on %d failed authentication\n",
+- fd));
++ "[smux_process] peer on %d failed authentication\n",
++ sock));
+ return -1;
+ }
+
+- /*
+- * he's OK
+- */
+-#ifdef SO_RCVTIMEO
+- if (setsockopt
+- (fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(tv)) < 0) {
+- DEBUGMSGTL(("smux",
+- "[smux_accept] setsockopt(SO_RCVTIMEO) failed fd %d\n",
+- fd));
+- snmp_log_perror("smux_accept: setsockopt SO_RCVTIMEO");
+- }
+-#endif
+ npeers++;
+- DEBUGMSGTL(("smux", "[smux_accept] fd %d\n", fd));
++ DEBUGMSGTL(("smux", "[smux_process] authenticated peer on fd %d\n", sock));
+
+ /*
+- * Process other PDUs already read, e.g. a registerRequest.
++ * Process other PDUs already read, e.g. a registerRequest.
+ */
+ len = length - (ptr - data);
+- if (smux_pdu_process(fd, ptr, len) < 0) {
+- /*
+- * Easy come, easy go. Clean-up is already done.
+- */
+- return -1;
++ if (len > 0) {
++ if (smux_pdu_process(sock, ptr, len) < 0) {
++ return -1;
++ }
+ }
++ return 0;
+ }
+- return fd;
+-}
+
+-int
+-smux_process(int fd)
+-{
+- int length, tmp_length;
+- u_char data[SMUXMAXPKTSIZE];
+- u_char type, *ptr;
+- size_t packet_len;
+-
+- do
+- {
+- length = recvfrom(fd, (char *) data, SMUXMAXPKTSIZE, MSG_PEEK, NULL,
++ /*
++ * Authenticated peer: process incoming data.
++ */
++ do {
++ length = recvfrom(sock, (char *) data, SMUXMAXPKTSIZE, MSG_PEEK, NULL,
+ NULL);
+- }
+- while((length == -1) && ((errno == EINTR) || (errno == EAGAIN)));
++ } while (length == -1 && errno == EINTR);
+
+- if (length <= 0)
+- {
+- if (length < 0)
+- snmp_log_perror("[smux_process] peek failed");
+- smux_peer_cleanup(fd);
+- return -1;
++ if (length < 0) {
++ if (errno == EAGAIN ||
++ (EAGAIN != EWOULDBLOCK && errno == EWOULDBLOCK))
++ return 0;
++ snmp_log_perror("[smux_process] peek failed");
++ smux_peer_cleanup(sock);
++ return -1;
++ }
++ if (length == 0) {
++ DEBUGMSGTL(("smux",
++ "[smux_process] peer on fd %d died or disconnected\n",
++ sock));
++ smux_peer_cleanup(sock);
++ return -1;
+ }
+
+ /*
+@@ -762,10 +802,12 @@ smux_process(int fd)
+ */
+ packet_len = length;
+ ptr = asn_parse_header(data, &packet_len, &type);
+- if (ptr == NULL)
++ if (ptr == NULL) {
++ smux_peer_cleanup(sock);
+ return -1;
++ }
+ packet_len += (ptr - data);
+- if (length > packet_len) {
++ if ((size_t)length > packet_len) {
+ /*
+ * set length to receive only the first packet
+ */
+@@ -773,26 +815,24 @@ smux_process(int fd)
+ }
+
+ tmp_length = length;
+- do
+- {
++ do {
+ length = tmp_length;
+- length = recvfrom(fd, (char *) data, length, 0, NULL, NULL);
+- }
+- while((length == -1) && ((errno == EINTR) || (errno == EAGAIN)));
++ length = recvfrom(sock, (char *) data, length, 0, NULL, NULL);
++ } while (length == -1 && errno == EINTR);
+
+ if (length <= 0) {
+- /*
+- * the peer went away, close this descriptor
+- * * and delete it from the list
+- */
++ if (length < 0 &&
++ (errno == EAGAIN ||
++ (EAGAIN != EWOULDBLOCK && errno == EWOULDBLOCK)))
++ return 0;
+ DEBUGMSGTL(("smux",
+ "[smux_process] peer on fd %d died or timed out\n",
+- fd));
+- smux_peer_cleanup(fd);
++ sock));
++ smux_peer_cleanup(sock);
+ return -1;
+ }
+
+- return smux_pdu_process(fd, data, length);
++ return smux_pdu_process(sock, data, length);
+ }
+
+ static int
+@@ -1907,11 +1947,6 @@ smux_peer_cleanup(int sd)
+ }
+ }
+
+- /*
+- * decrement the peer count
+- */
+- npeers--;
+-
+ /*
+ * make his auth available again
+ */
+@@ -1922,6 +1957,8 @@ smux_peer_cleanup(int sd)
+ snprint_objid(oid_name, sizeof(oid_name), Auths[i]->sa_oid,
+ Auths[i]->sa_oid_len);
+ DEBUGMSGTL(("smux", "peer disconnected: %s\n", oid_name));
++ if (npeers > 0)
++ npeers--;
+ }
+ }
+ }
@@ -25,6 +25,7 @@ SRC_URI = "${SOURCEFORGE_MIRROR}/net-snmp/net-snmp-${PV}.tar.gz \
file://0010-net-snmp-Reproducibility-Don-t-check-build-host-for.patch \
file://0011-ac_add_search_path.m4-keep-consistent-between-32bit-.patch \
file://netsnmp-swinst-crash.patch \
+ file://CVE-2026-89147.patch \
"
SRC_URI[sha256sum] = "16707719f833184a4b72835dac359ae188123b06b5e42817c00790d7dc1384bf"