For archival purposes, forwarding an incoming command email to
linux-...@vger.kernel.org,
syzkall...@googlegroups.com.
***
Subject: [PATCH] ext4: add validation and bounds checking in ext4_read_inline_data()
Author:
karti...@gmail.com
#syz test: git://
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git master
Add comprehensive validation and bounds checking to prevent use-after-free
and out-of-bounds read vulnerabilities in ext4_read_inline_data().
The function previously trusted that:
1. The iloc buffer_head remained valid throughout execution
2. The xattr entry's e_value_offs pointed within valid inode bounds
This led to two critical vulnerabilities:
1. Use-after-free: The iloc->bh buffer could be freed by another thread
between ext4_get_inode_loc() and ext4_read_inline_data(), causing
reads from freed memory.
2. Out-of-bounds read: A corrupted or malicious filesystem image could
set e_value_offs to point outside the inode's xattr area, causing
reads beyond valid memory bounds.
This patch adds validation to:
- Check iloc and iloc->bh are non-NULL
- Verify buffer is still uptodate
- Validate xattr entry pointer is within inode bounds
- Bounds-check e_value_offs against xattr area size
- Ensure the final memcpy source and length stay within valid memory
Additionally, comprehensive debug logging has been added to aid in
diagnosing similar issues in the future.
The fix prevents both memory safety issues by catching invalid states
before the vulnerable memcpy operation at line 228.
Reported-by:
syzbot+6986a3...@syzkaller.appspotmail.com
Closes:
https://syzkaller.appspot.com/bug?extid=6986a30df88382d1f7bf
Signed-off-by: Deepanshu Kartikey <
karti...@gmail.com>
---
fs/ext4/inline.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 90 insertions(+), 2 deletions(-)
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index 1f6bc05593df..f807598e96c9 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -188,16 +188,44 @@ static int ext4_read_inline_data(struct inode *inode, void *buffer,
struct ext4_xattr_ibody_header *header;
int cp_len = 0;
struct ext4_inode *raw_inode;
+ void *xattr_start, *xattr_end;
+ u32 value_offs;
+ void *source;
if (!len)
return 0;
+ printk(KERN_ERR "ext4_read_inline_data: START - inode=%lu, len=%u\n",
+ inode->i_ino, len);
+
+ /* VALIDATION 1: Check iloc and buffer validity */
+ if (!iloc) {
+ printk(KERN_ERR "ext4_read_inline_data: ERROR - iloc is NULL\n");
+ return -EFSCORRUPTED;
+ }
+
+ if (!iloc->bh) {
+ printk(KERN_ERR "ext4_read_inline_data: ERROR - iloc->bh is NULL\n");
+ return -EFSCORRUPTED;
+ }
+
+ printk(KERN_ERR "ext4_read_inline_data: iloc->bh=%px, refcount=%d\n",
+ iloc->bh, atomic_read(&iloc->bh->b_count));
+
+ if (!buffer_uptodate(iloc->bh)) {
+ printk(KERN_ERR "ext4_read_inline_data: ERROR - buffer not uptodate\n");
+ return -EIO;
+ }
+
BUG_ON(len > EXT4_I(inode)->i_inline_size);
cp_len = min_t(unsigned int, len, EXT4_MIN_INLINE_DATA_SIZE);
raw_inode = ext4_raw_inode(iloc);
+ printk(KERN_ERR "ext4_read_inline_data: raw_inode=%px\n", raw_inode);
+
memcpy(buffer, (void *)(raw_inode->i_block), cp_len);
+ printk(KERN_ERR "ext4_read_inline_data: copied %d bytes from i_block\n", cp_len);
len -= cp_len;
buffer += cp_len;
@@ -208,17 +236,77 @@ static int ext4_read_inline_data(struct inode *inode, void *buffer,
header = IHDR(inode, raw_inode);
entry = (struct ext4_xattr_entry *)((void *)raw_inode +
EXT4_I(inode)->i_inline_off);
+
+ printk(KERN_ERR "ext4_read_inline_data: header=%px, entry=%px, i_inline_off=%u\n",
+ header, entry, EXT4_I(inode)->i_inline_off);
+
+ /* VALIDATION 2: Calculate and check bounds */
+ xattr_start = (void *)IFIRST(header);
+ xattr_end = (void *)raw_inode + EXT4_INODE_SIZE(inode->i_sb);
+
+ printk(KERN_ERR "ext4_read_inline_data: BOUNDS - xattr_start=%px, xattr_end=%px, size=%ld\n",
+ xattr_start, xattr_end, (long)(xattr_end - xattr_start));
+
+ /* VALIDATION 3: Check entry pointer is within inode */
+ if ((void *)entry < (void *)raw_inode || (void *)entry >= xattr_end) {
+ printk(KERN_ERR "ext4_read_inline_data: ERROR - entry %px outside inode bounds [%px-%px]\n",
+ entry, raw_inode, xattr_end);
+ ext4_error_inode(inode, __func__, __LINE__, 0,
+ "xattr entry outside inode bounds");
+ return -EFSCORRUPTED;
+ }
+
+ /* VALIDATION 4: Check e_value_offs */
+ value_offs = le16_to_cpu(entry->e_value_offs);
+ printk(KERN_ERR "ext4_read_inline_data: e_value_offs=%u, e_value_size=%u\n",
+ value_offs, le32_to_cpu(entry->e_value_size));
+
+ if (value_offs > (xattr_end - xattr_start)) {
+ printk(KERN_ERR "ext4_read_inline_data: ERROR - value_offs %u exceeds xattr bounds %ld\n",
+ value_offs, (long)(xattr_end - xattr_start));
+ ext4_error_inode(inode, __func__, __LINE__, 0,
+ "corrupt xattr offset: %u", value_offs);
+ return -EFSCORRUPTED;
+ }
+
len = min_t(unsigned int, len,
(unsigned int)le32_to_cpu(entry->e_value_size));
- memcpy(buffer,
- (void *)IFIRST(header) + le16_to_cpu(entry->e_value_offs), len);
+ /* VALIDATION 5: Check final read bounds */
+ source = xattr_start + value_offs;
+
+ printk(KERN_ERR "ext4_read_inline_data: FINAL CHECK - source=%px, len=%u, end=%px\n",
+ source, len, source + len);
+
+ if (source < (void *)raw_inode) {
+ printk(KERN_ERR "ext4_read_inline_data: ERROR - source %px before inode start %px\n",
+ source, raw_inode);
+ return -EFSCORRUPTED;
+ }
+
+ if ((source + len) > xattr_end) {
+ printk(KERN_ERR "ext4_read_inline_data: ERROR - read [%px-%px] exceeds inode end %px\n",
+ source, source + len, xattr_end);
+ ext4_error_inode(inode, __func__, __LINE__, 0,
+ "xattr read exceeds inode bounds");
+ return -EFSCORRUPTED;
+ }
+
+ printk(KERN_ERR "ext4_read_inline_data: ABOUT TO MEMCPY - source=%px, dest=%px, len=%u\n",
+ source, buffer, len);
+
+ memcpy(buffer, source, len);
+
+ printk(KERN_ERR "ext4_read_inline_data: memcpy SUCCESS\n");
+
cp_len += len;
out:
+ printk(KERN_ERR "ext4_read_inline_data: DONE - copied %d bytes total\n", cp_len);
return cp_len;
}
+
/*
* write the buffer to the inline inode.
* If 'create' is set, we don't need to do the extra copy in the xattr
--
2.43.0