Stack clash¶
Bir process'in
stack'ini komşu bir memory region ile çarpışana kadar büyütüp, sonraguard page'in üzerinden "atlayarak" o region ile overlap edip onu corrupt etmek.
Mechanism¶
Bir process stack'i talep üzerine büyür: stack pointer eşlenmemiş belleğe
dokunduğunda, kernel'in page-fault handler'ı stack'i aşağı doğru genişletir.
Qualys'in 2017 tarihli "The Stack Clash" advisory'si, bu büyümenin komşu
mapping'lere karşı güvenli şekilde sınırlanmadığını gösterdi.
Note
Tutması gereken invariant, stack'in yalnızca boş alana doğru
büyüyebilmesi ve bir fault tarafından yakalanmasıdır. Advisory bunu kırar:
"if the stack-pointer of a process can move from the stack into another
memory region (which ends exactly where the stack starts) without raising a
page-fault, then the process uses this other memory region as if it were an
extension of the stack." Kernel'in savunması bir guard page'dir —
stack'in altındaki eşlenmemiş bir PROT_NONE page — ancak yeterince büyük
tek bir allocation onu atlar: "if the stack-pointer 'jumps' over the
guard-page ... then no page-fault exception is raised and the stack extends
into the other memory region." O teorik guard-page atlamasına
CVE-2017-1000364 atandı. stack ile bir komşu (heap, mmap veya
kütüphane) aynı page'leri overlap ettiğinde, birinden yapılan write'lar
diğerini ezer.
Advisory, exploitation'ı bir sekans olarak çerçeveler:
- Clash —
stackbaşka bir region'a dayanana kadar stack memory ayır. - Jump — guard page'e dokunmadan stack pointer'ı guard page üzerinden o region'a taşı.
- Smash —
stack'i diğer region ile overwrite et, ya da tersi.
Walkthrough¶
Clash, bir kerede büyük miktarda stack tüketen herhangi bir şeyle tetiklenir:
derin recursion, büyük alloca()/VLA'lar veya devasa argv/environ. Qualys,
kontrollü allocation'ı sağlayan birkaç gerçek bug için PoC'ler yayınladı; bunlara
CVE-2017-1000366 (glibc ld.so etkileşimi), CVE-2017-1000367 (Sudo tty
parsing) ve CVE-2017-1000369 (Exim) dahildir (tam liste için aşağıdaki
Qualys advisory'ye bak).
/* conceptual: a single large alloca jumps the stack pointer past the guard page */
void deep(size_t n) {
char gap[n]; /* attacker-influenced size = jump distance */
gap[0] = 0; /* first touch may land inside the heap/mmap region */
}
Çarpışmayı /proc//maps içinde gözlemleme
$ cat /proc/$(pidof victim)/maps
...
7ffff7a00000-7ffff7dd0000 r-xp ... libc.so <-- mmap region
7ffffffde000-7ffffffff000 rw-p ... [stack] <-- grows downward
# under a stack-clash the [stack] low bound descends toward the mapping above;
# a single oversized frame lets %rsp land inside the libc/mmap mapping,
# so subsequent stack stores overwrite that region's data.
[stack] altında duran 4 KiB'lık guard page'e dokunulmak yerine
üzerinden atlanır, dolayısıyla atlama anında hiçbir SIGSEGV oluşmaz.
Detection¶
stack'in bir mapping'e taşması, /proc/<pid>/maps içinde anormal [stack]
sınırları olarak ya da beklenmedik bir region içindeki fault'lar olarak ortaya
çıkar. Dağıtımlar, guard region'ı büyüten ve (bazıları için) bir stack-probing
ABI ekleyen kernel patch'leri yayınladı; uyumsuz probing, derleme zamanında
tespit edilebilir.
Mitigation¶
Koordineli düzeltme, kernel stack guard gap'ini büyüttü (Linux'ta 1 MiB'lık bir
guard region) ve compiler stack-probing (-fstack-clash-protection) ekledi;
böylece her büyük frame aşağı inerken her guard page'e dokunur ve bir atlama
yerine bir fault'u garanti eder. Bkz. guard page'ler
ve ASLR (advisory ayrıca
offset2lib varyantlarıyla ASLR'yi de yendi).