fix(storage/fatfs): clamp exFAT volume-label length in f_getlabel() (CVE-2026-6687)

f_getlabel() extracts the exFAT volume label with a loop bounded by the on-disk
byte dj.dir[XDIR_NumLabel] (0-255):

    for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++)
        wc = ld_16(dj.dir + XDIR_Label + si * 2);

The exFAT label field holds at most 11 UTF-16 units (22 bytes). A crafted
directory entry with a larger count both reads past the 22-byte label field and,
through put_utf(... &label[di], 4), writes past the end of the caller-provided
label buffer (the canonical API examples use small fixed stack buffers) -> stack
buffer overflow.

Clamp the character count to the exFAT maximum of 11 before the extraction loop.
Record the CVE in the component SBOM.

Note: f_getlabel() takes no destination-buffer size, so under UTF-8 output
(FF_LFN_UNICODE == 2) 11 units can still expand to up to 34 bytes; the clamp
downgrades this from attacker-unbounded to spec-bounded. ESP-IDF's VFS layer
does not call f_getlabel(); direct callers on untrusted media should size their
buffer accordingly. A complete fix requires an upstream size-aware API change.

Reference: https://www.runzero.com/blog/fatfs-bugs/
This commit is contained in:
Tomáš Rohlínek
2026-07-06 14:40:50 +02:00
parent e6e7c9f4bc
commit 01f386cc1f
2 changed files with 6 additions and 2 deletions
+2
View File
@@ -10,3 +10,5 @@ cve-exclude-list:
reason: exFAT divide-by-zero when NumClusters == 0. Patched by rejecting an empty cluster heap at mount and guarding the divisor (n_fatent > 2) in sync_fs().
- cve: CVE-2026-6685
reason: Unsigned-subtraction wrap in the dirty-cache refill check. Patched by requiring the cached sector to lie within the direct-I/O range in both f_write() and f_read().
- cve: CVE-2026-6687
reason: exFAT label-length overflow in f_getlabel(). Patched by clamping XDIR_NumLabel to the 11-unit exFAT label maximum.
+4 -2
View File
@@ -5566,9 +5566,11 @@ FRESULT f_getlabel (
#if FF_FS_EXFAT
if (fs->fs_type == FS_EXFAT) {
WCHAR hs;
UINT nw;
UINT nw, nchar;
for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */
nchar = dj.dir[XDIR_NumLabel]; /* Number of UTF-16 characters in the label entry */
if (nchar > 11) nchar = 11; /* CVE-2026-6687: clamp to the exFAT maximum (11) to prevent OOB read of the entry and overflow of the caller label buffer */
for (si = di = hs = 0; si < nchar; si++) { /* Extract volume label from 83 entry */
wc = ld_16(dj.dir + XDIR_Label + si * 2);
if (hs == 0 && IsSurrogate(wc)) { /* Is the code a surrogate? */
hs = wc; continue;