aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolai Stange <nicstange@gmail.com>2016-03-22 09:11:13 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2016-04-12 17:14:21 -0400
commit9fd4dcece43a53e5a9e65a973df5693702ee6401 (patch)
tree70fe5fc79019aec56775a74d71c26094e211abd5
parent3a3a5fece6f28c14d3d05c74fb7696412e53a067 (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.c91
-rw-r--r--fs/debugfs/inode.c13
-rw-r--r--fs/debugfs/internal.h24
-rw-r--r--include/linux/debugfs.h3
-rw-r--r--lib/Kconfig.debug1
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
26static ssize_t default_read_file(struct file *file, char __user *buf, 29static 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
38const struct file_operations debugfs_file_operations = { 41const 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 */
69static 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 */
88static 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
98static 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
124out:
125 fops_put(real_fops);
126 debugfs_use_file_finish(srcu_idx);
127 return r;
128}
129
130const struct file_operations debugfs_open_proxy_file_operations = {
131 .open = open_proxy_open,
132};
133
45static struct dentry *debugfs_create_mode(const char *name, umode_t mode, 134static 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
36DEFINE_SRCU(debugfs_srcu);
37
33static struct vfsmount *debugfs_mount; 38static struct vfsmount *debugfs_mount;
34static int debugfs_mount_count; 39static int debugfs_mount_count;
35static bool debugfs_registered; 40static 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}
574EXPORT_SYMBOL_GPL(debugfs_remove); 584EXPORT_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}
651EXPORT_SYMBOL_GPL(debugfs_remove_recursive); 662EXPORT_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
15struct file_operations;
16struct srcu_struct;
17
18/* declared over in file.c */
19extern const struct file_operations debugfs_noop_file_operations;
20extern const struct file_operations debugfs_open_proxy_file_operations;
21
22extern 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 */
47extern const struct file_operations debugfs_file_operations;
48
49struct dentry *debugfs_create_file(const char *name, umode_t mode, 46struct 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
258config DEBUG_FS 258config 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