| From d5e206778e96e8667d3bde695ad372c296dc9353 Mon Sep 17 00:00:00 2001 |
| From: "Acs, Jakub" <acsjakub@amazon.de> |
| Date: Thu, 20 Mar 2025 15:46:49 +0000 |
| Subject: ext4: fix OOB read when checking dotdot dir |
| |
| From: Acs, Jakub <acsjakub@amazon.de> |
| |
| commit d5e206778e96e8667d3bde695ad372c296dc9353 upstream. |
| |
| Mounting a corrupted filesystem with directory which contains '.' dir |
| entry with rec_len == block size results in out-of-bounds read (later |
| on, when the corrupted directory is removed). |
| |
| ext4_empty_dir() assumes every ext4 directory contains at least '.' |
| and '..' as directory entries in the first data block. It first loads |
| the '.' dir entry, performs sanity checks by calling ext4_check_dir_entry() |
| and then uses its rec_len member to compute the location of '..' dir |
| entry (in ext4_next_entry). It assumes the '..' dir entry fits into the |
| same data block. |
| |
| If the rec_len of '.' is precisely one block (4KB), it slips through the |
| sanity checks (it is considered the last directory entry in the data |
| block) and leaves "struct ext4_dir_entry_2 *de" point exactly past the |
| memory slot allocated to the data block. The following call to |
| ext4_check_dir_entry() on new value of de then dereferences this pointer |
| which results in out-of-bounds mem access. |
| |
| Fix this by extending __ext4_check_dir_entry() to check for '.' dir |
| entries that reach the end of data block. Make sure to ignore the phony |
| dir entries for checksum (by checking name_len for non-zero). |
| |
| Note: This is reported by KASAN as use-after-free in case another |
| structure was recently freed from the slot past the bound, but it is |
| really an OOB read. |
| |
| This issue was found by syzkaller tool. |
| |
| Call Trace: |
| [ 38.594108] BUG: KASAN: slab-use-after-free in __ext4_check_dir_entry+0x67e/0x710 |
| [ 38.594649] Read of size 2 at addr ffff88802b41a004 by task syz-executor/5375 |
| [ 38.595158] |
| [ 38.595288] CPU: 0 UID: 0 PID: 5375 Comm: syz-executor Not tainted 6.14.0-rc7 #1 |
| [ 38.595298] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014 |
| [ 38.595304] Call Trace: |
| [ 38.595308] <TASK> |
| [ 38.595311] dump_stack_lvl+0xa7/0xd0 |
| [ 38.595325] print_address_description.constprop.0+0x2c/0x3f0 |
| [ 38.595339] ? __ext4_check_dir_entry+0x67e/0x710 |
| [ 38.595349] print_report+0xaa/0x250 |
| [ 38.595359] ? __ext4_check_dir_entry+0x67e/0x710 |
| [ 38.595368] ? kasan_addr_to_slab+0x9/0x90 |
| [ 38.595378] kasan_report+0xab/0xe0 |
| [ 38.595389] ? __ext4_check_dir_entry+0x67e/0x710 |
| [ 38.595400] __ext4_check_dir_entry+0x67e/0x710 |
| [ 38.595410] ext4_empty_dir+0x465/0x990 |
| [ 38.595421] ? __pfx_ext4_empty_dir+0x10/0x10 |
| [ 38.595432] ext4_rmdir.part.0+0x29a/0xd10 |
| [ 38.595441] ? __dquot_initialize+0x2a7/0xbf0 |
| [ 38.595455] ? __pfx_ext4_rmdir.part.0+0x10/0x10 |
| [ 38.595464] ? __pfx___dquot_initialize+0x10/0x10 |
| [ 38.595478] ? down_write+0xdb/0x140 |
| [ 38.595487] ? __pfx_down_write+0x10/0x10 |
| [ 38.595497] ext4_rmdir+0xee/0x140 |
| [ 38.595506] vfs_rmdir+0x209/0x670 |
| [ 38.595517] ? lookup_one_qstr_excl+0x3b/0x190 |
| [ 38.595529] do_rmdir+0x363/0x3c0 |
| [ 38.595537] ? __pfx_do_rmdir+0x10/0x10 |
| [ 38.595544] ? strncpy_from_user+0x1ff/0x2e0 |
| [ 38.595561] __x64_sys_unlinkat+0xf0/0x130 |
| [ 38.595570] do_syscall_64+0x5b/0x180 |
| [ 38.595583] entry_SYSCALL_64_after_hwframe+0x76/0x7e |
| |
| Fixes: ac27a0ec112a0 ("[PATCH] ext4: initial copy of files from ext3") |
| Signed-off-by: Jakub Acs <acsjakub@amazon.de> |
| Cc: Theodore Ts'o <tytso@mit.edu> |
| Cc: Andreas Dilger <adilger.kernel@dilger.ca> |
| Cc: linux-ext4@vger.kernel.org |
| Cc: linux-kernel@vger.kernel.org |
| Cc: Mahmoud Adam <mngyadam@amazon.com> |
| Cc: stable@vger.kernel.org |
| Cc: security@kernel.org |
| Link: https://patch.msgid.link/b3ae36a6794c4a01944c7d70b403db5b@amazon.de |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/ext4/dir.c | 3 +++ |
| 1 file changed, 3 insertions(+) |
| |
| --- a/fs/ext4/dir.c |
| +++ b/fs/ext4/dir.c |
| @@ -104,6 +104,9 @@ int __ext4_check_dir_entry(const char *f |
| else if (unlikely(le32_to_cpu(de->inode) > |
| le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count))) |
| error_msg = "inode out of bounds"; |
| + else if (unlikely(next_offset == size && de->name_len == 1 && |
| + de->name[0] == '.')) |
| + error_msg = "'.' directory cannot be the last in data block"; |
| else |
| return 0; |
| |