msg_msgseg spray¶
DATALEN_MSG'den büyük bir mesaj,load_msg()'instruct msg_msgsegcontinuation chunk'larından oluşan bir chain allocate etmesine yol açar; bu chunk'ların tüm gövdesi (sadece 8-byte'lık birnextheader hariç) attacker-controlled'dır ve aynımsgsnd()çağrısından herhangi bir kmalloc cache'inde neredeyse tam kontrol edilebilir bir heap spray verir.
Mechanism¶
Note
Bir System V mesajının ilk chunk'ı, katı struct msg_msg header'ını
(0x30 byte) artı inline text'i taşır. Ama mesaj text'i
DATALEN_MSG = 0xfd0'yi aştığında, load_msg()/alloc_msg() geri kalanı tek yönlü
bağlı bir struct msg_msgseg chunk listesine böler. Her segment'in yalnızca 8-byte'lık bir header'ı
(next) vardır ve chunk'ın geri kalanı ham user data'dır:
struct msg_msgseg {
struct msg_msgseg *next; /* 0x00: chain pointer, set by the kernel */
/* inline data follows — fully attacker-controlled */
};
Bir segment'in kontrol edilebilir bölgesi chunk_size - 8 olduğundan ve chunk boyutunu
sen seçtiğinden, msg_msgseg msg_msg'den daha yüksek fidelity'li bir spray object'tir:
header chunk'ın başında 0x30 byte kernel-controlled metadata vardır, ama bir segment
yalnızca ilk 8 byte'ı kaybeder. Bu iki iş için önemlidir:
- Bir victim slab'i controlled byte'larla doldurmak (ör. fake bir object, bir
iovya da function pointer table forge etmek) — controlled data'nın chunk içinde olabildiğince erken başlamasına ihtiyaç duyduğun durumlar. - Bir UAF/cross-cache senaryosunda free edilmiş bir chunk'ı reclaim etmek, böylece free
edilen slot
+8offset'inden itibaren attacker byte'larıyla overwrite edilir.
Her non-final segment'in boyutu DATALEN_SEG = PAGE_SIZE - 8 = 0xff8'dir; final
segment artakalan byte'ları tutar, dolayısıyla toplam mesaj uzunluğunu seçerek herhangi
bir boyuttaki chunk'ı eşleşen kmalloc-* cache'ine yerleştirirsin.
DATALEN_MSG = 0xfd0, DATALEN_SEG = 0xff8. Örnek bir split (syst3mfailure
writeup'ından): 0x1fc8 byte'lık bir user message, bir header chunk (0x30 header + 0xfd0
inline = page) artı bir msg_msgseg chunk'ı (0x8 header + 0xff8 inline = page) haline gelir;
ikisi de kmalloc-4k'ya düşer.
Walkthrough¶
Belirli bir cache'i spray'lemek için her mesajı, segment'i hedefle eşleşecek şekilde boyutlandır.
Bir kmalloc-512 segment'i için, tam bir header chunk'ının ardından ~0x200 byte'lık bir final
segment istersin:
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define DATALEN_MSG 0xfd0 /* header-chunk inline capacity */
struct msgbuf { long mtype; char mtext[0x2000]; };
/* allocate one header chunk + one segment of (size - DATALEN_MSG) bytes of payload */
int spray_seg(int qid, size_t seg_bytes, char fill) {
struct msgbuf m = { .mtype = 1 };
size_t total = DATALEN_MSG + seg_bytes; /* > DATALEN_MSG forces a segment */
memset(m.mtext, fill, total);
/* the segment body (everything after the kernel-written 8-byte next) is == fill */
return msgsnd(qid, &m, total, 0);
}
int qid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);
/* spray N segment chunks into the target cache to groom / reclaim */
for (int i = 0; i < 256; i++)
spray_seg(qid, 0x1f8, 0x41); /* 0x8 + 0x1f8 = 0x200 -> kmalloc-512 */
Grooming'den sonra segment gövdelerini geri okuyarak onları neyin reclaim ettiğini inceleyebilirsin,
çünkü msgrcv chain'i next üzerinden gezer:
struct msgbuf out;
ssize_t n = msgrcv(qid, &out, sizeof(out.mtext), 0, MSG_COPY | IPC_NOWAIT);
/* out.mtext = header inline data || segment data; offset DATALEN_MSG is segment start */
Warning
Kernel her non-final segment'in 8-byte'lık next'ini yazar, dolayısıyla her reclaim edilen
chunk'ın ilk qword'ü senin değildir — senin controlled data'n +8'den başlar. Bir segment'i
free edilmiş bir victim'i overwrite etmek için kullanırken, victim'in ilk 8 byte'ının ya önemsiz
olduğundan ya da bunun yerine gövdeyi hedeflediğinden emin ol. Tersine, bir msg_msg
header'ının next'ini fake bir segment'e chain'lemek için corrupt ederken, fake segment'in ilk
qword'ü onun next'i olarak okunur ve panic olmadan chain'i sonlandırmak için NULL (ya da
geçerli bir continuation) olmalıdır — read/write object varyantıyla aynı NULL-qword invariant'ı.
Note
msg_msgseg chunk'ları, msg_msg header chunk'larıyla aynı cache'lerde yaşar; birlikte
ele alındığında, tek bir multi-segment msgsnd bir 0x30-header'lı chunk ve bir veya daha fazla
0x8-header'lı chunk bırakır. İkisini karıştırmak, tek bir syscall'da hem corrupt edilebilir bir
header object'i hem de high-control bir filler yerleştirmene olanak tanır.
Hijack edilmiş bir next'in bu segment'leri nasıl arbitrary read/write'a çevirdiğini görmek için
msg_msg arbitrary read/write object'e, placement için ise
slab grooming / kmalloc cache feng shui
sayfalarına bak.
Detection¶
- Tek bir process'ten
DATALEN_MSG'den (0xfd0) büyük mesajların tekrar tekrarmsgsndedilmesi, segment-spray fingerprint'idir; non-final segment'ler multi-page mesajlara işaret eder. - Non-destructive read-back için
msgrcv(..., MSG_COPY),CONFIG_CHECKPOINT_RESTOREgerektirir.
Mitigation¶
- Per-cgroup
kmalloc-cg-*cache'leri, accounted edilmiş IPC allocation'larını victim object'lerden ayırarak reclaim tabanlı spray'leri köreltir. CONFIG_SLAB_FREELIST_HARDENED/RANDOM, freelist grooming'in maliyetini artırır.- IPC namespace başına
msgmax/msgmni'yi sıkılaştırmak, hem segment sayısını hem de spray hacmini sınırlar.