In the Linux kernel, the following vulnerability has been resolved:
ext4: drop extent cache when splitting extent fails
When the split extent fails, we might leave some extents still being processed and return an error directly, which will result in stale extent entries remaining in the extent status tree. So drop all of the remaining potentially stale extents if the splitting fails.
ext4: avoid infinite loops caused by residual data
ext4: reject mount if bigalloc with sfirstdatablock != 0
ext4: fix use-after-free in updatesuperwork when racing with umount
In the Linux kernel, the following vulnerability has been resolved:
ext4: validate pidx bounds in ext4extcorrectindexes
ext4extcorrectindexes() walks up the extent tree correcting index entries when the first extent in a leaf is modified. Before accessing path[k].pidx->eiblock, there is no validation that pidx falls within the valid range of index entries for that level.
If the on-disk extent header contains a corrupted or crafted ehentries value, pidx can point past the end of the allocated buffer, causing a slab-out-of-bounds read.
Fix this by validating path[k].pidx against EXTLASTINDEX() at both access sites: before the while loop and inside it. Return -EFSCORRUPTED if the index pointer is out of range, consistent with how other bounds violations are handled in the ext4 extent tree code.
In the Linux kernel, the following vulnerability has been resolved:
ext4: fix dirtyclusters double decrement on fs shutdown
fstests test generic/388 occasionally reproduces a warning in ext4putsuper() associated with the dirty clusters count:
WARNING: CPU: 7 PID: 76064 at fs/ext4/super.c:1324 ext4putsuper+0x48c/0x590 [ext4]
Tracing the failure shows that the warning fires due to an sdirtyclusterscounter value of -1. IOW, this appears to be a spurious decrement as opposed to some sort of leak. Further tracing of the dirty cluster count deltas and an LLM scan of the resulting output identified the cause as a double decrement in the error path between ext4mbmarkdiskspaceused() and the caller ext4mbnewblocks().
First, note that generic/388 is a shutdown vs. fsstress test and so produces a random set of operations and shutdown injections. In the problematic case, the shutdown triggers an error return from the ext4handledirtymetadata() call(s) made from ext4mbmarkcontext(). The changed value is non-zero at this point, so ext4mbmarkdiskspaceused() does not exit after the error bubbles up from ext4mbmarkcontext(). Instead, the former decrements both cluster counters and returns the error up to ext4mbnewblocks(). The latter falls into the !ar->len out path which decrements the dirty clusters counter a second time, creating the inconsistency.
To avoid this problem and simplify ownership of the cluster reservation in this codepath, lift the counter reduction to a single place in the caller. This makes it more clear that ext4mbnewblocks() is responsible for acquiring cluster reservation (via ext4claimfreeclusters()) in the !delalloc case as well as releasing it, regardless of whether it ends up consumed or returned due to failure.
In the Linux kernel, the following vulnerability has been resolved:
ext4: fix e4b bitmap inconsistency reports
A bitmap inconsistency issue was observed during stress tests under mixed huge-page workloads. Ext4 reported multiple e4b bitmap check failures like:
ext4mbcomplexscangroup:2508: group 350, 8179 free clusters as per group info. But got 8192 blocks
Analysis and experimentation confirmed that the issue is caused by a race condition between page migration and bitmap modification. Although this timing window is extremely narrow, it is still hit in practice:
foliolock ext4mbloadbuddy migratefolio check ref count foliomccopy filemapgetfolio foliotryget(folio) ...... mbmarkused ext4mbunloadbuddy foliomigratemapping folioreffreeze foliounlock
The root cause of this issue is that the fast path of loadbuddy only increments the folio's reference count, which is insufficient to prevent concurrent folio migration. We observed that the folio migration process acquires the folio lock. Therefore, we can determine whether to take the fast path in loadbuddy by checking the lock status. If the folio is locked, we opt for the slow path (which acquires the lock) to close this concurrency window.
Additionally, this change addresses the following issues:
When the DOUBLECHECK macro is enabled to inspect bitmap-related issues, the following error may be triggered:
corruption in group 324 at byte 784(6272): f in copy != ff on disk/prealloc
Analysis reveals that this is a false positive. There is a specific race window where the bitmap and the group descriptor become momentarily inconsistent, leading to this error report:
ext4mbloadbuddy ext4mbloadbuddy filemapgetfolio(create|lock) foliolock ext4mbinitcache foliomarkuptodate filemapgetfolio(no lock) ...... mbmarkused mbmarkuseddouble mbcmpbitmaps mbsetbits(e4b->bdbitmap) foliounlock
The original logic assumed that since mbcmpbitmaps is called when the bitmap is newly loaded from disk, the folio lock would be sufficient to prevent concurrent access. However, this overlooks a specific race condition: if another process attempts to load buddy and finds the folio is already in an uptodate state, it will immediately begin using it without holding folio lock.
In the Linux kernel, the following vulnerability has been resolved:
ext4: don't cache extent during splitting extent
Caching extents during the splitting process is risky, as it may result in stale extents remaining in the status tree. Moreover, in most cases, the corresponding extent block entries are likely already cached before the split happens, making caching here not particularly useful.
Assume we have an unwritten extent, and then DIO writes the first half.
[UUUUUUUUUUUUUUUU] on-disk extent U: unwritten extent [UUUUUUUUUUUUUUUU] extent status tree |<- ->| ----> dio write this range
First, when ext4splitextentat() splits this extent, it truncates the existing extent and then inserts a new one. During this process, this extent status entry may be shrunk, and calls to ext4findextent() and ext4cacheextents() may occur, which could potentially insert the truncated range as a hole into the extent status tree. After the split is completed, this hole is not replaced with the correct status.
[UUUUUUU|UUUUUUUU] on-disk extent U: unwritten extent [UUUUUUU|HHHHHHHH] extent status tree H: hole
Then, the outer calling functions will not correct this remaining hole extent either. Finally, if we perform a delayed buffer write on this latter part, it will re-insert the delayed extent and cause an error in space accounting.
In adition, if the unwritten extent cache is not shrunk during the splitting, ext4cacheextents() also conflicts with existing extents when caching extents. In the future, we will add checks when caching extents, which will trigger a warning. Therefore, Do not cache extents that are being split.
ext4: fix bounds check in checkxattrs() to prevent out-of-bounds access
ext4: replace BUGON with proper error handling in ext4readinlinefolio
In the Linux kernel, the following vulnerability has been resolved:
ext4: fix iloc.bh leak in ext4fcreplayinode() error paths
During code review, Joseph found that ext4fcreplayinode() calls ext4getfcinodeloc() to get the inode location, which holds a reference to iloc.bh that must be released via brelse().
However, several error paths jump to the 'out' label without releasing iloc.bh:
- ext4handledirtymetadata() failure - syncdirtybuffer() failure - ext4markinodeused() failure - ext4iget() failure
Fix this by introducing an 'outbrelse' label placed just before the existing 'out' label to ensure iloc.bh is always released.
Additionally, make ext4fcreplayinode() propagate errors properly instead of always returning 0.
In the Linux kernel, the following vulnerability has been resolved:
ext4: move ext4percpuparaminit() before ext4mbinit()
When running kvm-xfstests -c ext4/1k -C 1 generic/383 with the DOUBLECHECK macro defined, the following panic is triggered:
================================================================== EXT4-fs error (device vdc): ext4validateblockbitmap:423: comm mount: bg 0: bad block bitmap checksum BUG: unable to handle page fault for address: ff110000fa2cc000 PGD 3e01067 P4D 3e02067 PUD 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 0 UID: 0 PID: 2386 Comm: mount Tainted: G W 6.18.0-gba65a4e7120a-dirty #1152 PREEMPT(none) RIP: 0010:percpucounteraddbatch+0x13/0xa0 Call Trace: <TASK> ext4markgroupbitmapcorrupted+0xcb/0xe0 ext4validateblockbitmap+0x2a1/0x2f0 ext4readblockbitmap+0x33/0x50 mbgroupbbbitmapalloc+0x33/0x80 ext4mbaddgroupinfo+0x190/0x250 ext4mbinitbackend+0x87/0x290 ext4mbinit+0x456/0x640 ext4fillsuper+0x1072/0x1680 ext4fillsuper+0xd3/0x280 gettreebdevflags+0x132/0x1d0 vfsgettree+0x29/0xd0 vfscmdcreate+0x59/0xe0 dosysfsconfig+0x4f6/0x6b0 dosyscall64+0x50/0x1f0 entrySYSCALL64afterhwframe+0x76/0x7e ==================================================================
This issue can be reproduced using the following commands: mkfs.ext4 -F -q -b 1024 /dev/sda 5G tune2fs -O quota,project /dev/sda mount /dev/sda /tmp/test
With DOUBLECHECK defined, mbgroupbbbitmapalloc() reads and validates the block bitmap. When the validation fails, ext4markgroupbitmapcorrupted() attempts to update sbi->sfreeclusterscounter. However, this percpucounter has not been initialized yet at this point, which leads to the panic described above.
Fix this by moving the execution of ext4percpuparaminit() to occur before ext4mbinit(), ensuring the per-CPU counters are initialized before they are used.
In the Linux kernel, the following vulnerability has been resolved:
ext4: don't zero the entire extent if EXT4EXTDATAPARTIALVALID1
When allocating initialized blocks from a large unwritten extent, or when splitting an unwritten extent during end I/O and converting it to initialized, there is currently a potential issue of stale data if the extent needs to be split in the middle.
0 A B N [UUUUUUUUUUUU] U: unwritten extent [--DDDDDDDD--] D: valid data |<- ->| ----> this range needs to be initialized
ext4splitextent() first try to split this extent at B with EXT4EXTDATAENTIREVALID1 and EXT4EXTMAYZEROOUT flag set, but ext4splitextentat() failed to split this extent due to temporary lack of space. It zeroout B to N and mark the entire extent from 0 to N as written.
0 A B N [WWWWWWWWWWWW] W: written extent [SSDDDDDDDDZZ] Z: zeroed, S: stale data
ext4splitextent() then try to split this extent at A with EXT4EXTDATAVALID2 flag set. This time, it split successfully and left a stale written extent from 0 to A.
0 A B N [WW|WWWWWWWWWW] [SS|DDDDDDDDZZ]
Fix this by pass EXT4EXTDATAPARTIALVALID1 to ext4splitextentat() when splitting at B, don't convert the entire extent to written and left it as unwritten after zeroing out B to N. The remaining work is just like the standard two-part split. ext4splitextent() will pass the EXT4EXTDATAVALID2 flag when it calls ext4splitextentat() for the second time, allowing it to properly handle the split. If the split is successful, it will keep extent from 0 to A as unwritten.
ext4: fix missing brelse() in ext4xattrinodedecrefall()
In the Linux kernel, the following vulnerability has been resolved:
ext4: don't set EXT4GETBLOCKSCONVERT when splitting before submitting I/O
When allocating blocks during within-EOF DIO and writeback with dioreadnolock enabled, EXT4GETBLOCKSPREIO was set to split an existing large unwritten extent. However, EXT4GETBLOCKSCONVERT was set when calling ext4splitconvertextents(), which may potentially result in stale data issues.
Assume we have an unwritten extent, and then DIO writes the second half.
[UUUUUUUUUUUUUUUU] on-disk extent U: unwritten extent [UUUUUUUUUUUUUUUU] extent status tree |<- ->| ----> dio write this range
First, ext4iomapalloc() call ext4mapblocks() with EXT4GETBLOCKSPREIO, EXT4GETBLOCKSUNWRITEXT and EXT4GETBLOCKSCREATE flags set. ext4mapblocks() find this extent and call ext4splitconvertextents() with EXT4GETBLOCKSCONVERT and the above flags set.
Then, ext4splitconvertextents() calls ext4splitextent() with EXT4EXTMAYZEROOUT, EXT4EXTMARKUNWRIT2 and EXT4EXTDATAVALID2 flags set, and it calls ext4splitextentat() to split the second half with EXT4EXTDATAVALID2, EXT4EXTMARKUNWRIT1, EXT4EXTMAYZEROOUT and EXT4EXTMARKUNWRIT2 flags set. However, ext4splitextentat() failed to insert extent since a temporary lack -ENOSPC. It zeroes out the first half but convert the entire on-disk extent to written since the EXT4EXTDATAVALID2 flag set, but left the second half as unwritten in the extent status tree.
[0000000000SSSSSS] data S: stale data, 0: zeroed [WWWWWWWWWWWWWWWW] on-disk extent W: written extent [WWWWWWWWWWUUUUUU] extent status tree
Finally, if the DIO failed to write data to the disk, the stale data in the second half will be exposed once the cached extent entry is gone.
Fix this issue by not passing EXT4GETBLOCKSCONVERT when splitting an unwritten extent before submitting I/O, and make ext4splitconvertextents() to zero out the entire extent range to zero for this case, and also mark the extent in the extent status tree for consistency.