diff options
author | Kees Cook <keescook@chromium.org> | 2017-04-27 18:53:21 -0400 |
---|---|---|
committer | Kees Cook <keescook@chromium.org> | 2017-04-27 23:35:34 -0400 |
commit | 3a7d2fd16c57a1ef47dc2891171514231c9c7c6e (patch) | |
tree | 89837e53f2b86e76275eeca582bdfe68dacc97e5 | |
parent | 041939c1ec54208b42f5cd819209173d52a29d34 (diff) |
pstore: Solve lockdep warning by moving inode locks
Lockdep complains about a possible deadlock between mount and unlink
(which is technically impossible), but fixing this improves possible
future multiple-backend support, and keeps locking in the right order.
The lockdep warning could be triggered by unlinking a file in the
pstore filesystem:
-> #1 (&sb->s_type->i_mutex_key#14){++++++}:
lock_acquire+0xc9/0x220
down_write+0x3f/0x70
pstore_mkfile+0x1f4/0x460
pstore_get_records+0x17a/0x320
pstore_fill_super+0xa4/0xc0
mount_single+0x89/0xb0
pstore_mount+0x13/0x20
mount_fs+0xf/0x90
vfs_kern_mount+0x66/0x170
do_mount+0x190/0xd50
SyS_mount+0x90/0xd0
entry_SYSCALL_64_fastpath+0x1c/0xb1
-> #0 (&psinfo->read_mutex){+.+.+.}:
__lock_acquire+0x1ac0/0x1bb0
lock_acquire+0xc9/0x220
__mutex_lock+0x6e/0x990
mutex_lock_nested+0x16/0x20
pstore_unlink+0x3f/0xa0
vfs_unlink+0xb5/0x190
do_unlinkat+0x24c/0x2a0
SyS_unlinkat+0x16/0x30
entry_SYSCALL_64_fastpath+0x1c/0xb1
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&sb->s_type->i_mutex_key#14);
lock(&psinfo->read_mutex);
lock(&sb->s_type->i_mutex_key#14);
lock(&psinfo->read_mutex);
Reported-by: Marta Lofstedt <marta.lofstedt@intel.com>
Reported-by: Chris Wilson <chris@chris-wilson.co.uk>
Signed-off-by: Kees Cook <keescook@chromium.org>
Acked-by: Namhyung Kim <namhyung@kernel.org>
-rw-r--r-- | fs/pstore/inode.c | 37 | ||||
-rw-r--r-- | fs/pstore/internal.h | 5 | ||||
-rw-r--r-- | fs/pstore/platform.c | 10 |
3 files changed, 36 insertions, 16 deletions
diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c index 06504b69575b..792a4e5f9226 100644 --- a/fs/pstore/inode.c +++ b/fs/pstore/inode.c | |||
@@ -311,9 +311,8 @@ bool pstore_is_mounted(void) | |||
311 | * Load it up with "size" bytes of data from "buf". | 311 | * Load it up with "size" bytes of data from "buf". |
312 | * Set the mtime & ctime to the date that this record was originally stored. | 312 | * Set the mtime & ctime to the date that this record was originally stored. |
313 | */ | 313 | */ |
314 | int pstore_mkfile(struct pstore_record *record) | 314 | int pstore_mkfile(struct dentry *root, struct pstore_record *record) |
315 | { | 315 | { |
316 | struct dentry *root = pstore_sb->s_root; | ||
317 | struct dentry *dentry; | 316 | struct dentry *dentry; |
318 | struct inode *inode; | 317 | struct inode *inode; |
319 | int rc = 0; | 318 | int rc = 0; |
@@ -322,6 +321,8 @@ int pstore_mkfile(struct pstore_record *record) | |||
322 | unsigned long flags; | 321 | unsigned long flags; |
323 | size_t size = record->size + record->ecc_notice_size; | 322 | size_t size = record->size + record->ecc_notice_size; |
324 | 323 | ||
324 | WARN_ON(!inode_is_locked(d_inode(root))); | ||
325 | |||
325 | spin_lock_irqsave(&allpstore_lock, flags); | 326 | spin_lock_irqsave(&allpstore_lock, flags); |
326 | list_for_each_entry(pos, &allpstore, list) { | 327 | list_for_each_entry(pos, &allpstore, list) { |
327 | if (pos->record->type == record->type && | 328 | if (pos->record->type == record->type && |
@@ -336,7 +337,7 @@ int pstore_mkfile(struct pstore_record *record) | |||
336 | return rc; | 337 | return rc; |
337 | 338 | ||
338 | rc = -ENOMEM; | 339 | rc = -ENOMEM; |
339 | inode = pstore_get_inode(pstore_sb); | 340 | inode = pstore_get_inode(root->d_sb); |
340 | if (!inode) | 341 | if (!inode) |
341 | goto fail; | 342 | goto fail; |
342 | inode->i_mode = S_IFREG | 0444; | 343 | inode->i_mode = S_IFREG | 0444; |
@@ -394,11 +395,9 @@ int pstore_mkfile(struct pstore_record *record) | |||
394 | break; | 395 | break; |
395 | } | 396 | } |
396 | 397 | ||
397 | inode_lock(d_inode(root)); | ||
398 | |||
399 | dentry = d_alloc_name(root, name); | 398 | dentry = d_alloc_name(root, name); |
400 | if (!dentry) | 399 | if (!dentry) |
401 | goto fail_lockedalloc; | 400 | goto fail_private; |
402 | 401 | ||
403 | inode->i_size = private->total_size = size; | 402 | inode->i_size = private->total_size = size; |
404 | 403 | ||
@@ -413,12 +412,9 @@ int pstore_mkfile(struct pstore_record *record) | |||
413 | list_add(&private->list, &allpstore); | 412 | list_add(&private->list, &allpstore); |
414 | spin_unlock_irqrestore(&allpstore_lock, flags); | 413 | spin_unlock_irqrestore(&allpstore_lock, flags); |
415 | 414 | ||
416 | inode_unlock(d_inode(root)); | ||
417 | |||
418 | return 0; | 415 | return 0; |
419 | 416 | ||
420 | fail_lockedalloc: | 417 | fail_private: |
421 | inode_unlock(d_inode(root)); | ||
422 | free_pstore_private(private); | 418 | free_pstore_private(private); |
423 | fail_alloc: | 419 | fail_alloc: |
424 | iput(inode); | 420 | iput(inode); |
@@ -427,6 +423,27 @@ fail: | |||
427 | return rc; | 423 | return rc; |
428 | } | 424 | } |
429 | 425 | ||
426 | /* | ||
427 | * Read all the records from the persistent store. Create | ||
428 | * files in our filesystem. Don't warn about -EEXIST errors | ||
429 | * when we are re-scanning the backing store looking to add new | ||
430 | * error records. | ||
431 | */ | ||
432 | void pstore_get_records(int quiet) | ||
433 | { | ||
434 | struct pstore_info *psi = psinfo; | ||
435 | struct dentry *root; | ||
436 | |||
437 | if (!psi || !pstore_sb) | ||
438 | return; | ||
439 | |||
440 | root = pstore_sb->s_root; | ||
441 | |||
442 | inode_lock(d_inode(root)); | ||
443 | pstore_get_backend_records(psi, root, quiet); | ||
444 | inode_unlock(d_inode(root)); | ||
445 | } | ||
446 | |||
430 | static int pstore_fill_super(struct super_block *sb, void *data, int silent) | 447 | static int pstore_fill_super(struct super_block *sb, void *data, int silent) |
431 | { | 448 | { |
432 | struct inode *inode; | 449 | struct inode *inode; |
diff --git a/fs/pstore/internal.h b/fs/pstore/internal.h index af1df5a36d86..c416e653dc4f 100644 --- a/fs/pstore/internal.h +++ b/fs/pstore/internal.h | |||
@@ -25,7 +25,10 @@ extern struct pstore_info *psinfo; | |||
25 | 25 | ||
26 | extern void pstore_set_kmsg_bytes(int); | 26 | extern void pstore_set_kmsg_bytes(int); |
27 | extern void pstore_get_records(int); | 27 | extern void pstore_get_records(int); |
28 | extern int pstore_mkfile(struct pstore_record *record); | 28 | extern void pstore_get_backend_records(struct pstore_info *psi, |
29 | struct dentry *root, int quiet); | ||
30 | extern int pstore_mkfile(struct dentry *root, | ||
31 | struct pstore_record *record); | ||
29 | extern bool pstore_is_mounted(void); | 32 | extern bool pstore_is_mounted(void); |
30 | 33 | ||
31 | #endif | 34 | #endif |
diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index 43b3ca5e045f..d468eec9b8a6 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c | |||
@@ -810,17 +810,17 @@ static void decompress_record(struct pstore_record *record) | |||
810 | } | 810 | } |
811 | 811 | ||
812 | /* | 812 | /* |
813 | * Read all the records from the persistent store. Create | 813 | * Read all the records from one persistent store backend. Create |
814 | * files in our filesystem. Don't warn about -EEXIST errors | 814 | * files in our filesystem. Don't warn about -EEXIST errors |
815 | * when we are re-scanning the backing store looking to add new | 815 | * when we are re-scanning the backing store looking to add new |
816 | * error records. | 816 | * error records. |
817 | */ | 817 | */ |
818 | void pstore_get_records(int quiet) | 818 | void pstore_get_backend_records(struct pstore_info *psi, |
819 | struct dentry *root, int quiet) | ||
819 | { | 820 | { |
820 | struct pstore_info *psi = psinfo; | ||
821 | int failed = 0; | 821 | int failed = 0; |
822 | 822 | ||
823 | if (!psi) | 823 | if (!psi || !root) |
824 | return; | 824 | return; |
825 | 825 | ||
826 | mutex_lock(&psi->read_mutex); | 826 | mutex_lock(&psi->read_mutex); |
@@ -850,7 +850,7 @@ void pstore_get_records(int quiet) | |||
850 | break; | 850 | break; |
851 | 851 | ||
852 | decompress_record(record); | 852 | decompress_record(record); |
853 | rc = pstore_mkfile(record); | 853 | rc = pstore_mkfile(root, record); |
854 | if (rc) { | 854 | if (rc) { |
855 | /* pstore_mkfile() did not take record, so free it. */ | 855 | /* pstore_mkfile() did not take record, so free it. */ |
856 | kfree(record->buf); | 856 | kfree(record->buf); |