Files
Laurence Oberman 139f57343b scsi: mpi3mr: Fix use-after-free on tgt_dev->starget during target device refresh/update
mpi3mr_refresh_tgtdevs() and mpi3mr_devinfochg_evt_bh() read
tgt_dev->starget and immediately pass it to starget_for_each_device()
without holding mrioc->tgtdev_lock. Every writer of this field --
mpi3mr_target_alloc(), mpi3mr_target_destroy(), mpi3mr_slave_destroy()
and mpi3mr_sdev_init() -- correctly serializes access under tgtdev_lock,
but these two read sites do not, which leaves a check-then-use window
against the SCSI core's target teardown path (scsi_remove_target(),
invoked e.g. via a concurrent host reset, sysfs "delete", or SCSI EH
device offlining running independently of the fwevt workqueue).

Sequence observed on production hardware, triggered on the
mpi3mr0_fwevt_wrkr workqueue during a SAS topology change shortly after
a controller reset:

  BUG: kernel NULL pointer dereference, address: 0000000000000058
  RIP: scsi_is_host_device+0x7/0x20
  Call Trace:
   starget_for_each_device+0x34/0x100
   mpi3mr_refresh_tgtdevs+0x152/0x1d0 [mpi3mr]
   mpi3mr_fwevt_bh+0x514/0x6c0 [mpi3mr]
   mpi3mr_fwevt_worker+0x1a/0x50 [mpi3mr]
   process_one_work+0x194/0x380
   worker_thread+0x2fe/0x410

mpi3mr_refresh_tgtdevs() reads tgt_dev->starget as non-NULL, but by the
time starget_for_each_device() dereferences it, a concurrent
mpi3mr_target_destroy() has already cleared tgt_dev->starget under
tgtdev_lock and the SCSI/device core has freed the underlying
scsi_target (and its embedded struct device). The stale pointer is then
walked by dev_to_shost() -> scsi_is_host_device(), producing the
NULL/garbage dereference above.

Fix this by taking mrioc->tgtdev_lock around every read of
tgt_dev->starget, matching the existing writer-side discipline. Since
starget_for_each_device() and mpi3mr_update_sdev() can end up doing
non-atomic work (e.g. queue_limits_commit_update()), the lock cannot be
held across the whole call, so instead pin the target's device with
get_device() while holding the lock, drop the lock, then run
starget_for_each_device() against the pinned reference and put_device()
afterwards. This closes the TOCTOU window instead of merely narrowing
it.

The same unlocked read-and-dereference pattern also exists earlier in
mpi3mr_refresh_tgtdevs()'s first removal-scan loop
(tgt_dev->starget->hostdata); fix it the same way by holding tgtdev_lock
across that check, which is cheap since it only touches plain struct
fields.

Assisted-by: Claude:Sonnet5 [Claude Code]
Signed-off-by: Laurence Oberman <loberman@redhat.com>
Acked-by: Chandrakanth Patil <chandrakanth.patil@broadcom.com>
Link: https://patch.msgid.link/20260831120047.14690-1-loberman@redhat.com
Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
2026-09-01 22:18:54 -04:00
..