From 3530cb831d939d34f02099d09fdacaa1da1582eb Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Tue, 21 Apr 2026 05:48:26 -0700 Subject: [PATCH] ext4: replace BUG_ON with error handling in ext4_write_inline_data Replace the two BUG_ON() calls in ext4_write_inline_data() with proper error handling. A syzbot-style crafted ext4 image can trigger the assertion: BUG_ON(pos + len > EXT4_I(inode)->i_inline_size); at fs/ext4/inline.c:240 by presenting an inode whose inline-data state is inconsistent with the write parameters, crashing the kernel instead of reporting filesystem corruption gracefully. Two prior fixes addressed related triggers of this same BUG_ON: - 892e1cf17555 ("ext4: refresh inline data size before write operations") addressed staleness due to concurrent xattr modification inside ext4_prepare_inline_data(). - ed9356a30e59 ("ext4: convert inline data to extents when truncate exceeds inline size") guarded the ext4_setattr() path when truncate grows a file beyond inline capacity. Both are present in the affected tree. The crash reported against 7.0.0-08391-g1d51b370a0f8 represents a remaining instance: a crafted image whose inode already has i_size > i_inline_size at mount time bypasses the setattr guard, and the remaining race window between write_begin's xattr-lock release and write_end's re-acquisition can cause ext4_find_inline_data_nolock() to observe a smaller i_inline_size than ext4_prepare_inline_data() established. Replace both BUG_ON()s with ext4_error_inode() calls that mark the filesystem corrupted and return -EFSCORRUPTED. Change the function return type from void to int so callers can propagate the error. Update ext4_write_inline_data_end() to check the return value and release held locks on error. Update ext4_restore_inline_data() to log the failure. This follows the pattern established by commit 099b847ccc6c ("ext4: do not BUG when INLINE_DATA_FL lacks system.data xattr") and commit 356227096eb6 ("ext4: replace BUG_ON with proper error handling in ext4_read_inline_folio"). Link: https://lore.kernel.org/r/CAPHJ_VJeBAL_fk+P79guYTABZgW1hkcAz8t=c_nVK1mbn3_FYw@mail.gmail.com Oops-Analysis: http://oops.fenrus.org/reports/email/CAPHJ_VJeBAL_fk+P79guYTABZgW1hkcAz8t=c_nVK1mbn3_FYw_mail.gmail.com/report.html Fixes: 67cf5b09a46f ("ext4: add the basic function for inline data support") Assisted-by: GitHub Copilot CLI:claude-sonnet-4.6 linux-kernel-oops-x86. Signed-off-by: Arjan van de Ven Cc: linux-ext4@vger.kernel.org Cc: tytso@mit.edu Cc: adilger.kernel@dilger.ca Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- fs/ext4/inline.c | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index 8045e4ff270c7..a01ade8973ebc 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -225,8 +225,8 @@ static int ext4_read_inline_data(struct inode *inode, void *buffer, * value since it is already handled by ext4_xattr_ibody_set. * That saves us one memcpy. */ -static void ext4_write_inline_data(struct inode *inode, struct ext4_iloc *iloc, - void *buffer, loff_t pos, unsigned int len) +static int ext4_write_inline_data(struct inode *inode, struct ext4_iloc *iloc, + void *buffer, loff_t pos, unsigned int len) { struct ext4_xattr_entry *entry; struct ext4_xattr_ibody_header *header; @@ -234,10 +234,19 @@ static void ext4_write_inline_data(struct inode *inode, struct ext4_iloc *iloc, int cp_len = 0; if (unlikely(ext4_emergency_state(inode->i_sb))) - return; + return 0; - BUG_ON(!EXT4_I(inode)->i_inline_off); - BUG_ON(pos + len > EXT4_I(inode)->i_inline_size); + if (unlikely(!EXT4_I(inode)->i_inline_off)) { + ext4_error_inode(inode, __func__, __LINE__, 0, + "inline data xattr not found"); + return -EFSCORRUPTED; + } + if (unlikely(pos + len > EXT4_I(inode)->i_inline_size)) { + ext4_error_inode(inode, __func__, __LINE__, 0, + "write beyond inline size: pos=%lld len=%u inline_size=%u", + pos, len, EXT4_I(inode)->i_inline_size); + return -EFSCORRUPTED; + } raw_inode = ext4_raw_inode(iloc); buffer += pos; @@ -253,7 +262,7 @@ static void ext4_write_inline_data(struct inode *inode, struct ext4_iloc *iloc, } if (!len) - return; + return 0; pos -= EXT4_MIN_INLINE_DATA_SIZE; header = IHDR(inode, raw_inode); @@ -262,6 +271,7 @@ static void ext4_write_inline_data(struct inode *inode, struct ext4_iloc *iloc, memcpy((void *)IFIRST(header) + le16_to_cpu(entry->e_value_offs) + pos, buffer, len); + return 0; } static int ext4_create_inline_data(handle_t *handle, @@ -822,8 +832,15 @@ int ext4_write_inline_data_end(struct inode *inode, loff_t pos, unsigned len, (void) ext4_find_inline_data_nolock(inode); kaddr = kmap_local_folio(folio, 0); - ext4_write_inline_data(inode, &iloc, kaddr, pos, copied); + ret = ext4_write_inline_data(inode, &iloc, kaddr, pos, copied); kunmap_local(kaddr); + if (unlikely(ret)) { + ext4_write_unlock_xattr(inode, &no_expand); + brelse(iloc.bh); + folio_unlock(folio); + folio_put(folio); + goto out; + } folio_mark_uptodate(folio); /* clear dirty flag so that writepages wouldn't work for us. */ folio_clear_dirty(folio); @@ -1083,7 +1100,12 @@ static void ext4_restore_inline_data(handle_t *handle, struct inode *inode, inode->i_ino, ret); return; } - ext4_write_inline_data(inode, iloc, buf, 0, inline_size); + if (ext4_write_inline_data(inode, iloc, buf, 0, inline_size)) { + ext4_msg(inode->i_sb, KERN_EMERG, + "error writing inline_data for inode -- potential data loss! (inode %llu)", + inode->i_ino); + return; + } ext4_set_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA); } -- 2.53.0