Skip to content

race-condition UAF LPE (CVE-2020-27786)

ALSA rawmidi ioctl buffer-resize path'indeki bir race, eşzamanlı bir read/write hâlâ kullanırken runtime buffer'ı free eder; local root'a yükselen bir use-after-free doğurur.

Mechanism

Note

İhlal edilen invariant şudur: "başka bir thread ona canlı bir pointer tutarken bir buffer free edilmemelidir." ALSA rawmidi core'u, user space'in runtime->buffer'ı ioctl (SNDRV_RAWMIDI_IOCTL_PARAMS) üzerinden resize etmesine izin verir. Bu sırada snd_rawmidi_kernel_read1() / snd_rawmidi_kernel_write1(), user space'e/'den kopyalamak zorundadır ve bunu yapmak için copy_from_user/copy_to_user çevresinde runtime spinlock'u geçici olarak bırakırlar. O unlocked aralık race window'udur: ikinci bir thread buffer'ı resize edip eski runtime->buffer'ı free edebilir, birinci thread ise devam edip artık free edilmiş pointer'ı dereference eder — bir use-after-free write/read.

Bug, ders kitabı niteliğinde bir race-condition-uaf-lpe desenidir: user memory'ye güvenli dokunmak için bir lock bırakılır, ama koruduğu shared object o boşlukta mutate edilip free edilebilir. User hem kopya zamanlamasını (page fault'lar yoluyla) hem de resize'ı kontrol ettiği için, window deterministik biçimde genişletilebilir.

Walkthrough

Public write-up ve oss-security ifşasına göre race, iki thread artı kopyayı durdurmak için userfaultfd ile sürülür.

Kavramsal race yapısı
// Thread A: issue a write whose source page is backed by userfaultfd.
// copy_from_user() blocks mid-copy with the runtime lock released,
// while A still holds a pointer into runtime->buffer.
write(midi_fd, uffd_backed_page, len);

// Thread B: resize the runtime buffer, which frees the old buffer.
struct snd_rawmidi_params p = { .buffer_size = new_size, ... };
ioctl(midi_fd, SNDRV_RAWMIDI_IOCTL_PARAMS, &p);

// Thread A resumes the copy -> writes into freed memory (UAF).

Mantıksal adımlar:

  1. Bir rawmidi cihazı aç ve user buffer'ı fault veren (userfaultfd) bir read ya da write başlat, böylece kernel'i lock bırakılmış hâlde kopya ortasında askıya al.
  2. Başka bir thread'den runtime buffer'ı resize et, böylece eski allocation kvfree() edilir.
  3. Durdurulmuş kopyayı devam ettir; kernel artık free edilmiş slab memory'sini okur/yazar.
  4. UAF'i kontrollü bir write'a çevirmek için free edilmiş object'i saldırgan kontrolündeki bir allocation ile (heap grooming) reclaim et, sonra yükselt. Bkz. slab-grooming ve arbitrary-write-primitive.

Detection

  • Audio rolü olmayan process'lerin /dev/snd/midi* / rawmidi node'larını open() etmesini, özellikle userfaultfd kullanımıyla birlikte denetleyin.
  • Güvenilmeyen process'lerden gelen userfaultfd syscall'ları birçok kernel UAF race'i için güçlü bir exploitation sinyalidir; workload'un ihtiyaç duymadığı yerlerde bunları logla/alarm ver.
  • KASAN-açık kernel'ler "use-after-free in snd_rawmidi_kernel_write1/read1" splat'ları yayar — test/CI fleet'lerinde paha biçilmez.
  • Aynı fd üzerinde read/write ile iç içe geçmiş SNDRV_RAWMIDI_IOCTL_PARAMS'ı zorlayan multi-threaded process'leri izleyin.

Mitigation

Warning

snd_rawmidi_buffer_ref() / snd_rawmidi_buffer_unref() reference counting'i ekleyen c13f1463d84b86bedb664e509838bef37e6ea317 commit'ini içeren bir kernel'e yamalayın; resize_runtime_buffer() ardından runtime->buffer_ref sıfır olmadığı sürece free etmeyi reddeder (bir hata döndürür) ve window'u kapatır.

  • Distribution kernel'leri (SUSE, Debian, Ubuntu vb.) düzeltmeleri 2021 başında dağıttı — vendor güncellemelerini uygulayın.
  • UAF exploitation'ını genel olarak körelten hardening: vm.unprivileged_userfaultfd=0 (userfaultfd'yi privileged user'lara kısıtla), slab-freelist-hardening ve freelist randomization exploitation maliyetini yükseltir.
  • rawmidi gerekmediğinde /dev/snd/ cihaz node'larına erişimi kısıtlayın.

References