KASAN: slab-out-of-bounds Read in ocfs2_check_dir_entry

4 views
Skip to first unread message

杜义恒

unread,
Mar 6, 2026, 7:41:28 AMMar 6
to ma...@fasheh.com, jl...@evilplan.org, jose...@linux.alibaba.com, syzk...@googlegroups.com

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

==================================================================


bisect.log
kconfig
report0
repro.cprog
Reply all
Reply to author
Forward
0 new messages