(CVE-2023-1872) Linux Kernel io_uring Missing Lock in io_file_get_fixed Leading to Use-After-Free and Local Privilege Escalation

CVE: CVE-2023-1872

Affected Versions: Linux kernel 5.7 through 5.17 (exclusive); stable branches 5.10.170, 5.15.96

CVSS3.1: 7.8 (High) — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Summary

Product Linux Kernel (io_uring)
Vendor Linux Kernel
Severity High — a local unprivileged attacker may exploit this to achieve local privilege escalation
Affected Versions Linux kernel 5.7 through 5.17; stable branches 5.10.170, 5.15.96
CVE Identifier CVE-2023-1872
CVE Description A use-after-free vulnerability in the Linux kernel io_uring subsystem can be exploited to achieve local privilege escalation
CWE Classification(s) CWE-416: Use After Free

CVSS3.1 Scoring System

Base Score: 7.8 (High) Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Local
Attack Complexity (AC) Low
Privileges Required (PR) Low
User Interaction (UI) None
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Technical Details

The vulnerability exists in io_file_get_fixed, which retrieves a fixed file slot for a given file descriptor without holding ctx->uring_lock:

static inline struct file *io_file_get_fixed(struct io_ring_ctx *ctx,
                         struct io_kiocb *req, int fd)
{
    struct file *file;
    unsigned long file_ptr;

    if (unlikely((unsigned int)fd >= ctx->nr_user_files))
        return NULL;
    fd = array_index_nospec(fd, ctx->nr_user_files);
    file_ptr = io_fixed_file_slot(&ctx->file_table, fd)->file_ptr;
    file = (struct file *) (file_ptr & FFS_MASK);
    file_ptr &= ~FFS_MASK;
    /* mask in overlapping REQ_F and FFS bits */
    req->flags |= (file_ptr << REQ_F_NOWAIT_READ_BIT);
    io_req_set_rsrc_node(req);
    return file;
}

Without the lock, a concurrent file unregistration can free the fixed file while io_file_get_fixed is still referencing it. This race is reachable via an async IORING_OP_SPLICE operation, where the splice path calls into io_file_get_fixed from a worker thread without holding ctx->uring_lock, while another thread unregisters the fixed file set.

The resulting use-after-free of a struct file object (allocated from the filp slab cache) is exploitable for local privilege escalation.

KASAN Crash

[  139.075384] ==================================================================
[  139.086687] BUG: KASAN: use-after-free in do_splice_to+0x27/0xc0
[  139.092853] Read of size 4 at addr ffff88812e42265c by task iou-wrk-516/517
[  139.101692] CPU: 1 PID: 517 Comm: iou-wrk-516 Tainted: G            E     5.10.170 #5
[  139.109361] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[  139.116726] Call Trace:
[  139.117775]  dump_stack+0x7d/0xa7
[  139.119009]  print_address_description.constprop.0+0x1e/0x220
[  139.121202]  ? do_splice_to+0x27/0xc0
[  139.125151]  kasan_report.cold+0x1f/0x37
[  139.129144]  ? do_splice_to+0x27/0xc0
[  139.130459]  do_splice_to+0x27/0xc0
[  139.131759]  do_splice+0x96d/0xa30
[  139.134765]  ? generic_splice_sendpage+0x20/0x20
[  139.136308]  io_issue_sqe+0x24b7/0x2ba0
[  139.139245]  ? fill_random_ptr_key+0x30/0x30
[  139.141846]  ? io_write+0x620/0x620
[  139.143165]  ? do_raw_spin_lock+0x11a/0x1e0
[  139.147506]  io_wq_submit_work+0xb2/0x170
[  139.150457]  io_worker_handle_work+0x45d/0xa10
[  139.152716]  io_wqe_worker+0x341/0x5b0
[  139.157273]  ? io_worker_handle_work+0xa10/0xa10
[  139.161554]  ? recalc_sigpending_tsk+0x83/0xc0
[  139.163205]  ret_from_fork+0x22/0x30

[  139.165024] Allocated by task 516:
[  139.167674]  __kasan_kmalloc.constprop.0+0xc9/0xd0
[  139.169310]  kmem_cache_alloc+0x12d/0x450
[  139.170722]  __alloc_file+0x25/0x1f0
[  139.171951]  alloc_empty_file+0x41/0xc0
[  139.173133]  path_openat+0xd1/0x18d0
[  139.174379]  do_filp_open+0x136/0x1c0
[  139.176924]  do_sys_open+0x8a/0xe0

[  139.181596] Freed by task 23:
[  139.186809]  __kasan_slab_free+0x10f/0x160
[  139.188259]  kmem_cache_free+0xe7/0x440
[  139.189678]  rcu_core+0x36d/0x900
[  139.190926]  __do_softirq+0xff/0x388

[  139.205774] The buggy address belongs to the object at ffff88812e422600
[  139.205774]  which belongs to the cache filp of size 320
[  139.210015] The buggy address is located 92 bytes inside of
[  139.210015]  320-byte region [ffff88812e422600, ffff88812e422740)
==================================================================

Fix

The fix adds ctx->uring_lock acquisition in io_file_get_fixed to prevent concurrent file unregistration during the splice path.

Credit

Billy Jheng Bing-Jhong of STAR Labs SG Pte. Ltd.

Timeline

  • 2023-03-02 — Reported to Linux Kernel Security Team
  • 2023-04-12 — CVE-2023-1872 published and fix merged