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 | |
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>
-rw-r--r-- | fs/debugfs/file.c | 91 | ||||
-rw-r--r-- | fs/debugfs/inode.c | 13 | ||||
-rw-r--r-- | fs/debugfs/internal.h | 24 | ||||
-rw-r--r-- | include/linux/debugfs.h | 3 | ||||
-rw-r--r-- | lib/Kconfig.debug | 1 |
5 files changed, 127 insertions, 5 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_ */ | ||
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index 981e53ab84e8..fcafe2d389f9 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h | |||
@@ -43,9 +43,6 @@ extern struct dentry *arch_debugfs_dir; | |||
43 | 43 | ||
44 | #if defined(CONFIG_DEBUG_FS) | 44 | #if defined(CONFIG_DEBUG_FS) |
45 | 45 | ||
46 | /* declared over in file.c */ | ||
47 | extern const struct file_operations debugfs_file_operations; | ||
48 | |||
49 | struct dentry *debugfs_create_file(const char *name, umode_t mode, | 46 | struct dentry *debugfs_create_file(const char *name, umode_t mode, |
50 | struct dentry *parent, void *data, | 47 | struct dentry *parent, void *data, |
51 | const struct file_operations *fops); | 48 | const struct file_operations *fops); |
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1e9a607534ca..ddb0e8337aae 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug | |||
@@ -257,6 +257,7 @@ config PAGE_OWNER | |||
257 | 257 | ||
258 | config DEBUG_FS | 258 | config DEBUG_FS |
259 | bool "Debug Filesystem" | 259 | bool "Debug Filesystem" |
260 | select SRCU | ||
260 | help | 261 | help |
261 | debugfs is a virtual file system that kernel developers use to put | 262 | debugfs is a virtual file system that kernel developers use to put |
262 | debugging files into. Enable this option to be able to read and | 263 | debugging files into. Enable this option to be able to read and |