scst_vdisk: Protect the backend during sysfs sync

Writing to the sync attribute after the last target device has detached,
or while the virtual device is inactive, dereferences a NULL file pointer.
The blockio branch likewise passes a missing block device to the flush
helper. The sync attribute was introduced in 2015 without checking whether
the backend was open or serializing against its release.

Run the operation through the SCST sysfs work mechanism and keep a device
reference until it finishes. Suspend command and task management activity
to exclude backend reopen from MODE SELECT and resets. Hold scst_mutex and
the ALUA lock across the open check and flush to exclude detach, explicit
activation and ALUA transitions. This also closes the race that a NULL
check alone would leave behind.

Activity remains globally suspended until the flush completes, so a slow
backend can delay commands to other devices as well.

Return -ENOMEDIUM for a closed backend instead of opening an inactive
device. Preserve the nullio no-op and the existing fileio/blockio flush
selection. The sysfs work mechanism avoids taking scst_mutex directly in
a sysfs callback and provides the usual -EAGAIN completion protocol.

A device kobject reference does not keep the handler attached after the
sysfs store returns. Check dh_priv under scst_mutex as well and return
-ENODEV if handler teardown won the race with the queued work.

Keep backend dispatch in a private helper so KUnit can exercise it with
task-scoped stubs on an unregistered device. Enable the existing closed-
backend cases without queueing a synthetic device to the sysfs thread.

Fixes: 530af782ed ("scst_vdisk: Add 'sync' sysfs attribute")
Link: https://github.com/SCST-project/scst/issues/394
This commit is contained in:
Gleb Chesnokov
2026-10-14 17:39:14 +03:00
parent a246e3fd2a
commit 6a4f85e6eb
3 changed files with 64 additions and 23 deletions
@@ -205,12 +205,6 @@ static void vdisk_backend_test(struct kunit *test)
struct vdisk_test_fixture *f = test->priv;
int ret;
/* Avoid dereferencing a closed backend until its lifetime is fixed. */
if (p->closed && !p->nullio) {
kunit_skip(test, "closed-backend sync needs a separate lifetime fix");
return;
}
f->vdev.dev_active = !p->inactive;
f->vdev.nullio = p->nullio;
f->vdev.blockio = p->blockio;
@@ -221,9 +215,9 @@ static void vdisk_backend_test(struct kunit *test)
f->block_flush_result = p->error;
f->sysfs_sync = true;
/* Exercise the existing store callback with an unregistered private device. */
ret = vdisk_sysfs_sync_store(&f->dev.dev_kobj, NULL, "1", 1);
KUNIT_EXPECT_EQ(test, ret, p->expected ? p->expected : 1);
/* Keep task-scoped backend stubs in the test thread on this private device. */
ret = vdisk_flush_backend(&f->dev);
KUNIT_EXPECT_EQ(test, ret, p->expected);
/* Empty files may still need metadata synchronization. */
if (!p->empty)
KUNIT_EXPECT_EQ(test, f->flush_count, p->files);
+60 -12
View File
@@ -7492,24 +7492,72 @@ out_e_unlock:
goto out_resume;
}
/*
* For a live device, the caller must suspend activity and hold scst_mutex
* and the ALUA lock to prevent the backend from being closed or reopened.
*/
SCST_KUNIT_VISIBLE
ssize_t vdisk_sysfs_sync_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
int vdisk_flush_backend(struct scst_device *dev)
{
struct scst_device *dev =
container_of(kobj, struct scst_device, dev_kobj);
struct scst_vdisk_dev *virt_dev = dev->dh_priv;
/* Queued work can outlive removal of the handler's sysfs files. */
if (!virt_dev)
return -ENODEV;
if (virt_dev->nullio)
return 0;
if (!vdisk_is_open(virt_dev))
return -ENOMEDIUM;
if (virt_dev->blockio)
return vdisk_blockio_flush(virt_dev->bdev_desc.bdev, GFP_KERNEL, false,
NULL, false);
return __vdisk_fsync_fileio(0, i_size_read(file_inode(virt_dev->fd)),
dev, NULL, virt_dev->fd);
}
static int vdisk_sysfs_process_sync_store(struct scst_sysfs_work_item *work)
{
struct scst_device *dev = work->dev;
int res;
if (virt_dev->nullio)
res = 0;
else if (virt_dev->blockio)
res = vdisk_blockio_flush(virt_dev->bdev_desc.bdev, GFP_KERNEL, false, NULL,
false);
else
res = __vdisk_fsync_fileio(0, i_size_read(file_inode(virt_dev->fd)),
dev, NULL, virt_dev->fd);
/* MODE SELECT and task management can also reopen the backing file. */
res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER);
if (res)
goto out;
res = mutex_lock_interruptible(&scst_mutex);
if (res)
goto resume;
/* Serialize with target detach, activation and ALUA state changes. */
scst_alua_lock();
res = vdisk_flush_backend(dev);
scst_alua_unlock();
mutex_unlock(&scst_mutex);
resume:
scst_resume_activity();
out:
kobject_put(&dev->dev_kobj);
return res;
}
static ssize_t vdisk_sysfs_sync_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct scst_device *dev = container_of(kobj, struct scst_device, dev_kobj);
struct scst_sysfs_work_item *work;
int res;
res = scst_alloc_sysfs_work(vdisk_sysfs_process_sync_store, false, &work);
if (res)
return res;
work->dev = dev;
SCST_SET_DEP_MAP(work, &scst_dev_dep_map);
kobject_get(&dev->dev_kobj);
res = scst_sysfs_queue_wait_work(work);
return res ? : count;
}
+1 -2
View File
@@ -19,8 +19,7 @@ void vdisk_on_free_cmd_params(const struct vdisk_cmd_params *p);
enum compl_status_e vdisk_exec_sai_16(struct vdisk_cmd_params *p);
enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p);
enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p);
ssize_t vdisk_sysfs_sync_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
int vdisk_flush_backend(struct scst_device *dev);
#endif /* SCST_KUNIT_ENABLED */
#endif /* SCST_VDISK_TEST_H */