Repository navigation
[Bug] cromfs: directory name_size is not bounded, ls/getdents overflows the dirent buffer and hijacks the kernel PC #11905
Description
RT-Thread Version
master @ 1c801efabc36 (2026-10-19, v5.4.0-dev). Both copies still contain the unbounded copy, read via the contents API on 2026-10-20:
components/dfs/dfs_v1/filesystems/cromfs/dfs_cromfs.c:1121-1124components/dfs/dfs_v2/filesystems/cromfs/dfs_cromfs.c:1181-1184
Introduced by commit 0bc01ad0d9 ([dfs] sync cromfs (#6739), 2022-12-14). Absent at v4.1.1; present from v5.0.0 (2023-03-31) through v5.3.0 and current master. The v2 copy was split out by #7200 (2023-04-08) and still has the same copy. #9197 and #10054 fixed other cromfs bugs and did not add a bound here. No later commit on either file touches dfs_cromfs_getdents.
Affected area
File systems (DFS)
Hardware/BSP vendor
Not applicable / Other
Architecture
ARM / AArch64
Board and hardware details
Reproduced on QEMU vexpress-a9 (Cortex-A9), stock bsp/qemu-vexpress-a9 plus RT_USING_DFS_CROMFS=y. The bug is portable C and is not specific to that board. In-tree BSPs that actually ship cromfs and mount it as the root filesystem are bsp/k230 (applications/mnt.c, dfs_mount(NULL, "/", "crom", 0, data)) and bsp/cvitek (drivers/port/mnt_cromfs.c, mount table entry "crom" on /).
Develop Toolchain
GCC (arm-none-eabi-gcc 10.3.1, scons)
Describe the bug
dfs_cromfs_getdents copies a directory name out of the cromfs image using the image's own uint16 name_size, with no comparison against struct dirent.d_name. d_name is DIRENT_NAME_MAX (256) bytes (components/libc/compilers/common/include/dirent.h). name_size can be 0..65535.
/* dfs_v1/.../cromfs/dfs_cromfs.c:1121-1124
* dfs_v2/.../cromfs/dfs_cromfs.c:1181-1184 is the same four lines */
d->d_namlen = sub_dirent->dirent.name_size;
d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
memcpy(d->d_name, (char *)name, sub_dirent->dirent.name_size);
d->d_name[sub_dirent->dirent.name_size] = '\0';name_size >= 256 writes past d_name. The NUL is stored at index name_size, so it is also out of the array. Callers do not clip this. The msh ls command keeps one struct dirent on the shell thread's stack and passes sizeof(struct dirent):
/* dfs_v1/src/dfs_file.c:786-819 (dfs_v2/src/dfs_file.c:2708 and :2752, and copydir at :3024) */
void ls(const char *pathname)
{
struct dirent dirent;
...
length = dfs_file_getdents(&fd, &dirent, sizeof(struct dirent));getdents turns that byte count into an entry count (count / sizeof(struct dirent) == 1) and then copies name_size bytes into that one object. A name longer than 255 bytes therefore overflows the stack object. The same pattern is used by readdir (DIR.buf[512]) and by sys_getdents (a kernel buffer of cnt * sizeof(struct dirent)).
Reproduction (stock mount + ls on RT-Thread, QEMU vexpress-a9, one boot, control then crash):
- Enable
RT_USING_DFS_CROMFSand provide the image through a devicemountcan read (any byte-addressed device; cromfs usescromfs_noblk_read_bytesfor non-block devices). A minimal image is a 256-byte header (magic "CROMFSMG",root_dir_pos = 256,partition_sizecovering the blob,root_dir_size= the directory blob) followed by onecromfs_dirent:attr = 0,name_size, three zerouint32s, then the name bytes. No zlib stream is involved; directory entries are stored in the clear. - Control image,
name_size = 2, name =ok:Result:mount crom_ok / crom ls / echo CONTROL_OK umount /mount ... succeed!,lsprintsok,CONTROL_OKis printed,umountsucceeds. The shell is still alive. - Same steps with
name_size = 512, name ='A'*256 + 'B'*256:mount crom_evil / crom # succeed ls /lsprintsBAD file:and a run ofAs (the in-object part of the name;dfs_file.c:842), then the shell thread takes a prefetch abort and the kernel shuts down:prefetch abort: fp :0x42424242 ip :0xffffffff sp :0x60114088 lr :0x6007a590 pc :0x42424246 cpsr:0x200f0013 tshell running shutdown... assertion failed at function:rt_hw_cpu_shutdown0x42is'B', i.e. the bytes pastd_name[256].fpis the saved frame pointer ofls.pc = 0x42424246is that value plus 4, the faulting fetch.lr = 0x6007a590resolves withaddr2linetolsatdfs_file.c:848(dfs_file_close/ the epilogue). The backtrace printer itself is handed0x42424242.r4–r9are still0xdeadbeef, so this is the overflowed frame, not a wiped register file. The command afterls(EVIL_SURVIVED) is never reached.
The only difference between the two images is name_size and the name bytes. Mount accepts both (magic and partition_size are consistent).
Expected behavior
dfs_cromfs_getdents must refuse a directory entry whose name_size >= DIRENT_NAME_MAX (the copy plus the terminating NUL have to fit in d_name). The same check belongs in both the v1 and the v2 copy. The entry should be skipped or the call should fail; it must not memcpy name_size bytes into the caller buffer. The name should also be required to lie inside the directory blob that was just loaded (fi->size), so a name_size that runs off the end of that buffer cannot be used as a copy length either.
Other additional context
- Attack precondition: the attacker supplies a cromfs image. That image is the filesystem's input — a block device, or the
datapointer passed todfs_mount(..., "crom", ..., data)as k230 and cvitek do at boot. After the image is mounted, an ordinary directory listing (ls,readdir, orgetdents) is enough. This is not a network bug, andRT_USING_DFS_CROMFSis not a global default (it is selected by the k230 and cvitek BSPs). On a non-Smart build the shell that runsmount/lsis already kernel context; the trust boundary being crossed is the filesystem image, not a user/kernel syscall. The corruption happens inside the parser before any file on that image is executed. - Impact: kernel stack buffer overflow in the thread that lists the directory, with the overflowing bytes chosen by the image. On the reproduced path that is the msh thread, and the saved frame pointer becomes attacker bytes, so the return from
lstransfers control to an attacker-chosen address (here0x42424246, which faults). A name that does not fault can instead return into chosen kernel code. CVSS 3.1: 7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. CWE-787 (the length comes from an unchecked image field, CWE-1284). - Not the same bug as [HUST CSE][dfs] fix stack buffer overflow on long path components in devtmpfs #11842 / [HUST CSE][dfs-v1]fix: validate tmpfs path components and ramfs entry names #11304 (devtmpfs / tmpfs / ramfs path-component limits), CVE-2024-24334 (
dfs_file), or the GPT/MBR/mtd-cfi reports. No open issue or PR mentionscromfstogether withname_size,getdents, ord_name.
Activity
github-actions commentedon Oct 20, 2026
如需认领并处理此 Issue,请评论
/claim。To claim and work on this issue, comment
/claim.