diff options
| author | Nicolai Stange <nicstange@gmail.com> | 2016-03-22 09:11:13 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-04-12 17:14:21 -0400 |
| commit | 9fd4dcece43a53e5a9e65a973df5693702ee6401 (patch) | |
| tree | 70fe5fc79019aec56775a74d71c26094e211abd5 /fs/debugfs | |
| parent | 3a3a5fece6f28c14d3d05c74fb7696412e53a067 (diff) | |
debugfs: prevent access to possibly dead file_operations at file open
Nothing prevents a dentry found by path lookup before a return of
__debugfs_remove() to actually get opened after that return. Now, after
the return of __debugfs_remove(), there are no guarantees whatsoever
regarding the memory the corresponding inode's file_operations object
had been kept in.
Since __debugfs_remove() is seldomly invoked, usually from module exit
handlers only, the race is hard to trigger and the impact is very low.
A discussion of the problem outlined above as well as a suggested
solution can be found in the (sub-)thread rooted at
http://lkml.kernel.org/g/20130401203445.GA20862@ZenIV.linux.org.uk
("Yet another pipe related oops.")
Basically, Greg KH suggests to introduce an intermediate fops and
Al Viro points out that a pointer to the original ones may be stored in
->d_fsdata.
Follow this line of reasoning:
- Add SRCU as a reverse dependency of DEBUG_FS.
- Introduce a srcu_struct object for the debugfs subsystem.
- In debugfs_create_file(), store a pointer to the original
file_operations object in ->d_fsdata.
- Make debugfs_remove() and debugfs_remove_recursive() wait for a
SRCU grace period after the dentry has been delete()'d and before they
return to their callers.
- Introduce an intermediate file_operations object named
"debugfs_open_proxy_file_operations". It's ->open() functions checks,
under the protection of a SRCU read lock, whether the dentry is still
alive, i.e. has not been d_delete()'d and if so, tries to acquire a
reference on the owning module.
On success, it sets the file object's ->f_op to the original
file_operations and forwards the ongoing open() call to the original
->open().
- For clarity, rename the former debugfs_file_operations to
debugfs_noop_file_operations -- they are in no way canonical.
The choice of SRCU over "normal" RCU is justified by the fact, that the
former may also be used to protect ->i_private data from going away
during the execution of a file's readers and writers which may (and do)
sleep.
Finally, introduce the fs/debugfs/internal.h header containing some
declarations internal to the debugfs implementation.
Signed-off-by: Nicolai Stange <nicstange@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/debugfs')
| -rw-r--r-- | fs/debugfs/file.c | 91 | ||||
| -rw-r--r-- | fs/debugfs/inode.c | 13 | ||||
| -rw-r--r-- | fs/debugfs/internal.h | 24 |
3 files changed, 126 insertions, 2 deletions
diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c index d2ba12e23ed9..736ab3c988f2 100644 --- a/fs/debugfs/file.c +++ b/fs/debugfs/file.c | |||
| @@ -22,6 +22,9 @@ | |||
| 22 | #include <linux/slab.h> | 22 | #include <linux/slab.h> |
| 23 | #include <linux/atomic.h> | 23 | #include <linux/atomic.h> |
| 24 | #include <linux/device.h> | 24 | #include <linux/device.h> |
| 25 | #include <linux/srcu.h> | ||
| 26 | |||
| 27 | #include "internal.h" | ||
| 25 | 28 | ||
| 26 | static ssize_t default_read_file(struct file *file, char __user *buf, | 29 | static ssize_t default_read_file(struct file *file, char __user *buf, |
| 27 | size_t count, loff_t *ppos) | 30 | size_t count, loff_t *ppos) |
| @@ -35,13 +38,99 @@ static ssize_t default_write_file(struct file *file, const char __user *buf, | |||
| 35 | return count; | 38 | return count; |
| 36 | } | 39 | } |
| 37 | 40 | ||
| 38 | const struct file_operations debugfs_file_operations = { | 41 | const struct file_operations debugfs_noop_file_operations = { |
| 39 | .read = default_read_file, | 42 | .read = default_read_file, |
| 40 | .write = default_write_file, | 43 | .write = default_write_file, |
| 41 | .open = simple_open, | 44 | .open = simple_open, |
| 42 | .llseek = noop_llseek, | 45 | .llseek = noop_llseek, |
| 43 | }; | 46 | }; |
| 44 | 47 | ||
| 48 | /** | ||
| 49 | * debugfs_use_file_start - mark the beginning of file data access | ||
| 50 | * @dentry: the dentry object whose data is being accessed. | ||
| 51 | * @srcu_idx: a pointer to some memory to store a SRCU index in. | ||
| 52 | * | ||
| 53 | * Up to a matching call to debugfs_use_file_finish(), any | ||
| 54 | * successive call into the file removing functions debugfs_remove() | ||
| 55 | * and debugfs_remove_recursive() will block. Since associated private | ||
| 56 | * file data may only get freed after a successful return of any of | ||
| 57 | * the removal functions, you may safely access it after a successful | ||
| 58 | * call to debugfs_use_file_start() without worrying about | ||
| 59 | * lifetime issues. | ||
| 60 | * | ||
| 61 | * If -%EIO is returned, the file has already been removed and thus, | ||
| 62 | * it is not safe to access any of its data. If, on the other hand, | ||
| 63 | * it is allowed to access the file data, zero is returned. | ||
| 64 | * | ||
| 65 | * Regardless of the return code, any call to | ||
| 66 | * debugfs_use_file_start() must be followed by a matching call | ||
| 67 | * to debugfs_use_file_finish(). | ||
| 68 | */ | ||
| 69 | static int debugfs_use_file_start(const struct dentry *dentry, int *srcu_idx) | ||
| 70 | __acquires(&debugfs_srcu) | ||
| 71 | { | ||
| 72 | *srcu_idx = srcu_read_lock(&debugfs_srcu); | ||
| 73 | barrier(); | ||
| 74 | if (d_unlinked(dentry)) | ||
| 75 | return -EIO; | ||
| 76 | return 0; | ||
| 77 | } | ||
| 78 | |||
| 79 | /** | ||
| 80 | * debugfs_use_file_finish - mark the end of file data access | ||
| 81 | * @srcu_idx: the SRCU index "created" by a former call to | ||
| 82 | * debugfs_use_file_start(). | ||
| 83 | * | ||
| 84 | * Allow any ongoing concurrent call into debugfs_remove() or | ||
| 85 | * debugfs_remove_recursive() blocked by a former call to | ||
| 86 | * debugfs_use_file_start() to proceed and return to its caller. | ||
| 87 | */ | ||
| 88 | static void debugfs_use_file_finish(int srcu_idx) __releases(&debugfs_srcu) | ||
| 89 | { | ||
| 90 | srcu_read_unlock(&debugfs_srcu, srcu_idx); | ||
| 91 | } | ||
| 92 | |||
| 93 | #define F_DENTRY(filp) ((filp)->f_path.dentry) | ||
| 94 | |||
| 95 | #define REAL_FOPS_DEREF(dentry) \ | ||
| 96 | ((const struct file_operations *)(dentry)->d_fsdata) | ||
| 97 | |||
| 98 | static int open_proxy_open(struct inode *inode, struct file *filp) | ||
| 99 | { | ||
| 100 | const struct dentry *dentry = F_DENTRY(filp); | ||
| 101 | const struct file_operations *real_fops = NULL; | ||
| 102 | int srcu_idx, r; | ||
| 103 | |||
| 104 | r = debugfs_use_file_start(dentry, &srcu_idx); | ||
| 105 | if (r) { | ||
| 106 | r = -ENOENT; | ||
| 107 | goto out; | ||
| 108 | } | ||
| 109 | |||
| 110 | real_fops = REAL_FOPS_DEREF(dentry); | ||
| 111 | real_fops = fops_get(real_fops); | ||
| 112 | if (!real_fops) { | ||
| 113 | /* Huh? Module did not clean up after itself at exit? */ | ||
| 114 | WARN(1, "debugfs file owner did not clean up at exit: %pd", | ||
| 115 | dentry); | ||
| 116 | r = -ENXIO; | ||
| 117 | goto out; | ||
| 118 | } | ||
| 119 | replace_fops(filp, real_fops); | ||
| 120 | |||
| 121 | if (real_fops->open) | ||
| 122 | r = real_fops->open(inode, filp); | ||
| 123 | |||
| 124 | out: | ||
| 125 | fops_put(real_fops); | ||
| 126 | debugfs_use_file_finish(srcu_idx); | ||
| 127 | return r; | ||
| 128 | } | ||
| 129 | |||
| 130 | const struct file_operations debugfs_open_proxy_file_operations = { | ||
| 131 | .open = open_proxy_open, | ||
| 132 | }; | ||
| 133 | |||
| 45 | static struct dentry *debugfs_create_mode(const char *name, umode_t mode, | 134 | static struct dentry *debugfs_create_mode(const char *name, umode_t mode, |
| 46 | struct dentry *parent, void *value, | 135 | struct dentry *parent, void *value, |
| 47 | const struct file_operations *fops, | 136 | const struct file_operations *fops, |
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index b1e7f35f3cd4..2905dd160575 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c | |||
| @@ -27,9 +27,14 @@ | |||
| 27 | #include <linux/parser.h> | 27 | #include <linux/parser.h> |
| 28 | #include <linux/magic.h> | 28 | #include <linux/magic.h> |
| 29 | #include <linux/slab.h> | 29 | #include <linux/slab.h> |
| 30 | #include <linux/srcu.h> | ||
| 31 | |||
| 32 | #include "internal.h" | ||
| 30 | 33 | ||
| 31 | #define DEBUGFS_DEFAULT_MODE 0700 | 34 | #define DEBUGFS_DEFAULT_MODE 0700 |
| 32 | 35 | ||
| 36 | DEFINE_SRCU(debugfs_srcu); | ||
| 37 | |||
| 33 | static struct vfsmount *debugfs_mount; | 38 | static struct vfsmount *debugfs_mount; |
| 34 | static int debugfs_mount_count; | 39 | static int debugfs_mount_count; |
| 35 | static bool debugfs_registered; | 40 | static bool debugfs_registered; |
| @@ -341,8 +346,12 @@ struct dentry *debugfs_create_file(const char *name, umode_t mode, | |||
| 341 | return failed_creating(dentry); | 346 | return failed_creating(dentry); |
| 342 | 347 | ||
| 343 | inode->i_mode = mode; | 348 | inode->i_mode = mode; |
| 344 | inode->i_fop = fops ? fops : &debugfs_file_operations; | ||
| 345 | inode->i_private = data; | 349 | inode->i_private = data; |
| 350 | |||
| 351 | inode->i_fop = fops ? &debugfs_open_proxy_file_operations | ||
| 352 | : &debugfs_noop_file_operations; | ||
| 353 | dentry->d_fsdata = (void *)fops; | ||
| 354 | |||
| 346 | d_instantiate(dentry, inode); | 355 | d_instantiate(dentry, inode); |
| 347 | fsnotify_create(d_inode(dentry->d_parent), dentry); | 356 | fsnotify_create(d_inode(dentry->d_parent), dentry); |
| 348 | return end_creating(dentry); | 357 | return end_creating(dentry); |
| @@ -570,6 +579,7 @@ void debugfs_remove(struct dentry *dentry) | |||
| 570 | inode_unlock(d_inode(parent)); | 579 | inode_unlock(d_inode(parent)); |
| 571 | if (!ret) | 580 | if (!ret) |
| 572 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); | 581 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); |
| 582 | synchronize_srcu(&debugfs_srcu); | ||
| 573 | } | 583 | } |
| 574 | EXPORT_SYMBOL_GPL(debugfs_remove); | 584 | EXPORT_SYMBOL_GPL(debugfs_remove); |
| 575 | 585 | ||
| @@ -647,6 +657,7 @@ void debugfs_remove_recursive(struct dentry *dentry) | |||
| 647 | if (!__debugfs_remove(child, parent)) | 657 | if (!__debugfs_remove(child, parent)) |
| 648 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); | 658 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); |
| 649 | inode_unlock(d_inode(parent)); | 659 | inode_unlock(d_inode(parent)); |
| 660 | synchronize_srcu(&debugfs_srcu); | ||
| 650 | } | 661 | } |
| 651 | EXPORT_SYMBOL_GPL(debugfs_remove_recursive); | 662 | EXPORT_SYMBOL_GPL(debugfs_remove_recursive); |
| 652 | 663 | ||
diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h new file mode 100644 index 000000000000..c7aaa5cb6685 --- /dev/null +++ b/fs/debugfs/internal.h | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | /* | ||
| 2 | * internal.h - declarations internal to debugfs | ||
| 3 | * | ||
| 4 | * Copyright (C) 2016 Nicolai Stange <nicstange@gmail.com> | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU General Public License version | ||
| 8 | * 2 as published by the Free Software Foundation. | ||
| 9 | * | ||
| 10 | */ | ||
| 11 | |||
| 12 | #ifndef _DEBUGFS_INTERNAL_H_ | ||
| 13 | #define _DEBUGFS_INTERNAL_H_ | ||
| 14 | |||
| 15 | struct file_operations; | ||
| 16 | struct srcu_struct; | ||
| 17 | |||
| 18 | /* declared over in file.c */ | ||
| 19 | extern const struct file_operations debugfs_noop_file_operations; | ||
| 20 | extern const struct file_operations debugfs_open_proxy_file_operations; | ||
| 21 | |||
| 22 | extern struct srcu_struct debugfs_srcu; | ||
| 23 | |||
| 24 | #endif /* _DEBUGFS_INTERNAL_H_ */ | ||
