msg_msg arbitrary read/write object¶
System V message'leri (
msgsnd/msgrcv), inline header'ı —m_tsvenext— kernel'in ne kadar ve nereden/nereye kopyalayacağını belirleyen elastic kmalloc object'lerdir; birstruct msg_msgüzerindeki tek seferlik corruption'ı relative OOB read'e veyanextpointer ü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 olarakm_tsbyte'ı userland'e kopyalar.m_ts'yi gerçek payload'ın ötesine şişirmek bir relative OOB read verir.next— birstruct msg_msgsegcontinuation 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ışancopy_from_useraracılığıyla)next'e kopyalar. Bu yüzdennext'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_COPYread'leriCONFIG_CHECKPOINT_RESTOREgerektirir; unprivilegedmsgrcv(..., MSG_COPY)nadirdir ve flag'lenebilir.- Çok sayıda aynı boyutta message allocate eden
msgget/msgsndpatlamaları (heap spray imzası). Per-namespacemsgmni/msgmaxaccounting'i hacmi sınırlar ve denetler.
Mitigation¶
copy_msg()memcpykullanı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_VARSIZEvemsg_msg'yi victim object'lerden uzak tutan per-cgroupkmalloc-cg-*ayrımı.CONFIG_CHECKPOINT_RESTORE'u devre dışı bırakmak non-destructiveMSG_COPYread yolunu kaldırır.msgmax/msgmni'yi kısıtlamak spray yüzeyini küçültür.