Dear Linux maintainers and reviewers:
We are reporting a Linux kernel bug titled **KASAN: slab-out-of-bounds Read in ocfs2_check_dir_entry**, discovered using a modified version of Syzkaller.
Linux version: ccd1cdca5cd433c8a5dff78b69a79b31d9b77ee1
The bisection log shows the first introduced commit is 255547c6bb8940a97eea94ef9d464ea5967763fb.
Commit log for cause commit 255547c6bb8940a97eea94ef9d464ea5967763fb:
commit 255547c6bb8940a97eea94ef9d464ea5967763fb
Author: lei lu <llfa...@gmail.com>
Date: Wed Jun 26 18:44:33 2024 +0800
ocfs2: add bounds checking to ocfs2_check_dir_entry()
This adds sanity checks for ocfs2_dir_entry to make sure all members of
ocfs2_dir_entry don't stray beyond valid memory region.
Link: https://lkml.kernel.org/r/20240626104433.1...@gmail.com
Signed-off-by: lei lu <llfa...@gmail.com>
Reviewed-by: Heming Zhao <hemin...@suse.com>
Reviewed-by: Joseph Qi <jose...@linux.alibaba.com>
Cc: Mark Fasheh <ma...@fasheh.com>
Cc: Joel Becker <jl...@evilplan.org>
Cc: Junxiao Bi <junxi...@oracle.com>
Cc: Changwei Ge <gecha...@live.cn>
Cc: Gang He <g...@suse.com>
Cc: Jun Piao <pia...@huawei.com>
Signed-off-by: Andrew Morton <ak...@linux-foundation.org>
diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
index d620d4c53c6f..f0beb173dbba 100644
--- a/fs/ocfs2/dir.c
+++ b/fs/ocfs2/dir.c
@@ -294,13 +294,16 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
* bh passed here can be an inode block or a dir data block, depending
* on the inode inline data flag.
*/
-static int ocfs2_check_dir_entry(struct inode * dir,
- struct ocfs2_dir_entry * de,
- struct buffer_head * bh,
+static int ocfs2_check_dir_entry(struct inode *dir,
+ struct ocfs2_dir_entry *de,
+ struct buffer_head *bh,
+ char *buf,
+ unsigned int size,
unsigned long offset)
{
const char *error_msg = NULL;
const int rlen = le16_to_cpu(de->rec_len);
+ const unsigned long next_offset = ((char *) de - buf) + rlen;
if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
error_msg = "rec_len is smaller than minimal";
@@ -308,9 +311,11 @@ static int ocfs2_check_dir_entry(struct inode * dir,
error_msg = "rec_len % 4 != 0";
else if (unlikely(rlen < OCFS2_DIR_REC_LEN(de->name_len)))
error_msg = "rec_len is too small for name_len";
- else if (unlikely(
- ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
- error_msg = "directory entry across blocks";
+ else if (unlikely(next_offset > size))
+ error_msg = "directory entry overrun";
+ else if (unlikely(next_offset > size - OCFS2_DIR_REC_LEN(1)) &&
+ next_offset != size)
+ error_msg = "directory entry too close to end";
if (unlikely(error_msg != NULL))
mlog(ML_ERROR, "bad entry in directory #%llu: %s - "
@@ -352,16 +357,17 @@ static inline int ocfs2_search_dirblock(struct buffer_head *bh,
de_buf = first_de;
dlimit = de_buf + bytes;
- while (de_buf < dlimit) {
+ while (de_buf < dlimit - OCFS2_DIR_MEMBER_LEN) {
/* this code is executed quadratically often */
/* do minimal checking `by hand' */
de = (struct ocfs2_dir_entry *) de_buf;
- if (de_buf + namelen <= dlimit &&
+ if (de->name + namelen <= dlimit &&
ocfs2_match(namelen, name, de)) {
/* found a match - just to be sure, do a full check */
- if (!ocfs2_check_dir_entry(dir, de, bh, offset)) {
+ if (!ocfs2_check_dir_entry(dir, de, bh, first_de,
+ bytes, offset)) {
ret = -1;
goto bail;
}
@@ -1138,7 +1144,7 @@ static int __ocfs2_delete_entry(handle_t *handle, struct inode *dir,
pde = NULL;
de = (struct ocfs2_dir_entry *) first_de;
while (i < bytes) {
- if (!ocfs2_check_dir_entry(dir, de, bh, i)) {
+ if (!ocfs2_check_dir_entry(dir, de, bh, first_de, bytes, i)) {
status = -EIO;
mlog_errno(status);
goto bail;
@@ -1635,7 +1641,8 @@ int __ocfs2_add_entry(handle_t *handle,
/* These checks should've already been passed by the
* prepare function, but I guess we can leave them
* here anyway. */
- if (!ocfs2_check_dir_entry(dir, de, insert_bh, offset)) {
+ if (!ocfs2_check_dir_entry(dir, de, insert_bh, data_start,
+ size, offset)) {
retval = -ENOENT;
goto bail;
}
@@ -1774,7 +1781,8 @@ static int ocfs2_dir_foreach_blk_id(struct inode *inode,
}
de = (struct ocfs2_dir_entry *) (data->id_data + ctx->pos);
- if (!ocfs2_check_dir_entry(inode, de, di_bh, ctx->pos)) {
+ if (!ocfs2_check_dir_entry(inode, de, di_bh, (char *)data->id_data,
+ i_size_read(inode), ctx->pos)) {
/* On error, skip the f_pos to the end. */
ctx->pos = i_size_read(inode);
break;
@@ -1867,7 +1875,8 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
while (ctx->pos < i_size_read(inode)
&& offset < sb->s_blocksize) {
de = (struct ocfs2_dir_entry *) (bh->b_data + offset);
- if (!ocfs2_check_dir_entry(inode, de, bh, offset)) {
+ if (!ocfs2_check_dir_entry(inode, de, bh, bh->b_data,
+ sb->s_blocksize, offset)) {
/* On error, skip the f_pos to the
next block. */
ctx->pos = (ctx->pos | (sb->s_blocksize - 1)) + 1;
@@ -3339,7 +3348,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
struct super_block *sb = dir->i_sb;
struct ocfs2_dinode *di = (struct ocfs2_dinode *)di_bh->b_data;
struct ocfs2_dir_entry *de, *last_de = NULL;
- char *de_buf, *limit;
+ char *first_de, *de_buf, *limit;
unsigned long offset = 0;
unsigned int rec_len, new_rec_len, free_space;
@@ -3352,14 +3361,16 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
else
free_space = dir->i_sb->s_blocksize - i_size_read(dir);
- de_buf = di->id2.i_data.id_data;
+ first_de = di->id2.i_data.id_data;
+ de_buf = first_de;
limit = de_buf + i_size_read(dir);
rec_len = OCFS2_DIR_REC_LEN(namelen);
while (de_buf < limit) {
de = (struct ocfs2_dir_entry *)de_buf;
- if (!ocfs2_check_dir_entry(dir, de, di_bh, offset)) {
+ if (!ocfs2_check_dir_entry(dir, de, di_bh, first_de,
+ i_size_read(dir), offset)) {
ret = -ENOENT;
goto out;
}
@@ -3441,7 +3452,8 @@ static int ocfs2_find_dir_space_el(struct inode *dir, const char *name,
/* move to next block */
de = (struct ocfs2_dir_entry *) bh->b_data;
}
- if (!ocfs2_check_dir_entry(dir, de, bh, offset)) {
+ if (!ocfs2_check_dir_entry(dir, de, bh, bh->b_data, blocksize,
+ offset)) {
status = -ENOENT;
goto bail;
}
The test case, kernel config and full bisection log are attached.
The report is (The full report is attached):
(kworker/u8:1,13,1):ocfs2_read_blocks_sync:112 ERROR: status = -12
(kworker/u8:1,13,1):ocfs2_read_locked_inode:599 ERROR: status = -12
==================================================================
BUG: KASAN: slab-out-of-bounds in ocfs2_check_dir_entry.constprop.0+0x3d3/0x410 fs/ocfs2/dir.c:318
Read of size 2 at addr ff110002e303b780 by task kworker/u8:1/13
CPU: 1 UID: 0 PID: 13 Comm: kworker/u8:1 Not tainted 6.19.0-rc2-gccd1cdca5cd4-dirty #2 PREEMPT(full)
Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: ocfs2_wq ocfs2_complete_recovery
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x116/0x1b0 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xca/0x5f0 mm/kasan/report.c:482
kasan_report+0xca/0x100 mm/kasan/report.c:595
ocfs2_check_dir_entry.constprop.0+0x3d3/0x410 fs/ocfs2/dir.c:318
ocfs2_dir_foreach_blk_id+0x1ea/0x970 fs/ocfs2/dir.c:1826
ocfs2_dir_foreach_blk fs/ocfs2/dir.c:1954 [inline]
ocfs2_dir_foreach+0x11b/0x130 fs/ocfs2/dir.c:1965
ocfs2_queue_orphans+0x142/0x3e0 fs/ocfs2/journal.c:2215
ocfs2_recover_orphans+0x41a/0xfb0 fs/ocfs2/journal.c:2299
ocfs2_complete_recovery+0x650/0xf90 fs/ocfs2/journal.c:1366
process_one_work+0x990/0x1af0 kernel/workqueue.c:3257
process_scheduled_works kernel/workqueue.c:3340 [inline]
worker_thread+0x67e/0xe90 kernel/workqueue.c:3421
kthread+0x446/0x890 kernel/kthread.c:463
ret_from_fork+0x95b/0xae0 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246
</TASK>
Allocated by task 11253:
kasan_save_stack+0x24/0x50 mm/kasan/common.c:56
kasan_save_track+0x14/0x30 mm/kasan/common.c:77
poison_kmalloc_redzone mm/kasan/common.c:397 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:414
kasan_kmalloc include/linux/kasan.h:262 [inline]
__do_kmalloc_node mm/slub.c:5657 [inline]
__kmalloc_noprof+0x342/0xa30 mm/slub.c:5669
kmalloc_noprof include/linux/slab.h:961 [inline]
tomoyo_realpath_from_path+0xc3/0x600 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_perm+0x237/0x420 security/tomoyo/file.c:822
security_inode_getattr+0x115/0x280 security/security.c:1869
vfs_getattr fs/stat.c:259 [inline]
vfs_fstat+0x4c/0xc0 fs/stat.c:281
__do_sys_newfstat+0x79/0x100 fs/stat.c:555
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x72/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x76/0x7e
Freed by task 11253:
kasan_save_stack+0x24/0x50 mm/kasan/common.c:56
kasan_save_track+0x14/0x30 mm/kasan/common.c:77
kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:252 [inline]
__kasan_slab_free+0x61/0x80 mm/kasan/common.c:284
kasan_slab_free include/linux/kasan.h:234 [inline]
slab_free_hook mm/slub.c:2540 [inline]
slab_free mm/slub.c:6670 [inline]
kfree+0x2c8/0x6c0 mm/slub.c:6878
tomoyo_realpath_from_path+0x193/0x600 security/tomoyo/realpath.c:286
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_perm+0x237/0x420 security/tomoyo/file.c:822
security_inode_getattr+0x115/0x280 security/security.c:1869
vfs_getattr fs/stat.c:259 [inline]
vfs_fstat+0x4c/0xc0 fs/stat.c:281
__do_sys_newfstat+0x79/0x100 fs/stat.c:555
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x72/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x76/0x7e
The buggy address belongs to the object at ff110002e303a000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1920 bytes to the right of
allocated 4096-byte region [ff110002e303a000, ff110002e303b000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x2e3038
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x57ff00000000040(head|node=1|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 057ff00000000040 ff11000100038140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000040 ff11000100038140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000003 ffd400000b8c0e01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 4890, tgid 4890 (systemd-udevd), ts 45433775650, free_ts 39794312036
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1ca/0x240 mm/page_alloc.c:1846
prep_new_page mm/page_alloc.c:1854 [inline]
get_page_from_freelist+0xdb3/0x2a60 mm/page_alloc.c:3915
__alloc_frozen_pages_noprof+0x217/0x3a0 mm/page_alloc.c:5210
alloc_pages_mpol+0x1f1/0x550 mm/mempolicy.c:2486
alloc_slab_page mm/slub.c:3075 [inline]
allocate_slab+0x299/0x3f0 mm/slub.c:3248
new_slab mm/slub.c:3302 [inline]
___slab_alloc+0xe39/0x1bb0 mm/slub.c:4656
__slab_alloc.constprop.0+0x66/0x110 mm/slub.c:4779
__slab_alloc_node mm/slub.c:4855 [inline]
slab_alloc_node mm/slub.c:5251 [inline]
__do_kmalloc_node mm/slub.c:5656 [inline]
__kmalloc_noprof+0x62c/0xa30 mm/slub.c:5669
kmalloc_noprof include/linux/slab.h:961 [inline]
tomoyo_realpath_from_path+0xc3/0x600 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x298/0x3a0 security/tomoyo/file.c:771
tomoyo_file_open+0x69/0x90 security/tomoyo/tomoyo.c:334
security_file_open+0x7e/0x1d0 security/security.c:2636
do_dentry_open+0x576/0x1590 fs/open.c:939
vfs_open+0x82/0x3f0 fs/open.c:1094
do_open fs/namei.c:4628 [inline]
path_openat+0x1fc1/0x2cb0 fs/namei.c:4787
do_filp_open+0x1f7/0x460 fs/namei.c:4814
page last free pid 4951 tgid 4951 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
free_pages_prepare mm/page_alloc.c:1395 [inline]
__free_frozen_pages+0x83d/0x1160 mm/page_alloc.c:2943
discard_slab mm/slub.c:3346 [inline]
__put_partials+0x130/0x170 mm/slub.c:3886
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x4e/0xf0 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x195/0x1e0 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x67/0x90 mm/kasan/common.c:349
kasan_slab_alloc include/linux/kasan.h:252 [inline]
slab_post_alloc_hook mm/slub.c:4953 [inline]
slab_alloc_node mm/slub.c:5263 [inline]
kmem_cache_alloc_lru_noprof+0x26b/0x830 mm/slub.c:5282
shmem_alloc_inode+0x27/0x50 mm/shmem.c:5152
alloc_inode+0x68/0x250 fs/inode.c:346
new_inode+0x22/0x1d0 fs/inode.c:1175
__shmem_get_inode mm/shmem.c:3068 [inline]
shmem_get_inode+0x19c/0x1000 mm/shmem.c:3142
shmem_mknod+0x1a9/0x3c0 mm/shmem.c:3872
lookup_open.isra.0+0x125a/0x1720 fs/namei.c:4440
open_last_lookups fs/namei.c:4540 [inline]
path_openat+0xe97/0x2cb0 fs/namei.c:4784
do_filp_open+0x1f7/0x460 fs/namei.c:4814
do_sys_openat2+0x106/0x250 fs/open.c:1430
do_sys_open fs/open.c:1436 [inline]
__do_sys_openat fs/open.c:1452 [inline]
__se_sys_openat fs/open.c:1447 [inline]
__x64_sys_openat+0x13f/0x1f0 fs/open.c:1447
Memory state around the buggy address:
ff110002e303b680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ff110002e303b700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ff110002e303b780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
^
ff110002e303b800: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ff110002e303b880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
==================================================================