[PATCH 4.14 08/94] btrfs: btrfs_shrink_device should call commit transaction at the end
From: Greg Kroah-Hartman
Date: Mon Oct 08 2018 - 14:42:12 EST
4.14-stable review patch. If anyone has any objections, please let me know.
------------------
From: Anand Jain <anand.jain@xxxxxxxxxx>
[ Upstream commit 801660b040d132f67fac6a95910ad307c5929b49 ]
Test case btrfs/164 reports use-after-free:
[ 6712.084324] general protection fault: 0000 [#1] PREEMPT SMP
..
[ 6712.195423] btrfs_update_commit_device_size+0x75/0xf0 [btrfs]
[ 6712.201424] btrfs_commit_transaction+0x57d/0xa90 [btrfs]
[ 6712.206999] btrfs_rm_device+0x627/0x850 [btrfs]
[ 6712.211800] btrfs_ioctl+0x2b03/0x3120 [btrfs]
Reason for this is that btrfs_shrink_device adds the resized device to
the fs_devices::resized_devices after it has called the last commit
transaction.
So the list fs_devices::resized_devices is not empty when
btrfs_shrink_device returns. Now the parent function
btrfs_rm_device calls:
btrfs_close_bdev(device);
call_rcu(&device->rcu, free_device_rcu);
and then does the transactio ncommit. It goes through the
fs_devices::resized_devices in btrfs_update_commit_device_size and
leads to use-after-free.
Fix this by making sure btrfs_shrink_device calls the last needed
btrfs_commit_transaction before the return. This is consistent with what
the grow counterpart does and this makes sure the on-disk state is
persistent when the function returns.
Reported-by: Lu Fengqi <lufq.fnst@xxxxxxxxxxxxxx>
Tested-by: Lu Fengqi <lufq.fnst@xxxxxxxxxxxxxx>
Signed-off-by: Anand Jain <anand.jain@xxxxxxxxxx>
Reviewed-by: David Sterba <dsterba@xxxxxxxx>
[ update changelog ]
Signed-off-by: David Sterba <dsterba@xxxxxxxx>
Signed-off-by: Sasha Levin <alexander.levin@xxxxxxxxxxxxx>
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
fs/btrfs/volumes.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -4517,7 +4517,12 @@ again:
/* Now btrfs_update_device() will change the on-disk size. */
ret = btrfs_update_device(trans, device);
- btrfs_end_transaction(trans);
+ if (ret < 0) {
+ btrfs_abort_transaction(trans, ret);
+ btrfs_end_transaction(trans);
+ } else {
+ ret = btrfs_commit_transaction(trans);
+ }
done:
btrfs_free_path(path);
if (ret) {