When a file has inline data with an xattr entry but e_value_size is 0, ext4_prepare_inline_data() incorrectly uses the theoretical maximum inline size (128 bytes) instead of the actual current capacity (60 bytes from i_block only). This causes it to accept writes that exceed the actual capacity, leading to a kernel crash in ext4_write_inline_data_end() when the BUG_ON(pos + len > EXT4_I(inode)->i_inline_size) is triggered. This scenario occurs when: 1. A file is created with inline data 2. The file is truncated, leaving an xattr entry with e_value_size=0 3. A write is attempted that exceeds i_block capacity (>60 bytes) The bug occurs because ext4_prepare_inline_data() calls ext4_get_max_inline_size() which returns the theoretical maximum (128) even when the xattr value space is not allocated. This leads to: - ext4_prepare_inline_data() thinks the write will fit (120 < 128) - Does not return -ENOSPC - Inline write path is taken - ext4_write_inline_data_end() detects overflow and crashes The fix checks e_value_size in ext4_prepare_inline_data(): - If e_value_size is 0: xattr exists but has no data, cannot expand, use actual current capacity (i_inline_size) - If e_value_size > 0: xattr has data, expansion possible, use theoretical maximum (ext4_get_max_inline_size) - If no xattr entry: use theoretical maximum This ensures the capacity check accurately reflects available space, triggering proper conversion to extents when needed and preventing the overflow crash. Reported-by: syzbot+f3185be57d7e8dda32b8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f3185be57d7e8dda32b8 Signed-off-by: Deepanshu Kartikey --- fs/ext4/inline.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index 1b094a4f3866..3a3aa2d803db 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -413,7 +413,30 @@ static int ext4_prepare_inline_data(handle_t *handle, struct inode *inode, if (!ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) return -ENOSPC; - size = ext4_get_max_inline_size(inode); + if (ei->i_inline_off) { + struct ext4_iloc iloc; + struct ext4_inode *raw_inode; + struct ext4_xattr_entry *entry; + + ret = ext4_get_inode_loc(inode, &iloc); + if (ret) + return ret; + + raw_inode = ext4_raw_inode(&iloc); + entry = (struct ext4_xattr_entry *) + ((void *)raw_inode + ei->i_inline_off); + + if (le32_to_cpu(entry->e_value_size) == 0) { + ext4_find_inline_data_nolock(inode); + size = ei->i_inline_size; + } else { + size = ext4_get_max_inline_size(inode); + } + + brelse(iloc.bh); + } else { + size = ext4_get_max_inline_size(inode); + } if (size < len) return -ENOSPC; -- 2.43.0