Skip to content

watch_queue OOB write "Blue Klotski" (CVE-2022-0995)

watch_queue notification filter kodundaki tutarsız bit-width bounds check, gap aralığındaki bir filter type'ın allocation sizing'i bypass etmesine ama yine de kabul edilmesine izin veriyor; sonuçta public exploit'lerin page seviyesinde bir use-after-free'e groom ederek local privilege escalation'a çevirdiği bir slab out-of-bounds bit-set write ortaya çıkıyor.

CVE attribution — önce bunu oku

Bu girdinin alias listesi CVE-2021-3573'ü miras almış, ama o identifier aslında Bluetooth HCI socket use-after-free'i (hci_sock_bound_ioctl() ile hci_unregister_dev() arasındaki race) — farklı bir bug class ve subsystem. Burada anlatılan watch_queue filter-notification OOB writeCVE-2022-0995'tir; NVD/vendor advisory'leri ve Quarkslab write-up'ına karşı doğrulandı. İkisini sessizce birbirine karıştırmak yerine CVE-2022-0995'i yetkili identifier olarak kullanıyor ve alias uyuşmazlığını işaretliyoruz. (CVE numaraları uydurulmadı; her ikisi de fetch edilip disambiguate edildi.)

Mechanism

Note

watch_queue_set_filter(), user'ın verdiği filter entry'lerini type_filter bitmap'inin bit width'i konusunda anlaşamayan iki yerde validate eder. Bir loop type'ı sizeof(type_filter) * 8 (bits-per-byte) ile sınırlarken gerçek set işlemi BITS_PER_LONG-ölçekli bir limit kullanır; dolayısıyla iki limit arasındaki gap'e düşen bir filter type, allocation'ı boyutlandıran accounting'i atlatır ama __set_bit(type, type_filter) tarafından hâlâ aralık içinde gibi muamele görür. İhlal edilen invariant şu: "set edilen her bit index, bitmap boyutlanırken sayılmıştı." Sonuç, allocate edilmiş watch_type_filter object'inin sonunu aşan bir __set_bit write'tır — küçük bir kmalloc slab içinde kontrollü, single-bit bir out-of-bounds write.

Tek bitlik bir OOB set kendi başına zayıftır; gücü yanında ne durduğundan gelir. Eğer overflow edilen object, bir kernel pointer tutan bir yapının (public çalışma onu bir pipe_buffer yakınına yerleştirir) hemen bitişiğine groom edilirse, o pointer'daki tek bir biti flip etmek onu yeniden hedefler.

Walkthrough

Public Quarkslab "PageJack" analizini takip eden yüksek seviyeli reproduction (sadece kavramsal parçalar — offset ya da gadget yok):

  1. Uyumsuzluğu tetikle. type'ı gap aralığına düşen bir watch-queue filter gönder; böylece ikinci loop'u geçer ama hiç boyutlandırılmamıştır, bu da __set_bit'in small-slab object'in hemen ötesinde tek bir biti flip etmesine yol açar.
  2. Komşuyu groom et. Spray yaparak bir pipe_buffer'ın (bir page pointer'ı tutan) filter object'inin hemen ardına gelmesini sağla — shaping disiplini için bkz. heap-grooming-feng-shui ve kernel-low-fragmentation-heap-exploitation.
  3. Pointer'ı bit-flip et. OOB __set_bit, bitişikteki pipe_buffer.page'in bir bitini flip ederek onun farklı bir physical page'e alias yapmasını sağlar — artık iki pipe aynı page'e referans verir.
  4. Bir page UAF üret. Bir pipe'ı free et; page geri verilir ama ikinci pipe hâlâ canlı bir reference tutmaktadır, böylece page seviyesinde bir use-after-free ortaya çıkar (page-uaf / puaf-primitive şekli).
  5. Reclaim et ve escalate et. Free edilen page'i bir target object ile spray et ve hayatta kalan reference üzerinden onu düzenleyerek privilege-ilgili alanları flip et, root shell ile bitir (bkz. page-spray).
İki limitin uyuşmazlığı, ruhen

/* loop A — accounting/size: limit ~= sizeof(type_filter) * 8   */
/* loop B — apply: limit ~= sizeof(type_filter) * BITS_PER_LONG */
__set_bit(tf[i].type, wfilter->type_filter); /* type in the gap => OOB */
Fix, her iki loop'u aynı upper bound üzerinde anlaşır hale getirir; böylece kabul edilen hiçbir bit index boyutlandırılmış bitmap'i aşamaz.

Detection

  • Source audit: bounds-check çarpanının allocation/sizing çarpanından farklı olduğu (* 8 vs * BITS_PER_LONG) herhangi bir bitmap kodunu işaretle — bu off-by-bit-width sınıfının imzasıdır. Ağaçta, user'ın verdiği index'ler üzerinde dönen ve __set_bit'i besleyen eşleşik loop'ları ara.
  • Runtime / hardening: CONFIG_SLAB_FREELIST_HARDENING ve redzoning ile (test fleet'lerinde KASAN, ya da canary host'larda production SLUB debug), OOB __set_bit redzone/bitişik metadata'yı bozar ve slab corruption olarak yakalanır.
  • Behavioural: exploit yoğun pipe()/filter churn'ü ve unprivileged bir process'ten alışılmadık bir watch-queue filter syscall patlaması üretir; watch-queue kullanım telemetry'sini ani bir cred/uid geçişiyle eşleştirmek güçlü bir sinyaldir.
  • General: herhangi bir page-UAF LPE'sinde olduğu gibi, meşru bir setuid yolu olmadan gerçekleşen bir process credential değişimini izlemek en güvenilir backstop olarak kalır.

Mitigation

  • Patch: upstream'de filter loop'ları aynı bound üzerinde anlaşır hale getirerek düzeltildi; böylece kabul edilen bir type asla boyutlandırılmış type_filter bitmap'ini aşan bir index'e denk gelemez. Fix stable tree'lere merge edildi (5.17 release'inden önce düzeltildi; geniş çapta backport edildi). Patch'li bir kernel'e güncelle.
  • Attack-surface reduction: watch_queue, keyring/pipe notification interface'leri üzerinden erişilebilir; kullanılmadığı yerde, CONFIG_WATCH_QUEUE olmadan derlenen kernel'ler bu yüzeyi tamamen kaldırır.
  • Defence-in-depth: SLUB freelist hardening, randomised freelist'ler ve init_on_free/init_on_alloc, page-spray/reclaim adımının maliyetini artırır; vm.unprivileged_userfaultfd=0 ve benzerleri, bu exploit sınıfının dayandığı grooming primitive'lerini azaltır.

References