oss-sec mailing list archives

Linux kernel: use-after-free in io_sqpoll_wait_sq


From: Xingyuan Mo <hdthky0 () gmail com>
Date: Thu, 22 Dec 2022 11:35:14 +0800

Hello,

There is a use-after-free vulnerability in io_sqpoll_wait_sq() in fs/io_uring.c
in linux-5.10.y through v5.10.154, which allows an attacker to crash the kernel,
resulting in Denial of Service.

=*=*=*=*=*=*=*=*=  Bug Details  =*=*=*=*=*=*=*=*=

9028:  static int io_sqpoll_wait_sq(struct io_ring_ctx *ctx)
9029:  {
9030:   int ret = 0;
9031:   DEFINE_WAIT(wait);
9032:
9033:   do {
9034:           if (!io_sqring_full(ctx))
9035:                   break;
9036:
9037:           prepare_to_wait(&ctx->sqo_sq_wait, &wait, TASK_INTERRUPTIBLE);
9038:
9039:           if (unlikely(ctx->sqo_dead)) {
9040:                   ret = -EOWNERDEAD;
9041:                   goto out;
9042:           }
9043:
9044:           if (!io_sqring_full(ctx))
9045:                   break;
9046:
9047:           schedule();
9048:   } while (!signal_pending(current));
9049:
9050:   finish_wait(&ctx->sqo_sq_wait, &wait);
9051:  out:
9052:   return ret;
9053:  }

On line 9037 of fs/io_uring.c, a wait_queue_entry object on the stack named wait
is added to wait queue ctx->sqo_sq_wait, which should be removed from
ctx->sqo_sq_wait by calling finish_wait() once the current task does not need to
wait for an available submission queue entry. Though, On line 9039, if
ctx->sqo_dead is not 0, the control flow jumps to out, skipping the call to
finish_wait() on line 9050. As a result, wait still exists in ctx->sqo_sq_wait
even when the current task exits kernel mode or comes to an end, which means
that the two entries before and after wait each contain a stale pointer to the
expired kernel stack space. If one of the two entries is later unlinked from
ctx->sqo_dead, the memory of the expired stack space pointed to by the stale
pointer will be corrupted, resulting in use-after-free.

As mentioned earlier, the condition for triggering the vulnerability is that
ctx->sqo_dead is not 0, which can be achieved by forking a new process and
terminating it quickly. When the new process exits, the copied io_uring file
descriptor will be closed, causing the following call chain to be triggered:
io_uring_flush()->io_uring_cancel_task_requests()->io_disable_sqo_submit(). In
io_disable_sqo_submit(), ctx->sqo_dead is assigned 1 on line 8732.

8729:  static void io_disable_sqo_submit(struct io_ring_ctx *ctx)
8730:  {
8731:   mutex_lock(&ctx->uring_lock);
8732:   ctx->sqo_dead = 1;
8733:   if (ctx->flags & IORING_SETUP_R_DISABLED)
8734:           io_sq_offload_start(ctx);
8735:   mutex_unlock(&ctx->uring_lock);
8736:
8737:   /* make sure callers enter the ring to get error */
8738:   if (ctx->rings)
8739:           io_ring_set_wakeup_flag(ctx);
8740:  }

=*=*=*=*=*=*=*=*=  Patch  =*=*=*=*=*=*=*=*=

The patch can be found here:
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v5.10.161&id=0f544353fec8e717d37724d95b92538e1de79e86

=*=*=*=*=*=*=*=*=  Credit  =*=*=*=*=*=*=*=*=

Xingyuan Mo and Gengjia Chen of IceSword Lab, Qihoo 360 Technology Co. Ltd.

Best Regards,
Xingyuan Mo


Current thread: