Skip to content

msg_msgseg spray

DATALEN_MSG'den büyük bir mesaj, load_msg()'in struct msg_msgseg continuation 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 bir next header 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 iov ya 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 +8 offset'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 tekrar msgsnd edilmesi, 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_RESTORE gerektirir.

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.

References