corrupted mutex and KASAN global-out-of-bounds in hfsplus_ext_write_extent() (7.2-rc7, writeback vs umount/BLKPG race)

From: 杨贵琨

Date: Mon Aug 24 2026 - 02:20:23 EST


Hello,

While fuzzing mainline (7.2.0-rc7-00232-gdcb68831eac7) with syzkaller I

hit what looks like in-place memory corruption inside struct

hfsplus_inode_info during filesystem writeback.  I did not find a

matching report on syzbot for this signature.


The crash

---------


A writeback worker for the loop device backing the hfsplus mount

("flush-7:4") first trips the mutex debug check, and immediately after

KASAN flags a wild read through the corrupted owner field:


DEBUG_LOCKS_WARN_ON(lock->magic != lock):

WARNING: kernel/locking/mutex.c:625 at __mutex_lock

CPU: 1 PID: 71193 Comm: kworker/u8:2

Workqueue: writeback wb_workfn (flush-7:4)

Call Trace:

__mutex_lock+0xd30/0x1d80

hfsplus_ext_write_extent+0x83/0x200 fs/hfsplus/extents.c:151

hfsplus_write_inode+0x2c/0x670 fs/hfsplus/super.c:175

write_inode fs/fs-writeback.c:1607 [inline]

__writeback_single_inode+0xc85/0x1320 fs/fs-writeback.c:1850

writeback_sb_inodes+0x71c/0x1b60 fs/fs-writeback.c:2079

wb_writeback+0x404/0xb80 fs/fs-writeback.c:2264

wb_workfn+0x143/0xc60


BUG: KASAN: global-out-of-bounds in owner_on_cpu

include/linux/sched.h:2323 [inline]

Read of size 1 at addr ffffffff8bafcbb4 by task kworker/u8:2

The buggy address belongs to the variable:

(some unrelated global) sugov_group+0x4b4/0xe00

Call Trace:

mutex_can_spin_on_owner+0x1a5/0x1c0 kernel/locking/mutex.c:442

mutex_optimistic_spin kernel/locking/mutex.c:485 [inline]

__mutex_lock+0x379/0x1d80

hfsplus_ext_write_extent+0x83/0x200 fs/hfsplus/extents.c:151


i.e. mutex_lock(&HFSPLUS_I(inode)->extents_lock) found the mutex magic

destroyed and its owner field containing a pointer into kernel global

data.


What I verified

---------------


* The lock is hip->extents_lock in fs/hfsplus/extents.c:151.


* The hfsplus_inode_info memory is NOT freed: kernel/locking/mutex.c is

  KASAN-instrumented (the owner read above was flagged by it), yet the

  lock->magic read produced no slab-use-after-free report.  So this is

  in-place corruption of live memory, not a UAF on the inode.


* An overflow of the extent arrays does not explain it: first_extents

  and cached_extents sit right before extent_state/extents_lock in

  struct hfsplus_inode_info, but every writer uses fixed sizes, and

  __hfsplus_ext_read_extent() rejects records with

  fd->entrylength != sizeof(hfsplus_extent_rec) (-EIO) before doing a

  fixed-size hfs_bnode_read().


* All hfsplus_iget() paths call mutex_init() before the inode becomes

  visible, so the lock was initialized at some point.


  The garbage in the owner field looks like stale slab contents (e.g.

  pointers to global ops tables), which would suggest the memory was

  reused without re-initialization somewhere - but I could not identify

  the path from code reading alone.


Trigger context

---------------


The syzkaller program that produced this (single execution, sandbox

none) mounts a crafted 512 KiB HFSX image on a loop device and then

dirties it:


syz_mount_image$hfsplus(..., './file0', ..., crafted image)

unlinkat(AT_FDCWD, './file1', 0)

setxattr$trusted_overlay('./file0', ..., 0, 0, 1)


with an ioctl$BLKPG (online partition edit) on a loop device in the

same program, on a loop device whose bdi had just served a different

filesystem (btrfs).  The crash fired ~0.7 s after the image attach,

in the middle of the mount/umount churn, on the writeback worker.


I could not re-trigger it with a plain mount/dirty/umount stress loop

(~36k cycles), so the BLKPG partition edit and/or the bdi reuse seem

to be required ingredients.


The full syzkaller program, the crafted image, the console log and

the complete KASAN report are available on request.


Kernel: stock mainline 7.2.0-rc7-00232-gdcb68831eac7, syzkaller's

upstream KASAN config (KASAN inline, lockdep enabled).


Found-by: syzkaller on linux-7.2.0-rc7-00232-gdcb68831eac7

Attachment: 2_hfsplus.zip
Description: Zip compressed data