Re: [PATCH] io_uring/zcrx: don't clear master_ctx from the import path

From: Pavel Begunkov

Date: Thu Jul 30 2026 - 15:18:42 EST


On 7/30/26 17:27, Woraphat Khiaodaeng wrote:
import_zcrx() attaches an existing ifq to another ring. It never calls
zcrx_set_ring_ctx() and so never takes the ->master_ctx reference, but
its error path still passes @ctx to zcrx_unregister(), which clears
->master_ctx and drops its percpu_ref whenever ifq->master_ctx == ctx.

That condition is reachable. A ring that registers an ifq with a
non-zero event type_mask gets ->master_ctx pointed at itself, and
nothing stops it from exporting that ifq with ZCRX_CTRL_EXPORT and
importing the resulting fd back into the same ring. Failing the import
after the refcount bumps -- an argument page mapped PROT_READ makes the
copy_to_user() in import_zcrx() return -EFAULT -- then clears the
->master_ctx owned by the original registration, which is still live.

Refcounts stay balanced and nothing is freed early, so there is no
splat. The ring silently stops receiving ZCRX_EVENT_ALLOC_FAIL and
ZCRX_EVENT_COPY: zcrx_send_notif() returns early on a NULL
->master_ctx, and ->master_ctx is only ever set on a freshly allocated
ifq, so it cannot be restored without tearing the ring down.

Yep, it effectively disables event CQEs.

Reviewed-by: Pavel Begunkov <asml.silence@xxxxxxxxx>

...> diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 76b9b0d54af9e..fea2b27272ec2 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -808,7 +808,8 @@ static int import_zcrx(struct io_ring_ctx *ctx,
scoped_guard(mutex, &ctx->mmap_lock)
xa_erase(&ctx->zcrx_ctxs, id);
err:
- zcrx_unregister(ifq, ctx);
+ /* the import path never took the ->master_ctx ref, don't drop it */

nit: I'd say "... it never set ->master_ctx ..." as it's about having
a ctx and not references, but it's not worth of respinning.

+ zcrx_unregister(ifq, NULL);
return ret;
}
--
Pavel Begunkov