Skip to content

top chunk extension

top chunk (wilderness) size field'ını şişirilmiş bir değerle overwrite ederek allocator'ın sahip olduğundan çok daha fazla contiguous bellek sahibi olduğuna inanmasını sağlamak; böylece top pointer'ı arbitrary bir adrese doğru yürüten oversized allocation'lara olanak verir.

Mechanism

Note

glibc ptmalloc'ta top chunk (a.k.a. wilderness), arena'nın allocate edilmemiş kuyruğudur, av->top ile takip edilir; ilk word'leri, size field'ı ne kadar free alan kaldığını kaydeden normal bir chunk header'dır. Bir request bin/fastbin'lerden karşılanamadığında, _int_malloc onu top'tan oyar: eğer request < top->size ise bir chunk döndürür ve av->top = old_top + request yapar (wilderness'i ilerletir), aksi takdirde heap'i büyütmek için sysmalloc/mmap'e geri döner.

Allocator'ın güvendiği invariant, top->size'ın contiguous region'ı dürüstçe sınırlamasıdır. Top chunk extension bunu kırar: top->size'ı büyük bir değerle overwrite et (uç durum -10xffffffffffffffff), böylece request < top->size bounds check'i her zaman geçer. Bunun üzerine malloc asla mmap'e gitmez ve keyfi büyüklükte bir request'i basitçe av->top'u ilerleterek karşılar. Bu, House of Force'un kalbindeki bozulmadır: top->size forge edilmişken, hazırlanmış tek bir devasa request av->top'u bir hedef adrese taşır ve sonraki normal malloc o hedefte bir chunk döndürür — bir arbitrary allocation/write.

Walkthrough

how2heap'in house_of_force.c'sinde olduğu gibi, top chunk'ın size field'ını overwrite et (top pointer'dan sizeof(long) offset ötesi size word'üdür):

/* clobber wilderness size with -1 so malloc never bounds-checks us out */
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;   /* top->size = 0xffffffffffffffff */

Sonra bir request'i öyle boyutlandır ki av->top'u ilerletmek tam olarak hedefe insin:

/* place the next top at `bss_var`; account for the two chunk headers how2heap subtracts */
unsigned long evil_size =
    (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
malloc(evil_size);          /* advances av->top to bss_var */

char *p = malloc(small);    /* returns a chunk AT bss_var */
strcpy(p, payload);         /* arbitrary write at the target */
Çıkarma teriminin nedeni

new_top = old_top + request ve user pointer chunk header'ın ötesinde oturur. how2heap, devasa chunk'ın header'ı artı hedefte geri verilecek chunk'ın header'ı için telafi olarak sizeof(long)*4 çıkarır; böylece döndürülen pointer — raw top değil — bss_var'a eşit olur. Tam terim (2* mı yoksa 4*sizeof(long) mı) hedefteki alignment/header'lara bağlıdır; bir sabite güvenmek yerine onu gözlemlenen top adresinden türet.

Warning

old_top'u bilmek ve evil_size'ı hesaplamak için bir heap leak'e ihtiyacın var. Ayrıca dikkat et ki çıkarma unsigned long üzerindedir, dolayısıyla wrap eder: heap'in altındaki hedeflere wrap-around ile ulaşılabilir; House of Force'un heap'ten daha düşük .bss/GOT adreslerine vurabilmesinin yolu budur.

Detection

  • sizeold_end'de page-aligned olmayan, MINSIZE'ın altında olan veya prev_inuse'u clear olan bir top chunk bozuktur.

Mitigation

  • glibc ≥ 2.29, sysmalloc'ta wilderness size'ı mantıksız olduğunda abort eden bir top-chunk integrity assertion'ı ekledi:
assert ((old_top == initial_top (av) && old_size == 0) ||
        ((unsigned long) (old_size) >= MINSIZE &&
         prev_inuse (old_top) &&
         ((unsigned long) old_end & (pagesize - 1)) == 0));

-1 olan forge edilmiş bir top->size bunu geçemez (page-aligned değil, size mantıksız), dolayısıyla klasik House of Force modern glibc'de ölüdür — ama altta yatan primitive (top->size'ı overwrite etmek), size'ın makul bir değere büyütüldüğü diğer heap hilelerini hâlâ besler.

References