Skip to content

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)
$ cat /proc/sys/vm/mmap_min_addr
65536
$ ./map0
mmap(0) failed, errno=13 (Permission denied)

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:

  1. MMAP_PAGE_ZERO personality bit'ini set et, ardından CAP_SYS_RAWIO tutan bir setuid binary'sini exec et — capability mmap_min_addr kontrolünü bypass eder.
  2. 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.
  3. 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.
  • sysctl aracılığıyla vm.mmap_min_addr=0 yazan 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_addr LSM hook'u / SELinux memprotect), 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.

References