@@ -94,6 +94,7 @@ int debugfs_file_get(struct dentry *dentry)
fsd->real_fops = (void *)((unsigned long)d_fsd &
~DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);
+ fsd->fop_release = fsd->real_fops->release;
refcount_set(&fsd->active_users, 1);
init_completion(&fsd->active_users_drained);
if (cmpxchg(&dentry->d_fsdata, d_fsd, fsd) != d_fsd) {
@@ -258,8 +259,8 @@ static __poll_t full_proxy_poll(struct file *filp,
static int full_proxy_release(struct inode *inode, struct file *filp)
{
- const struct dentry *dentry = F_DENTRY(filp);
- const struct file_operations *real_fops = debugfs_real_fops(filp);
+ struct dentry *dentry = F_DENTRY(filp);
+ struct debugfs_fsdata *fsd = dentry->d_fsdata;
const struct file_operations *proxy_fops = filp->f_op;
int r = 0;
@@ -268,13 +269,26 @@ static int full_proxy_release(struct inode *inode, struct file *filp)
* original releaser should be called unconditionally in order
* not to leak any resources. Releasers must not assume that
* ->i_private is still being meaningful here.
+ *
+ * Note, however, that we don't reference real_fops (unless we
+ * can guarantee it's still around). We made a copy of release()
+ * before, in case it was NULL we then will not call anything and
+ * don't need to use real_fops at all. This allows us to allow
+ * module unloading of modules exposing debugfs files if they
+ * don't have release() methods.
*/
- if (real_fops->release)
- r = real_fops->release(inode, filp);
+ if (fsd->fop_release)
+ r = fsd->fop_release(inode, filp);
replace_fops(filp, d_inode(dentry)->i_fop);
kfree((void *)proxy_fops);
- fops_put(real_fops);
+
+ /* fops_put() only if not already gone */
+ if (refcount_inc_not_zero(&fsd->active_users)) {
+ fops_put(fsd->real_fops);
+ debugfs_file_put(dentry);
+ }
+
return r;
}
@@ -377,6 +377,13 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
struct dentry *dentry;
struct inode *inode;
+ if (WARN(real_fops->release &&
+ is_module_text_address((unsigned long)real_fops->release) &&
+ !real_fops->owner,
+ "%ps is in a module but %ps doesn't have an owner",
+ real_fops->release, real_fops))
+ return ERR_PTR(-EINVAL);
+
if (!(mode & S_IFMT))
mode |= S_IFREG;
BUG_ON(!S_ISREG(mode));
@@ -672,6 +679,8 @@ static void __debugfs_file_removed(struct dentry *dentry)
return;
if (!refcount_dec_and_test(&fsd->active_users))
wait_for_completion(&fsd->active_users_drained);
+ fops_put(fsd->real_fops);
+ fsd->real_fops = NULL;
}
static void remove_one(struct dentry *victim)
@@ -17,6 +17,7 @@ extern const struct file_operations debugfs_full_proxy_file_operations;
struct debugfs_fsdata {
const struct file_operations *real_fops;
+ int (*fop_release)(struct inode *, struct file *);
refcount_t active_users;
struct completion active_users_drained;
};