Skip to contentSkip to conversation

btrfs: fix use-after-free of block device file in __btrfs_free_extra_devids()

PATCH In review Shubham Pushpkar 3 participants 3 messages Feb 3, 2025

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 btrfs: fix use-after-free of block device file in __btrfs_free_extra_devids()

From: Zhihao Cheng <[email protected]>

commit aec8e6bf839101784f3ef037dcdb9432c3f32343 ("btrfs:
fix use-after-free of block device file in __btrfs_free_extra_devids()")

Mounting btrfs from two images (which have the same one fsid and two different dev_uuids) in certain executing order may trigger an UAF for variable 'device->bdev_file' in __btrfs_free_extra_devids(). And following are the details:

1. Attach image_1 to loop0, attach image_2 to loop1, and scan btrfs devices by ioctl(BTRFS_IOC_SCAN_DEV):

/ btrfs_device_1 → loop0
fs_device
\ btrfs_device_2 → loop1
2. mount /dev/loop0 /mnt
btrfs_open_devices

    btrfs_device_1->bdev_file = btrfs_get_bdev_and_sb(loop0)
    btrfs_device_2->bdev_file = btrfs_get_bdev_and_sb(loop1)
   btrfs_fill_super
    open_ctree
     fail: btrfs_close_devices // -ENOMEM
	    btrfs_close_bdev(btrfs_device_1)
             fput(btrfs_device_1->bdev_file)
	      // btrfs_device_1->bdev_file is freed
	    btrfs_close_bdev(btrfs_device_2)
             fput(btrfs_device_2->bdev_file)

3. mount /dev/loop1 /mnt
btrfs_open_devices

    btrfs_get_bdev_and_sb(&bdev_file)
     // EIO, btrfs_device_1->bdev_file is not assigned,
     // which points to a freed memory area
    btrfs_device_2->bdev_file = btrfs_get_bdev_and_sb(loop1)
   btrfs_fill_super
    open_ctree
     btrfs_free_extra_devids
      if (btrfs_device_1->bdev_file)
       fput(btrfs_device_1->bdev_file) // UAF !

Fix it by setting 'device->bdev_file' as 'NULL' after closing the btrfs_device in btrfs_close_one_device().

Fixes 142388194191 ("btrfs: do not background blkdev_put()")
(cherry picked from commit aec8e6bf839101784f3ef037dcdb9432c3f32343)
---
 fs/btrfs/volumes.c | 1 +
 1 file changed, 1 insertion(+)
fs/btrfs/volumes.c +1 -0
index b9a0b26d08e1..ab2412542ce5 100644
@@ -1176,6 +1176,7 @@ static void btrfs_close_one_device(struct btrfs_device *device)
 	if (device->bdev) {
 		fs_devices->open_devices--;
 		device->bdev = NULL;
+		device->bdev_file = NULL;
 	}
 	clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
 	btrfs_destroy_dev_zone_info(device);
Signature · 1 line
2.35.6
#1 lore raw

Hi Shubham,

Quoted 61 lines · Shubham Pushpkar

From: Zhihao Cheng <[email protected]>

commit aec8e6bf839101784f3ef037dcdb9432c3f32343 ("btrfs:
fix use-after-free of block device file in __btrfs_free_extra_devids()")

Mounting btrfs from two images (which have the same one fsid and two different dev_uuids) in certain executing order may trigger an UAF for variable 'device->bdev_file' in __btrfs_free_extra_devids(). And following are the details:

1. Attach image_1 to loop0, attach image_2 to loop1, and scan btrfs devices by ioctl(BTRFS_IOC_SCAN_DEV):

/ btrfs_device_1 → loop0
fs_device
\ btrfs_device_2 → loop1
2. mount /dev/loop0 /mnt

    btrfs_open_devices
     btrfs_device_1->bdev_file = btrfs_get_bdev_and_sb(loop0)
     btrfs_device_2->bdev_file = btrfs_get_bdev_and_sb(loop1)
    btrfs_fill_super
     open_ctree
      fail: btrfs_close_devices // -ENOMEM
	    btrfs_close_bdev(btrfs_device_1)
              fput(btrfs_device_1->bdev_file)
	      // btrfs_device_1->bdev_file is freed
	    btrfs_close_bdev(btrfs_device_2)
              fput(btrfs_device_2->bdev_file)

3. mount /dev/loop1 /mnt

    btrfs_open_devices
     btrfs_get_bdev_and_sb(&bdev_file)
      // EIO, btrfs_device_1->bdev_file is not assigned,
      // which points to a freed memory area
     btrfs_device_2->bdev_file = btrfs_get_bdev_and_sb(loop1)
    btrfs_fill_super
     open_ctree
      btrfs_free_extra_devids
       if (btrfs_device_1->bdev_file)
        fput(btrfs_device_1->bdev_file) // UAF !

Fix it by setting 'device->bdev_file' as 'NULL' after closing the btrfs_device in btrfs_close_one_device().

Fixes 142388194191 ("btrfs: do not background blkdev_put()")
(cherry picked from commit aec8e6bf839101784f3ef037dcdb9432c3f32343)
---
  fs/btrfs/volumes.c | 1 +
  1 file changed, 1 insertion(+)
fs/btrfs/volumes.c +0 -0
index b9a0b26d08e1..ab2412542ce5 100644

fs/btrfs/volumes.c static void btrfs_close_one_device(struct btrfs_device *device) · line 1179

Quoted 4 lines
fs/btrfs/volumes.c +1 -0
@@ -1176,6 +1176,7 @@ static void btrfs_close_one_device(struct btrfs_device *device)
  	if (device->bdev) {
  		fs_devices->open_devices--;
  		device->bdev = NULL;
+		device->bdev_file = NULL;

Looks bad.

"bdev_file" is not a member of struct btrfs_device in 6.6.y. It is added only in commit: 9ae061cf2a46 ("btrfs: port device access to file") which is not in 6.6.y

Hint: This has CC:stable and Fixes tag and a clean cherry-pick so there should be a reason why this didn't get into stable easily.(reason in this case in build failure)

Thanks,
Harshit

Quoted 3 lines
fs/btrfs/volumes.c +0 -0
  	}
  	clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
  	btrfs_destroy_dev_zone_info(device);
maintainer Reviewed-by David Sterba on v1 1/1
#2 lore raw
In reply to Shubham Pushpkar

[ Sasha's backport helper bot ]

Hi,

Found matching upstream commit: aec8e6bf839101784f3ef037dcdb9432c3f32343

WARNING: Author mismatch between patch and found commit:
Backport author: Shubham Pushpkar<[email protected]> Commit author: Zhihao Cheng<[email protected]>

Status in newer kernel trees:
6.13.y | Present (exact SHA1)
6.12.y | Present (exact SHA1)
6.6.y | Not found

Note: The patch differs from the upstream commit:
---
1: aec8e6bf83910 ! 1: afbc8c0c36536 btrfs: fix use-after-free of block device file in __btrfs_free_extra_devids()
@@ Metadata
## Commit message ##
btrfs: fix use-after-free of block device file in __btrfs_free_extra_devids()

+ commit aec8e6bf839101784f3ef037dcdb9432c3f32343 ("btrfs:
+ fix use-after-free of block device file in __btrfs_free_extra_devids()")
+
Mounting btrfs from two images (which have the same one fsid and two different dev_uuids) in certain executing order may trigger an UAF for variable 'device->bdev_file' in __btrfs_free_extra_devids(). And
@@ Commit message

+ (cherry picked from commit aec8e6bf839101784f3ef037dcdb9432c3f32343)
+ Signed-off-by: Shubham Pushpkar <[email protected]>

## fs/btrfs/volumes.c ##
@@ fs/btrfs/volumes.c: static void btrfs_close_one_device(struct btrfs_device *device)
---

Results of testing on various branches:

| Branch                    | Patch Apply | Build Test |
|---------------------------|-------------|------------|
| stable/linux-6.6.y        |  Success    |  Failed    |

Build Errors:
Build error for stable/linux-6.6.y:
lib/test_dhry.o: warning: objtool: dhry() falls through to next function dhry_run_set.cold() fs/btrfs/volumes.c: In function 'btrfs_close_one_device':
fs/btrfs/volumes.c:1179:23: error: 'struct btrfs_device' has no member named 'bdev_file'
1179 | device->bdev_file = NULL;
| ^~
make[4]: *** [scripts/Makefile.build:243: fs/btrfs/volumes.o] Error 1 make[4]: Target 'fs/btrfs/' not remade because of errors.
make[3]: *** [scripts/Makefile.build:480: fs/btrfs] Error 2
make[3]: Target 'fs/' not remade because of errors.
make[2]: *** [scripts/Makefile.build:480: fs] Error 2
make[2]: Target './' not remade because of errors.
make[1]: *** [/home/sasha/build/linus-next/Makefile:1921: .] Error 2 make[1]: Target '__all' not remade because of errors.
make: *** [Makefile:234: __sub-make] Error 2
make: Target '__all' not remade because of errors.