Skip to content

io_uring fixed-buffer OOB physical memory access (CVE-2023-2598)

io_uring fixed-buffer registration'ındaki hatalı bir folio-coalescing optimizasyonu, çok kez map'lenen tek bir physical page olan bir buffer'ın fiziksel olarak contiguous gibi ele alınmasına izin verir, böylece READ_FIXED/WRITE_FIXED o page'in ötesindeki physical memory'yi okur ve yazar.

Mechanism

IORING_REGISTER_BUFFERS, fixed buffer'ları io_sqe_buffer_register() (io_uring/rsrc.c) üzerinden register eder. Bir buffer'ı verimli temsil etmek için function, tüm page'lerinin tek bir folio'ya (compound page) ait olduğu durumu tespit etmeye ve onları tek bir bio_vec'e collapse etmeye çalışır.

Note

Check, page'lerin aynı folio'ya map'lendiğini doğrular ama onların ardışık olduğunu asla doğrulamaz:

for (i = 1; i < nr_pages; i++) {
        if (page_folio(pages[i]) != folio) { folio = NULL; break; }
}
Aynı physical page farklı virtual adreslerde çok kez map'lenebilir, dolayısıyla tüm "page'ler" tek bir folio paylaşır ve check geçer. Buffer ardından tek bir bio_vec ile tanımlanır; bv_page'i o tek page'e işaret eder ama bv_len'i tam virtual length'e ayarlanır. Sonraki fixed I/O, bv_page'ten başlayan bv_len byte'ı geçerli olarak ele alır — page'in ötesindeki physical memory'yi okur veya yazar.

Kavramsal görünüm (boyutlar/adresler kernel'e göre değişir):

 Virtual view (registration sırasında görülen)        Physical reality
 ┌──────────┬──────────┬──────────┬──────────┐        ┌──────────┐
 │ vpage 0  │ vpage 1  │ vpage 2  │ vpage 3  │        │  PAGE P  │  <- tek physical page
 └──────────┴──────────┴──────────┴──────────┘        ├──────────┤
   her biri MAP_FIXED ile PAGE P'ye map'li            │ ??? (OOB)│  <- ardışık varsayılan,
   → page_folio() hepsi için aynı folio               │ ??? (OOB)│     aslında ilgisiz
                                                       │ ??? (OOB)│     physical memory
 Coalesce sonrası tek bio_vec:                         └──────────┘
   bv_page = PAGE P            (tek page)
   bv_len  = 4 * PAGE_SIZE     (tüm virtual span)
                 └─ fixed I/O bv_page'ten itibaren bv_len byte okur/yazar:
                    ilk PAGE_SIZE byte geçerli, kalanı PAGE P'nin ötesindeki
                    physical memory'ye OOB read/write.

Warning

v6.3-rc1'de (commit 57bebf807e2a) tanıtıldı, v6.4-rc1'de (commit 776617db78c6, "io_uring/rsrc: check for nonconsecutive pages") düzeltildi; 6.3.2 için backport 14ad317320c7c. Sınıflandırma CWE-787 (OOB write); CVSS 3.1 7.8 (High), AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. Primitive, physical memory'ye OOB erişimdir, ki bu alışılmadık biçimde güçlüdür.

Walkthrough

Reproduction (Yousef Sajjadi / anatomic.rip PoC):

  1. Bir memfd oluştur ve içinde tek bir page fallocate et.
  2. O tek page'i MAP_FIXED ile ardışık virtual adreslere tekrar tekrar mmap'le.
  3. Tüm region'ı IORING_REGISTER_BUFFERS ile bir fixed buffer olarak register et — folio check kandırılır, böylece bvec.bv_len virtual span olur ama bv_page tek bir page'dir.
  4. IORING_OP_WRITE_FIXED buffer'ı başka bir file'a yazar → physical memory'nin OOB read'i; IORING_OP_READ_FIXED buffer'a okur → physical memory'ye OOB write.

Yayınlanan exploit bunu, KASLR'ı yenmek için bir struct sock leak ederek, ioctlcall_usermodehelper_exec()'e işaret eden bir struct proto forge ederek ve onu bir root komutu çalıştırmak için çağırarak LPE'ye zincirler.

Detection

KASAN physical OOB'yi doğrudan yakalamaz (erişim geçerli bir bio_vec üzerindendir); tanıtan/düzelten commit'ler ve version aralığı güvenilir göstergelerdir. Memfd + tek bir page'in tekrarlı MAP_FIXED'i ardından buffer registration gözlemlenebilir pattern'dir.

Mitigation

≥ 6.4-rc1 / backport'lara güncelle; burada registration kodu page contiguity'sini doğrular. Unprivileged io_uring'i kısıtlamak (io_uring_disabled) registration path'ine erişimi kaldırır.

References