From 362fd7380e204b3c0bf987fa47635f45339dc5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Mon, 6 Jul 2026 13:38:29 +0200 Subject: [PATCH] fix(storage/fatfs): guard dirty-cache refill against unsigned LBA wrap (CVE-2026-6685) 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/ --- components/fatfs/sbom.yml | 2 ++ components/fatfs/src/ff.c | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/components/fatfs/sbom.yml b/components/fatfs/sbom.yml index d69a672ce6c..6c994059fbb 100644 --- a/components/fatfs/sbom.yml +++ b/components/fatfs/sbom.yml @@ -8,3 +8,5 @@ cve-exclude-list: reason: Integer overflow in exFAT mount size validation. Patched by promoting the cluster-heap and bitmap-base multiplies to 64-bit in mount_volume(). - cve: CVE-2026-6683 reason: exFAT divide-by-zero when NumClusters == 0. The vulnerable exFAT PercInUse sync division was introduced in R0.16 and is not present in this R0.15 release; an empty cluster heap is additionally rejected at mount as defense-in-depth. + - 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(). diff --git a/components/fatfs/src/ff.c b/components/fatfs/src/ff.c index 3ae06c2b999..77a88e18fde 100644 --- a/components/fatfs/src/ff.c +++ b/components/fatfs/src/ff.c @@ -4004,11 +4004,11 @@ FRESULT f_read ( if (disk_read(fs->pdrv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); #if !FF_FS_READONLY && FF_FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */ #if FF_FS_TINY - if (fs->wflag && fs->winsect - sect < cc) { + if (fs->wflag && fs->winsect >= sect && fs->winsect - sect < cc) { /* CVE-2026-6685: guard against unsigned wrap when winsect < sect (mis-offset would be an OOB write into rbuff) */ memcpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs)); } #else - if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) { + if ((fp->flag & FA_DIRTY) && fp->sect >= sect && fp->sect - sect < cc) { /* CVE-2026-6685: guard against unsigned wrap when fp->sect < sect (mis-offset would be an OOB write into rbuff) */ memcpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs)); } #endif @@ -4119,12 +4119,12 @@ FRESULT f_write ( if (disk_write(fs->pdrv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); #if FF_FS_MINIMIZE <= 2 #if FF_FS_TINY - if (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + if (fs->winsect >= sect && fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write (CVE-2026-6685: guard against unsigned wrap when winsect < sect) */ memcpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs)); fs->wflag = 0; } #else - if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + if (fp->sect >= sect && fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write (CVE-2026-6685: guard against unsigned wrap when fp->sect < sect) */ memcpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs)); fp->flag &= (BYTE)~FA_DIRTY; }