In the Linux kernel, the following vulnerability has been resolved:
btrfs: fix a NULL pointer dereference when failed to start a new trasacntion
[BUG] Syzbot reported a NULL pointer dereference with the following crash:
FAULTINJECTION: forcing a failure. starttransaction+0x830/0x1670 fs/btrfs/transaction.c:676 preparetorelocate+0x31f/0x4c0 fs/btrfs/relocation.c:3642 relocateblockgroup+0x169/0xd20 fs/btrfs/relocation.c:3678 ... BTRFS info (device loop0): balance: ended with status: -12 Oops: general protection fault, probably for non-canonical address 0xdffffc00000000cc: 0000 [#1] PREEMPT SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000660-0x0000000000000667] RIP: 0010:btrfsupdaterelocroot+0x362/0xa80 fs/btrfs/relocation.c:926 Call Trace: <TASK> commitfsroots+0x2ee/0x720 fs/btrfs/transaction.c:1496 btrfscommittransaction+0xfaf/0x3740 fs/btrfs/transaction.c:2430 delbalanceitem fs/btrfs/volumes.c:3678 [inline] resetbalancestate+0x25e/0x3c0 fs/btrfs/volumes.c:3742 btrfsbalance+0xead/0x10c0 fs/btrfs/volumes.c:4574 btrfsioctlbalance+0x493/0x7c0 fs/btrfs/ioctl.c:3673 vfsioctl fs/ioctl.c:51 [inline] _dosysioctl fs/ioctl.c:907 [inline] _sesysioctl+0xf9/0x170 fs/ioctl.c:893 dosyscallx64 arch/x86/entry/common.c:52 [inline] dosyscall64+0xf3/0x230 arch/x86/entry/common.c:83 entrySYSCALL64after_hwframe+0x77/0x7f
[CAUSE] The allocation failure happens at the starttransaction() inside preparetorelocate(), and during the error handling we call unsetreloccontrol(), which makes fsinfo->balance_ctl to be NULL.
Then we continue the error path cleanup in btrfsbalance() by calling resetbalancestate() which will call delbalance_item() to fully delete the balance item in the root tree.
However during the small window between setreloccontrl() and unsetreloccontrol(), we can have a subvolume tree update and created a reloc_root for that subvolume.
Then we go into the final btrfscommittransaction() of delbalanceitem(), and into btrfsupdaterelocroot() inside commitfs_roots().
That function checks if fsinfo->relocctl is in the mergereloctree stage, but since fsinfo->relocctl is NULL, it results a NULL pointer dereference.
[FIX] Just add extra check on fsinfo->relocctl inside btrfsupdaterelocroot(), before checking fsinfo->relocctl->mergereloc_tree.
That DEADRELOCTREE handling is to prevent further modification to the reloc tree during merge stage, but since there is no reloc_ctl at all, we do not need to bother that.