From: Dave Chinner <david@fromorbit.com>
To: Miklos Szeredi <miklos@szeredi.hu>
Cc: Ian Kent <raven@themaw.net>, xfs <linux-xfs@vger.kernel.org>,
"Darrick J. Wong" <djwong@kernel.org>,
Brian Foster <bfoster@redhat.com>,
Al Viro <viro@zeniv.linux.org.uk>,
David Howells <dhowells@redhat.com>,
linux-fsdevel <linux-fsdevel@vger.kernel.org>,
Kernel Mailing List <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH 2/2] xfs: make sure link path does not go away at access
Date: Mon, 15 Nov 2021 10:18:34 +1100 [thread overview]
Message-ID: <20211114231834.GM449541@dread.disaster.area> (raw)
In-Reply-To: <CAJfpegvHDM_Mtc8+ASAcmNLd6RiRM+KutjBOoycun_Oq2=+p=w@mail.gmail.com>
On Fri, Nov 12, 2021 at 08:23:24AM +0100, Miklos Szeredi wrote:
> On Fri, 12 Nov 2021 at 01:32, Dave Chinner <david@fromorbit.com> wrote:
> >
> > On Thu, Nov 11, 2021 at 11:39:30AM +0800, Ian Kent wrote:
> > > When following a trailing symlink in rcu-walk mode it's possible to
> > > succeed in getting the ->get_link() method pointer but the link path
> > > string be deallocated while it's being used.
> > >
> > > Utilize the rcu mechanism to mitigate this risk.
> > >
> > > Suggested-by: Miklos Szeredi <miklos@szeredi.hu>
> > > Signed-off-by: Ian Kent <raven@themaw.net>
> > > ---
> > > fs/xfs/kmem.h | 4 ++++
> > > fs/xfs/xfs_inode.c | 4 ++--
> > > fs/xfs/xfs_iops.c | 10 ++++++++--
> > > 3 files changed, 14 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/fs/xfs/kmem.h b/fs/xfs/kmem.h
> > > index 54da6d717a06..c1bd1103b340 100644
> > > --- a/fs/xfs/kmem.h
> > > +++ b/fs/xfs/kmem.h
> > > @@ -61,6 +61,10 @@ static inline void kmem_free(const void *ptr)
> > > {
> > > kvfree(ptr);
> > > }
> > > +static inline void kmem_free_rcu(const void *ptr)
> > > +{
> > > + kvfree_rcu(ptr);
> > > +}
> > >
> > >
> > > static inline void *
> > > diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
> > > index a4f6f034fb81..aaa1911e61ed 100644
> > > --- a/fs/xfs/xfs_inode.c
> > > +++ b/fs/xfs/xfs_inode.c
> > > @@ -2650,8 +2650,8 @@ xfs_ifree(
> > > * already been freed by xfs_attr_inactive.
> > > */
> > > if (ip->i_df.if_format == XFS_DINODE_FMT_LOCAL) {
> > > - kmem_free(ip->i_df.if_u1.if_data);
> > > - ip->i_df.if_u1.if_data = NULL;
> > > + kmem_free_rcu(ip->i_df.if_u1.if_data);
> > > + RCU_INIT_POINTER(ip->i_df.if_u1.if_data, NULL);
> > > ip->i_df.if_bytes = 0;
> > > }
> >
> > How do we get here in a way that the VFS will walk into this inode
> > during a lookup?
> >
> > I mean, the dentry has to be validated and held during the RCU path
> > walk, so if we are running a transaction to mark the inode as free
> > here it has already been unlinked and the dentry turned
> > negative. So anything that is doing a lockless pathwalk onto that
> > dentry *should* see that it is a negative dentry at this point and
> > hence nothing should be walking any further or trying to access the
> > link that was shared from ->get_link().
> >
> > AFAICT, that's what the sequence check bug you fixed in the previous
> > patch guarantees. It makes no difference if the unlinked inode has
> > been recycled or not, the lookup race condition is the same in that
> > the inode has gone through ->destroy_inode and is now owned by the
> > filesystem and not the VFS.
>
> Yes, the concern here is that without locking all the above can
> theoretically happen between the sequence number check and if_data
> being dereferenced.
It would be good to describe the race condition in the commit message,
because it's not at all obvious to readers how this race condition
is triggered.
> > Otherwise, it might just be best to memset the buffer to zero here
> > rather than free it, and leave it to be freed when the inode is
> > freed from the RCU callback in xfs_inode_free_callback() as per
> > normal.
Just as a FYI ext4_evict_inode() does exactly this with it's inline
symlink data buffer it passes to ->get_link() when it is freeing the
inode when the last reference to an unlinked inode goes away:
/*
* Set inode->i_size to 0 before calling ext4_truncate(). We need
* special handling of symlinks here because i_size is used to
* determine whether ext4_inode_info->i_data contains symlink data or
* block mappings. Setting i_size to 0 will remove its fast symlink
* status. Erase i_data so that it becomes a valid empty block map.
*/
if (ext4_inode_is_fast_symlink(inode))
memset(EXT4_I(inode)->i_data, 0, sizeof(EXT4_I(inode)->i_data));
inode->i_size = 0;
IOWs, if the pointer returned from ->get_link() on an ext4
filesystem is accessed by the VFS after ->evict() is called, then it
sees an empty buffer. IOWs, it's just not safe for the VFS to access
the inode's link buffer pointer the moment the last reference to an
unlinked inode goes away because it's contents are no longer
guaranteed to be valid.
I note that ext4 then immediately sets the VFS inode size to 0
length, indicating the link is no longer valid. It may well be that
XFS is not setting the VFS inode size to zero in this case (I don't
think the generic evict() path does that) so perhaps that's a guard
that could be used at the VFS level to avoid the race condition...
> My suggestion was to use .free_inode instead of .destroy_inode, the
> former always being called after an RCU grace period.
Which doesn't address the ext4 "zero the buffer in .evict", either.
And for XFS, it means we then likely need to add synchronise_rcu()
calls wherever we need to wait for inodes to be inactivated and/or
reclaimed because they won't even get queued for inactivation until
the current grace period expires. That's a potential locking
nightmare because inode inactivation and reclaim uses rcu protected
lockless algorithms to access XFS inodes without taking reference
counts....
I just can't see how this race condition is XFS specific and why
fixing it requires XFS to sepcifically handle it while we ignore
similar theoretical issues in other filesystems...
Cheers,
Dave.
--
Dave Chinner
david@fromorbit.com
next prev parent reply other threads:[~2021-11-14 23:19 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-11-11 3:39 [PATCH 1/2] vfs: check dentry is still valid in get_link() Ian Kent
2021-11-11 3:39 ` [PATCH 2/2] xfs: make sure link path does not go away at access Ian Kent
2021-11-11 16:08 ` Brian Foster
2021-11-11 23:10 ` Ian Kent
2021-11-12 11:47 ` Brian Foster
2021-11-13 5:17 ` Ian Kent
2021-11-12 0:32 ` Dave Chinner
2021-11-12 1:26 ` Ian Kent
2021-11-12 7:23 ` Miklos Szeredi
2021-11-14 23:18 ` Dave Chinner [this message]
2021-11-15 9:21 ` Miklos Szeredi
2021-11-15 22:24 ` Dave Chinner
2021-11-16 1:03 ` Ian Kent
2021-11-16 3:01 ` Dave Chinner
2021-11-16 10:12 ` Miklos Szeredi
2021-11-16 16:04 ` Brian Foster
2021-11-16 13:38 ` Ian Kent
2021-11-16 15:59 ` Brian Foster
2021-11-17 0:22 ` Dave Chinner
2021-11-17 2:19 ` Ian Kent
2021-11-17 21:06 ` Dave Chinner
2021-11-17 18:56 ` Brian Foster
2021-11-17 21:48 ` Dave Chinner
2021-11-19 19:44 ` Brian Foster
2021-11-22 0:08 ` Dave Chinner
2021-11-22 19:27 ` Brian Foster
2021-11-22 23:26 ` Dave Chinner
2021-11-24 20:56 ` Brian Foster
2021-12-15 3:54 ` Ian Kent
2021-12-15 5:06 ` Darrick J. Wong
2021-12-16 2:45 ` Ian Kent
2021-11-17 20:38 ` kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20211114231834.GM449541@dread.disaster.area \
--to=david@fromorbit.com \
--cc=bfoster@redhat.com \
--cc=dhowells@redhat.com \
--cc=djwong@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-xfs@vger.kernel.org \
--cc=miklos@szeredi.hu \
--cc=raven@themaw.net \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®