Skip to content

Relative read/write

Bilinen bir base pointer'a attacker-etkili bir offset uygulanır; bu da hedeflenen object'in dışında, base + offset'te bellek okuma ya da yazmaya izin verir — bounded ama konumlandırılabilir bir access; CWE-823.

Mechanism

Invariant

Kod bir alana, bir base pointer'a offset ekleyerek ulaşır: p = base + i*stride. Kodun korumak zorunda olduğu invariant, hesaplanan pointer'ın hedeflenen object'in içinde kalmasıdır — 0 <= i < count. CWE-823 ("Use of Out-of-range Pointer Offset") ihlali tam olarak şöyle tanımlar: ürün "geçerli bir pointer üzerinde pointer arithmetic yapar, ama ortaya çıkan pointer için geçerli bellek konumlarının hedeflenen aralığının dışına işaret edebilecek bir offset kullanır."

Offset/index güvenilmeyen bir kaynaktan (ya da bir yanlış hesaptan) geldiğinde ve bounds-check edilmediğinde, ortaya çıkan access relative'dir: attacker henüz arbitrary bir pointer'a sahip değildir, ama access'i doğrudan kontrol etmediği bir base'den bir displacement kadar uzakta konumlandırabilir. Bu, bir arbitrary read/write'tan daha zayıftır — base, allocator/layout tarafından sabittir — fakat sabit bir overflow'dan çok daha güçlüdür, çünkü offset (ve genelde işareti) attacker-chosen'dır:

  • Relative readbase + offset'te ne yaşıyorsa leak et: bir heap pointer, kaydedilmiş bir return address, bir canary, bir libc adresi — yükselmek için gereken base'lerin ta kendisini açığa çıkararak ASLR/KASLR'ı atlatır.
  • Relative write → seçilmiş bir komşuyu boz: komşu bir object'in size/length alanı, bir function pointer, allocator metadata ya da bir sonraki access'i genişleterek onu gerçek bir arbitrary primitive'e çeviren bir length.

Klasik bootstrap şudur: bir relative read bir base adresi leak eder, sonra bir relative (ya da yükseltilmiş) write onu kullanır. Bir length ya da pointer alanına inen bir relative write, "konumlandırılabilir"den "arbitrary"ye geçişin alışılmış basamağıdır.

Walkthrough

Bounds check'siz, bir pointer dizisine user-controlled bir index, kanonik relative read/write'tır — CWE-823'ün işaret ettiği şekil (bir structure'ı indekslemek için kullanılan untrusted offset):

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint64_t *table;          /* base pointer */
size_t    count = 4;

uint64_t rel_read(long i)            { return table[i]; }          /* table + i*8 */
void     rel_write(long i, uint64_t v){ table[i] = v; }            /* no 0<=i<count check */

int main(void) {
    table = calloc(count, sizeof(uint64_t));
    long i; uint64_t v;
    while (scanf("%ld", &i) == 1) {
        if (i < 0) { scanf("%lu", &v); rel_write(i, v); }          /* negative i: write below base (table + i*8) */
        else        printf("%p\n", (void*)rel_read(i));            /* positive i: read above base */
    }
    return 0;
}

Index signed/sınırsız olduğundan, negatif ve büyük-pozitif index'ler table'ın her iki ucundan da dışarı taşar:

$ gcc -g rel.c -o rel
$ printf '64\n' | ./rel            # read table+64*8 = 512 bytes past the 32-byte object
0x7f3a9c0d2b40                     # leaks an adjacent heap/libc pointer -> ASLR defeated
Kavramsal olarak relative'den arbitrary'ye

Bir relative read önce bir base (heap ya da libc) leak eder. Attacker sonra table'dan bir hedefe (örn. kaydedilmiş bir GOT girdisi ya da komşu bir object'in length alanı) olan displacement'i hesaplar ve oraya bir relative write yapar. O write, sonraki bir kopyayı kontrol eden bir length/size alanına inerse, bir sonraki işlem attacker-chosen boyutta bir out-of-bounds write olur — yani relative primitive bir arbitrary write'a yükseltilir. Bu sınıftaki gerçek CVE'ler, untrusted bir dosya değerini bir pointer offset olarak ele alan media/file parser'ları içerir (CVE-2010-1281, CVE-2009-3129, ikisi de CWE-823 tarafından alıntılanır).

İşaret ve stride tuzakları

  • Signed bir index base'in üstü kadar altına da access verir; unsigned olan yalnızca ileri yürür. Negatif index'ler, bir heap chunk'ın hemen önünde oturan allocator metadata'ya giden kolay yoldur.
  • Displacement element cinsindendir, stride (sizeof(*table)) ile ölçeklenir. Çarpmayı (burada i*8) unutmak hedefi yanlış hesaplar; access granularity'si byte değil, element boyutudur.
  • Page boundary'ler ısırır: object'in çok ötesine bir relative access, leak etmek yerine unmapped bir page'e isabet edip crash edebilir (bir DoS) — bir oracle olarak faydalı, veriye ihtiyacın varsa ölümcül.

Detection

  • AddressSanitizer, allocation'ın redzone'u dışındaki read/write'ları işaretler ve out-of-range offset'i tam olarak gösterir.
  • Statik analiz (CodeQL, Coverity, Polyspace CWE-823 checker'ları), offset'in tainted olduğu ya da [0, count) içinde olduğu kanıtlanamayan pointer arithmetic'i işaretler.
  • index/offset alanlarını fuzz'lamak onları aralık dışına sürer ve crash/leak'i yüzeye çıkarır.

Mitigation

  • Arithmetic'ten önce her index/offset'i object'in element sayısına karşı bounds-check et; unsigned-amaçlı access'ler için negatif index'leri reddet.
  • Length taşıyan güvenli soyutlamalar kullan (Rust slice'ları, std::span/std::vector::at, fat pointer'lar) ki bounds buffer ile birlikte gezsin.
  • Hassas veriyi (pointer'lar, length'ler, canary'ler) attacker-indekslenen veriyle aynı düz dizide tutma; trust domain'lerini ayır.

References

Ayrıca bkz: arbitrary-read-primitive, arbitrary-write-primitive, out-of-bounds-write, improper-validation-of-array-index, information-leak-memory-disclosure.