The initial CVE-2026-6682 fix (merged in !50362) hardened the exFAT
mount path, but the CVE as reported by runZero is a FAT32 defect in
mount_volume() and is reachable in ESP-IDF's default configuration
(exFAT and 64-bit LBA disabled). This corrects the fix.
Root cause: `fasize *= fs->n_fats` is a DWORD multiply with no overflow
guard. A crafted BPB_FATSz32 such as 0x80000001 with NumFATs=2 wraps
`fasize` to 0x00000002. The wrapped (too-small) FAT size then places
`fs->database` inside the FAT region, so a forged directory entry in the
overlapping sector yields an attacker-controlled `finfo.fsize`. Any
caller that uses that size as a read length overflows its buffer with
attacker-controlled bytes (CVSS 7.6, path to RCE). The later
`fs->fsize < szbfat` check does not catch this because it compares the
un-doubled single-FAT size, which is still large.
Fix: reject a per-FAT size that overflows DWORD when multiplied by the
FAT count, and reject a reserved+FAT+root system-area size that overflows
DWORD, before either value is used to derive the data-area base.
The previous exFAT cluster-heap/bitmap 64-bit promotions are retained as
defense-in-depth and their comments relabeled accordingly (they are not
CVE-2026-6682). The SBOM cve-exclude-list reason is updated to describe
the FAT32 overflow and its fix.
Document the three runZero "Seven FatFs bugs" CVEs that require no source change
in this component, so vulnerability scanners have their disposition:
- CVE-2026-6684: GPT partition-scan loop DoS. Already fixed upstream in R0.16,
where test_gpt_header() caps the partition-entry count at 128.
- CVE-2026-6686: read of uninitialized clusters after f_lseek() past EOF.
Longstanding, behavioral; not a memory-safety defect and zero-filling every
extended cluster is prohibitively costly on flash.
- CVE-2026-6688: long-filename overflow in downstream callers. Not exposed in
ESP-IDF; vfs_fat.c uses bounded copies and fname is bounded by FF_MAX_LFN.
Reference: https://www.runzero.com/blog/fatfs-bugs/
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/
After a direct multi-sector disk_read()/disk_write(), FatFs decides whether the
cached sector overlaps the direct-I/O range with:
fp->sect - sect < cc (and the FF_FS_TINY variant fs->winsect - sect < cc)
`sect`, `fp->sect` and `fs->winsect` are unsigned LBA_t. On 32-bit LBA_t builds,
if the cached sector is below `sect`, the subtraction wraps to a huge value that
can still compare `< cc`, so the code computes a bogus large offset:
- in f_write() it mis-copies from the direct write buffer (data corruption);
- in f_read() it is worse: memcpy(rbuff + (wrapped_offset * SS), ...) is an
out-of-bounds WRITE into the caller-supplied read buffer.
Add an explicit lower-bound check (fp->sect >= sect, resp. fs->winsect >= sect)
before the range test on both the read and write paths and both the FF_FS_TINY
and normal variants, so the condition is exactly "cached sector lies within
[sect, sect + cc)". Record the CVE in the component SBOM.
Reference: https://www.runzero.com/blog/fatfs-bugs/
The FAT12/16/32 mount path rejects a zero cluster count, but the exFAT path
accepted NumClusters == 0. That yields fs->n_fatent == 2, and sync_fs() later
computes the "percent in use" field as:
... * 100 / (fs->n_fatent - 2)
which is a division by zero (n_fatent - 2 == 0) -> crash. On a device that
syncs during an update this can brick the unit.
Reject ncl == 0 at exFAT mount time, and add a defense-in-depth
`fs->n_fatent > 2` guard around the division in sync_fs() so the divisor can
never be zero even if some future path produces such a filesystem object.
Record the CVE in the component SBOM.
Reference: https://www.runzero.com/blog/fatfs-bugs/
The exFAT mount path validates that the media is large enough to hold the
declared cluster heap with:
if (maxlba < (QWORD)fs->database + ncl * fs->csize) ...
`ncl` (DWORD, up to MAX_EXFAT) and `fs->csize` (WORD) are both promoted to
`unsigned int`, so `ncl * fs->csize` is evaluated in 32-bit arithmetic and can
wrap before the QWORD promotion of the sum. A crafted image with a large
NumClusters/SecPerClus can therefore make an undersized volume pass the "size
is large enough" check; subsequent cluster->sector math then addresses media
outside the actual device.
Promote the multiply to 64-bit ((QWORD)ncl * fs->csize). Apply the same
promotion to the bitmap-base computation ((LBA_t)fs->csize * (bcl - 2)), which
has the identical overflow shape. Record the CVE in the component SBOM.
Reference: https://www.runzero.com/blog/fatfs-bugs/
The vendored FatFs sources are revision R0.16 (FF_DEFINED == 80386, per
components/fatfs/src/ff.h and ff.c) but the SBOM recorded R0.15. Correct the
recorded version so vulnerability tracking matches the actual sources.
Add a new CMake function esp_partition_flash_binary() that provides a
unified API for registering partition data binaries to be flashed. It
replaces the direct esptool_py_flash_target calls scattered across
components (spiffs, fatfs, nvs_flash) with a single function that:
- Resolves partition offset from the partition table automatically
- Determines encryption requirements (auto-detect or ALWAYS_PLAINTEXT)
- Creates per-partition flash targets (e.g. idf.py <partition>-flash)
- Optionally includes the binary in `idf.py flash` via FLASH_IN_PROJECT
On the linux target, the function registers binaries for pre-loading
into the emulated flash. A build-time manifest (linux_flash_data.txt)
is generated via file(GENERATE), and partition_linux.c reads it at
runtime to copy each binary into the memory-mapped flash buffer at
the correct offset.
The partition_ops example is updated to use the new function and
includes a custom_partition with pre-built data to demonstrate the
full workflow, including on the linux target.
The condition filtering attributes lacked parentheses, causing the
'not startswith(_)' guard to only apply to str attributes. Python 3.14's
new __firstlineno__ (int) class attribute leaked into the output.
Also includes ruff auto-formatting fixes (imports, trailing commas,
union type annotations).
Rewrite build_lfn_short_entry_name() and add _gen_numname_suffix() helper
to match the gen_numname() algorithm in ff.c. This fixes:
- chr(order) producing raw binary instead of ASCII digits
- Collision for order >= 10 when str(order) makes the name exceed 8 chars
- Hex suffix with dynamic stem shortening (matching C implementation)
- CRC16-CCITT hash for seq > 5 to reduce collision probability
Also fix LDIR_Name2_SIZE typo in long_filename_utils.py (should be
LDIR_Name3_SIZE), which made the assertion guard too permissive.
Add ShortFilenameGenerationTestCase with 9 unit tests covering single-digit,
multi-digit, hash-based, and collision-free generation scenarios.