[PATCH] io_uring/rsrc: use refcount_t for io_rsrc_node.refs

Oleg Sevostyanov <[email protected]>
Newsgroups org.kernel.vger.io-uring
Message-ID <CAJv4CsvbaJd5GoHjYPzi3bgEO0fPT-3xj+UV7JMhqTyh2qr5tg@mail.gmail.com>
Hello,

This patch converts the refs field in struct io_rsrc_node from plain
int to refcount_t.

Background
----------
During a static analysis pass of io_uring/rsrc.{c,h} I examined all
sites that touch io_rsrc_node.refs:

  - io_rsrc_node_alloc()     rsrc.c:147   initialises to 1
  - io_buf_node_lookup()     rsrc.c:1117  refs++ under io_ring_submit_lock
  - io_clone_buffers() x2    rsrc.c:1199  refs++ under uring_lock
(lockdep_assert_held
                             rsrc.c:1232  asserted on both ctx's)
  - io_put_rsrc_node()       rsrc.h:107   --refs under uring_lock

All four sites are correctly guarded by ctx->uring_lock, so there is no
present race or overflow risk.  This is a defence-in-depth change only.

Rationale
---------
io_mapped_ubuf (defined in the same header, rsrc.h:40) already uses
refcount_t for its own refs field.  Aligning io_rsrc_node to the same
convention:

  1. Gives lockless overflow/underflow detection "for free" on kernels
     built with REFCOUNT_FULL or on architectures that provide
     REFCOUNT_ARCH_OPTIMIZED (x86 since 4.14).

  2. Makes it harder for a future patch that removes or relaxes locking
     to silently introduce a refcount bug—the saturating behaviour of
     refcount_t would catch wraps and emit a WARN_ONCE before a
     use-after-free could occur.

  3. Self-documents the intent: the field is a reference counter, not an
     arbitrary signed integer.

The change is mechanical:

  int refs = 1           -> refcount_set(&node->refs, 1)
  node->refs++           -> refcount_inc(&node->refs)
  if (!--node->refs)     -> if (refcount_dec_and_test(&node->refs))

No functional change is intended.  I do not have a stable kernel build
environment that includes the full io_uring tree, so I am unable to
provide a Tested-by, but the patch compiles cleanly against the 6.8
source tree (io_uring/ sparse checkout).

Oleg Sevostyanov
0001-io_uring-rsrc-use-refcount_t-for-io_rsrc_node.refs.patch (application/octet-stream, 3 KB)
From: Oleg Sevostyanov 
Date: Thu, 14 May 2026 00:00:00 +0000
Subject: [PATCH] io_uring/rsrc: use refcount_t for io_rsrc_node.refs

The refs field in struct io_rsrc_node is a plain int protected by
ctx->uring_lock. While the current locking is correct and prevents
concurrent access, using a plain int means the kernel's refcount
overflow/underflow detection (REFCOUNT_FULL or REFCOUNT_ARCH_OPTIMIZED)
is bypassed.

If a future change accidentally removes the lock or introduces a new
path that bumps refs without holding uring_lock, a plain int would
silently wrap. refcount_t would catch this via WARN_ONCE and saturate
at 0 or INT_MAX, preventing use-after-free in the node.

io_mapped_ubuf in the same header already uses refcount_t for its refs
field; align io_rsrc_node with the same convention.

No functional change intended.

Signed-off-by: Oleg Sevostyanov 
---
 io_uring/rsrc.c |  8 ++++----
 io_uring/rsrc.h |  4 +++-
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index xxxxxxx..xxxxxxx 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -144,7 +144,7 @@ struct io_rsrc_node *io_rsrc_node_alloc(struct io_ring_ctx *ctx, int type)
 	node = io_cache_alloc(&ctx->node_cache, GFP_KERNEL);
 	if (node) {
 		node->type = type;
-		node->refs = 1;
+		refcount_set(&node->refs, 1);
 		node->tag = 0;
 		node->file_ptr = 0;
 	}
@@ -1114,7 +1114,7 @@ struct io_rsrc_node *io_buf_node_lookup(struct io_uring_buf_node_req *req,
 	io_ring_submit_lock(ctx, issue_flags);
 	node = io_rsrc_node_lookup(&ctx->buf_table, req->buf_index);
 	if (node) {
-		node->refs++;
+		refcount_inc(&node->refs);
 		req->buf_node = node;
 		io_ring_submit_unlock(ctx, issue_flags);
 		return node;
@@ -1196,7 +1196,7 @@ int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx,
 	for (i = 0; i < min(arg->dst_off, ctx->buf_table.nr); i++) {
 		struct io_rsrc_node *node = ctx->buf_table.nodes[i];

 		if (node) {
 			data.nodes[i] = node;
-			node->refs++;
+			refcount_inc(&node->refs);
 		}
 	}
@@ -1229,7 +1229,7 @@ int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx,
 	for (i = nbufs; i < ctx->buf_table.nr; i++) {
 		struct io_rsrc_node *node = ctx->buf_table.nodes[i];

 		if (node) {
 			data.nodes[i] = node;
-			node->refs++;
+			refcount_inc(&node->refs);
 		}
 	}

diff --git a/io_uring/rsrc.h b/io_uring/rsrc.h
index xxxxxxx..xxxxxxx 100644
--- a/io_uring/rsrc.h
+++ b/io_uring/rsrc.h
@@ -2,6 +2,8 @@
 #ifndef IOU_RSRC_H
 #define IOU_RSRC_H

+#include <linux/refcount.h>
+
 #include <linux/io_uring_types.h>
 #include <linux/lockdep.h>

@@ -14,7 +16,7 @@ enum {
 struct io_rsrc_node {
 	unsigned char			type;
-	int				refs;
+	refcount_t			refs;

 	u64 tag;
 	union {
@@ -104,7 +106,7 @@ static inline void io_put_rsrc_node(struct io_ring_ctx *ctx,
 				    struct io_rsrc_node *node)
 {
 	lockdep_assert_held(&ctx->uring_lock);
-	if (!--node->refs)
+	if (refcount_dec_and_test(&node->refs))
 		io_free_rsrc_node(ctx, node);
 }
--
2.44.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.