Skip to content

Commit 5715c4d

Browse files
paravmellanoxawilliam
authored andcommitted
vfio/mdev: Synchronize device create/remove with parent removal
In following sequences, child devices created while removing mdev parent device can be left out, or it may lead to race of removing half initialized child mdev devices. issue-1: -------- cpu-0 cpu-1 ----- ----- mdev_unregister_device() device_for_each_child() mdev_device_remove_cb() mdev_device_remove() create_store() mdev_device_create() [...] device_add() parent_remove_sysfs_files() /* BUG: device added by cpu-0 * whose parent is getting removed * and it won't process this mdev. */ issue-2: -------- Below crash is observed when user initiated remove is in progress and mdev_unregister_driver() completes parent unregistration. cpu-0 cpu-1 ----- ----- remove_store() mdev_device_remove() active = false; mdev_unregister_device() parent device removed. [...] parents->ops->remove() /* * BUG: Accessing invalid parent. */ This is similar race like create() racing with mdev_unregister_device(). BUG: unable to handle kernel paging request at ffffffffc0585668 PGD e8f618067 P4D e8f618067 PUD e8f61a067 PMD 85adca067 PTE 0 Oops: 0000 [#1] SMP PTI CPU: 41 PID: 37403 Comm: bash Kdump: loaded Not tainted 5.1.0-rc6-vdevbus+ #6 Hardware name: Supermicro SYS-6028U-TR4+/X10DRU-i+, BIOS 2.0b 08/09/2016 RIP: 0010:mdev_device_remove+0xfa/0x140 [mdev] Call Trace: remove_store+0x71/0x90 [mdev] kernfs_fop_write+0x113/0x1a0 vfs_write+0xad/0x1b0 ksys_write+0x5a/0xe0 do_syscall_64+0x5a/0x210 entry_SYSCALL_64_after_hwframe+0x49/0xbe Therefore, mdev core is improved as below to overcome above issues. Wait for any ongoing mdev create() and remove() to finish before unregistering parent device. This continues to allow multiple create and remove to progress in parallel for different mdev devices as most common case. At the same time guard parent removal while parent is being accessed by create() and remove() callbacks. create()/remove() and unregister_device() are synchronized by the rwsem. Refactor device removal code to mdev_device_remove_common() to avoid acquiring unreg_sem of the parent. Fixes: 7b96953 ("vfio: Mediated device Core driver") Signed-off-by: Parav Pandit <[email protected]> Reviewed-by: Cornelia Huck <[email protected]> Signed-off-by: Alex Williamson <[email protected]>
1 parent 26c9e39 commit 5715c4d

File tree

2 files changed

+56
-18
lines changed

2 files changed

+56
-18
lines changed

drivers/vfio/mdev/mdev_core.c

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,35 @@ static void mdev_put_parent(struct mdev_parent *parent)
102102
kref_put(&parent->ref, mdev_release_parent);
103103
}
104104

105+
/* Caller must hold parent unreg_sem read or write lock */
106+
static void mdev_device_remove_common(struct mdev_device *mdev)
107+
{
108+
struct mdev_parent *parent;
109+
struct mdev_type *type;
110+
int ret;
111+
112+
type = to_mdev_type(mdev->type_kobj);
113+
mdev_remove_sysfs_files(&mdev->dev, type);
114+
device_del(&mdev->dev);
115+
parent = mdev->parent;
116+
lockdep_assert_held(&parent->unreg_sem);
117+
ret = parent->ops->remove(mdev);
118+
if (ret)
119+
dev_err(&mdev->dev, "Remove failed: err=%d\n", ret);
120+
121+
/* Balances with device_initialize() */
122+
put_device(&mdev->dev);
123+
mdev_put_parent(parent);
124+
}
125+
105126
static int mdev_device_remove_cb(struct device *dev, void *data)
106127
{
107-
if (dev_is_mdev(dev))
108-
mdev_device_remove(dev);
128+
if (dev_is_mdev(dev)) {
129+
struct mdev_device *mdev;
109130

131+
mdev = to_mdev_device(dev);
132+
mdev_device_remove_common(mdev);
133+
}
110134
return 0;
111135
}
112136

@@ -148,6 +172,7 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops)
148172
}
149173

150174
kref_init(&parent->ref);
175+
init_rwsem(&parent->unreg_sem);
151176

152177
parent->dev = dev;
153178
parent->ops = ops;
@@ -206,21 +231,23 @@ void mdev_unregister_device(struct device *dev)
206231
dev_info(dev, "MDEV: Unregistering\n");
207232

208233
list_del(&parent->next);
234+
mutex_unlock(&parent_list_lock);
235+
236+
down_write(&parent->unreg_sem);
237+
209238
class_compat_remove_link(mdev_bus_compat_class, dev, NULL);
210239

211240
device_for_each_child(dev, NULL, mdev_device_remove_cb);
212241

213242
parent_remove_sysfs_files(parent);
243+
up_write(&parent->unreg_sem);
214244

215-
mutex_unlock(&parent_list_lock);
216245
mdev_put_parent(parent);
217246
}
218247
EXPORT_SYMBOL(mdev_unregister_device);
219248

220-
static void mdev_device_release(struct device *dev)
249+
static void mdev_device_free(struct mdev_device *mdev)
221250
{
222-
struct mdev_device *mdev = to_mdev_device(dev);
223-
224251
mutex_lock(&mdev_list_lock);
225252
list_del(&mdev->next);
226253
mutex_unlock(&mdev_list_lock);
@@ -229,6 +256,13 @@ static void mdev_device_release(struct device *dev)
229256
kfree(mdev);
230257
}
231258

259+
static void mdev_device_release(struct device *dev)
260+
{
261+
struct mdev_device *mdev = to_mdev_device(dev);
262+
263+
mdev_device_free(mdev);
264+
}
265+
232266
int mdev_device_create(struct kobject *kobj,
233267
struct device *dev, const guid_t *uuid)
234268
{
@@ -265,6 +299,13 @@ int mdev_device_create(struct kobject *kobj,
265299

266300
mdev->parent = parent;
267301

302+
/* Check if parent unregistration has started */
303+
if (!down_read_trylock(&parent->unreg_sem)) {
304+
mdev_device_free(mdev);
305+
ret = -ENODEV;
306+
goto mdev_fail;
307+
}
308+
268309
device_initialize(&mdev->dev);
269310
mdev->dev.parent = dev;
270311
mdev->dev.bus = &mdev_bus_type;
@@ -287,6 +328,7 @@ int mdev_device_create(struct kobject *kobj,
287328

288329
mdev->active = true;
289330
dev_dbg(&mdev->dev, "MDEV: created\n");
331+
up_read(&parent->unreg_sem);
290332

291333
return 0;
292334

@@ -295,6 +337,7 @@ int mdev_device_create(struct kobject *kobj,
295337
add_fail:
296338
parent->ops->remove(mdev);
297339
ops_create_fail:
340+
up_read(&parent->unreg_sem);
298341
put_device(&mdev->dev);
299342
mdev_fail:
300343
mdev_put_parent(parent);
@@ -305,8 +348,6 @@ int mdev_device_remove(struct device *dev)
305348
{
306349
struct mdev_device *mdev, *tmp;
307350
struct mdev_parent *parent;
308-
struct mdev_type *type;
309-
int ret;
310351

311352
mdev = to_mdev_device(dev);
312353

@@ -329,18 +370,13 @@ int mdev_device_remove(struct device *dev)
329370
mdev->active = false;
330371
mutex_unlock(&mdev_list_lock);
331372

332-
type = to_mdev_type(mdev->type_kobj);
333-
mdev_remove_sysfs_files(dev, type);
334-
device_del(&mdev->dev);
335373
parent = mdev->parent;
336-
ret = parent->ops->remove(mdev);
337-
if (ret)
338-
dev_err(&mdev->dev, "Remove failed: err=%d\n", ret);
339-
340-
/* Balances with device_initialize() */
341-
put_device(&mdev->dev);
342-
mdev_put_parent(parent);
374+
/* Check if parent unregistration has started */
375+
if (!down_read_trylock(&parent->unreg_sem))
376+
return -ENODEV;
343377

378+
mdev_device_remove_common(mdev);
379+
up_read(&parent->unreg_sem);
344380
return 0;
345381
}
346382

drivers/vfio/mdev/mdev_private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ struct mdev_parent {
2323
struct list_head next;
2424
struct kset *mdev_types_kset;
2525
struct list_head type_list;
26+
/* Synchronize device creation/removal with parent unregistration */
27+
struct rw_semaphore unreg_sem;
2628
};
2729

2830
struct mdev_device {

0 commit comments

Comments
 (0)