Add support for the POLLFREE flag to force complete iocb inline in aio_poll_wake(). A thread may use it to signal it's exit and/or request to cleanup while pending poll request. In this case, aio_poll_wake() needs to make sure it doesn't keep any reference to the queue entry before returning from wake to avoid possible use after free via poll_cancel() path. UAF issue was found during binder and aio interactions in certain sequence of events [1]. The POLLFREE flag is no more exclusive to the epoll and is being shared with the aio. Remove comment from poll.h to avoid confusion. [1] https://lore.kernel.org/r/CAKUd0B_TCXRY4h1hTztfwWbNSFQqsudDLn2S_28csgWZmZAG3Q@xxxxxxxxxxxxxx/ Fixes: af5c72b1fc7a ("Fix aio_poll() races") Signed-off-by: Ramji Jiyani <ramjiyani@xxxxxxxxxx> Reviewed-by: Jeff Moyer <jmoyer@xxxxxxxxxx> Cc: stable@xxxxxxxxxxxxxxx # 4.19+ --- Changes since v1: - Removed parenthesis around POLLFREE macro definition as per review. - Updated description to refer UAF issue discussion this patch fixes. - Updated description to remove reference to parenthesis change. - Added Reviewed-by Changes since v2: - Added Fixes tag. - Added stable tag for backporting on 4.19+ LTS releases Changes since v3: - Updated patch description - Updated Fixes tag to issue manifestation origin --- fs/aio.c | 45 ++++++++++++++++++--------------- include/uapi/asm-generic/poll.h | 2 +- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/fs/aio.c b/fs/aio.c index 51b08ab01dff..5d539c05df42 100644 --- a/fs/aio.c +++ b/fs/aio.c @@ -1674,6 +1674,7 @@ static int aio_poll_wake(struct wait_queue_entry *wait, unsigned mode, int sync, { struct poll_iocb *req = container_of(wait, struct poll_iocb, wait); struct aio_kiocb *iocb = container_of(req, struct aio_kiocb, poll); + struct kioctx *ctx = iocb->ki_ctx; __poll_t mask = key_to_poll(key); unsigned long flags; @@ -1683,29 +1684,33 @@ static int aio_poll_wake(struct wait_queue_entry *wait, unsigned mode, int sync, list_del_init(&req->wait.entry); - if (mask && spin_trylock_irqsave(&iocb->ki_ctx->ctx_lock, flags)) { - struct kioctx *ctx = iocb->ki_ctx; + /* + * Use irqsave/irqrestore because not all filesystems (e.g. fuse) + * call this function with IRQs disabled and because IRQs have to + * be disabled before ctx_lock is obtained. + */ + if (mask & POLLFREE) { + /* Force complete iocb inline to remove refs to deleted entry */ + spin_lock_irqsave(&ctx->ctx_lock, flags); + } else if (!(mask && spin_trylock_irqsave(&ctx->ctx_lock, flags))) { + /* Can't complete iocb inline; schedule for later */ + schedule_work(&req->work); + return 1; + } - /* - * Try to complete the iocb inline if we can. Use - * irqsave/irqrestore because not all filesystems (e.g. fuse) - * call this function with IRQs disabled and because IRQs - * have to be disabled before ctx_lock is obtained. - */ - list_del(&iocb->ki_list); - iocb->ki_res.res = mangle_poll(mask); - req->done = true; - if (iocb->ki_eventfd && eventfd_signal_allowed()) { - iocb = NULL; - INIT_WORK(&req->work, aio_poll_put_work); - schedule_work(&req->work); - } - spin_unlock_irqrestore(&ctx->ctx_lock, flags); - if (iocb) - iocb_put(iocb); - } else { + /* complete iocb inline */ + list_del(&iocb->ki_list); + iocb->ki_res.res = mangle_poll(mask); + req->done = true; + if (iocb->ki_eventfd && eventfd_signal_allowed()) { + iocb = NULL; + INIT_WORK(&req->work, aio_poll_put_work); schedule_work(&req->work); } + spin_unlock_irqrestore(&ctx->ctx_lock, flags); + if (iocb) + iocb_put(iocb); + return 1; } diff --git a/include/uapi/asm-generic/poll.h b/include/uapi/asm-generic/poll.h index 41b509f410bf..f9c520ce4bf4 100644 --- a/include/uapi/asm-generic/poll.h +++ b/include/uapi/asm-generic/poll.h @@ -29,7 +29,7 @@ #define POLLRDHUP 0x2000 #endif -#define POLLFREE (__force __poll_t)0x4000 /* currently only for epoll */ +#define POLLFREE (__force __poll_t)0x4000 #define POLL_BUSY_LOOP (__force __poll_t)0x8000 -- 2.33.0.800.g4c38ced690-goog