Skip to content
cvekernelf2fsout-of-bounds

Linux kernel f2fs slab out-of-bounds write

6 min read

Overview

mounting crafted image can cause out-of-bounds-write vulnerability in __remove_dirty_segment

Target

Linux kernel f2fs FileSystem

Linux Version Availablity
5.0.21 True

Reproduce

mkdir mnt
mount poc_2019_19814.img ./mnt

Root cause

fs/f2fs/segment.c:809 (link)

static void __remove_dirty_segment(struct f2fs_sb_info *sbi, unsigned int segno,
		enum dirty_type dirty_type)
{
	struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 
	if (test_and_clear_bit(segno, dirty_i->dirty_segmap[dirty_type]))
		dirty_i->nr_dirty[dirty_type]--;
 
	if (dirty_type == DIRTY) {
[1]		struct seg_entry *sentry = get_seg_entry(sbi, segno); // segno = 0
		enum dirty_type t = sentry->type;
 
[2]		if (test_and_clear_bit(segno, dirty_i->dirty_segmap[t])) // KASAN here, t:63 (0x3f)
			dirty_i->nr_dirty[t]--;

When the local variable t (sentry->type) is 63, dirty_i->dirty_segmap[t] triggers an out-of-bounds write.

Debugger / KASAN

Debugger view:

─────────────────────────────────────────────────────────── registers ────
$rax   : 0x000000000000003f  →  0x000000000000003f
$rbx   : 0xffff888068ad6c00  →  0xffffffff83d4a040  →  0xffffffff81c51d60 →  0x5441554156415741  →  0x5441554156415741
$rcx   : 0x1ffff1100d15adc0  →  0x1ffff1100d15adc0
$rdx   : 0xdffffc0000000000  →  0xdffffc0000000000
$rsp   : 0xffff88806546f6f0  →  0x0000000081ca3176  →  0x0000000081ca3176
$rbp   : 0xffff88806c00e600  →  0xffff88806c00dd80  →  0xffffffff84532580 →  0xffff88806cc21100  →  0xffff88806cc21980  →  0xffff88806cc24400  →  0xffff88806cc25500  →  0xffff88806cc26600
$rsi   : 0x0000000000000000  →  0x0000000000000000
$rdi   : 0xffff888068ad6e00  →  0xffff888068ad6f00  →  0x0000000000000000 →  0x0000000000000000
$rip   : 0xffffffff81c9eb54  →  0x448b48ff87a4d7e8  →  0x448b48ff87a4d7e8
$r8    : 0xffffed100ca8dedc  →  0x0000000000000000  →  0x0000000000000000
$r9    : 0xffffed100ca8dedc  →  0x0000000000000000  →  0x0000000000000000
$r10   : 0x0000000000000001  →  0x0000000000000001
$r11   : 0xffffed100ca8dedb  →  0x0000000000000000  →  0x0000000000000000
$r12   : 0x000000000000003f  →  0x000000000000003f
$r13   : 0x0000000000000000  →  0x0000000000000000
$r14   : 0xffff88806c00e680  →  0xffff888068ad6a00  →  0xffff888068ad6b00 →  0xffffffff83d4b1e0  →  0xffffffff81cad380  →  0x0000b94856415741  →  0x0000b94856415741
$r15   : 0x0000000000000000  →  0x0000000000000000
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflowresume virtualx86 identification]
$cs: 0x0010 $ss: 0x0018 $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────── stack ────
0xffff88806546f6f0│+0x0000: 0x0000000081ca3176  →  0x0000000081ca3176	 ← $rsp
0xffff88806546f6f8│+0x0008: 0x000000000000003f  →  0x000000000000003f
0xffff88806546f700│+0x0010: 0x0000000000000000  →  0x0000000000000000
0xffff88806546f708│+0x0018: 0xffff888068ad6c48  →  0xffff888064b98cc0  →0x0000000080000000  →  0x0000000080000000
0xffff88806546f710│+0x0020: 0xffff88806c00e600  →  0xffff88806c00dd80  →0xffffffff84532580  →  0xffff88806cc21100  →  0xffff88806cc21980  →  0xffff88806cc24400  →  0xffff88806cc25500
0xffff88806546f718│+0x0028: 0xffff88806beef180  →  0x000000000000ffff  →0x000000000000ffff
0xffff88806546f720│+0x0030: 0x00000000000003ff  →  0x00000000000003ff
0xffff88806546f728│+0x0038: 0xffff88806546f880  →  0xffff8880661ac4c0  →0x00000000000a41ed  →  0x00000000000a41ed
───────────────────────────────────────────────────────── code:x86:64 ────
   0xffffffff81c9eb46 <__remove_dirty_segment+1174> jmp    0xffffffff81c9e88a <__remove_dirty_segment+474>
   0xffffffff81c9eb4b <__remove_dirty_segment+1179> mov    QWORD PTR [rsp+0x8], rax
   0xffffffff81c9eb50 <__remove_dirty_segment+1184> mov    DWORD PTR [rsp+0x4], esi
 → 0xffffffff81c9eb54 <__remove_dirty_segment+1188> call   0xffffffff81519030 <__asan_report_load8_noabort>
   ↳  0xffffffff81519030 <__asan_report_load8_noabort+0> mov    rcx, QWORD PTR [rsp]
      0xffffffff81519034 <__asan_report_load8_noabort+4> xor    edx, edx
      0xffffffff81519036 <__asan_report_load8_noabort+6> mov    esi, 0x8
      0xffffffff8151903b <__asan_report_load8_noabort+11> jmp    0xffffffff815185f0 <kasan_report>
      0xffffffff81519040 <__asan_report_load16_noabort+0> mov    rcx, QWORD PTR [rsp]
      0xffffffff81519044 <__asan_report_load16_noabort+4> xor    edx, edx
─────────────────────────────────────────────────────────── arguments ────
__asan_report_load8_noabort (
   long unsigned int var_0 = 0xffff888068ad6e00 → 0xffff888068ad6f00 → 0x0000000000000000 → 0x0000000000000000
)
──────────────────────────────────────── source:fs/f2fs/segment.c+809 ────
    804
    805	 	if (dirty_type == DIRTY) {
    806	 		struct seg_entry *sentry = get_seg_entry(sbi, segno); // segno = 0
    807	 		enum dirty_type t = sentry->type;
    808
 →  809	 		if (test_and_clear_bit(segno, dirty_i->dirty_segmap[t])) // KASAN here, t:63 (0x3f)
    810	 			dirty_i->nr_dirty[t]--; // can i use this?
    811
    812	 		if (get_valid_blocks(sbi, segno, true) == 0)
    813	 			clear_bit(GET_SEC_FROM_SEG(sbi, segno),
    814	 						dirty_i->victim_secmap);
───────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "", stopped, reason: BREAKPOINT
[#1] Id 2, Name: "", stopped, reason: BREAKPOINT
─────────────────────────────────────────────────────────────── trace ────
[#0] 0xffffffff81c9eb54 → __remove_dirty_segment(sbi=0xffff88806c00e600, segno=0x0, dirty_type=<optimized out>)
[#1] 0xffffffff81c9ef8d → locate_dirty_segment(sbi=0xffff88806c00e600, segno=0x0)
[#2] 0xffffffff81cab008 → f2fs_invalidate_blocks(sbi=0xffff88806c00e600, addr=0x0)
[#3] 0xffffffff81c83de2 → truncate_node(dn=0xffff88806546f880)
[#4] 0xffffffff81c90d8c → f2fs_remove_inode_page(inode=0xffff8880661ac4c0)
[#5] 0xffffffff81bfd050 → f2fs_evict_inode(inode=0xffff8880661ac4c0)
[#6] 0xffffffff8158d030 → evict(inode=0xffff888068ad6e00)
[#7] 0xffffffff815802bd → __dentry_kill(dentry=0xffff88806b7e3300)
[#8] 0xffffffff8158068a → dentry_kill(dentry=0xffff88806b7e3300)
[#9] 0xffffffff81583e8c → dput(dentry=0xffff88806b7e3300)
──────────────────────────────────────────────────────────────────────────

Thread 1 hit Breakpoint 1, __remove_dirty_segment (sbi=0xffff88806c00e600, segno=0x0, dirty_type=<optimized out>) at fs/f2fs/segment.c:809
warning: Source file is more recent than executable.
809			if (test_and_clear_bit(segno, dirty_i->dirty_segmap[t])) // KASAN here, t:63 (0x3f)
gef➤  p t
$1 = 63
gef➤

KASAN log:

[   59.585415] ==================================================================
[   59.585706] BUG: KASAN: slab-out-of-bounds in __remove_dirty_segment+0x4a9/0x550
[   59.585706] Read of size 8 at addr ffff888068ad6e00 by task mount/1861
[   59.585706]
[   59.585706] CPU: 0 PID: 1861 Comm: mount Tainted: G        W         5.0.21 #1
[   59.585706] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
[   59.585706] Call Trace:
[   59.585706]  dump_stack+0x5b/0x8b
[   59.585706]  print_address_description+0x70/0x280
[   59.585706]  ? __remove_dirty_segment+0x4a9/0x550
[   59.585706]  kasan_report+0x13a/0x19b
[   59.585706]  ? __remove_dirty_segment+0x4a9/0x550
[   59.585706]  ? __remove_dirty_segment+0x4a9/0x550
[   59.585706]  __remove_dirty_segment+0x4a9/0x550
[   59.585706]  locate_dirty_segment+0x38d/0x450
[   59.585706]  f2fs_invalidate_blocks+0xf8/0x3b0
[   59.585706]  truncate_node+0x162/0xc10
[   59.585706]  ? truncate_dnode+0x1a0/0x1a0
[   59.585706]  ? f2fs_get_node_info+0xc60/0xc60
[   59.585706]  f2fs_remove_inode_page+0x25c/0x920
[   59.585706]  ? f2fs_get_dnode_of_data+0x16f0/0x16f0
[   59.585706]  ? f2fs_mark_inode_dirty_sync.part.30+0xd/0x30
[   59.585706]  ? f2fs_truncate+0x20d/0x350
[   59.585706]  f2fs_evict_inode+0x960/0xcd0
[   59.585706]  evict+0x290/0x570
[   59.585706]  ? dentry_unlink_inode+0x2fb/0x3b0
[   59.585706]  __dentry_kill+0x2bd/0x600
[   59.585706]  dentry_kill+0x8a/0x4e0
[   59.585706]  dput+0x29c/0x3c0
[   59.585706]  f2fs_fill_super+0x458d/0x6440
[   59.585706]  ? f2fs_commit_super+0x640/0x640
[   59.585706]  ? f2fs_commit_super+0x640/0x640
[   59.585706]  ? mount_bdev+0x25d/0x310
[   59.585706]  mount_bdev+0x25d/0x310
[   59.585706]  mount_fs+0xd0/0x320
[   59.585706]  ? emergency_thaw_all+0x180/0x180
[   59.585706]  ? memcpy+0x34/0x50
[   59.585706]  vfs_kern_mount+0x5f/0x310
[   59.585706]  do_mount+0x35b/0x2720
[   59.585706]  ? copy_mount_string+0x20/0x20
[   59.585706]  ? lockref_put_return+0x130/0x130
[   59.585706]  ? __kasan_slab_free+0x147/0x180
[   59.585706]  ? kasan_unpoison_shadow+0x31/0x40
[   59.585706]  ? __kasan_kmalloc+0xd5/0xf0
[   59.585706]  ? strndup_user+0x42/0x90
[   59.585706]  ? __kmalloc_track_caller+0xfd/0x1d0
[   59.585706]  ? _copy_from_user+0x73/0xa0
[   59.585706]  ? memdup_user+0x39/0x60
[   59.585706]  ksys_mount+0x79/0xc0
[   59.585706]  __x64_sys_mount+0xb5/0x150
[   59.585706]  do_syscall_64+0x8c/0x280
[   59.585706]  ? prepare_exit_to_usermode+0xe1/0x140
[   59.585706]  entry_SYSCALL_64_after_hwframe+0x44/0xa9
[   59.585706] RIP: 0033:0x7fbecd98c48a
[   59.585706] Code: 48 8b 0d 11 fa 2a 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 49 89 ca b8 a5 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d de f9 2a 00 f7 d8 64 89 01 48
[   59.585706] RSP: 002b:00007ffe9e5f3598 EFLAGS: 00000202 ORIG_RAX: 00000000000000a5
[   59.585706] RAX: ffffffffffffffda RBX: 000055eb9eec0080 RCX: 00007fbecd98c48a
[   59.585706] RDX: 000055eb9eec2760 RSI: 000055eb9eec1f60 RDI: 000055eb9eec6600
[   59.585706] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000020
[   59.585706] R10: 00000000c0ed0000 R11: 0000000000000202 R12: 000055eb9eec6600
[   59.585706] R13: 000055eb9eec2760 R14: 0000000000000000 R15: 00000000ffffffff
[   59.585706]
[   59.585706] Allocated by task 0:
[   59.585706] (stack is not available)
[   59.585706]
[   59.585706] Freed by task 0:
[   59.585706] (stack is not available)
[   59.585706]
[   59.585706] The buggy address belongs to the object at ffff888068ad6e00
[   59.585706]  which belongs to the cache kmalloc-192 of size 192
[   59.585706] The buggy address is located 0 bytes inside of
[   59.585706]  192-byte region [ffff888068ad6e00, ffff888068ad6ec0)
[   59.585706] The buggy address belongs to the page:
[   59.585706] page:ffffea0001a2b580 count:1 mapcount:0 mapping:ffff88806cc01500 index:0x0
[   59.585706] flags: 0x100000000000200(slab)
[   59.585706] raw: 0100000000000200 dead000000000100 dead000000000200 ffff88806cc01500
[   59.585706] raw: 0000000000000000 0000000080100010 00000001ffffffff 0000000000000000
[   59.585706] page dumped because: kasan: bad access detected
[   59.585706]
[   59.585706] Memory state around the buggy address:
[   59.585706]  ffff888068ad6d00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[   59.585706]  ffff888068ad6d80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[   59.585706] >ffff888068ad6e00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[   59.585706]                    ^
[   59.585706]  ffff888068ad6e80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[   59.585706]  ffff888068ad6f00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[   59.585706] ==================================================================

Details

CVSS 7.8 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H), CWE-787, published 2019-12-17, last modified 2024-11-21.

Attribution

Found as part of the Best of the Best (BoB) 8th bobfuzzer team project — a five-person team that ported the JANUS file-system fuzzer. This is team work, not sole authorship.

References