msg_msg
SysV Message Queue (
msg_msg) IPC
1. msg_msg / msg_msgseg Structures
struct msg_msgdefined ininclude/linux/msg.hstruct msg_msgsegdefined locally inipc/msgutil.c
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; // message text size
struct msg_msgseg *next; // next segment, if message > DATALEN_MSG
void *security;
/* the actual message follows immediately */
};
struct msg_msgseg {
struct msg_msgseg *next;
/* the next part of the message follows immediately */
};
m_ts: Message text size, controlled bymsgsnd(msgsz), useful for heap grooming.next: Points to the nextmsg_msgsegfor larger messages. Corrupting it can enable arbitrary read/write by making the kernel follow an attacker-controlled pointer.
Size:
sizeof(struct msg_msg)is 48 bytes (m_list16 +m_type8 +m_ts8 +next8 +security8).DATALEN_MSG = PAGE_SIZE - sizeof(struct msg_msg)→4096 - 48 = 4048bytes fit inline in the first allocation.- If
msgsz > DATALEN_MSG, the remainder is split intomsg_msgsegchunks ofDATALEN_SEG = PAGE_SIZE - sizeof(struct msg_msgseg)→4096 - 8 = 4088bytes each, chained via->next. - Total allocation size for the first chunk is
sizeof(struct msg_msg) + min(len, DATALEN_MSG)=48 + msgszformsgsz <= 4048. This means we can can hit any kmalloc size class fromkmalloc-64up tokmalloc-4kjust by choosingmsgsz = target_size - 48.
2. Exploitation
Three syscalls cover the whole primitive:
msgget()— create the message queue (IPC object) that will hold the sprayed objects.
int msg_id = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
msgsnd()— allocate + fill amsg_msg(callsload_msg()→alloc_msg()→kmem_buckets_alloc()).
struct msgbuf {
long mtype;
char mtext[SPRAY_SIZE];
};
struct msgbuf msg = { .mtype = 1 };
memset(msg.mtext, 'A', SPRAY_SIZE); // SPRAY_SIZE = target_kmalloc_size - 48
msgsnd(msg_id, &msg, SPRAY_SIZE - HEADER_SIZE, 0);
msgrcv()— read a message back out (callsdo_msgrcv()→ copiesm_tsbytes to userspace, then frees viafree_msg()unless peeking). This is the leak / reclaim step:- If a bug lets you inflate
m_ts(OOB write) or you UAF a freedmsg_msgand respray something else into it,msgrcv()will happily copy back more bytes than you originally sent — an OOB read / heap-leak primitive. MSG_COPYflag (with a positivemsgtypused as an index) lets you peek at a message without dequeuing it, handy for repeated leaks from the same spray.
- If a bug lets you inflate
char leak[SPRAY_SIZE];
ssize_t n = msgrcv(qid, leak, sizeof(leak), 0, 0);
// leak now contains SPRAY_SIZE
- msgctl():
msgctl(qid, IPC_RMID, NULL)frees every queued message in one shot (freeque()→free_msg()on each). Useful for triggering a batch of frees at once (double-free / UAF setup), same rolefcntl(F_SETPIPE_SZ)plays forpipe_buffer.
msgctl(msg_id, IPC_RMID, NULL);
3. Debugging
load_msg/alloc_msg(ipc/msgutil.c) , We can break here in gdb to watchmsg_msg/msg_msgsegallocation and size selection.free_msg(ipc/msgutil.c) , break here to analyse frees.do_msgsnd/do_msgrcv(ipc/msg.c) , syscall entry points.
4. Mitigations
- Dedicated slab bucket —
ipc/msgutil.c:42-52now allocatesmsg_msgfrom a privatekmem_buckets_create("msg_msg", SLAB_ACCOUNT, ...)bucket set instead of the sharedkmalloc-Ncaches. - seccomp-bpf syscall filtering — Eplicitly deny
msgget/msgsnd/msgrcv/msgctl. Seen in hardened Kubernetes seccomp profiles.
Similar Notes: https://leo1.cc/posts/docs/msg_msg/