littlefs/lfs.c invalid pointer dereference in lfs_dir_commitattr via lfs_migrate
littlefs/lfs.c invalid pointer dereference in lfs_dir_commitattr via lfs_migrate
Description:
A crafted littlefs v1 filesystem image passed to lfs_migrate() can drive littlefs into an invalid metadata traversal state. During migration, stale or
corrupted metadata can be interpreted as the reserved pseudo-tag LFS_FROM_USERATTRS.
When this happens in lfs_dir_traverse(), the function can enter the LFS_FROM_USERATTRS branch while its active buffer pointer still refers to a
stack-local struct lfs_diskoff disk, not to an array of struct lfs_attr. The code then casts that stack object to const struct lfs_attr * and reads
attribute fields past the end of the stack object.
Potential attached reproducer, this path is reached from the commit callback flow. A fake a[i].buffer value is passed to lfs_dir_commitattr(), where it
is interpreted as const struct lfs_diskoff * and dereferenced. In a release-style non-sanitized build this produces a native SIGSEGV at lfs.c:1651
Impact:
An attacker who can provide a filesystem image that is later passed to lfs_migrate() can cause an out-of-bounds stack read and a process crash.
This is a denial-of-service issue for applications that migrate untrusted or externally supplied littlefs v1 images. The native crash was reproduced without ASan or UBSan. ASan additionally reports the earlier stack out-of-bounds read in lfs_dir_traverse() at lfs.c:1059:32.
To Reproduce:
Place fuzz_migrate.c and poc3_lfs_dir_traverse_commit_userattrs.bin in a directory under the littlefs checkout, for example fuzz/migrate/poc/.
Build a release-style non-sanitized reproducer:
cd fuzz/migrate/poc ;
clang -I../../.. -DLFS_MIGRATE -DNDEBUG \
-DLFS_NO_ERROR -DLFS_NO_WARN -DLFS_NO_DEBUG \
-std=c99 -O2 -g -fno-omit-frame-pointer -fno-common \
-ffunction-sections -fdata-sections \
-Wall -Wextra -Wno-unused-parameter -Wno-sign-compare \
fuzz_migrate.c ../../../lfs.c ../../../lfs_util.c \
-o migrate_fuzz_o2_ndebug -Wl,--gc-sectionsRun the PoC:
./migrate_fuzz_o2_ndebug poc3_lfs_dir_traverse_commit_userattrs.binAsan reproduction:
Build an Asan reproducer:
cd fuzz/migrate/poc
clang -I../../.. -DLFS_MIGRATE \
-DLFS_NO_ERROR -DLFS_NO_WARN -DLFS_NO_DEBUG \
-std=c99 -O1 -g -fno-omit-frame-pointer -fno-common \
-fsanitize=address,undefined \
-Wall -Wextra -Wno-unused-parameter -Wno-sign-compare \
fuzz_migrate.c ../../../lfs.c ../../../lfs_util.c \
-o migrate_fuzz_asanRun:
ASAN_OPTIONS=handle_abort=1:print_stacktrace=1 \
./migrate_fuzz_asan poc3_lfs_dir_traverse_commit_userattrs.binASan output:
show full - click to expand../../lfs.c:1384:error: Corrupted dir pair at {0x1, 0x0}
=================================================================
==711778==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b5d13201330 at pc 0x55e99cd17e63 bp 0x7fff23aa3890 sp 0x7fff23aa3888
READ of size 4 at 0x7b5d13201330 thread T0
#0 0x55e99cd17e62 in lfs_dir_traverse /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:1059:32
#1 0x55e99cd08f9d in lfs_dir_splittingcompact /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:2139:23
#2 0x55e99cd08f9d in lfs_dir_relocatingcommit /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:2348:13
#3 0x55e99cd032a6 in lfs_dir_orphaningcommit /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:2432:17
#4 0x55e99ccf5271 in lfs_dir_commit /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:2604:19
#5 0x55e99ccf5271 in lfs_migrate_ /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:5829:23
#6 0x55e99ccf5271 in lfs_migrate /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:6551:11
#7 0x55e99cce982b in run_one_input /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/poc/fuzz_migrate.c:306:9
#8 0x55e99cce982b in LLVMFuzzerTestOneInput /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/poc/fuzz_migrate.c:336:5
#9 0x55e99cce9ba0 in main /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/poc/fuzz_migrate.c:431:8
#10 0x7f5d1518ef74 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#11 0x7f5d1518f026 in __libc_start_main csu/../csu/libc-start.c:360:3
#12 0x55e99cc0b340 in _start (/run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/build_asan/migrate_fuzz+0x50340) (BuildId: 301346b4f9742af4e7bdf7bd478503cd8146cbdc)
Address 0x7b5d13201330 is located in stack of thread T0 at offset 304 in frame
#0 0x55e99cd15d77 in lfs_dir_traverse /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:917
This frame has 3 object(s):
[32, 208) 'stack' (line 920)
[272, 276) 'tag' (line 925)
[288, 296) 'disk' (line 927) <== Memory access at offset 304 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /run/media/user/8ed8205b-4114-4c2a-b2d0-e2ad6640262d/littlefs/littlefs/fuzz/migrate/../../lfs.c:1059:32 in lfs_dir_traverse
Shadow bytes around the buggy address:
0x7b5d13201080: f8 f8 f8 f2 f2 f2 f2 f2 00 00 f2 f2 f8 f8 f2 f2
0x7b5d13201100: 00 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x7b5d13201180: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x7b5d13201200: f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00 00 00
0x7b5d13201280: 00 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2
=>0x7b5d13201300: f2 f2 04 f2 00 f3[f3]f3 f3 f3 f3 f3 f3 f3 f3 f3
0x7b5d13201380: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x7b5d13201400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b5d13201480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b5d13201500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b5d13201580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==711778==ABORTING
Environment:
OS: Linux rack1 6.19.14+kali-amd64 #1 SMP PREEMPT_DYNAMIC Kali 6.19.14-1+kali1 (2026-05-05) x86_64 GNU/Linux Compiler: Debian clang version 21.1.8 (3+b1) CPU: x86_64 ASan build opts: -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined Non-sanitized build opts: -O2 -g -DNDEBUG -DLFS_MIGRATE -DLFS_NO_ERROR -DLFS_NO_WARN -DLFS_NO_DEBUG
Notes:
With assertions enabled, this input may hit an earlier LFS_ASSERT and abort with SIGABRT. The native SIGSEGV is reproducible in release-style builds with assertions disabled through -DNDEBUG.
- Tested commit:
6cb4e86540eca0d9ba62500a298385c9d863c8be - Entry point:
lfs_migrate() - Build condition:
LFS_MIGRATEenabled
Suggested fix direction:
Do not allow on-disk/stale metadata tags to be interpreted as LFS_FROM_USERATTRS unless buffer is known to point to a valid caller-owned
struct lfs_attr array. The LFS_FROM_USERATTRS pseudo-tag should be accepted only from the synthetic attr traversal path, or the traversal state should carry explicit provenance/type information for buffer before casting it.
Screenshots:

Source: littlefs-project/littlefs