From 691d63dbddcb6db4379c58e94325b8115a7bf124 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 2 Dec 2025 19:27:56 +0900 Subject: [PATCH] sd-netlink: also read the reply for NFNL_MSG_BATCH_BEGIN message When we send a batch of nfnl messages, but e.g. without sufficient privilege, the kernel may only return an error message for NFNL_MSG_BATCH_BEGIN and ignore all later messages. So, we need to read the response for the NFNL_MSG_BATCH_BEGIN, and if it is an error ignore the replies for the rest. --- .../sd-netlink/netlink-message-nfnl.c | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/libsystemd/sd-netlink/netlink-message-nfnl.c b/src/libsystemd/sd-netlink/netlink-message-nfnl.c index 8708aac1023..a485fd096fd 100644 --- a/src/libsystemd/sd-netlink/netlink-message-nfnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-nfnl.c @@ -7,7 +7,6 @@ #include "sd-netlink.h" #include "alloc-util.h" -#include "errno-util.h" #include "iovec-util.h" #include "netlink-internal.h" #include "netlink-util.h" @@ -119,7 +118,7 @@ int sd_nfnl_send_batch( return -ENOMEM; if (ret_serials) { - serials = new(uint32_t, n_messages); + serials = new(uint32_t, n_messages + 2); if (!serials) return -ENOMEM; } @@ -133,6 +132,9 @@ int sd_nfnl_send_batch( return r; netlink_seal_message(nfnl, batch_begin); + if (serials) + serials[c] = message_get_serial(batch_begin); + iovs[c++] = IOVEC_MAKE(batch_begin->hdr, batch_begin->hdr->nlmsg_len); for (size_t i = 0; i < n_messages; i++) { @@ -147,7 +149,7 @@ int sd_nfnl_send_batch( netlink_seal_message(nfnl, messages[i]); if (serials) - serials[i] = message_get_serial(messages[i]); + serials[c] = message_get_serial(messages[i]); /* It seems that the kernel accepts an arbitrary number. Let's set the lower 16 bits of the * serial of the first message. */ @@ -161,6 +163,9 @@ int sd_nfnl_send_batch( return r; netlink_seal_message(nfnl, batch_end); + if (serials) + serials[c] = message_get_serial(batch_end); + iovs[c++] = IOVEC_MAKE(batch_end->hdr, batch_end->hdr->nlmsg_len); assert(c == n_messages + 2); @@ -192,12 +197,36 @@ int sd_nfnl_call_batch( if (r < 0) return r; - for (size_t i = 0; i < n_messages; i++) - RET_GATHER(r, sd_netlink_read(nfnl, serials[i], usec, /* ret= */ NULL)); - if (r < 0) - return r; + for (size_t i = 1; i <= n_messages; i++) { + /* If we have received an error, kernel may not send replies for later messages. Let's ignore + * remaining replies. */ + if (r < 0) { + (void) sd_netlink_ignore_serial(nfnl, serials[i], usec); + continue; + } + + r = sd_netlink_read(nfnl, serials[i], usec, /* ret= */ NULL); + if (r != -ETIMEDOUT) + continue; + + /* The kernel returns some errors, e.g. unprivileged, to the BATCH_BEGIN. Hence, if we have + * not received any replies for the batch body, try to read an error in the reply for the + * batch begin. Note, since v6.10 (bf2ac490d28c21a349e9eef81edc45320fca4a3c), we can expect + * that the kernel always replies the batch begin and end. When we bump the kernel baseline, + * we can read the reply for the batch begin at first. */ + int k = sd_netlink_read(nfnl, serials[0], usec, /* ret= */ NULL); + if (k < 0) + r = k; + + serials[0] = 0; /* indicates that we have read the reply. */ + } - return 0; + /* Ignore replies for batch begin and end if we have not read them. */ + if (serials[0] != 0) + (void) sd_netlink_ignore_serial(nfnl, serials[0], usec); + (void) sd_netlink_ignore_serial(nfnl, serials[n_messages + 1], usec); + + return r; } int sd_nfnl_nft_message_new_basechain( -- 2.47.3