Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <e18a885b-de15-c287-e4a4-a0df7904f15f@tholl.xyz>
Date: Mon, 8 May 2023 16:01:59 +0200
From: Tobias Holl <tobias@...ll.xyz>
To: oss-security@...ts.openwall.com
Subject: Linux kernel io_uring out-of-bounds access to physical memory

Hi all,

a bug in the fixed buffer registration code for io_uring
(io_sqe_buffer_register in io_uring/rsrc.c) allows out-of-bounds access
to physical memory beyond the end of the buffer. This can be used to
achieve full local privilege escalation.

The vulnerable code landed in 6.3-rc1 with commit 57bebf807e2a
("io_uring/rsrc: optimise registered huge pages")¹.

A fix has been committed upstream for 6.4-rc1 in commit 776617db78c6
("io_uring/rsrc: check for nonconsecutive pages")². The fix has also
been staged³ for 6.3.2.

CVE assignment for this issue is pending.


The idea behind the original commit is that instead of splitting huge
pages that are registered as a buffer into individual bvec entries
(expensive), we can just have a single bvec entry for all parts of the
huge page that are in the buffer (not expensive). Specifically, if all
pages in the buffer map to the same folio, it will use the first struct
page and the length of the buffer in a single bvec entry rather than
mapping each page individually.

For a normal huge page, this works. Unfortunately, this misses the fact
that just because the pages map to the same folio, they do not
necessarily have to be consecutive. In fact, they can all be the _same_
page of memory (e.g. by repeatedly mapping at offset 0 from a memfd).
Then, the bvec will span far beyond the single page that it is actually
allowed to touch. Later, IORING_OP_READ_FIXED and IORING_OP_WRITE_FIXED
allow us to read from and write to the buffer (i.e. the memory pointed
to by the bvec) at will. This allows read/write access to the physical
memory behind the single page that we actually have.

The actual length of the buffer (and therefore the limit of our OOB
access) is only limited by the number of pages we can map (generally
close to vm.max_map_count, since each mapping of the same page requires
a new mapping). If hugetlbfs is enabled and huge pages are set up, you
can use them to access even more memory, but this isn't generally
required in order to find something interesting in the accessible
memory.


TL;DR bug reproduction steps:
  1. Create a memfd
  2. fallocate a single page in that file descriptor
  3. Use MAP_FIXED to map this page repeatedly, in consecutive locations
  4. Register the entire region that you just filled up with that page as
     a fixed buffer with IORING_REGISTER_BUFFERS
  5. Use IORING_OP_WRITE_FIXED to write the buffer to some other file
     (OOB read) or IORING_OP_READ_FIXED to read data into the buffer (OOB
     write).

Of course, from there, we can simply find any interesting object in
physical memory and start overwriting function pointers to get code
execution and escalate privileges. A full proof-of-concept exploit with
a bit more robustness can be found at
   https://tholl.xyz/static/bugs/2023-io_uring-fixed-buffers/exploit.c


-- Tobias


¹ https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=57bebf807e2abcf87d96b9de1266104ee2d8fc2f
² https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=776617db78c6d208780e7c69d4d68d1fa82913de
³ Commit 14ad317320c7c000e89ee5a928b9ca165443af0e for 6.3.2-rc1,
   https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable-rc.git/commit/?h=linux-6.3.y&id=14ad317320c7c000e89ee5a928b9ca165443af0e

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.