NULL-page mapping (mmap NULL page)¶
Virtual adres 0'ı map'le, böylece bir kernel (ya da program) NULL-pointer dereference'i fault vermek yerine attacker-controlled veri ve kod okusun.
Mechanism¶
Bir process virtual adres 0'da bir page map'lediğinde, "null" pointer artık
unmapped bir adres değildir: *(0)'ın bir dereference'i artık saldırganın doldurduğu
belleğe çarpar. Linux kernel'inin data ve code segment'leri sıfır tabanlı olduğu ve kernel
tarihsel olarak userland pointer'larını doğrudan dereference ettiği için, bir kernel
NULL-pointer dereference'i, fault veren process'in adres alanındaki aynı page-zero'ya
dokunurdu. Adres 0'a sahte bir struct (userland bir payload'a nişan alan bir function
pointer ile) yerleştiren bir saldırgan, bu nedenle zararsız görünen bir
NULL-deref bug'ını kernel code execution ve privilege escalation'a çevirebilirdi.
Note
Mitigation'ın dayattığı invariant şudur: "adres 0, ayrıcalıksız kod için unmapped
kalmalı." Linux 2.6.23 vm.mmap_min_addr sysctl'ini ekledi. Kernel, düşük mapping'leri
fiilen if ((addr < mmap_min_addr) && !capable(CAP_SYS_RAWIO)) return -EACCES;
ile reddeder, dolayısıyla ayrıcalıksız bir mmap(0, ..., MAP_FIXED, ...) EACCES ile
başarısız olur. Teknik yalnızca bu kontrol bypass edilebildiğinde işe yarar (ayrıcalıklı
context, yanlış yapılandırılmış bir mmap_min_addr=0, bir SELinux/LSM açığı ya da bir
setuid helper'a uygulanmış MMAP_PAGE_ZERO personality'si). SMEP/SMAP ve sıfırdan farklı bir
mmap_min_addr ile, düz NULL-deref bug'ları "modern kernel sürümlerinde genellikle bir güvenlik
sorunu olarak değerlendirilmez."
Walkthrough¶
Tarihsel primitive: zero page'i MAP_FIXED ile açıkça iste.
#include <sys/mman.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
void *p = mmap((void *)0, 0x1000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) {
printf("mmap(0) failed, errno=%d (%s)\n", errno, strerror(errno));
return 1;
}
printf("mmap(0) => %p\n", p);
return 0;
}
Modern, doğru yapılandırılmış bir kernel'de bu başarısız olur çünkü mmap_min_addr
varsayılan olarak sıfırdan farklı bir değerdir (yaygın olarak 65536):
Beklenen çıktı (mitigation aktif)
mmap_min_addr 0'a set edildiğinde (eski/yanlış yapılandırılmış) aynı çağrı
0x0 döndürür ve ardından kernel bug'ını tetiklemeden önce page zero'ya sahte bir
object artı payload yazabilirsin.
Warning
vm.mmap_min_addr=0 set etmek (bazen eski dosemu/Wine/qemu kurulumlarını çalıştırmak
için yapılır) bu tüm bug sınıfını yeniden açar. Birkaç CVE (örn. CVE-2009-2695 ve
sonraki LSM/personality açıkları), kernel'in sysctl sıfırdan farklı olsa bile belirli
path'lerde mmap_min_addr'i dayatmayı reddetmesinden kaynaklandı.
cr0/grsecurity tarafından belgelenen bypass zinciri:
MMAP_PAGE_ZEROpersonality bit'ini set et, ardındanCAP_SYS_RAWIOtutan bir setuid binary'siniexecet — capabilitymmap_min_addrkontrolünü bypass eder.- Ya da ayrıcalık düşüren ama bir library yüklemene izin veren ayrıcalıklı bir helper'ı
(örn.
pulseaudio -L) suistimal et, page zero map'lendikten sonra kontrolü yeniden kazan. - Alanı büyütüp yeniden permission'lamak için
mremap/mprotect, ardından deref'i tetikle.
Detection¶
/proc/sys/vm/mmap_min_addr'i oku;0(ya da çok düşük) bir değer bir tehlike işaretidir. Dağıtımlar varsayılan olarak 4096–65536 kullanır.sysctlaracılığıylavm.mmap_min_addr=0yazan kod ya da unit dosyalarını denetle.- LSM'ler (SELinux
mmap_zero/ AppArmor) hangi domain'lerin düşük belleği map'leyebileceğini kapı tutar; o iznin verilmesinde alarm ver.
Mitigation¶
vm.mmap_min_addr'i dağıtım varsayılanında tut (düşürme).- SMEP ve SMAP'ı etkinleştir, böylece page zero map'lenmiş olsa bile kernel user page'lerinden komut fetch edemesin ya da okuyamasın.
- Bir LSM kullan (
mmap_min_addrLSM hook'u / SELinuxmemprotect), böylece düşük mapping'leri yalnızca sysctl değil, policy yönetsin. - Bug'ın kendisi için, NULL dereference'i düzelt; NULL-deref'leri yalnızca crash değil, (Project Zero'ya göre) güvenlik bug'ı olarak ele al.