aboutsummaryrefslogtreecommitdiffstats
path: root/fs/debugfs
diff options
context:
space:
mode:
authorNicolai Stange <nicstange@gmail.com>2017-10-30 19:15:48 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-11-07 14:25:02 -0500
commite9117a5a4bf65d8e99f060d356a04d27a60b436d (patch)
treebd7ad76ed4c2f538f13ab38afb0dda9478ad044e /fs/debugfs
parent7c8d469877b16d2c1cecf101a0abb7b218db85bc (diff)
debugfs: implement per-file removal protection
Since commit 49d200deaa68 ("debugfs: prevent access to removed files' private data"), accesses to a file's private data are protected from concurrent removal by covering all file_operations with a SRCU read section and sychronizing with those before returning from debugfs_remove() by means of synchronize_srcu(). As pointed out by Johannes Berg, there are debugfs files with forever blocking file_operations. Their corresponding SRCU read side sections would block any debugfs_remove() forever as well, even unrelated ones. This results in a livelock. Because a remover can't cancel any indefinite blocking within foreign files, this is a problem. Resolve this by introducing support for more granular protection on a per-file basis. This is implemented by introducing an 'active_users' refcount_t to the per-file struct debugfs_fsdata state. At file creation time, it is set to one and a debugfs_remove() will drop that initial reference. The new debugfs_file_get() and debugfs_file_put(), intended to be used in place of former debugfs_use_file_start() and debugfs_use_file_finish(), increment and decrement it respectively. Once the count drops to zero, debugfs_file_put() will signal a completion which is possibly being waited for from debugfs_remove(). Thus, as long as there is a debugfs_file_get() not yet matched by a corresponding debugfs_file_put() around, debugfs_remove() will block. Actual users of debugfs_use_file_start() and -finish() will get converted to the new debugfs_file_get() and debugfs_file_put() by followup patches. Fixes: 49d200deaa68 ("debugfs: prevent access to removed files' private data") Reported-by: Johannes Berg <johannes@sipsolutions.net> 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.c48
-rw-r--r--fs/debugfs/inode.c29
-rw-r--r--fs/debugfs/internal.h2
3 files changed, 73 insertions, 6 deletions
diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index b6f5ddab66bf..6644bfdea2f8 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -109,6 +109,54 @@ const struct file_operations *debugfs_real_fops(const struct file *filp)
109} 109}
110EXPORT_SYMBOL_GPL(debugfs_real_fops); 110EXPORT_SYMBOL_GPL(debugfs_real_fops);
111 111
112/**
113 * debugfs_file_get - mark the beginning of file data access
114 * @dentry: the dentry object whose data is being accessed.
115 *
116 * Up to a matching call to debugfs_file_put(), any successive call
117 * into the file removing functions debugfs_remove() and
118 * debugfs_remove_recursive() will block. Since associated private
119 * file data may only get freed after a successful return of any of
120 * the removal functions, you may safely access it after a successful
121 * call to debugfs_file_get() without worrying about lifetime issues.
122 *
123 * If -%EIO is returned, the file has already been removed and thus,
124 * it is not safe to access any of its data. If, on the other hand,
125 * it is allowed to access the file data, zero is returned.
126 */
127int debugfs_file_get(struct dentry *dentry)
128{
129 struct debugfs_fsdata *fsd = dentry->d_fsdata;
130
131 /* Avoid starvation of removers. */
132 if (d_unlinked(dentry))
133 return -EIO;
134
135 if (!refcount_inc_not_zero(&fsd->active_users))
136 return -EIO;
137
138 return 0;
139}
140EXPORT_SYMBOL_GPL(debugfs_file_get);
141
142/**
143 * debugfs_file_put - mark the end of file data access
144 * @dentry: the dentry object formerly passed to
145 * debugfs_file_get().
146 *
147 * Allow any ongoing concurrent call into debugfs_remove() or
148 * debugfs_remove_recursive() blocked by a former call to
149 * debugfs_file_get() to proceed and return to its caller.
150 */
151void debugfs_file_put(struct dentry *dentry)
152{
153 struct debugfs_fsdata *fsd = dentry->d_fsdata;
154
155 if (refcount_dec_and_test(&fsd->active_users))
156 complete(&fsd->active_users_drained);
157}
158EXPORT_SYMBOL_GPL(debugfs_file_put);
159
112static int open_proxy_open(struct inode *inode, struct file *filp) 160static int open_proxy_open(struct inode *inode, struct file *filp)
113{ 161{
114 const struct dentry *dentry = F_DENTRY(filp); 162 const struct dentry *dentry = F_DENTRY(filp);
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
index a9c3d3e9af39..6449eb935540 100644
--- a/fs/debugfs/inode.c
+++ b/fs/debugfs/inode.c
@@ -374,6 +374,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
374 374
375 inode->i_fop = proxy_fops; 375 inode->i_fop = proxy_fops;
376 fsd->real_fops = real_fops; 376 fsd->real_fops = real_fops;
377 refcount_set(&fsd->active_users, 1);
377 dentry->d_fsdata = fsd; 378 dentry->d_fsdata = fsd;
378 379
379 d_instantiate(dentry, inode); 380 d_instantiate(dentry, inode);
@@ -631,18 +632,34 @@ struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent,
631} 632}
632EXPORT_SYMBOL_GPL(debugfs_create_symlink); 633EXPORT_SYMBOL_GPL(debugfs_create_symlink);
633 634
635static void __debugfs_remove_file(struct dentry *dentry, struct dentry *parent)
636{
637 struct debugfs_fsdata *fsd;
638
639 simple_unlink(d_inode(parent), dentry);
640 d_delete(dentry);
641 fsd = dentry->d_fsdata;
642 init_completion(&fsd->active_users_drained);
643 if (!refcount_dec_and_test(&fsd->active_users))
644 wait_for_completion(&fsd->active_users_drained);
645}
646
634static int __debugfs_remove(struct dentry *dentry, struct dentry *parent) 647static int __debugfs_remove(struct dentry *dentry, struct dentry *parent)
635{ 648{
636 int ret = 0; 649 int ret = 0;
637 650
638 if (simple_positive(dentry)) { 651 if (simple_positive(dentry)) {
639 dget(dentry); 652 dget(dentry);
640 if (d_is_dir(dentry)) 653 if (!d_is_reg(dentry)) {
641 ret = simple_rmdir(d_inode(parent), dentry); 654 if (d_is_dir(dentry))
642 else 655 ret = simple_rmdir(d_inode(parent), dentry);
643 simple_unlink(d_inode(parent), dentry); 656 else
644 if (!ret) 657 simple_unlink(d_inode(parent), dentry);
645 d_delete(dentry); 658 if (!ret)
659 d_delete(dentry);
660 } else {
661 __debugfs_remove_file(dentry, parent);
662 }
646 dput(dentry); 663 dput(dentry);
647 } 664 }
648 return ret; 665 return ret;
diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h
index 512601eed3ce..0eea99432840 100644
--- a/fs/debugfs/internal.h
+++ b/fs/debugfs/internal.h
@@ -21,6 +21,8 @@ extern const struct file_operations debugfs_full_proxy_file_operations;
21 21
22struct debugfs_fsdata { 22struct debugfs_fsdata {
23 const struct file_operations *real_fops; 23 const struct file_operations *real_fops;
24 refcount_t active_users;
25 struct completion active_users_drained;
24}; 26};
25 27
26#endif /* _DEBUGFS_INTERNAL_H_ */ 28#endif /* _DEBUGFS_INTERNAL_H_ */