Skip to content

Read-only after init

__ro_after_init: data that must be writable during boot but never again is placed in the .data..ro_after_init section, which mark_rodata_ro() flips to read-only once __init finishes — protecting late-set pointers and ops tables from later corruption.

Mechanism

Neden çalışır

Birçok kernel objesi yalnızca erken initialization sırasında yazılır ve sonra sonsuza dek constant gibi ele alınır: boot'ta çözülen function-pointer tabloları, feature flag'leri, vDSO image'i, güvenlikle ilgili global'ler. Bunlar basitçe const işaretlenemez çünkü — orijinal patch yazarının dediği gibi — __init sırasında "gerçekten onlara yazmamız gerekiyor"; compiler gerçekten-const bir objeyi .rodata'ya koyar ve o yazmaları reddederdi.

__ro_after_init bu gerilimi iki fazlı bir yaşam süresiyle çözer. Değişken, tüm boot boyunca writable olan özel bir section'a, .data..ro_after_init'e iner. Tüm __init kodu çalıştıktan sonra kernel mark_rodata_ro()'yu çağırır; bu, kernel page table'larını walk eder ve o section'ı destekleyen page'lerdeki writable bit'i temizler. O andan itibaren veri hardware-enforced read-only'dir.

Invariant: init'te tam olarak bir kez set edilen bir değer, sistemin ömrü boyunca değişmez hale gelir. Sonradan böyle bir pointer'ı — klasik bir data-only saldırı hedefini — overwrite etmeye çalışan bir exploit fault üretir, çünkü write bir read-only page'e çarpar.

Walkthrough

1. Makro. Yalnızca bir section attribute'üdür:

#define __ro_after_init __section(".data..ro_after_init")

/* usage */
static const struct foo_ops *foo_ops __ro_after_init;

static int __init foo_init(void)
{
        foo_ops = pick_ops();   /* legal: section is still writable here */
        return 0;
}

Pointer __init sırasında atanır; init'ten sonra okunabilir ama yazılamaz.

2. Flip. Boot'un geç bir aşamasında, kernel bellek korumalarını sonlandıran yolda, kernel rodata bölgesini — ve onunla birlikte .data..ro_after_init'i — non-writable işaretler:

mark_rodata_ro()
    -> set the .rodata / .data..ro_after_init page range read-only
       (architecture page-table update)

Bu, __init kodu/verisi free edildikten sonra çalışır, bu yüzden her __ro_after_init write'ının zaten gerçekleşmiş olması gerekir.

3. Yerleşimi doğrula. Section, kernel'in symbol/section map'inde görünür:

$ grep ro_after_init /proc/kallsyms        # bounds symbols
... __start_ro_after_init
... __end_ro_after_init
# or inspect the linker map / readelf -S vmlinux for .data..ro_after_init

Orijinal seri bunu özellikle x86 ve arm vDSO'ya bile uyguladı, "mevcut bir kernel sömürü yöntemini öldürmek için."

Yalnızca annotate edileni korur

__ro_after_init bir değişkeni yalnızca yazar onu tag'lediyse kapsar. Sıradan .data'da bırakılmış bir function-pointer tablosu hâlâ writable'dır ve bir corruption hedefi olarak kalır. Ayrıca __init sırasında hiçbir şeyi korumaz — verinin writable olduğu bir boot-time penceresi vardır.

Detection

Koruma, "tetiklenen" bir şeyden ziyade yapısaldır. mark_rodata_ro()'dan sonra bir __ro_after_init objesine yapılan bir write, write-to-read-only-page imzalı bir page-fault / oops üretir; bu da başlı başına denenmiş bir bellek corruption'ının güçlü bir sinyalidir.

Mitigation

(Artık risk / bypass.) Page, normal mapping'de read-only'dir, ama kernel physical memory'nin writable bir alias'ını direct map / physmap'te tutar; o alias üzerinden veriye ulaşan arbitrary write'a sahip bir saldırgan ya da page-table entry'sinin kendisini flip'leyebilen biri, read-only işaretlemesini bypass eder. Bu, bağımsız bir savunma değil, strict kernel RWX'in bir katmanıdır.

References