Read-only after init¶
__ro_after_init: data that must be writable during boot but never again is placed in the.data..ro_after_initsection, whichmark_rodata_ro()flips to read-only once__initfinishes — 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¶
- LWN. Post-init read-only memory. — https://lwn.net/Articles/676145/