io_uring/waitid: return -EAGAIN for a non-blocking pidfd

IORING_OP_WAITID on a pidfd opened with PIDFD_NONBLOCK completes at
once with res 0 and an all-zero siginfo when the child has not changed
state, although WNOHANG was not requested. waitid(2) with the same
arguments fails with -EAGAIN. An application that submits the same
request it would pass to waitid(2) and treats res 0 as "the child
changed state" sees si_signo 0 and si_pid 0 instead.

kernel_waitid_prepare() turns an O_NONBLOCK pidfd into WNOHANG in
wo->wo_flags. kernel_waitid() then maps the resulting 0 return to
-EAGAIN when the caller did not ask for WNOHANG itself, as added in
commit ba7d25f3df ("exit: support non-blocking pidfds").
io_waitid() uses kernel_waitid_prepare() and __do_wait() directly and
does not do that mapping.

Do the same check in io_waitid() before completing the request. Only
the inline path needs it: with WNOHANG set, __do_wait() never returns
-ERESTARTSYS, so such a request is never armed on the wait queue.

Reproduced on x86_64 under QEMU: with a PIDFD_NONBLOCK pidfd of a
running child and options = WEXITED, waitid(2) fails with -EAGAIN and
IORING_OP_WAITID completed with res 0 before the change; it completes
with res -EAGAIN after it.

Fixes: f31ecf671d ("io_uring: add IORING_OP_WAITID support")
Assisted-by: LLM
Signed-off-by: Nickolai Zeldovich <nickolai@csail.mit.edu>
Link: https://patch.msgid.link/20261018025824.799245-1-nickolai@csail.mit.edu
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Nickolai Zeldovich 2026-10-17 22:58:24 -04:00 committed by Jens Axboe
parent 96262cfab5
commit c82621eb2c

View File

@ -10,6 +10,7 @@
#include <linux/io_uring.h>
#include <uapi/linux/io_uring.h>
#include <uapi/linux/wait.h>
#include "io_uring.h"
#include "cancel.h"
@ -368,6 +369,9 @@ int io_waitid(struct io_kiocb *req, unsigned int issue_flags)
hlist_del_init(&req->hash_node);
io_waitid_remove_wq(req);
/* a non-blocking pidfd implies WNOHANG, report it as waitid(2) does */
if (!ret && !(iw->options & WNOHANG) && (iwa->wo.wo_flags & WNOHANG))
ret = -EAGAIN;
ret = io_waitid_finish(req, ret);
io_ring_submit_unlock(ctx, issue_flags);