Skip to content

Commit e0bd70c

Browse files
committed
Btrfs: fix invalid page accesses in extent_same (dedup) ioctl
In the extent_same ioctl we are getting the pages for the source and target ranges and unlocking them immediately after, which is incorrect because later we attempt to map them (with kmap_atomic) and access their contents at btrfs_cmp_data(). When we do such access the pages might have been relocated or removed from memory, which leads to an invalid memory access. This issue is detected on a kernel with CONFIG_DEBUG_PAGEALLOC=y which produces a trace like the following: 186736.677437] general protection fault: 0000 [#1] PREEMPT SMP DEBUG_PAGEALLOC [186736.680382] Modules linked in: btrfs dm_flakey dm_mod ppdev xor raid6_pq sha256_generic hmac drbg ansi_cprng acpi_cpufreq evdev sg aesni_intel aes_x86_64 parport_pc ablk_helper tpm_tis psmouse parport i2c_piix4 tpm cryptd i2c_core lrw processor button serio_raw pcspkr gf128mul glue_helper loop autofs4 ext4 crc16 mbcache jbd2 sd_mod sr_mod cdrom ata_generic virtio_scsi ata_piix libata virtio_pci virtio_ring crc32c_intel scsi_mod e1000 virtio floppy [last unloaded: btrfs] [186736.681319] CPU: 13 PID: 10222 Comm: duperemove Tainted: G W 4.4.0-rc6-btrfs-next-18+ #1 [186736.681319] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS by qemu-project.org 04/01/2014 [186736.681319] task: ffff880132600400 ti: ffff880362284000 task.ti: ffff880362284000 [186736.681319] RIP: 0010:[<ffffffff81264d00>] [<ffffffff81264d00>] memcmp+0xb/0x22 [186736.681319] RSP: 0018:ffff880362287d70 EFLAGS: 00010287 [186736.681319] RAX: 000002c002468acf RBX: 0000000012345678 RCX: 0000000000000000 [186736.681319] RDX: 0000000000001000 RSI: 0005d129c5cf9000 RDI: 0005d129c5cf9000 [186736.681319] RBP: ffff880362287d70 R08: 0000000000000000 R09: 0000000000001000 [186736.681319] R10: ffff880000000000 R11: 0000000000000476 R12: 0000000000001000 [186736.681319] R13: ffff8802f91d4c88 R14: ffff8801f2a77830 R15: ffff880352e83e40 [186736.681319] FS: 00007f27b37fe700(0000) GS:ffff88043dda0000(0000) knlGS:0000000000000000 [186736.681319] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [186736.681319] CR2: 00007f27a406a000 CR3: 0000000217421000 CR4: 00000000001406e0 [186736.681319] Stack: [186736.681319] ffff880362287ea0 ffffffffa048d0bd 000000000009f000 0000000000001000 [186736.681319] 0100000000000000 ffff8801f2a77850 ffff8802f91d49b0 ffff880132600400 [186736.681319] 00000000000004f8 ffff8801c1efbe41 0000000000000000 0000000000000038 [186736.681319] Call Trace: [186736.681319] [<ffffffffa048d0bd>] btrfs_ioctl+0x24cb/0x2731 [btrfs] [186736.681319] [<ffffffff8108a8b0>] ? arch_local_irq_save+0x9/0xc [186736.681319] [<ffffffff8118b3d4>] ? rcu_read_unlock+0x3e/0x5d [186736.681319] [<ffffffff811822f8>] do_vfs_ioctl+0x42b/0x4ea [186736.681319] [<ffffffff8118b4f3>] ? __fget_light+0x62/0x71 [186736.681319] [<ffffffff8118240e>] SyS_ioctl+0x57/0x79 [186736.681319] [<ffffffff814872d7>] entry_SYSCALL_64_fastpath+0x12/0x6f [186736.681319] Code: 0a 3c 6e 74 0d 3c 79 74 04 3c 59 75 0c c6 06 01 eb 03 c6 06 00 31 c0 eb 05 b8 ea ff ff ff 5d c3 55 31 c9 48 89 e5 48 39 d1 74 13 <0f> b6 04 0f 44 0f b6 04 0e 48 ff c1 44 29 c0 74 ea eb 02 31 c0 (gdb) list *(btrfs_ioctl+0x24cb) 0x5e0e1 is in btrfs_ioctl (fs/btrfs/ioctl.c:2972). 2967 dst_addr = kmap_atomic(dst_page); 2968 2969 flush_dcache_page(src_page); 2970 flush_dcache_page(dst_page); 2971 2972 if (memcmp(addr, dst_addr, cmp_len)) 2973 ret = BTRFS_SAME_DATA_DIFFERS; 2974 2975 kunmap_atomic(addr); 2976 kunmap_atomic(dst_addr); So fix this by making sure we keep the pages locked and respect the same locking order as everywhere else: get and lock the pages first and then lock the range in the inode's io tree (like for example at __btrfs_buffered_write() and extent_readpages()). If an ordered extent is found after locking the range in the io tree, unlock the range, unlock the pages, wait for the ordered extent to complete and repeat the entire locking process until no overlapping ordered extents are found. Cc: [email protected] # 4.2+ Signed-off-by: Filipe Manana <[email protected]>
1 parent e410e34 commit e0bd70c

File tree

1 file changed

+76
-14
lines changed

1 file changed

+76
-14
lines changed

fs/btrfs/ioctl.c

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2811,7 +2811,6 @@ static struct page *extent_same_get_page(struct inode *inode, pgoff_t index)
28112811
return NULL;
28122812
}
28132813
}
2814-
unlock_page(page);
28152814

28162815
return page;
28172816
}
@@ -2830,10 +2829,17 @@ static int gather_extent_pages(struct inode *inode, struct page **pages,
28302829
return 0;
28312830
}
28322831

2833-
static inline void lock_extent_range(struct inode *inode, u64 off, u64 len)
2832+
static int lock_extent_range(struct inode *inode, u64 off, u64 len,
2833+
bool retry_range_locking)
28342834
{
2835-
/* do any pending delalloc/csum calc on src, one way or
2836-
another, and lock file content */
2835+
/*
2836+
* Do any pending delalloc/csum calculations on inode, one way or
2837+
* another, and lock file content.
2838+
* The locking order is:
2839+
*
2840+
* 1) pages
2841+
* 2) range in the inode's io tree
2842+
*/
28372843
while (1) {
28382844
struct btrfs_ordered_extent *ordered;
28392845
lock_extent(&BTRFS_I(inode)->io_tree, off, off + len - 1);
@@ -2851,8 +2857,11 @@ static inline void lock_extent_range(struct inode *inode, u64 off, u64 len)
28512857
unlock_extent(&BTRFS_I(inode)->io_tree, off, off + len - 1);
28522858
if (ordered)
28532859
btrfs_put_ordered_extent(ordered);
2860+
if (!retry_range_locking)
2861+
return -EAGAIN;
28542862
btrfs_wait_ordered_range(inode, off, len);
28552863
}
2864+
return 0;
28562865
}
28572866

28582867
static void btrfs_double_inode_unlock(struct inode *inode1, struct inode *inode2)
@@ -2877,15 +2886,24 @@ static void btrfs_double_extent_unlock(struct inode *inode1, u64 loff1,
28772886
unlock_extent(&BTRFS_I(inode2)->io_tree, loff2, loff2 + len - 1);
28782887
}
28792888

2880-
static void btrfs_double_extent_lock(struct inode *inode1, u64 loff1,
2881-
struct inode *inode2, u64 loff2, u64 len)
2889+
static int btrfs_double_extent_lock(struct inode *inode1, u64 loff1,
2890+
struct inode *inode2, u64 loff2, u64 len,
2891+
bool retry_range_locking)
28822892
{
2893+
int ret;
2894+
28832895
if (inode1 < inode2) {
28842896
swap(inode1, inode2);
28852897
swap(loff1, loff2);
28862898
}
2887-
lock_extent_range(inode1, loff1, len);
2888-
lock_extent_range(inode2, loff2, len);
2899+
ret = lock_extent_range(inode1, loff1, len, retry_range_locking);
2900+
if (ret)
2901+
return ret;
2902+
ret = lock_extent_range(inode2, loff2, len, retry_range_locking);
2903+
if (ret)
2904+
unlock_extent(&BTRFS_I(inode1)->io_tree, loff1,
2905+
loff1 + len - 1);
2906+
return ret;
28892907
}
28902908

28912909
struct cmp_pages {
@@ -2901,11 +2919,15 @@ static void btrfs_cmp_data_free(struct cmp_pages *cmp)
29012919

29022920
for (i = 0; i < cmp->num_pages; i++) {
29032921
pg = cmp->src_pages[i];
2904-
if (pg)
2922+
if (pg) {
2923+
unlock_page(pg);
29052924
page_cache_release(pg);
2925+
}
29062926
pg = cmp->dst_pages[i];
2907-
if (pg)
2927+
if (pg) {
2928+
unlock_page(pg);
29082929
page_cache_release(pg);
2930+
}
29092931
}
29102932
kfree(cmp->src_pages);
29112933
kfree(cmp->dst_pages);
@@ -2966,6 +2988,8 @@ static int btrfs_cmp_data(struct inode *src, u64 loff, struct inode *dst,
29662988

29672989
src_page = cmp->src_pages[i];
29682990
dst_page = cmp->dst_pages[i];
2991+
ASSERT(PageLocked(src_page));
2992+
ASSERT(PageLocked(dst_page));
29692993

29702994
addr = kmap_atomic(src_page);
29712995
dst_addr = kmap_atomic(dst_page);
@@ -3078,14 +3102,46 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen,
30783102
goto out_unlock;
30793103
}
30803104

3105+
again:
30813106
ret = btrfs_cmp_data_prepare(src, loff, dst, dst_loff, olen, &cmp);
30823107
if (ret)
30833108
goto out_unlock;
30843109

30853110
if (same_inode)
3086-
lock_extent_range(src, same_lock_start, same_lock_len);
3111+
ret = lock_extent_range(src, same_lock_start, same_lock_len,
3112+
false);
30873113
else
3088-
btrfs_double_extent_lock(src, loff, dst, dst_loff, len);
3114+
ret = btrfs_double_extent_lock(src, loff, dst, dst_loff, len,
3115+
false);
3116+
/*
3117+
* If one of the inodes has dirty pages in the respective range or
3118+
* ordered extents, we need to flush dellaloc and wait for all ordered
3119+
* extents in the range. We must unlock the pages and the ranges in the
3120+
* io trees to avoid deadlocks when flushing delalloc (requires locking
3121+
* pages) and when waiting for ordered extents to complete (they require
3122+
* range locking).
3123+
*/
3124+
if (ret == -EAGAIN) {
3125+
/*
3126+
* Ranges in the io trees already unlocked. Now unlock all
3127+
* pages before waiting for all IO to complete.
3128+
*/
3129+
btrfs_cmp_data_free(&cmp);
3130+
if (same_inode) {
3131+
btrfs_wait_ordered_range(src, same_lock_start,
3132+
same_lock_len);
3133+
} else {
3134+
btrfs_wait_ordered_range(src, loff, len);
3135+
btrfs_wait_ordered_range(dst, dst_loff, len);
3136+
}
3137+
goto again;
3138+
}
3139+
ASSERT(ret == 0);
3140+
if (WARN_ON(ret)) {
3141+
/* ranges in the io trees already unlocked */
3142+
btrfs_cmp_data_free(&cmp);
3143+
return ret;
3144+
}
30893145

30903146
/* pass original length for comparison so we stay within i_size */
30913147
ret = btrfs_cmp_data(src, loff, dst, dst_loff, olen, &cmp);
@@ -3907,9 +3963,15 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
39073963
u64 lock_start = min_t(u64, off, destoff);
39083964
u64 lock_len = max_t(u64, off, destoff) + len - lock_start;
39093965

3910-
lock_extent_range(src, lock_start, lock_len);
3966+
ret = lock_extent_range(src, lock_start, lock_len, true);
39113967
} else {
3912-
btrfs_double_extent_lock(src, off, inode, destoff, len);
3968+
ret = btrfs_double_extent_lock(src, off, inode, destoff, len,
3969+
true);
3970+
}
3971+
ASSERT(ret == 0);
3972+
if (WARN_ON(ret)) {
3973+
/* ranges in the io trees already unlocked */
3974+
goto out_unlock;
39133975
}
39143976

39153977
ret = btrfs_clone(src, inode, off, olen, len, destoff, 0);

0 commit comments

Comments
 (0)