Message ID | 20180806101237.20507-1-anand.jain@oracle.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v2] btrfs: btrfs_shrink_device should call commit transaction | expand |
On Mon, Aug 06, 2018 at 06:12:37PM +0800, Anand Jain wrote: > test case btrfs/164 reported UAF.. > > [ 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 device resized 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 commit transaction. > The commit transaction goes through the fs_devices::resized_devices > in btrfs_update_commit_device_size() and leads to UAF. > > Fix this by making sure btrfs_shrink_device() calls the last needed > btrfs_commit_transaction() before the return. > > Reported-by: Lu Fengqi <lufq.fnst@cn.fujitsu.com> > Signed-off-by: Anand Jain <anand.jain@oracle.com> > Tested-by: Lu Fengqi <lufq.fnst@cn.fujitsu.com> The transaction commit makes more sense here, also is consistent with what grow does. Reviewed-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index da86706123ff..f4405e430da6 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -4491,7 +4491,12 @@ int btrfs_shrink_device(struct btrfs_device *device, u64 new_size) /* 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) {