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

CVE-2026-45912

CVE-2026-45912

Description

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 ext4_split_extent_at() 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 ext4_find_extent() and ext4_cache_extents() 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, ext4_cache_extents() 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.

AI Insight

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

A race condition in ext4 extent splitting can leave stale hole entries in the status tree, leading to incorrect space accounting and potential filesystem corruption.

Vulnerability

A race condition exists in the Linux kernel's ext4 filesystem when splitting an unwritten extent. During the splitting process in ext4_split_extent_at(), the code path may call ext4_find_extent() and ext4_cache_extents(), which can insert a stale hole entry into the extent status tree after the original extent entry is shrunk. This stale hole is not corrected after the split completes, leaving the status tree in an inconsistent state [1][2]. The vulnerability affects kernel versions where this behavior was present; the fix is applied in commits targeting stable trees [1][2].

Exploitation

An attacker needs local access and the ability to trigger a specific I/O pattern: first, direct I/O (DIO) writes to the first half of an unwritten extent, causing the split. Then, a delayed buffer write to the latter half of the same extent. The race window occurs during the split, when caching reinserts a hole entry. Successful exploitation requires precise timing to hit the window, but no special privileges beyond the ability to perform these write operations on a filesystem that supports extents (default for ext4).

Impact

A successful exploit results in a kernel warning and incorrect space accounting in the extent status tree [1][2]. This can lead to filesystem metadata corruption, potential data loss, or denial of service due to subsequent write errors. The impact is limited to local users with write access to the filesystem, but no privilege escalation is achieved.

Mitigation

The vulnerability is fixed in the Linux kernel via commit 5b1f42904533 (and cherry-picked to stable as 9a2b95cdaf07) [1][2]. Users should apply the latest kernel updates from their distribution. No workaround is available other than patching. The CVE is not listed on CISA KEV as of this writing.

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

16
9a2b95cdaf07

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.19.4via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 91b56de60c9055..20513f37c3ef95 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 91b56de60c9055..20513f37c3ef95 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE;
    -- 
    cgit 1.3-korg
    
    
    
4c2d9dac4d32

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.1.165via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 1aad4ae0e7ae46..1398da08fd5a38 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3174,6 +3174,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3344,6 +3347,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 1aad4ae0e7ae46..1398da08fd5a38 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3174,6 +3174,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3344,6 +3347,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
8302b5b4aacd

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 5.10.252via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 12da59c03c7cf8..eb3ae1c913c4f4 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3168,6 +3168,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3338,6 +3341,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 12da59c03c7cf8..eb3ae1c913c4f4 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3168,6 +3168,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3338,6 +3341,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
692103feca37

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 5.15.202via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 35bc58a26f7f4e..79d50c1d2e7bc5 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3173,6 +3173,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3343,6 +3346,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 35bc58a26f7f4e..79d50c1d2e7bc5 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3173,6 +3173,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3343,6 +3346,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
93b2ebbbcb2e

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.6.128via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index a3d3c9fc64262f..e8012e0c777e8e 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3172,6 +3172,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3342,6 +3345,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index a3d3c9fc64262f..e8012e0c777e8e 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3172,6 +3172,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3342,6 +3345,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
96007fd3c106

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.12.75via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 4b8eafac5140e0..fca3eab419aa54 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3197,6 +3197,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3379,6 +3382,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 4b8eafac5140e0..fca3eab419aa54 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3197,6 +3197,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3379,6 +3382,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
5b1f42904533

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 6.18.14via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 68172b85847d9f..1f057df0be2ed2 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 68172b85847d9f..1f057df0be2ed2 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
8b4b19a2f963

ext4: don't cache extent during splitting extent

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhang YiNov 29, 2025Fixed in 7.0via kernel-cna
2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index daecf3f0b367c3..be9fd2ab86679e 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index daecf3f0b367c3..be9fd2ab86679e 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE;
    -- 
    cgit 1.3-korg
    
    
    
93b2ebbbcb2e

ext4: don't cache extent during splitting extent

1 file changed · +6 1
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index a3d3c9fc64262f..e8012e0c777e8e 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3172,6 +3172,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3342,6 +3345,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
8b4b19a2f963

ext4: don't cache extent during splitting extent

1 file changed · +6 1
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index daecf3f0b367c3..be9fd2ab86679e 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE;
    -- 
    cgit 1.3-korg
    
    
    
96007fd3c106

ext4: don't cache extent during splitting extent

2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 4b8eafac5140e0..fca3eab419aa54 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3197,6 +3197,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3379,6 +3382,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 4b8eafac5140e0..fca3eab419aa54 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3197,6 +3197,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3379,6 +3382,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
9a2b95cdaf07

ext4: don't cache extent during splitting extent

2 files changed · +12 2
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 91b56de60c9055..20513f37c3ef95 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 91b56de60c9055..20513f37c3ef95 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE;
    -- 
    cgit 1.3-korg
    
    
    
5b1f42904533

ext4: don't cache extent during splitting extent

1 file changed · +6 1
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 68172b85847d9f..1f057df0be2ed2 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) &&
     	       (split_flag & EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
4c2d9dac4d32

ext4: don't cache extent during splitting extent

1 file changed · +6 1
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 1aad4ae0e7ae46..1398da08fd5a38 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3174,6 +3174,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3344,6 +3347,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
692103feca37

ext4: don't cache extent during splitting extent

1 file changed · +6 1
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 35bc58a26f7f4e..79d50c1d2e7bc5 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3173,6 +3173,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3343,6 +3346,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    
8302b5b4aacd

ext4: don't cache extent during splitting extent

1 file changed · +6 1
  • fs/ext4/extents.c+6 1 modified
    diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
    index 12da59c03c7cf8..eb3ae1c913c4f4 100644
    --- a/fs/ext4/extents.c
    +++ b/fs/ext4/extents.c
    @@ -3168,6 +3168,9 @@ static int ext4_split_extent_at(handle_t *handle,
     	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
     	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	ext_debug(inode, "logical block %llu\n", (unsigned long long)split);
     
     	ext4_ext_show_leaf(inode, path);
    @@ -3338,6 +3341,9 @@ static int ext4_split_extent(handle_t *handle,
     	ee_len = ext4_ext_get_actual_len(ex);
     	unwritten = ext4_ext_is_unwritten(ex);
     
    +	/* Do not cache extents that are in the process of being modified. */
    +	flags |= EXT4_EX_NOCACHE;
    +
     	if (map->m_lblk + map->m_len < ee_block + ee_len) {
     		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
     		flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing suppression of extent caching during extent splitting allows stale hole entries to persist in the extent status tree, leading to inconsistent metadata and space accounting errors."

Attack vector

An attacker with write access to an ext4 filesystem can trigger a direct I/O (DIO) write that partially overlaps an unwritten extent. During the split of that extent, the functions `ext4_split_extent_at()` and `ext4_split_extent()` in `fs/ext4/extents.c` [patch_id=2661428] call `ext4_find_extent()` and `ext4_cache_extents()`, which may insert a stale hole entry for the truncated range into the extent status tree. After the split completes, the hole is never replaced with the correct status. If a subsequent delayed buffer write targets that range, it re-inserts a delayed extent and causes an error in space accounting. No special network path or authentication bypass is needed — the attacker only needs the ability to perform DIO writes on a filesystem that uses extents.

Affected code

The vulnerability is in `fs/ext4/extents.c` in the functions `ext4_split_extent_at()` and `ext4_split_extent()` [patch_id=2661428]. These functions split an existing on-disk extent and, during that process, could call `ext4_find_extent()` and `ext4_cache_extents()`, which might insert a stale hole entry into the extent status tree for the range being truncated.

What the fix does

The patch adds `flags |= EXT4_EX_NOCACHE;` at the beginning of both `ext4_split_extent_at()` and `ext4_split_extent()` in `fs/ext4/extents.c` [patch_id=2661428]. This flag prevents `ext4_cache_extents()` from inserting any new entries into the extent status tree while the split is in progress. By suppressing caching during modification, the patch eliminates the race window where a truncated range could be incorrectly recorded as a hole. The commit message notes that caching is not particularly useful here because the extent block entries are likely already cached before the split occurs.

Preconditions

  • inputAttacker must be able to perform direct I/O (DIO) writes that partially overlap an unwritten extent on an ext4 filesystem.
  • configThe filesystem must use extents (the default for ext4).

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

References

8

News mentions

0

No linked articles in our index yet.