ext4_read_inline_dir() can read a dirent header past the end of its inline buffer, triggering a slab-out-of-bounds read during getdents64(): BUG: KASAN: slab-out-of-bounds in __ext4_check_dir_entry Read of size 2 at addr ffff88800f3dd23c by task exploit/148 ... __ext4_check_dir_entry ext4_read_inline_dir iterate_dir The dirent payload lives in a buffer of exactly inline_size bytes: dir_buf = kmalloc(inline_size, GFP_NOFS); but iteration runs in a position space extra_offset bytes larger (extra_size = extra_offset + inline_size) so the synthetic "." and ".." land at their block-dir offsets. A dirent is formed at "dir_buf + pos - extra_offset", yet the loop bound (ctx->pos < extra_size) and the ext4_check_dir_entry() length argument both use the larger extra_size. A ctx->pos that is valid in extra_size space but whose de lies past inline_size is therefore accepted, and the rescan loop's rec_len probe and ext4_check_dir_entry() dereference de->rec_len before the entry is rejected. Bound the dirent header by inline_size in both loops: break out of the rescan loop once a minimum-size header no longer fits, reject such a position in the main loop before forming de, and pass inline_size rather than extra_size to ext4_check_dir_entry(). Fixes: c4d8b0235aa9 ("ext4: fix readdir error in case inline_data+^dir_index.") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei --- fs/ext4/inline.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index 8045e4ff270c..1266c8827cca 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -1454,6 +1454,9 @@ int ext4_read_inline_dir(struct file *file, /* for other entry, the real offset in * the buf has to be tuned accordingly. */ + if (i - extra_offset + ext4_dir_rec_len(1, NULL) > + inline_size) + break; de = (struct ext4_dir_entry_2 *) (dir_buf + i - extra_offset); /* It's too expensive to do a full @@ -1488,10 +1491,18 @@ int ext4_read_inline_dir(struct file *file, continue; } + /* + * de lives at dir_buf + ctx->pos - extra_offset, within the + * kmalloc(inline_size) buffer. Make sure its header fits before + * ext4_check_dir_entry() dereferences de->rec_len. + */ + if (ctx->pos - extra_offset + ext4_dir_rec_len(1, NULL) > + inline_size) + goto out; de = (struct ext4_dir_entry_2 *) (dir_buf + ctx->pos - extra_offset); if (ext4_check_dir_entry(inode, file, de, iloc.bh, dir_buf, - extra_size, ctx->pos)) + inline_size, ctx->pos)) goto out; if (le32_to_cpu(de->inode)) { if (!dir_emit(ctx, de->name, de->name_len, -- 2.43.0