.. | .. |
---|
29 | 29 | struct device devcd_dev; |
---|
30 | 30 | void *data; |
---|
31 | 31 | size_t datalen; |
---|
| 32 | + /* |
---|
| 33 | + * Here, mutex is required to serialize the calls to del_wk work between |
---|
| 34 | + * user/kernel space which happens when devcd is added with device_add() |
---|
| 35 | + * and that sends uevent to user space. User space reads the uevents, |
---|
| 36 | + * and calls to devcd_data_write() which try to modify the work which is |
---|
| 37 | + * not even initialized/queued from devcoredump. |
---|
| 38 | + * |
---|
| 39 | + * |
---|
| 40 | + * |
---|
| 41 | + * cpu0(X) cpu1(Y) |
---|
| 42 | + * |
---|
| 43 | + * dev_coredump() uevent sent to user space |
---|
| 44 | + * device_add() ======================> user space process Y reads the |
---|
| 45 | + * uevents writes to devcd fd |
---|
| 46 | + * which results into writes to |
---|
| 47 | + * |
---|
| 48 | + * devcd_data_write() |
---|
| 49 | + * mod_delayed_work() |
---|
| 50 | + * try_to_grab_pending() |
---|
| 51 | + * del_timer() |
---|
| 52 | + * debug_assert_init() |
---|
| 53 | + * INIT_DELAYED_WORK() |
---|
| 54 | + * schedule_delayed_work() |
---|
| 55 | + * |
---|
| 56 | + * |
---|
| 57 | + * Also, mutex alone would not be enough to avoid scheduling of |
---|
| 58 | + * del_wk work after it get flush from a call to devcd_free() |
---|
| 59 | + * mentioned as below. |
---|
| 60 | + * |
---|
| 61 | + * disabled_store() |
---|
| 62 | + * devcd_free() |
---|
| 63 | + * mutex_lock() devcd_data_write() |
---|
| 64 | + * flush_delayed_work() |
---|
| 65 | + * mutex_unlock() |
---|
| 66 | + * mutex_lock() |
---|
| 67 | + * mod_delayed_work() |
---|
| 68 | + * mutex_unlock() |
---|
| 69 | + * So, delete_work flag is required. |
---|
| 70 | + */ |
---|
| 71 | + struct mutex mutex; |
---|
| 72 | + bool delete_work; |
---|
32 | 73 | struct module *owner; |
---|
33 | 74 | ssize_t (*read)(char *buffer, loff_t offset, size_t count, |
---|
34 | 75 | void *data, size_t datalen); |
---|
.. | .. |
---|
88 | 129 | struct device *dev = kobj_to_dev(kobj); |
---|
89 | 130 | struct devcd_entry *devcd = dev_to_devcd(dev); |
---|
90 | 131 | |
---|
91 | | - mod_delayed_work(system_wq, &devcd->del_wk, 0); |
---|
| 132 | + mutex_lock(&devcd->mutex); |
---|
| 133 | + if (!devcd->delete_work) { |
---|
| 134 | + devcd->delete_work = true; |
---|
| 135 | + mod_delayed_work(system_wq, &devcd->del_wk, 0); |
---|
| 136 | + } |
---|
| 137 | + mutex_unlock(&devcd->mutex); |
---|
92 | 138 | |
---|
93 | 139 | return count; |
---|
94 | 140 | } |
---|
.. | .. |
---|
116 | 162 | { |
---|
117 | 163 | struct devcd_entry *devcd = dev_to_devcd(dev); |
---|
118 | 164 | |
---|
| 165 | + mutex_lock(&devcd->mutex); |
---|
| 166 | + if (!devcd->delete_work) |
---|
| 167 | + devcd->delete_work = true; |
---|
| 168 | + |
---|
119 | 169 | flush_delayed_work(&devcd->del_wk); |
---|
| 170 | + mutex_unlock(&devcd->mutex); |
---|
120 | 171 | return 0; |
---|
121 | 172 | } |
---|
122 | 173 | |
---|
123 | 174 | static ssize_t disabled_show(struct class *class, struct class_attribute *attr, |
---|
124 | 175 | char *buf) |
---|
125 | 176 | { |
---|
126 | | - return sprintf(buf, "%d\n", devcd_disabled); |
---|
| 177 | + return sysfs_emit(buf, "%d\n", devcd_disabled); |
---|
127 | 178 | } |
---|
| 179 | + |
---|
| 180 | +/* |
---|
| 181 | + * |
---|
| 182 | + * disabled_store() worker() |
---|
| 183 | + * class_for_each_device(&devcd_class, |
---|
| 184 | + * NULL, NULL, devcd_free) |
---|
| 185 | + * ... |
---|
| 186 | + * ... |
---|
| 187 | + * while ((dev = class_dev_iter_next(&iter)) |
---|
| 188 | + * devcd_del() |
---|
| 189 | + * device_del() |
---|
| 190 | + * put_device() <- last reference |
---|
| 191 | + * error = fn(dev, data) devcd_dev_release() |
---|
| 192 | + * devcd_free(dev, data) kfree(devcd) |
---|
| 193 | + * mutex_lock(&devcd->mutex); |
---|
| 194 | + * |
---|
| 195 | + * |
---|
| 196 | + * In the above diagram, It looks like disabled_store() would be racing with parallely |
---|
| 197 | + * running devcd_del() and result in memory abort while acquiring devcd->mutex which |
---|
| 198 | + * is called after kfree of devcd memory after dropping its last reference with |
---|
| 199 | + * put_device(). However, this will not happens as fn(dev, data) runs |
---|
| 200 | + * with its own reference to device via klist_node so it is not its last reference. |
---|
| 201 | + * so, above situation would not occur. |
---|
| 202 | + */ |
---|
128 | 203 | |
---|
129 | 204 | static ssize_t disabled_store(struct class *class, struct class_attribute *attr, |
---|
130 | 205 | const char *buf, size_t count) |
---|
.. | .. |
---|
164 | 239 | static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count, |
---|
165 | 240 | void *data, size_t datalen) |
---|
166 | 241 | { |
---|
167 | | - if (offset > datalen) |
---|
168 | | - return -EINVAL; |
---|
169 | | - |
---|
170 | | - if (offset + count > datalen) |
---|
171 | | - count = datalen - offset; |
---|
172 | | - |
---|
173 | | - if (count) |
---|
174 | | - memcpy(buffer, ((u8 *)data) + offset, count); |
---|
175 | | - |
---|
176 | | - return count; |
---|
| 242 | + return memory_read_from_buffer(buffer, count, &offset, data, datalen); |
---|
177 | 243 | } |
---|
178 | 244 | |
---|
179 | 245 | static void devcd_freev(void *data) |
---|
.. | .. |
---|
291 | 357 | devcd->read = read; |
---|
292 | 358 | devcd->free = free; |
---|
293 | 359 | devcd->failing_dev = get_device(dev); |
---|
| 360 | + devcd->delete_work = false; |
---|
294 | 361 | |
---|
| 362 | + mutex_init(&devcd->mutex); |
---|
295 | 363 | device_initialize(&devcd->devcd_dev); |
---|
296 | 364 | |
---|
297 | 365 | dev_set_name(&devcd->devcd_dev, "devcd%d", |
---|
298 | 366 | atomic_inc_return(&devcd_count)); |
---|
299 | 367 | devcd->devcd_dev.class = &devcd_class; |
---|
300 | 368 | |
---|
| 369 | + mutex_lock(&devcd->mutex); |
---|
301 | 370 | if (device_add(&devcd->devcd_dev)) |
---|
302 | 371 | goto put_device; |
---|
303 | 372 | |
---|
.. | .. |
---|
311 | 380 | |
---|
312 | 381 | INIT_DELAYED_WORK(&devcd->del_wk, devcd_del); |
---|
313 | 382 | schedule_delayed_work(&devcd->del_wk, DEVCD_TIMEOUT); |
---|
314 | | - |
---|
| 383 | + mutex_unlock(&devcd->mutex); |
---|
315 | 384 | return; |
---|
316 | 385 | put_device: |
---|
317 | 386 | put_device(&devcd->devcd_dev); |
---|
| 387 | + mutex_unlock(&devcd->mutex); |
---|
318 | 388 | put_module: |
---|
319 | 389 | module_put(owner); |
---|
320 | 390 | free: |
---|
.. | .. |
---|
323 | 393 | EXPORT_SYMBOL_GPL(dev_coredumpm); |
---|
324 | 394 | |
---|
325 | 395 | /** |
---|
326 | | - * dev_coredumpmsg - create device coredump that uses scatterlist as data |
---|
| 396 | + * dev_coredumpsg - create device coredump that uses scatterlist as data |
---|
327 | 397 | * parameter |
---|
328 | 398 | * @dev: the struct device for the crashed device |
---|
329 | 399 | * @table: the dump data |
---|