diff mbox series

securityfs: Add missing d_delete() call on removal

Message ID 202005051626.7648DC65@keescook (mailing list archive)
State New, archived
Headers show
Series securityfs: Add missing d_delete() call on removal | expand

Commit Message

Kees Cook May 5, 2020, 11:40 p.m. UTC
After using simple_unlink(), a call to d_delete() is needed in addition
to dput().

Signed-off-by: Kees Cook <keescook@chromium.org>
---
Is this correct? I went looking around and there are a lot of variations
on the simple_unlink() pattern...

Many using explicit locking and combinations of d_drop(), __d_drop(), etc.


Some missing d_delete()?

security/inode.c:			simple_unlink(dir, dentry);
security/inode.c-		d_delete(dentry);
security/inode.c-		dput(dentry);
--
arch/powerpc/platforms/cell/spufs/inode.c:			simple_unlink(d_inode(dir), dentry);
arch/powerpc/platforms/cell/spufs/inode.c-			/* XXX: what was dcache_lock protecting here? Other
arch/powerpc/platforms/cell/spufs/inode.c-			 * filesystems (IB, configfs) release dcache_lock
arch/powerpc/platforms/cell/spufs/inode.c-			 * before unlink */
arch/powerpc/platforms/cell/spufs/inode.c-			dput(dentry);

Should use d_delete() instead of d_drop()?

arch/s390/hypfs/inode.c:			simple_unlink(d_inode(parent), dentry);
arch/s390/hypfs/inode.c-	}
arch/s390/hypfs/inode.c-	d_drop(dentry);
arch/s390/hypfs/inode.c-	dput(dentry);
arch/s390/hypfs/inode.c-	inode_unlock(d_inode(parent));
arch/s390/hypfs/inode.c-}

Correct?

drivers/android/binderfs.c:		simple_unlink(parent_inode, dentry);
drivers/android/binderfs.c-		d_delete(dentry);
drivers/android/binderfs.c-		dput(dentry);
--
fs/nfsd/nfsctl.c:	ret = simple_unlink(dir, dentry);
fs/nfsd/nfsctl.c-	d_delete(dentry);
fs/nfsd/nfsctl.c-	dput(dentry);
--
net/sunrpc/rpc_pipe.c:	ret = simple_unlink(dir, dentry);
net/sunrpc/rpc_pipe.c-	if (!ret)
net/sunrpc/rpc_pipe.c-		fsnotify_unlink(dir, dentry);
net/sunrpc/rpc_pipe.c-	d_delete(dentry);
net/sunrpc/rpc_pipe.c-	dput(dentry);
--
security/apparmor/apparmorfs.c:			simple_unlink(dir, dentry);
security/apparmor/apparmorfs.c-		d_delete(dentry);
security/apparmor/apparmorfs.c-		dput(dentry);

---
 security/inode.c | 1 +
 1 file changed, 1 insertion(+)

Comments

Al Viro May 6, 2020, 1:14 a.m. UTC | #1
On Tue, May 05, 2020 at 04:40:35PM -0700, Kees Cook wrote:
> After using simple_unlink(), a call to d_delete() is needed in addition
> to dput().
> 
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
> Is this correct? I went looking around and there are a lot of variations
> on the simple_unlink() pattern...
> 
> Many using explicit locking and combinations of d_drop(), __d_drop(), etc.

Quite a few of those should switch to simple_recursive_removal().  As for
securityfs...  d_drop() is _probably_ a saner variant, but looking at the
callers of that thing... at least IMA ones seem to be garbage.

They _might_ work since nobody else is around that early to screw them over,
but... I'm not too optimistic about that.  And the lack of d_delete()/d_drop()
here is the least of the problems, really - look at e.g. the bulk of those
suckers in ima_fs_init().  And check the callchain - it'll lead you to this
        if (error && strcmp(hash_algo_name[ima_hash_algo],
                            CONFIG_IMA_DEFAULT_HASH) != 0) {
                pr_info("Allocating %s failed, going to use default hash algorithm %s\n",
                        hash_algo_name[ima_hash_algo], CONFIG_IMA_DEFAULT_HASH);
                hash_setup_done = 0;
                hash_setup(CONFIG_IMA_DEFAULT_HASH);
                error = ima_init();
        }

        error = register_blocking_lsm_notifier(&ima_lsm_policy_notifier);

And the other IMA caller (in ima_release_policy()) is... misguided, to put it
politely.  This kind of "unlink on final close" makes no sense - if nothing
else, it can be looked up until that very point.  Moreover, this
        inode->i_mode &= ~S_IWUSR;
is obviously racy wrt permission(), and there's no damn reason to do it there.
These checks belong in ->open() and they shouldn't rely upon the damn thing
disappearing from directory or permission() failing, etc..

And looking at their ->open()... ouch.  ->f_flags & O_ACCMODE is almost
never the right thing to check.  It should be looking at ->f_mode &
FMODE_{READ,WRITE}.

I hadn't looked into details for EVM, but at the first glance it's similar
to IMA failure handling.  And probably relies upon nobody being able to
open that stuff while the things are being set up.  There seems to be
something in TPM as well - and by the look of it they are trying to work
around the use of unsuitable primitive, and none too well, at that.
I mean,
        int err;
        struct seq_file *seq;
        struct tpm_chip_seqops *chip_seqops;
        const struct seq_operations *seqops;
        struct tpm_chip *chip;

        inode_lock(inode);
        if (!inode->i_private) {
                inode_unlock(inode);
                return -ENODEV;
        }
        chip_seqops = (struct tpm_chip_seqops *)inode->i_private;
        seqops = chip_seqops->seqops;
        chip = chip_seqops->chip;
        get_device(&chip->dev);
        inode_unlock(inode);

        /* now register seq file */
        err = seq_open(file, seqops);
        if (!err) {
                seq = file->private_data;
                seq->private = chip;
        }

        return err;
doesn't look sane - late error would appear to leak &chip->dev...
Kees Cook May 6, 2020, 3:28 a.m. UTC | #2
On Wed, May 06, 2020 at 02:14:31AM +0100, Al Viro wrote:
> On Tue, May 05, 2020 at 04:40:35PM -0700, Kees Cook wrote:
> > After using simple_unlink(), a call to d_delete() is needed in addition
> > to dput().
> > 
> > Signed-off-by: Kees Cook <keescook@chromium.org>
> > ---
> > Is this correct? I went looking around and there are a lot of variations
> > on the simple_unlink() pattern...
> > 
> > Many using explicit locking and combinations of d_drop(), __d_drop(), etc.
> 
> Quite a few of those should switch to simple_recursive_removal().  As for
> securityfs...  d_drop() is _probably_ a saner variant, but looking at the
> callers of that thing... at least IMA ones seem to be garbage.

Hmm, I dunno. I hadn't looked at these yet. I'm not sure what's needed
for those cases.

Is my patch to add d_delete() correct, though? I'm trying to construct
the right set of calls for pstore's filesystem, and I noticed that most
will do simple_unlink(), d_delete(), dput(), but securityfs seemed to be
missing it.
Al Viro May 6, 2020, 4:02 a.m. UTC | #3
On Tue, May 05, 2020 at 08:28:33PM -0700, Kees Cook wrote:
> On Wed, May 06, 2020 at 02:14:31AM +0100, Al Viro wrote:
> > On Tue, May 05, 2020 at 04:40:35PM -0700, Kees Cook wrote:
> > > After using simple_unlink(), a call to d_delete() is needed in addition
> > > to dput().
> > > 
> > > Signed-off-by: Kees Cook <keescook@chromium.org>
> > > ---
> > > Is this correct? I went looking around and there are a lot of variations
> > > on the simple_unlink() pattern...
> > > 
> > > Many using explicit locking and combinations of d_drop(), __d_drop(), etc.
> > 
> > Quite a few of those should switch to simple_recursive_removal().  As for
> > securityfs...  d_drop() is _probably_ a saner variant, but looking at the
> > callers of that thing... at least IMA ones seem to be garbage.
> 
> Hmm, I dunno. I hadn't looked at these yet. I'm not sure what's needed
> for those cases.
> 
> Is my patch to add d_delete() correct, though? I'm trying to construct
> the right set of calls for pstore's filesystem, and I noticed that most
> will do simple_unlink(), d_delete(), dput(), but securityfs seemed to be
> missing it.

d_drop().  d_delete() is for the situations when you want the sucker
to become a hashed negative, if at all possible.

Re pstore: context, please.
Kees Cook May 6, 2020, 3:34 p.m. UTC | #4
On Wed, May 06, 2020 at 05:02:52AM +0100, Al Viro wrote:
> On Tue, May 05, 2020 at 08:28:33PM -0700, Kees Cook wrote:
> > On Wed, May 06, 2020 at 02:14:31AM +0100, Al Viro wrote:
> > > On Tue, May 05, 2020 at 04:40:35PM -0700, Kees Cook wrote:
> > > > After using simple_unlink(), a call to d_delete() is needed in addition
> > > > to dput().
> > > > 
> > > > Signed-off-by: Kees Cook <keescook@chromium.org>
> > > > ---
> > > > Is this correct? I went looking around and there are a lot of variations
> > > > on the simple_unlink() pattern...
> > > > 
> > > > Many using explicit locking and combinations of d_drop(), __d_drop(), etc.
> > > 
> > > Quite a few of those should switch to simple_recursive_removal().  As for
> > > securityfs...  d_drop() is _probably_ a saner variant, but looking at the
> > > callers of that thing... at least IMA ones seem to be garbage.
> > 
> > Hmm, I dunno. I hadn't looked at these yet. I'm not sure what's needed
> > for those cases.
> > 
> > Is my patch to add d_delete() correct, though? I'm trying to construct
> > the right set of calls for pstore's filesystem, and I noticed that most
> > will do simple_unlink(), d_delete(), dput(), but securityfs seemed to be
> > missing it.
> 
> d_drop().  d_delete() is for the situations when you want the sucker
> to become a hashed negative, if at all possible.

I'm not sure what that means. :) Should stuff like apparmorfs be changed
to d_drop()?

> Re pstore: context, please.

Just posted the whole series:
https://lore.kernel.org/lkml/20200506152114.50375-1-keescook@chromium.org/

But the specific question was driven by this patch:
https://lore.kernel.org/lkml/20200506152114.50375-11-keescook@chromium.org/
Al Viro May 6, 2020, 6:49 p.m. UTC | #5
On Wed, May 06, 2020 at 08:34:29AM -0700, Kees Cook wrote:

> Just posted the whole series:
> https://lore.kernel.org/lkml/20200506152114.50375-1-keescook@chromium.org/
> 
> But the specific question was driven by this patch:
> https://lore.kernel.org/lkml/20200506152114.50375-11-keescook@chromium.org/

Yecchh...  First of all, you are leaving a dangling pointer in your
struct pstore_private ->dentry.  What's more, in your case d_delete()
is definitely wrong - either there are other references to dentry
(in which case d_delete() is the same as d_drop()), or dput() right
after it will drive ->d_count to zero and since you end up using
simple_dentry_operations, dentry will be freed immediately after
that.

I have not looked at the locking in that series yet, so no comments
on the races, but in any case - that d_delete() is a misspelled d_drop().
Kees Cook May 6, 2020, 10:49 p.m. UTC | #6
On Wed, May 06, 2020 at 07:49:20PM +0100, Al Viro wrote:
> On Wed, May 06, 2020 at 08:34:29AM -0700, Kees Cook wrote:
> 
> > Just posted the whole series:
> > https://lore.kernel.org/lkml/20200506152114.50375-1-keescook@chromium.org/
> > 
> > But the specific question was driven by this patch:
> > https://lore.kernel.org/lkml/20200506152114.50375-11-keescook@chromium.org/
> 
> Yecchh...  First of all, you are leaving a dangling pointer in your
> struct pstore_private ->dentry.  What's more, in your case d_delete()

Yeah, good idea: I can wipe out the pstore_private->dentry at this point
just for robustness. From what I could tell the evict got immediately
called after the dput().

> is definitely wrong - either there are other references to dentry
> (in which case d_delete() is the same as d_drop()), or dput() right
> after it will drive ->d_count to zero and since you end up using
> simple_dentry_operations, dentry will be freed immediately after
> that.

Do you mean the d_drop() isn't needed? What happens if someone has
the file open during this routine? The goal here is to make these files
disappear so they'll go through evict.

> I have not looked at the locking in that series yet, so no comments

Yeah, I would not be surprised by some more locking issues, but I think
it's an improvement over what was there. Most of the code seems to have
been designed to be non-modular. :P

> on the races, but in any case - that d_delete() is a misspelled d_drop().

I'll change it; thanks!
diff mbox series

Patch

diff --git a/security/inode.c b/security/inode.c
index 6c326939750d..606f390d21d2 100644
--- a/security/inode.c
+++ b/security/inode.c
@@ -306,6 +306,7 @@  void securityfs_remove(struct dentry *dentry)
 			simple_rmdir(dir, dentry);
 		else
 			simple_unlink(dir, dentry);
+		d_delete(dentry);
 		dput(dentry);
 	}
 	inode_unlock(dir);