Guard pages¶
Linear bir overflow'un komşu mapping'i sessizce corrupt etmek yerine fault vermesi için stack'lerin ve allocation'ların yanına yerleştirilen unmapped /
PROT_NONEpage'ler.
Mechanism¶
Bir guard page, büyüyebilen veya overflow olabilen bir region'a bitişik yerleştirilen, read/write/execute permission'ı olmayan page boyutunda bir deliktir. Page un-addressable olduğu için, korunan region'ın sonunu geçen ilk sıralı erişim komşu mapping'e düşmek yerine bir fault alır (userspace'te SIGSEGV, kernel'de bir oops/panic).
Kanonik kernel kullanımı stack guard gap'tir. Orijinal stack guard page (CVE-2010-2240 için eklendi) stack VMA'sı ile komşusu arasına tek bir 4 KiB trap koydu. Stack Clash (CVE-2017-1000364) bir page'in çok küçük olduğunu gösterdi: büyük stack frame'leri allocate ederek bir program 4 KiB gap'i atlayabilirdi, böylece stack'e bir erişim aslında bitişik mapping'e (tipik olarak heap) düşüyordu; bu da guard page'e hiç dokunmadan controlled corruption ve privilege escalation veriyordu.
Invariant a guard page enforces
Fix (commit 1be7107fbe18, "mm: larger stack guard gap, between vmas"),
gap'i stack VMA'sının içinde değil VMA'lar arasında boş alan olarak tutar
— ona uymak zorunda olan birkaç yerde, başlıca arch_get_unmapped_area() ve
VMA tree'sinin subtree-gap desteğinde, vm_start yerine vm_start_gap()
(ve VM_GROWSUP için vm_end_gap()) kullanır. Gap default olarak 256 page
(4 KiB-page sistemlerde ~1 MiB)'dir ve stack_guard_gap= boot parametresiyle
(page birimi olarak) ayarlanabilir.
Invariant: stack asla başka bir mapping'in stack_guard_gap page'i içine
kadar büyüyemez, dolayısıyla bir komşuya ulaşmak için gap'ten büyük bir frame
gerekir — ve tek bir linear walk ulaşmadan çok önce fault verir.
İlgili guard region'lar aynı "komşuya ulaşmadan önce fault" özelliğini başka
yerlerde de uygular: glibc her thread stack'ini bir guard region'la çevreler
(pthread_attr_setguardsize) ve kernel'in VMAP_STACK'i her thread'in kernel
stack'ini vmalloc alanında her iki tarafta unmapped guard page'lerle allocate
eder, böylece bir kernel-stack overflow'u komşu kernel belleğini ezmek yerine
fault verir.
Walkthrough¶
1. Mevcut stack guard gap'ini oku. Default'tan büyük bir gap, stack ile onun altındaki mapping arasında bir delik olarak görünür:
$ cat /proc/self/maps | tail -3
7ffd1c2a1000-7ffd1c2c2000 rw-p 00000000 00:00 0 [stack]
7ffd1c3f0000-7ffd1c3f4000 r--p 00000000 00:00 0 [vvar]
...
[stack]'in hemen altındaki unmapped span, guard gap'tir; stack allocator onun
stack_guard_gap page'i içine başka bir VMA yerleştirmeyi reddeder.
2. Guard'ın davranışını gözle (fixed hâl). Eski sistemlerde küçük guard'lar (tek page) büyük allocation'larla atlanabilirdi; modern sistemlerde ~1 MiB gap ile basit bir 4 KiB jump hâlâ guard gap'e düşer ve fault verir. Onu atlamak için gap'ten büyük bir frame gerekir — onsuz, gap'e yapılan ilk probe fault verir:
/* touch memory one page below the current stack pointer */
volatile char *p = (char *)&p - 4096;
*p = 0; /* lands in the guard gap -> SIGSEGV */
Inspecting a thread stack guard with glibc
A guard page only stops linear overruns
Tek bir un-addressable page sıralı bir walk'ı durdurur, ama onu atlayan bir overflow (Stack Clash) veya attacker-controlled offset'li bir arbitrary write guard'ın ötesine düşebilir. Gap'in bir page'te bırakılmak yerine 4 KiB'tan ~1 MiB'a genişletilmesinin nedeni tam olarak budur.
Mitigation¶
Guard page'leri kendileri bir mitigation'dır; heap object'leri için
init-on-alloc / init-on-free ve slab
hardening ile, kernel stack'leri için ise VMAP_STACK ile birleşir.
stack_guard_gap'i default'unda (veya daha büyük) tut ve CONFIG_VMAP_STACK ile
build edilmiş kernel'leri tercih et.
References¶
- torvalds/linux. mm: larger stack guard gap, between vmas (commit
1be7107fbe18). — https://github.com/torvalds/linux/commit/1be7107fbe18eed3e319a6c3e83c78254b693acb - Red Hat. Stack Guard Page Circumvention (CVE-2017-1000364 / Stack Clash). — https://access.redhat.com/security/vulnerabilities/stackguard
- NVD. CVE-2010-2240 (Linux kernel stack guard page). — https://nvd.nist.gov/vuln/detail/CVE-2010-2240