diff mbox

fuse: don't keep inode-less dentry at fuse_ctl_add_dentry().

Message ID b1f0e06c-1f66-2f31-fdad-95c2e50eeddf@I-love.SAKURA.ne.jp (mailing list archive)
State New, archived
Headers show

Commit Message

Tetsuo Handa May 9, 2018, 10:58 a.m. UTC
From 9f41081f8bd6762a6f629e5e23e6d07a62bba69c Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Sat, 28 Apr 2018 11:24:09 +0900
Subject: [PATCH] fuse: don't keep inode-less dentry at fuse_ctl_add_dentry().

syzbot is reporting NULL pointer dereference at fuse_ctl_remove_conn() [1].
Since fc->ctl_ndents is incremented by fuse_ctl_add_conn() when new_inode()
failed, fuse_ctl_remove_conn() reaches an inode-less dentry and tries to
clear d_inode(dentry)->i_private field. Fix this by calling dput() rather
than incrementing fc->ctl_ndents when new_inode() failed.

[1] https://syzkaller.appspot.com/bug?id=f396d863067238959c91c0b7cfc10b163638cac6

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Reported-by: syzbot <syzbot+32c236387d66c4516827@syzkaller.appspotmail.com>
Fixes: bafa96541b250a70 ("fuse: add control filesystem")
Cc: Miklos Szeredi <miklos@szeredi.hu>
---
 fs/fuse/control.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

Comments

Al Viro May 10, 2018, 8:07 p.m. UTC | #1
On Wed, May 09, 2018 at 07:58:58PM +0900, Tetsuo Handa wrote:
> >From 9f41081f8bd6762a6f629e5e23e6d07a62bba69c Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Date: Sat, 28 Apr 2018 11:24:09 +0900
> Subject: [PATCH] fuse: don't keep inode-less dentry at fuse_ctl_add_dentry().
> 
> syzbot is reporting NULL pointer dereference at fuse_ctl_remove_conn() [1].
> Since fc->ctl_ndents is incremented by fuse_ctl_add_conn() when new_inode()
> failed, fuse_ctl_remove_conn() reaches an inode-less dentry and tries to
> clear d_inode(dentry)->i_private field. Fix this by calling dput() rather
> than incrementing fc->ctl_ndents when new_inode() failed.

That looks bloody awful.  Sure, everything that accesses fc->ctl_dentry is
synchronous with this, but it would've been much easier to follow if
shoving dentry into that array happened only after it's been fully set
up.

Incidentally, there's a nasty headache waiting to happen in that code -
consider a twit mounting something on that.  And think what happens when
connection gets shut down...
Miklos Szeredi May 11, 2018, 7:55 a.m. UTC | #2
On Thu, May 10, 2018 at 10:07 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
> On Wed, May 09, 2018 at 07:58:58PM +0900, Tetsuo Handa wrote:
>> >From 9f41081f8bd6762a6f629e5e23e6d07a62bba69c Mon Sep 17 00:00:00 2001
>> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
>> Date: Sat, 28 Apr 2018 11:24:09 +0900
>> Subject: [PATCH] fuse: don't keep inode-less dentry at fuse_ctl_add_dentry().
>>
>> syzbot is reporting NULL pointer dereference at fuse_ctl_remove_conn() [1].
>> Since fc->ctl_ndents is incremented by fuse_ctl_add_conn() when new_inode()
>> failed, fuse_ctl_remove_conn() reaches an inode-less dentry and tries to
>> clear d_inode(dentry)->i_private field. Fix this by calling dput() rather
>> than incrementing fc->ctl_ndents when new_inode() failed.
>
> That looks bloody awful.  Sure, everything that accesses fc->ctl_dentry is
> synchronous with this, but it would've been much easier to follow if
> shoving dentry into that array happened only after it's been fully set
> up.
>
> Incidentally, there's a nasty headache waiting to happen in that code -
> consider a twit mounting something on that.  And think what happens when
> connection gets shut down...

Need to call d_invalidate() instead of d_drop() in there.  Is that
what you are referring to?

Thanks,
Miklos
Tetsuo Handa May 11, 2018, 10:30 a.m. UTC | #3
Miklos Szeredi wrote:
> On Thu, May 10, 2018 at 10:07 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
> > On Wed, May 09, 2018 at 07:58:58PM +0900, Tetsuo Handa wrote:
> >> >From 9f41081f8bd6762a6f629e5e23e6d07a62bba69c Mon Sep 17 00:00:00 2001
> >> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> >> Date: Sat, 28 Apr 2018 11:24:09 +0900
> >> Subject: [PATCH] fuse: don't keep inode-less dentry at fuse_ctl_add_dentry().
> >>
> >> syzbot is reporting NULL pointer dereference at fuse_ctl_remove_conn() [1].
> >> Since fc->ctl_ndents is incremented by fuse_ctl_add_conn() when new_inode()
> >> failed, fuse_ctl_remove_conn() reaches an inode-less dentry and tries to
> >> clear d_inode(dentry)->i_private field. Fix this by calling dput() rather
> >> than incrementing fc->ctl_ndents when new_inode() failed.
> >
> > That looks bloody awful.  Sure, everything that accesses fc->ctl_dentry is
> > synchronous with this, but it would've been much easier to follow if
> > shoving dentry into that array happened only after it's been fully set
> > up.
> >
> > Incidentally, there's a nasty headache waiting to happen in that code -
> > consider a twit mounting something on that.  And think what happens when
> > connection gets shut down...
> 
> Need to call d_invalidate() instead of d_drop() in there.  Is that
> what you are referring to?
> 
> Thanks,
> Miklos
> 
I couldn't catch what Al is worrying about. I assumed that dput() is fine
because proc_setup_thread_self() in fs/proc/thread_self.c is doing dput()
when new_inode_pseudo() failed after d_alloc_name().
Al Viro May 11, 2018, 10:12 p.m. UTC | #4
On Fri, May 11, 2018 at 09:55:00AM +0200, Miklos Szeredi wrote:
> On Thu, May 10, 2018 at 10:07 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
> > On Wed, May 09, 2018 at 07:58:58PM +0900, Tetsuo Handa wrote:
> >> >From 9f41081f8bd6762a6f629e5e23e6d07a62bba69c Mon Sep 17 00:00:00 2001
> >> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> >> Date: Sat, 28 Apr 2018 11:24:09 +0900
> >> Subject: [PATCH] fuse: don't keep inode-less dentry at fuse_ctl_add_dentry().
> >>
> >> syzbot is reporting NULL pointer dereference at fuse_ctl_remove_conn() [1].
> >> Since fc->ctl_ndents is incremented by fuse_ctl_add_conn() when new_inode()
> >> failed, fuse_ctl_remove_conn() reaches an inode-less dentry and tries to
> >> clear d_inode(dentry)->i_private field. Fix this by calling dput() rather
> >> than incrementing fc->ctl_ndents when new_inode() failed.
> >
> > That looks bloody awful.  Sure, everything that accesses fc->ctl_dentry is
> > synchronous with this, but it would've been much easier to follow if
> > shoving dentry into that array happened only after it's been fully set
> > up.
> >
> > Incidentally, there's a nasty headache waiting to happen in that code -
> > consider a twit mounting something on that.  And think what happens when
> > connection gets shut down...
> 
> Need to call d_invalidate() instead of d_drop() in there.  Is that
> what you are referring to?

Yes, and do that once on the entire subdirectory...
diff mbox

Patch

diff --git a/fs/fuse/control.c b/fs/fuse/control.c
index b9ea99c..a651f8e 100644
--- a/fs/fuse/control.c
+++ b/fs/fuse/control.c
@@ -211,10 +211,12 @@  static struct dentry *fuse_ctl_add_dentry(struct dentry *parent,
 	if (!dentry)
 		return NULL;
 
-	fc->ctl_dentry[fc->ctl_ndents++] = dentry;
 	inode = new_inode(fuse_control_sb);
-	if (!inode)
+	if (!inode) {
+		dput(dentry);
 		return NULL;
+	}
+	fc->ctl_dentry[fc->ctl_ndents++] = dentry;
 
 	inode->i_ino = get_next_ino();
 	inode->i_mode = mode;