VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-45985

CVE-2026-45985

Description

In the Linux kernel, the following vulnerability has been resolved:

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

When allocating blocks during within-EOF DIO and writeback with dioread_nolock enabled, EXT4_GET_BLOCKS_PRE_IO was set to split an existing large unwritten extent. However, EXT4_GET_BLOCKS_CONVERT was set when calling ext4_split_convert_extents(), 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, ext4_iomap_alloc() call ext4_map_blocks() with EXT4_GET_BLOCKS_PRE_IO, EXT4_GET_BLOCKS_UNWRIT_EXT and EXT4_GET_BLOCKS_CREATE flags set. ext4_map_blocks() find this extent and call ext4_split_convert_extents() with EXT4_GET_BLOCKS_CONVERT and the above flags set.

Then, ext4_split_convert_extents() calls ext4_split_extent() with EXT4_EXT_MAY_ZEROOUT, EXT4_EXT_MARK_UNWRIT2 and EXT4_EXT_DATA_VALID2 flags set, and it calls ext4_split_extent_at() to split the second half with EXT4_EXT_DATA_VALID2, EXT4_EXT_MARK_UNWRIT1, EXT4_EXT_MAY_ZEROOUT and EXT4_EXT_MARK_UNWRIT2 flags set. However, ext4_split_extent_at() 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 EXT4_EXT_DATA_VALID2 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 EXT4_GET_BLOCKS_CONVERT when splitting an unwritten extent before submitting I/O, and make ext4_split_convert_extents() to zero out the entire extent range to zero for this case, and also mark the extent in the extent status tree for consistency.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A race condition in ext4's block allocation for within-EOF DIO with dioread_nolock can expose stale data when splitting unwritten extents.

Vulnerability

In the Linux kernel, a race condition exists in the ext4 filesystem when allocating blocks for within-EOF direct I/O (DIO) and writeback with the dioread_nolock mount option enabled [1]. The bug occurs in ext4_iomap_alloc() and ext4_map_blocks() when a large existing unwritten extent must be split. The kernel sets EXT4_GET_BLOCKS_CONVERT alongside EXT4_GET_BLOCKS_PRE_IO, causing ext4_split_convert_extents() to mark the entire on-disk extent as written while only zeroing the first half (from a failed split). Affected kernels include versions with the dioread_nolock feature active; the patch targets the stable tree via commit 67cdb7bd7442 [1].

Exploitation

An attacker needs no special privileges beyond the ability to trigger within-EOF DIO writes on an ext4 filesystem mounted with dioread_nolock [1]. The race window occurs when a temporary lack of space (-ENOSPC) during extent split causes ext4_split_extent_at() to fail insertion. The function zeroes the first half but converts the entire on-disk extent to written (due to EXT4_EXT_DATA_VALID2), leaving the second half as unwritten in the extent status tree. If the DIO write then fails to commit data to disk, the stale data already present in the second half becomes exposed when the cached extent entry is evicted [1].

Impact

Successful exploitation results in disclosure of stale data (kernel memory or previously written file content) from the unwritten second half of the extent [1]. The on-disk extent is incorrectly marked as written, and the extent status tree shows the second half as unwritten, leading to data corruption when the stale data is read. The confidentiality impact is data exposure; integrity is also affected because the file may contain uninitialized or old data where new data was expected [1].

Mitigation

The fix is commit 67cdb7bd7442 ("ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O") in the Linux kernel stable tree [1]. The patch removes EXT4_GET_BLOCKS_CONVERT from the flags passed during split, ensuring ext4_split_convert_extents() zeros the entire extent range instead of converting prematurely. Users should apply the latest kernel updates containing this commit. No workaround is documented; disabling dioread_nolock may avoid the issue but may impact performance. The vulnerability is not listed on the CISA KEV as of publication [1].

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

14
77e407967cd8

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiFeb 25, 2026Fixed in 5.10.253via kernel-cna
1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index b0e8711fd7fd8b..e5b32d5f634a20 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3705,11 +3705,15 @@ static int ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, ppath, map, split_flag, flags);
    @@ -3874,7 +3878,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		ret = ext4_split_convert_extents(handle, inode, map, ppath,
    -					 flags | EXT4_GET_BLOCKS_CONVERT);
    +					 flags);
     		if (ret < 0) {
     			err = ret;
     			goto out2;
    -- 
    cgit 1.3-korg
    
    
    
37555690f39f

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiFeb 25, 2026Fixed in 5.15.203via kernel-cna
1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index fd2a096d8ba6a6..54df6fe69d792f 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3711,11 +3711,15 @@ static int ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, ppath, map, split_flag, flags);
    @@ -3880,7 +3884,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		ret = ext4_split_convert_extents(handle, inode, map, ppath,
    -					 flags | EXT4_GET_BLOCKS_CONVERT);
    +					 flags);
     		if (ret < 0) {
     			err = ret;
     			goto out2;
    -- 
    cgit 1.3-korg
    
    
    
feaf2a80e78f

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 7.0via kernel-cna
1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 1fee84ea20af1b..91b56de60c9055 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3746,15 +3746,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_SPLIT_NOMERGE;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3930,7 +3934,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
67cdb7bd7442

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.6.130via kernel-cna
1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 4507e42869854d..ed63260d792b1f 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3735,15 +3735,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3920,7 +3924,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
2920ec61c98b

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.12.77via kernel-cna
1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 7301cf17269038..bd556a3eac1987 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3762,15 +3762,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3949,7 +3953,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
2698731d2582

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.18.17via kernel-cna
1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 459453e8bb16bc..3ff8dcdd80ce93 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3764,15 +3764,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3951,7 +3955,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
716e7439a5a9

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.19.4via kernel-cna
1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 8d5ca450aa5d23..1ef23a2a94d535 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3735,15 +3735,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_SPLIT_NOMERGE;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3919,7 +3923,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
77e407967cd8

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index b0e8711fd7fd8b..e5b32d5f634a20 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3705,11 +3705,15 @@ static int ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, ppath, map, split_flag, flags);
    @@ -3874,7 +3878,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		ret = ext4_split_convert_extents(handle, inode, map, ppath,
    -					 flags | EXT4_GET_BLOCKS_CONVERT);
    +					 flags);
     		if (ret < 0) {
     			err = ret;
     			goto out2;
    -- 
    cgit 1.3-korg
    
    
    
37555690f39f

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index fd2a096d8ba6a6..54df6fe69d792f 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3711,11 +3711,15 @@ static int ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, ppath, map, split_flag, flags);
    @@ -3880,7 +3884,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		ret = ext4_split_convert_extents(handle, inode, map, ppath,
    -					 flags | EXT4_GET_BLOCKS_CONVERT);
    +					 flags);
     		if (ret < 0) {
     			err = ret;
     			goto out2;
    -- 
    cgit 1.3-korg
    
    
    
2698731d2582

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 459453e8bb16bc..3ff8dcdd80ce93 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3764,15 +3764,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3951,7 +3955,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
716e7439a5a9

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 8d5ca450aa5d23..1ef23a2a94d535 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3735,15 +3735,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_SPLIT_NOMERGE;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3919,7 +3923,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
feaf2a80e78f

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 1fee84ea20af1b..91b56de60c9055 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3746,15 +3746,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_SPLIT_NOMERGE;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3930,7 +3934,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
67cdb7bd7442

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 4507e42869854d..ed63260d792b1f 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3735,15 +3735,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3920,7 +3924,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    
2920ec61c98b

ext4: don't set EXT4_GET_BLOCKS_CONVERT when splitting before submitting I/O

1 file changed · +8 5
  • fs/ext4/extents.c+8 5 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 7301cf17269038..bd556a3eac1987 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3762,15 +3762,19 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
     	/* Convert to unwritten */
     	if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
     		split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1;
    -	/* Convert to initialized */
    -	} else if (flags & EXT4_GET_BLOCKS_CONVERT) {
    +	/* Split the existing unwritten extent */
    +	} else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT |
    +			    EXT4_GET_BLOCKS_CONVERT)) {
     		/*
     		 * It is safe to convert extent to initialized via explicit
     		 * zeroout only if extent is fully inside i_size or new_size.
     		 */
     		split_flag |= ee_block + ee_len <= eof_block ?
     			      EXT4_EXT_MAY_ZEROOUT : 0;
    -		split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
    +		split_flag |= EXT4_EXT_MARK_UNWRIT2;
    +		/* Convert to initialized */
    +		if (flags & EXT4_GET_BLOCKS_CONVERT)
    +			split_flag |= EXT4_EXT_DATA_VALID2;
     	}
     	flags |= EXT4_GET_BLOCKS_PRE_IO;
     	return ext4_split_extent(handle, inode, path, map, split_flag, flags,
    @@ -3949,7 +3953,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
     	/* get_block() before submitting IO, split the extent */
     	if (flags & EXT4_GET_BLOCKS_PRE_IO) {
     		path = ext4_split_convert_extents(handle, inode, map, path,
    -				flags | EXT4_GET_BLOCKS_CONVERT, allocated);
    +						  flags, allocated);
     		if (IS_ERR(path))
     			return path;
     		/*
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Incorrect flag propagation causes the extent-split error path to convert the entire on-disk extent to written, exposing stale data when the subsequent I/O fails."

Attack vector

An attacker who can trigger within-EOF direct I/O (DIO) or writeback on an ext4 filesystem mounted with `dioread_nolock` can cause a stale-data exposure. The bug is triggered when a DIO write targets the second half of an existing large unwritten extent. During extent splitting, if `ext4_split_extent_at()` fails with `-ENOSPC` (a temporary space shortage), the error path zeroes the first half but converts the entire on-disk extent to written due to the `EXT4_EXT_DATA_VALID2` flag, while the extent status tree still marks the second half as unwritten. If the DIO write subsequently fails to write data to disk, the stale pre-existing data in the second half becomes readable once the cached extent entry is evicted [patch_id=2660699].

Affected code

The vulnerability resides in `fs/ext4/extents.c` in the functions `ext4_split_convert_extents()` and `ext4_ext_handle_unwritten_extents()`. The call site in `ext4_ext_handle_unwritten_extents()` was passing `flags | EXT4_GET_BLOCKS_CONVERT` to `ext4_split_convert_extents()`, and the latter unconditionally set `EXT4_EXT_DATA_VALID2` in the split flags when `EXT4_GET_BLOCKS_CONVERT` was present [patch_id=2660699].

What the fix does

The patch makes two changes in `fs/ext4/extents.c`. First, in `ext4_ext_handle_unwritten_extents()`, the call to `ext4_split_convert_extents()` no longer ORs `EXT4_GET_BLOCKS_CONVERT` into the flags — it passes the original flags directly [patch_id=2660699]. Second, in `ext4_split_convert_extents()`, the condition for setting `EXT4_EXT_DATA_VALID2` is changed: instead of always setting it when `EXT4_GET_BLOCKS_CONVERT` is present, the code now only sets `EXT4_EXT_DATA_VALID2` if `EXT4_GET_BLOCKS_CONVERT` is explicitly set in the flags (separate from `EXT4_GET_BLOCKS_UNWRIT_EXT`). This ensures that when splitting an unwritten extent before I/O submission (the `PRE_IO` path), the `EXT4_EXT_DATA_VALID2` flag is not set, preventing the error path from incorrectly converting the entire on-disk extent to written and leaving stale data exposed [patch_id=2660699].

Preconditions

  • configext4 filesystem must be mounted with the 'dioread_nolock' option
  • inputA large unwritten extent must exist on disk
  • inputA direct I/O (DIO) write must target the second half of that unwritten extent
  • inputA temporary ENOSPC condition must occur during extent splitting

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.