File fix-crash-if-poll-callback-closes.patch of Package libuv

From 327be9eec853bbc67f08682d3c43b70fc45aabee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Juan=20Jos=C3=A9=20Arboleda?= <soyjuanarbol@gmail.com>
Date: Mon, 2 Mar 2026 12:28:44 -0500
Subject: [PATCH] linux: fix crash if poll callback closes handle before
 `POLLERR`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Previously, `uv__udp_io` would proceed to call `uv__udp_recvmsg` for
the `POLLERR` event even if the handle was just closed by the `POLLIN`
callback.

This commit adds a guard to verify the handle is still active before
processing the error queue. It also adds a regression test that
mimics this recursive closure behavior.

Fixes: https://github.com/libuv/libuv/issues/5030
Signed-off-by: Juan José Arboleda <soyjuanarbol@gmail.com>
---
 CMakeLists.txt                        |   1 +
 Makefile.am                           |   1 +
 src/unix/udp.c                        |   6 +-
 test/test-list.h                      |   4 +
 test/test-udp-recv-cb-close-pollerr.c | 112 ++++++++++++++++++++++++++
 5 files changed, 123 insertions(+), 1 deletion(-)
 create mode 100644 test/test-udp-recv-cb-close-pollerr.c

Index: libuv-v1.52.0/CMakeLists.txt
===================================================================
--- libuv-v1.52.0.orig/CMakeLists.txt
+++ libuv-v1.52.0/CMakeLists.txt
@@ -700,6 +700,7 @@ if(LIBUV_BUILD_TESTS)
        test/test-udp-send-immediate.c
        test/test-udp-sendmmsg-error.c
        test/test-udp-send-unreachable.c
+       test/test-udp-recv-cb-close-pollerr.c
        test/test-udp-recvmsg-unreachable-error.c
        test/test-udp-try-send.c
        test/test-udp-recv-in-a-row.c
Index: libuv-v1.52.0/Makefile.am
===================================================================
--- libuv-v1.52.0.orig/Makefile.am
+++ libuv-v1.52.0/Makefile.am
@@ -325,6 +325,7 @@ test_run_tests_SOURCES = test/blackhole-
                          test/test-udp-send-immediate.c \
                          test/test-udp-sendmmsg-error.c \
                          test/test-udp-send-unreachable.c \
+                         test/test-udp-recv-cb-close-pollerr.c \
                          test/test-udp-recvmsg-unreachable-error.c \
                          test/test-udp-try-send.c \
                          test/test-udp-recv-in-a-row.c \
Index: libuv-v1.52.0/src/unix/udp.c
===================================================================
--- libuv-v1.52.0.orig/src/unix/udp.c
+++ libuv-v1.52.0/src/unix/udp.c
@@ -183,7 +183,11 @@ void uv__udp_io(uv_loop_t* loop, uv__io_
 
   /* Just Linux support for now. */
 #if defined(__linux__)
-  if (revents & POLLERR)
+  /* Guard against the case where the POLLIN callback above (e.g. via
+   * uv_udp_recv_stop + uv_close) already cleared recv_cb in the same
+   * revents iteration.  uv__udp_recvmsg asserts recv_cb != NULL, so
+   * calling it with a NULL recv_cb would be wrong regardless of POLLERR. */
+  if ((revents & POLLERR) && uv__is_active(handle))
     uv__udp_recvmsg(handle, MSG_ERRQUEUE);
 #endif
 
Index: libuv-v1.52.0/test/test-list.h
===================================================================
--- libuv-v1.52.0.orig/test/test-list.h
+++ libuv-v1.52.0/test/test-list.h
@@ -174,8 +174,10 @@ TEST_DECLARE   (udp_send_and_recv)
 TEST_DECLARE   (udp_send_hang_loop)
 TEST_DECLARE   (udp_send_immediate)
 TEST_DECLARE   (udp_send_unreachable)
+TEST_DECLARE   (udp_recv_cb_close_pollerr)
 TEST_DECLARE   (udp_recvmsg_unreachable_error)
 TEST_DECLARE   (udp_recvmsg_unreachable_error6)
+TEST_DECLARE   (udp_send_pollerr_no_recv)
 TEST_DECLARE   (udp_mmsg)
 TEST_DECLARE   (udp_multicast_ttl)
 TEST_DECLARE   (udp_dgram_too_big)
@@ -802,8 +804,10 @@ TASK_LIST_START
   TEST_ENTRY  (udp_send_hang_loop)
   TEST_ENTRY  (udp_send_immediate)
   TEST_ENTRY  (udp_send_unreachable)
+  TEST_ENTRY  (udp_recv_cb_close_pollerr)
   TEST_ENTRY  (udp_recvmsg_unreachable_error)
   TEST_ENTRY  (udp_recvmsg_unreachable_error6)
+  TEST_ENTRY  (udp_send_pollerr_no_recv)
   TEST_ENTRY  (udp_dgram_too_big)
   TEST_ENTRY  (udp_dual_stack)
   TEST_ENTRY  (udp_ipv6_only)
Index: libuv-v1.52.0/test/test-udp-recv-cb-close-pollerr.c
===================================================================
--- /dev/null
+++ libuv-v1.52.0/test/test-udp-recv-cb-close-pollerr.c
@@ -0,0 +1,112 @@
+/* Copyright libuv contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "uv.h"
+#include "task.h"
+
+static uv_udp_send_t send_req;
+static uv_udp_t client;
+static int alloc_cb_called;
+static int recv_cb_called;
+static int send_cb_called;
+
+static void alloc_cb(uv_handle_t* handle, size_t sz, uv_buf_t* buf) {
+  alloc_cb_called++;
+  buf->base = "uv";
+  buf->len = 2;
+}
+
+static void recv_cb(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf,
+                    const struct sockaddr* addr, unsigned flags) {
+  recv_cb_called++;
+  /* Stop receiving and unset recv_cb and alloc_cb */
+  uv_close((uv_handle_t*) handle, NULL);
+}
+
+static void send_cb(uv_udp_send_t* req, int status) {
+  send_cb_called++;
+  /* The send completed; now just close the handle and let the loop drain.
+   * If a POLLERR arrives before the close completes, uv__udp_io must not
+   * crash when no recv callback is installed. */
+  uv_close((uv_handle_t*) req->handle, NULL);
+}
+
+/* Refs: https://github.com/libuv/libuv/issues/5030 */
+TEST_IMPL(udp_recv_cb_close_pollerr) {
+#ifndef __linux__
+  RETURN_SKIP("ICMP error handling is Linux-specific");
+#endif
+  struct sockaddr_in any_addr;
+  struct sockaddr_in addr;
+  uv_buf_t buf;
+
+  ASSERT_OK(uv_udp_init(uv_default_loop(), &client));
+
+  ASSERT_OK(uv_ip4_addr("0.0.0.0", 0, &any_addr));
+  ASSERT_OK(uv_udp_bind(&client, (const struct sockaddr*) &any_addr,
+                        UV_UDP_LINUX_RECVERR));
+
+  ASSERT_OK(uv_ip4_addr("127.0.0.1", 9999, &addr));
+  ASSERT_OK(uv_udp_connect(&client, (const struct sockaddr*) &addr));
+
+  ASSERT_OK(uv_udp_recv_start(&client, alloc_cb, recv_cb));
+
+  buf = uv_buf_init("PING", 4);
+  ASSERT_GT(uv_udp_try_send(&client, &buf, 1, NULL), 0);
+
+  uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+
+  ASSERT_EQ(1, alloc_cb_called);
+  ASSERT_EQ(1, recv_cb_called);
+
+  MAKE_VALGRIND_HAPPY(uv_default_loop());
+  return 0;
+}
+
+/* Same as above but WITHOUT uv_udp_recv_start.
+ * The ICMP POLLERR still fires on the fd; uv__udp_io must not crash when
+ * no recv/alloc callback is installed. */
+TEST_IMPL(udp_send_pollerr_no_recv) {
+#ifndef __linux__
+  RETURN_SKIP("ICMP error handling is Linux-specific");
+#endif
+  struct sockaddr_in any_addr;
+  struct sockaddr_in addr;
+  uv_buf_t buf;
+
+  ASSERT_OK(uv_udp_init(uv_default_loop(), &client));
+
+  ASSERT_OK(uv_ip4_addr("0.0.0.0", 0, &any_addr));
+  ASSERT_OK(uv_udp_bind(&client, (const struct sockaddr*) &any_addr,
+                        UV_UDP_LINUX_RECVERR));
+
+  ASSERT_OK(uv_ip4_addr("127.0.0.1", 9999, &addr));
+  ASSERT_OK(uv_udp_connect(&client, (const struct sockaddr*) &addr));
+
+  buf = uv_buf_init("PING", 4);
+  ASSERT_OK(uv_udp_send(&send_req, &client, &buf, 1, NULL, send_cb));
+
+  uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+
+  ASSERT_EQ(1, send_cb_called);
+
+  MAKE_VALGRIND_HAPPY(uv_default_loop());
+  return 0;
+}
\ No newline at end of file
openSUSE Build Service is sponsored by