Skip to content

Commit 9fd4dce

Browse files
nicstangegregkh
authored andcommitted
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/[email protected] ("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 <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 3a3a5fe commit 9fd4dce

File tree

5 files changed

+127
-5
lines changed

5 files changed

+127
-5
lines changed

fs/debugfs/file.c

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
#include <linux/slab.h>
2323
#include <linux/atomic.h>
2424
#include <linux/device.h>
25+
#include <linux/srcu.h>
26+
27+
#include "internal.h"
2528

2629
static ssize_t default_read_file(struct file *file, char __user *buf,
2730
size_t count, loff_t *ppos)
@@ -35,13 +38,99 @@ static ssize_t default_write_file(struct file *file, const char __user *buf,
3538
return count;
3639
}
3740

38-
const struct file_operations debugfs_file_operations = {
41+
const struct file_operations debugfs_noop_file_operations = {
3942
.read = default_read_file,
4043
.write = default_write_file,
4144
.open = simple_open,
4245
.llseek = noop_llseek,
4346
};
4447

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+
45134
static struct dentry *debugfs_create_mode(const char *name, umode_t mode,
46135
struct dentry *parent, void *value,
47136
const struct file_operations *fops,

fs/debugfs/inode.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@
2727
#include <linux/parser.h>
2828
#include <linux/magic.h>
2929
#include <linux/slab.h>
30+
#include <linux/srcu.h>
31+
32+
#include "internal.h"
3033

3134
#define DEBUGFS_DEFAULT_MODE 0700
3235

36+
DEFINE_SRCU(debugfs_srcu);
37+
3338
static struct vfsmount *debugfs_mount;
3439
static int debugfs_mount_count;
3540
static bool debugfs_registered;
@@ -341,8 +346,12 @@ struct dentry *debugfs_create_file(const char *name, umode_t mode,
341346
return failed_creating(dentry);
342347

343348
inode->i_mode = mode;
344-
inode->i_fop = fops ? fops : &debugfs_file_operations;
345349
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+
346355
d_instantiate(dentry, inode);
347356
fsnotify_create(d_inode(dentry->d_parent), dentry);
348357
return end_creating(dentry);
@@ -570,6 +579,7 @@ void debugfs_remove(struct dentry *dentry)
570579
inode_unlock(d_inode(parent));
571580
if (!ret)
572581
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
582+
synchronize_srcu(&debugfs_srcu);
573583
}
574584
EXPORT_SYMBOL_GPL(debugfs_remove);
575585

@@ -647,6 +657,7 @@ void debugfs_remove_recursive(struct dentry *dentry)
647657
if (!__debugfs_remove(child, parent))
648658
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
649659
inode_unlock(d_inode(parent));
660+
synchronize_srcu(&debugfs_srcu);
650661
}
651662
EXPORT_SYMBOL_GPL(debugfs_remove_recursive);
652663

fs/debugfs/internal.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* internal.h - declarations internal to debugfs
3+
*
4+
* Copyright (C) 2016 Nicolai Stange <[email protected]>
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_ */

include/linux/debugfs.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ extern struct dentry *arch_debugfs_dir;
4343

4444
#if defined(CONFIG_DEBUG_FS)
4545

46-
/* declared over in file.c */
47-
extern const struct file_operations debugfs_file_operations;
48-
4946
struct dentry *debugfs_create_file(const char *name, umode_t mode,
5047
struct dentry *parent, void *data,
5148
const struct file_operations *fops);

lib/Kconfig.debug

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ config PAGE_OWNER
257257

258258
config DEBUG_FS
259259
bool "Debug Filesystem"
260+
select SRCU
260261
help
261262
debugfs is a virtual file system that kernel developers use to put
262263
debugging files into. Enable this option to be able to read and

0 commit comments

Comments
 (0)