diff mbox series

nfsd: validate the nfsd_serv pointer before calling svc_wake_up

Message ID 20250125-kdevops-v1-1-a76cf79127b8@kernel.org (mailing list archive)
State Under Review
Delegated to: Chuck Lever
Headers show
Series nfsd: validate the nfsd_serv pointer before calling svc_wake_up | expand

Commit Message

Jeff Layton Jan. 26, 2025, 1:13 a.m. UTC
nfsd_file_dispose_list_delayed can be called from the filecache
laundrette, which is shut down after the nfsd threads are shut down and
the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
are no threads to wake.

Ensure that the nn->nfsd_serv pointer is non-NULL before calling
svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
svc_serv is not freed until after the filecache laundrette is cancelled.

Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
Reported-by: Salvatore Bonaccorso <carnil@debian.org>
Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
This is only lightly tested, but I think it will fix the bug that
Salvatore reported.
---
 fs/nfsd/filecache.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)


---
base-commit: 7541a5b8073cf0d9e2d288cac581f1aa6c11671d
change-id: 20250125-kdevops-0989825ae8db

Best regards,

Comments

NeilBrown Jan. 26, 2025, 2:39 a.m. UTC | #1
On Sun, 26 Jan 2025, Jeff Layton wrote:
> nfsd_file_dispose_list_delayed can be called from the filecache
> laundrette, which is shut down after the nfsd threads are shut down and
> the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> are no threads to wake.
> 
> Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> svc_serv is not freed until after the filecache laundrette is cancelled.
> 
> Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
> Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> This is only lightly tested, but I think it will fix the bug that
> Salvatore reported.
> ---
>  fs/nfsd/filecache.c | 11 ++++++++++-
>  1 file changed, 10 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> --- a/fs/nfsd/filecache.c
> +++ b/fs/nfsd/filecache.c
> @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
>  						struct nfsd_file, nf_gc);
>  		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
>  		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> +		struct svc_serv *serv;
>  
>  		spin_lock(&l->lock);
>  		list_move_tail(&nf->nf_gc, &l->freeme);
>  		spin_unlock(&l->lock);
> -		svc_wake_up(nn->nfsd_serv);
> +
> +		/*
> +		 * The filecache laundrette is shut down after the
> +		 * nn->nfsd_serv pointer is cleared, but before the
> +		 * svc_serv is freed.
> +		 */
> +		serv = nn->nfsd_serv;

I wonder if this should be READ_ONCE() to tell the compiler that we
could race with clearing nn->nfsd_serv.  Would the comment still be
needed?

Otherwise:

 Reviewed-by: NeilBrown <neilb@suse.de>

Thanks,
NeilBrown


> +		if (serv)
> +			svc_wake_up(serv);
>  	}
>  }
>  
> 
> ---
> base-commit: 7541a5b8073cf0d9e2d288cac581f1aa6c11671d
> change-id: 20250125-kdevops-0989825ae8db
> 
> Best regards,
> -- 
> Jeff Layton <jlayton@kernel.org>
> 
>
Jeff Layton Jan. 26, 2025, 12:36 p.m. UTC | #2
On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> On Sun, 26 Jan 2025, Jeff Layton wrote:
> > nfsd_file_dispose_list_delayed can be called from the filecache
> > laundrette, which is shut down after the nfsd threads are shut down and
> > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> > are no threads to wake.
> > 
> > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> > svc_serv is not freed until after the filecache laundrette is cancelled.
> > 
> > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
> > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > ---
> > This is only lightly tested, but I think it will fix the bug that
> > Salvatore reported.
> > ---
> >  fs/nfsd/filecache.c | 11 ++++++++++-
> >  1 file changed, 10 insertions(+), 1 deletion(-)
> > 
> > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > --- a/fs/nfsd/filecache.c
> > +++ b/fs/nfsd/filecache.c
> > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
> >  						struct nfsd_file, nf_gc);
> >  		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
> >  		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > +		struct svc_serv *serv;
> >  
> >  		spin_lock(&l->lock);
> >  		list_move_tail(&nf->nf_gc, &l->freeme);
> >  		spin_unlock(&l->lock);
> > -		svc_wake_up(nn->nfsd_serv);
> > +
> > +		/*
> > +		 * The filecache laundrette is shut down after the
> > +		 * nn->nfsd_serv pointer is cleared, but before the
> > +		 * svc_serv is freed.
> > +		 */
> > +		serv = nn->nfsd_serv;
> 
> I wonder if this should be READ_ONCE() to tell the compiler that we
> could race with clearing nn->nfsd_serv.  Would the comment still be
> needed?
> 

I think we need a comment at least. The linkage between the laundrette
and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
doesn't convey that well, and is unnecessary here.

> Otherwise:
> 
>  Reviewed-by: NeilBrown <neilb@suse.de>
> 

Thanks!

> 
> > +		if (serv)
> > +			svc_wake_up(serv);
> >  	}
> >  }
> >  
> > 
> > ---
> > base-commit: 7541a5b8073cf0d9e2d288cac581f1aa6c11671d
> > change-id: 20250125-kdevops-0989825ae8db
> > 
> > Best regards,
> > -- 
> > Jeff Layton <jlayton@kernel.org>
> > 
> > 
>
Chuck Lever Jan. 26, 2025, 6:58 p.m. UTC | #3
From: Chuck Lever <chuck.lever@oracle.com>

On Sat, 25 Jan 2025 20:13:18 -0500, Jeff Layton wrote:
> nfsd_file_dispose_list_delayed can be called from the filecache
> laundrette, which is shut down after the nfsd threads are shut down and
> the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> are no threads to wake.
> 
> Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> svc_serv is not freed until after the filecache laundrette is cancelled.
> 
> [...]

Applied to nfsd-testing, thanks!

[1/1] nfsd: validate the nfsd_serv pointer before calling svc_wake_up
      commit: a71d376104bd893a3fc349c5ffeb23a66ec78bcc

--
Chuck Lever
NeilBrown Jan. 26, 2025, 9:53 p.m. UTC | #4
On Sun, 26 Jan 2025, Jeff Layton wrote:
> On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > nfsd_file_dispose_list_delayed can be called from the filecache
> > > laundrette, which is shut down after the nfsd threads are shut down and
> > > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> > > are no threads to wake.
> > > 
> > > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> > > svc_serv is not freed until after the filecache laundrette is cancelled.
> > > 
> > > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
> > > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > > Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > ---
> > > This is only lightly tested, but I think it will fix the bug that
> > > Salvatore reported.
> > > ---
> > >  fs/nfsd/filecache.c | 11 ++++++++++-
> > >  1 file changed, 10 insertions(+), 1 deletion(-)
> > > 
> > > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > > index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > > --- a/fs/nfsd/filecache.c
> > > +++ b/fs/nfsd/filecache.c
> > > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
> > >  						struct nfsd_file, nf_gc);
> > >  		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
> > >  		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > > +		struct svc_serv *serv;
> > >  
> > >  		spin_lock(&l->lock);
> > >  		list_move_tail(&nf->nf_gc, &l->freeme);
> > >  		spin_unlock(&l->lock);
> > > -		svc_wake_up(nn->nfsd_serv);
> > > +
> > > +		/*
> > > +		 * The filecache laundrette is shut down after the
> > > +		 * nn->nfsd_serv pointer is cleared, but before the
> > > +		 * svc_serv is freed.
> > > +		 */
> > > +		serv = nn->nfsd_serv;
> > 
> > I wonder if this should be READ_ONCE() to tell the compiler that we
> > could race with clearing nn->nfsd_serv.  Would the comment still be
> > needed?
> > 
> 
> I think we need a comment at least. The linkage between the laundrette
> and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> doesn't convey that well, and is unnecessary here.

Why do you say "is unnecessary here" ?
If the code were
   if (nn->nfsd_serv)
            svc_wake_up(nn->nfsd_serv);
that would be wrong as nn->nfds_serv could be set to NULL between the
two.
And the C compile is allowed to load the value twice because the C memory
model declares that would have the same effect.
While I doubt it would actually change how the code is compiled, I think
we should have READ_ONCE() here (and I've been wrong before about what
the compiler will actually do).

Thanks,
NeilBrown
Jeff Layton Jan. 26, 2025, 10:48 p.m. UTC | #5
On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
> On Sun, 26 Jan 2025, Jeff Layton wrote:
> > On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > nfsd_file_dispose_list_delayed can be called from the filecache
> > > > laundrette, which is shut down after the nfsd threads are shut down and
> > > > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> > > > are no threads to wake.
> > > > 
> > > > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > > > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> > > > svc_serv is not freed until after the filecache laundrette is cancelled.
> > > > 
> > > > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
> > > > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > > > Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > > ---
> > > > This is only lightly tested, but I think it will fix the bug that
> > > > Salvatore reported.
> > > > ---
> > > >  fs/nfsd/filecache.c | 11 ++++++++++-
> > > >  1 file changed, 10 insertions(+), 1 deletion(-)
> > > > 
> > > > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > > > index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > > > --- a/fs/nfsd/filecache.c
> > > > +++ b/fs/nfsd/filecache.c
> > > > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
> > > >  						struct nfsd_file, nf_gc);
> > > >  		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
> > > >  		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > > > +		struct svc_serv *serv;
> > > >  
> > > >  		spin_lock(&l->lock);
> > > >  		list_move_tail(&nf->nf_gc, &l->freeme);
> > > >  		spin_unlock(&l->lock);
> > > > -		svc_wake_up(nn->nfsd_serv);
> > > > +
> > > > +		/*
> > > > +		 * The filecache laundrette is shut down after the
> > > > +		 * nn->nfsd_serv pointer is cleared, but before the
> > > > +		 * svc_serv is freed.
> > > > +		 */
> > > > +		serv = nn->nfsd_serv;
> > > 
> > > I wonder if this should be READ_ONCE() to tell the compiler that we
> > > could race with clearing nn->nfsd_serv.  Would the comment still be
> > > needed?
> > > 
> > 
> > I think we need a comment at least. The linkage between the laundrette
> > and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> > doesn't convey that well, and is unnecessary here.
> 
> Why do you say "is unnecessary here" ?
> If the code were
>    if (nn->nfsd_serv)
>             svc_wake_up(nn->nfsd_serv);
> that would be wrong as nn->nfds_serv could be set to NULL between the
> two.
> And the C compile is allowed to load the value twice because the C memory
> model declares that would have the same effect.
> While I doubt it would actually change how the code is compiled, I think
> we should have READ_ONCE() here (and I've been wrong before about what
> the compiler will actually do).
> 
> 

It's unnecessary because the outcome of either case is acceptable.

When racing with shutdown, either it's NULL and the laundrette won't
call svc_wake_up(), or it's non-NULL and it will. In the non-NULL case,
the call to svc_wake_up() will be a no-op because the threads are shut
down.

The vastly common case in this code is that this pointer will be non-
NULL, because the server is running (i.e. not racing with shutdown). I
don't see the need in making all of those accesses volatile.
NeilBrown Jan. 27, 2025, 12:15 a.m. UTC | #6
On Mon, 27 Jan 2025, Jeff Layton wrote:
> On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
> > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > nfsd_file_dispose_list_delayed can be called from the filecache
> > > > > laundrette, which is shut down after the nfsd threads are shut down and
> > > > > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> > > > > are no threads to wake.
> > > > > 
> > > > > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > > > > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> > > > > svc_serv is not freed until after the filecache laundrette is cancelled.
> > > > > 
> > > > > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
> > > > > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > > > > Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > > > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > > > ---
> > > > > This is only lightly tested, but I think it will fix the bug that
> > > > > Salvatore reported.
> > > > > ---
> > > > >  fs/nfsd/filecache.c | 11 ++++++++++-
> > > > >  1 file changed, 10 insertions(+), 1 deletion(-)
> > > > > 
> > > > > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > > > > index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > > > > --- a/fs/nfsd/filecache.c
> > > > > +++ b/fs/nfsd/filecache.c
> > > > > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
> > > > >  						struct nfsd_file, nf_gc);
> > > > >  		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
> > > > >  		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > > > > +		struct svc_serv *serv;
> > > > >  
> > > > >  		spin_lock(&l->lock);
> > > > >  		list_move_tail(&nf->nf_gc, &l->freeme);
> > > > >  		spin_unlock(&l->lock);
> > > > > -		svc_wake_up(nn->nfsd_serv);
> > > > > +
> > > > > +		/*
> > > > > +		 * The filecache laundrette is shut down after the
> > > > > +		 * nn->nfsd_serv pointer is cleared, but before the
> > > > > +		 * svc_serv is freed.
> > > > > +		 */
> > > > > +		serv = nn->nfsd_serv;
> > > > 
> > > > I wonder if this should be READ_ONCE() to tell the compiler that we
> > > > could race with clearing nn->nfsd_serv.  Would the comment still be
> > > > needed?
> > > > 
> > > 
> > > I think we need a comment at least. The linkage between the laundrette
> > > and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> > > doesn't convey that well, and is unnecessary here.
> > 
> > Why do you say "is unnecessary here" ?
> > If the code were
> >    if (nn->nfsd_serv)
> >             svc_wake_up(nn->nfsd_serv);
> > that would be wrong as nn->nfds_serv could be set to NULL between the
> > two.
> > And the C compile is allowed to load the value twice because the C memory
> > model declares that would have the same effect.
> > While I doubt it would actually change how the code is compiled, I think
> > we should have READ_ONCE() here (and I've been wrong before about what
> > the compiler will actually do).
> > 
> > 
> 
> It's unnecessary because the outcome of either case is acceptable.
> 
> When racing with shutdown, either it's NULL and the laundrette won't
> call svc_wake_up(), or it's non-NULL and it will. In the non-NULL case,
> the call to svc_wake_up() will be a no-op because the threads are shut
> down.
> 
> The vastly common case in this code is that this pointer will be non-
> NULL, because the server is running (i.e. not racing with shutdown). I
> don't see the need in making all of those accesses volatile.

One of us is confused.  I hope it isn't me.

The hypothetical problem I see is that the C compiler could generate
code to load the value "nn->nfsd_serv" twice.  The first time it is not
NULL, the second time it is NULL.
The first is used for the test, the second is passed to svc_wake_up().

Unlikely though this is, it is possible and READ_ONCE() is designed
precisely to prevent this.
To quote from include/asm-generic/rwonce.h it will
 "Prevent the compiler from merging or refetching reads"

A "volatile" access does not add any cost (in this case).  What it does
is break any aliasing that the compile might have deduced.
Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
won't think it has the result of READ_ONCE(nn->nfsd_serv) in that register.
And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
won't decide that it can just read nn->nfsd_serv again.  It MUST keep
the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not needed
any more.

NeilBrown
Jeff Layton Jan. 27, 2025, 1:07 p.m. UTC | #7
On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
> On Mon, 27 Jan 2025, Jeff Layton wrote:
> > On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
> > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> > > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > > nfsd_file_dispose_list_delayed can be called from the filecache
> > > > > > laundrette, which is shut down after the nfsd threads are shut down and
> > > > > > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> > > > > > are no threads to wake.
> > > > > > 
> > > > > > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > > > > > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> > > > > > svc_serv is not freed until after the filecache laundrette is cancelled.
> > > > > > 
> > > > > > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
> > > > > > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > > > > > Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > > > > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > > > > ---
> > > > > > This is only lightly tested, but I think it will fix the bug that
> > > > > > Salvatore reported.
> > > > > > ---
> > > > > >  fs/nfsd/filecache.c | 11 ++++++++++-
> > > > > >  1 file changed, 10 insertions(+), 1 deletion(-)
> > > > > > 
> > > > > > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > > > > > index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > > > > > --- a/fs/nfsd/filecache.c
> > > > > > +++ b/fs/nfsd/filecache.c
> > > > > > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
> > > > > >  						struct nfsd_file, nf_gc);
> > > > > >  		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
> > > > > >  		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > > > > > +		struct svc_serv *serv;
> > > > > >  
> > > > > >  		spin_lock(&l->lock);
> > > > > >  		list_move_tail(&nf->nf_gc, &l->freeme);
> > > > > >  		spin_unlock(&l->lock);
> > > > > > -		svc_wake_up(nn->nfsd_serv);
> > > > > > +
> > > > > > +		/*
> > > > > > +		 * The filecache laundrette is shut down after the
> > > > > > +		 * nn->nfsd_serv pointer is cleared, but before the
> > > > > > +		 * svc_serv is freed.
> > > > > > +		 */
> > > > > > +		serv = nn->nfsd_serv;
> > > > > 
> > > > > I wonder if this should be READ_ONCE() to tell the compiler that we
> > > > > could race with clearing nn->nfsd_serv.  Would the comment still be
> > > > > needed?
> > > > > 
> > > > 
> > > > I think we need a comment at least. The linkage between the laundrette
> > > > and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> > > > doesn't convey that well, and is unnecessary here.
> > > 
> > > Why do you say "is unnecessary here" ?
> > > If the code were
> > >    if (nn->nfsd_serv)
> > >             svc_wake_up(nn->nfsd_serv);
> > > that would be wrong as nn->nfds_serv could be set to NULL between the
> > > two.
> > > And the C compile is allowed to load the value twice because the C memory
> > > model declares that would have the same effect.
> > > While I doubt it would actually change how the code is compiled, I think
> > > we should have READ_ONCE() here (and I've been wrong before about what
> > > the compiler will actually do).
> > > 
> > > 
> > 
> > It's unnecessary because the outcome of either case is acceptable.
> > 
> > When racing with shutdown, either it's NULL and the laundrette won't
> > call svc_wake_up(), or it's non-NULL and it will. In the non-NULL case,
> > the call to svc_wake_up() will be a no-op because the threads are shut
> > down.
> > 
> > The vastly common case in this code is that this pointer will be non-
> > NULL, because the server is running (i.e. not racing with shutdown). I
> > don't see the need in making all of those accesses volatile.
> 
> One of us is confused.  I hope it isn't me.
> 

It's probably me. I think you have a much better understanding of
compiler design than I do. Still...

> The hypothetical problem I see is that the C compiler could generate
> code to load the value "nn->nfsd_serv" twice.  The first time it is not
> NULL, the second time it is NULL.
> The first is used for the test, the second is passed to svc_wake_up().
> 
> Unlikely though this is, it is possible and READ_ONCE() is designed
> precisely to prevent this.
> To quote from include/asm-generic/rwonce.h it will
>  "Prevent the compiler from merging or refetching reads"
> 
> A "volatile" access does not add any cost (in this case).  What it does
> is break any aliasing that the compile might have deduced.
> Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
> won't think it has the result of READ_ONCE(nn->nfsd_serv) in that register.
> And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
> won't decide that it can just read nn->nfsd_serv again.  It MUST keep
> the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not needed
> any more.

I'm mainly just considering the resulting pointer. There are two
possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
pointer that points to the svc_serv, or it's NULL. The resulting code
can handle either case, so it doesn't seem like adding READ_ONCE() will
create any material difference here.

Maybe I should ask it this way: What bad outcome could result if we
don't add READ_ONCE() here?
Chuck Lever Jan. 27, 2025, 1:22 p.m. UTC | #8
On 1/27/25 8:07 AM, Jeff Layton wrote:
> On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
>> On Mon, 27 Jan 2025, Jeff Layton wrote:
>>> On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
>>>>> On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
>>>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
>>>>>>> nfsd_file_dispose_list_delayed can be called from the filecache
>>>>>>> laundrette, which is shut down after the nfsd threads are shut down and
>>>>>>> the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
>>>>>>> are no threads to wake.
>>>>>>>
>>>>>>> Ensure that the nn->nfsd_serv pointer is non-NULL before calling
>>>>>>> svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
>>>>>>> svc_serv is not freed until after the filecache laundrette is cancelled.
>>>>>>>
>>>>>>> Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
>>>>>>> Reported-by: Salvatore Bonaccorso <carnil@debian.org>
>>>>>>> Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
>>>>>>> Signed-off-by: Jeff Layton <jlayton@kernel.org>
>>>>>>> ---
>>>>>>> This is only lightly tested, but I think it will fix the bug that
>>>>>>> Salvatore reported.
>>>>>>> ---
>>>>>>>   fs/nfsd/filecache.c | 11 ++++++++++-
>>>>>>>   1 file changed, 10 insertions(+), 1 deletion(-)
>>>>>>>
>>>>>>> diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
>>>>>>> index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
>>>>>>> --- a/fs/nfsd/filecache.c
>>>>>>> +++ b/fs/nfsd/filecache.c
>>>>>>> @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
>>>>>>>   						struct nfsd_file, nf_gc);
>>>>>>>   		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
>>>>>>>   		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
>>>>>>> +		struct svc_serv *serv;
>>>>>>>   
>>>>>>>   		spin_lock(&l->lock);
>>>>>>>   		list_move_tail(&nf->nf_gc, &l->freeme);
>>>>>>>   		spin_unlock(&l->lock);
>>>>>>> -		svc_wake_up(nn->nfsd_serv);
>>>>>>> +
>>>>>>> +		/*
>>>>>>> +		 * The filecache laundrette is shut down after the
>>>>>>> +		 * nn->nfsd_serv pointer is cleared, but before the
>>>>>>> +		 * svc_serv is freed.
>>>>>>> +		 */
>>>>>>> +		serv = nn->nfsd_serv;
>>>>>>
>>>>>> I wonder if this should be READ_ONCE() to tell the compiler that we
>>>>>> could race with clearing nn->nfsd_serv.  Would the comment still be
>>>>>> needed?
>>>>>>
>>>>>
>>>>> I think we need a comment at least. The linkage between the laundrette
>>>>> and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
>>>>> doesn't convey that well, and is unnecessary here.
>>>>
>>>> Why do you say "is unnecessary here" ?
>>>> If the code were
>>>>     if (nn->nfsd_serv)
>>>>              svc_wake_up(nn->nfsd_serv);
>>>> that would be wrong as nn->nfds_serv could be set to NULL between the
>>>> two.
>>>> And the C compile is allowed to load the value twice because the C memory
>>>> model declares that would have the same effect.
>>>> While I doubt it would actually change how the code is compiled, I think
>>>> we should have READ_ONCE() here (and I've been wrong before about what
>>>> the compiler will actually do).
>>>>
>>>>
>>>
>>> It's unnecessary because the outcome of either case is acceptable.
>>>
>>> When racing with shutdown, either it's NULL and the laundrette won't
>>> call svc_wake_up(), or it's non-NULL and it will. In the non-NULL case,
>>> the call to svc_wake_up() will be a no-op because the threads are shut
>>> down.
>>>
>>> The vastly common case in this code is that this pointer will be non-
>>> NULL, because the server is running (i.e. not racing with shutdown). I
>>> don't see the need in making all of those accesses volatile.
>>
>> One of us is confused.  I hope it isn't me.
>>
> 
> It's probably me. I think you have a much better understanding of
> compiler design than I do. Still...
> 
>> The hypothetical problem I see is that the C compiler could generate
>> code to load the value "nn->nfsd_serv" twice.  The first time it is not
>> NULL, the second time it is NULL.
>> The first is used for the test, the second is passed to svc_wake_up().
>>
>> Unlikely though this is, it is possible and READ_ONCE() is designed
>> precisely to prevent this.
>> To quote from include/asm-generic/rwonce.h it will
>>   "Prevent the compiler from merging or refetching reads"
>>
>> A "volatile" access does not add any cost (in this case).  What it does
>> is break any aliasing that the compile might have deduced.
>> Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
>> won't think it has the result of READ_ONCE(nn->nfsd_serv) in that register.
>> And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
>> won't decide that it can just read nn->nfsd_serv again.  It MUST keep
>> the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not needed
>> any more.
> 
> I'm mainly just considering the resulting pointer. There are two
> possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
> pointer that points to the svc_serv, or it's NULL. The resulting code
> can handle either case, so it doesn't seem like adding READ_ONCE() will
> create any material difference here.
> 
> Maybe I should ask it this way: What bad outcome could result if we
> don't add READ_ONCE() here?

Neil just described it. The compiler would generate two load operations,
one for the test and one for the function call argument. The first load
can retrieve a non-NULL address, and the second a NULL address.

I agree a READ_ONCE() is necessary.
Jeff Layton Jan. 27, 2025, 1:32 p.m. UTC | #9
On Mon, 2025-01-27 at 08:22 -0500, Chuck Lever wrote:
> On 1/27/25 8:07 AM, Jeff Layton wrote:
> > On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
> > > On Mon, 27 Jan 2025, Jeff Layton wrote:
> > > > On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
> > > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > > On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> > > > > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > > > > nfsd_file_dispose_list_delayed can be called from the filecache
> > > > > > > > laundrette, which is shut down after the nfsd threads are shut down and
> > > > > > > > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> > > > > > > > are no threads to wake.
> > > > > > > > 
> > > > > > > > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > > > > > > > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> > > > > > > > svc_serv is not freed until after the filecache laundrette is cancelled.
> > > > > > > > 
> > > > > > > > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
> > > > > > > > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > > > > > > > Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > > > > > > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > > > > > > ---
> > > > > > > > This is only lightly tested, but I think it will fix the bug that
> > > > > > > > Salvatore reported.
> > > > > > > > ---
> > > > > > > >   fs/nfsd/filecache.c | 11 ++++++++++-
> > > > > > > >   1 file changed, 10 insertions(+), 1 deletion(-)
> > > > > > > > 
> > > > > > > > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > > > > > > > index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > > > > > > > --- a/fs/nfsd/filecache.c
> > > > > > > > +++ b/fs/nfsd/filecache.c
> > > > > > > > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
> > > > > > > >   						struct nfsd_file, nf_gc);
> > > > > > > >   		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
> > > > > > > >   		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > > > > > > > +		struct svc_serv *serv;
> > > > > > > >   
> > > > > > > >   		spin_lock(&l->lock);
> > > > > > > >   		list_move_tail(&nf->nf_gc, &l->freeme);
> > > > > > > >   		spin_unlock(&l->lock);
> > > > > > > > -		svc_wake_up(nn->nfsd_serv);
> > > > > > > > +
> > > > > > > > +		/*
> > > > > > > > +		 * The filecache laundrette is shut down after the
> > > > > > > > +		 * nn->nfsd_serv pointer is cleared, but before the
> > > > > > > > +		 * svc_serv is freed.
> > > > > > > > +		 */
> > > > > > > > +		serv = nn->nfsd_serv;
> > > > > > > 
> > > > > > > I wonder if this should be READ_ONCE() to tell the compiler that we
> > > > > > > could race with clearing nn->nfsd_serv.  Would the comment still be
> > > > > > > needed?
> > > > > > > 
> > > > > > 
> > > > > > I think we need a comment at least. The linkage between the laundrette
> > > > > > and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> > > > > > doesn't convey that well, and is unnecessary here.
> > > > > 
> > > > > Why do you say "is unnecessary here" ?
> > > > > If the code were
> > > > >     if (nn->nfsd_serv)
> > > > >              svc_wake_up(nn->nfsd_serv);
> > > > > that would be wrong as nn->nfds_serv could be set to NULL between the
> > > > > two.
> > > > > And the C compile is allowed to load the value twice because the C memory
> > > > > model declares that would have the same effect.
> > > > > While I doubt it would actually change how the code is compiled, I think
> > > > > we should have READ_ONCE() here (and I've been wrong before about what
> > > > > the compiler will actually do).
> > > > > 
> > > > > 
> > > > 
> > > > It's unnecessary because the outcome of either case is acceptable.
> > > > 
> > > > When racing with shutdown, either it's NULL and the laundrette won't
> > > > call svc_wake_up(), or it's non-NULL and it will. In the non-NULL case,
> > > > the call to svc_wake_up() will be a no-op because the threads are shut
> > > > down.
> > > > 
> > > > The vastly common case in this code is that this pointer will be non-
> > > > NULL, because the server is running (i.e. not racing with shutdown). I
> > > > don't see the need in making all of those accesses volatile.
> > > 
> > > One of us is confused.  I hope it isn't me.
> > > 
> > 
> > It's probably me. I think you have a much better understanding of
> > compiler design than I do. Still...
> > 
> > > The hypothetical problem I see is that the C compiler could generate
> > > code to load the value "nn->nfsd_serv" twice.  The first time it is not
> > > NULL, the second time it is NULL.
> > > The first is used for the test, the second is passed to svc_wake_up().
> > > 
> > > Unlikely though this is, it is possible and READ_ONCE() is designed
> > > precisely to prevent this.
> > > To quote from include/asm-generic/rwonce.h it will
> > >   "Prevent the compiler from merging or refetching reads"
> > > 
> > > A "volatile" access does not add any cost (in this case).  What it does
> > > is break any aliasing that the compile might have deduced.
> > > Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
> > > won't think it has the result of READ_ONCE(nn->nfsd_serv) in that register.
> > > And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
> > > won't decide that it can just read nn->nfsd_serv again.  It MUST keep
> > > the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not needed
> > > any more.
> > 
> > I'm mainly just considering the resulting pointer. There are two
> > possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
> > pointer that points to the svc_serv, or it's NULL. The resulting code
> > can handle either case, so it doesn't seem like adding READ_ONCE() will
> > create any material difference here.
> > 
> > Maybe I should ask it this way: What bad outcome could result if we
> > don't add READ_ONCE() here?
> 
> Neil just described it. The compiler would generate two load operations,
> one for the test and one for the function call argument. The first load
> can retrieve a non-NULL address, and the second a NULL address.
> 
> I agree a READ_ONCE() is necessary.
> 
> 

Now I'm confused:

                struct svc_serv *serv;

		[...]

                /*
                 * The filecache laundrette is shut down after the
                 * nn->nfsd_serv pointer is cleared, but before the
                 * svc_serv is freed.
                 */
                serv = nn->nfsd_serv;
                if (serv)
                        svc_wake_up(serv);

This code is explicitly asking to fetch nn->nfsd_serv into the serv
variable, and then is testing that copy of the pointer and passing it
into svc_wake_up().

How is the compiler allowed to suddenly refetch a NULL pointer into
serv after testing that serv is non-NULL?
Chuck Lever Jan. 27, 2025, 1:39 p.m. UTC | #10
On 1/27/25 8:32 AM, Jeff Layton wrote:
> On Mon, 2025-01-27 at 08:22 -0500, Chuck Lever wrote:
>> On 1/27/25 8:07 AM, Jeff Layton wrote:
>>> On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
>>>> On Mon, 27 Jan 2025, Jeff Layton wrote:
>>>>> On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
>>>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
>>>>>>> On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
>>>>>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
>>>>>>>>> nfsd_file_dispose_list_delayed can be called from the filecache
>>>>>>>>> laundrette, which is shut down after the nfsd threads are shut down and
>>>>>>>>> the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
>>>>>>>>> are no threads to wake.
>>>>>>>>>
>>>>>>>>> Ensure that the nn->nfsd_serv pointer is non-NULL before calling
>>>>>>>>> svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
>>>>>>>>> svc_serv is not freed until after the filecache laundrette is cancelled.
>>>>>>>>>
>>>>>>>>> Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue")
>>>>>>>>> Reported-by: Salvatore Bonaccorso <carnil@debian.org>
>>>>>>>>> Closes: https://lore.kernel.org/linux-nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
>>>>>>>>> Signed-off-by: Jeff Layton <jlayton@kernel.org>
>>>>>>>>> ---
>>>>>>>>> This is only lightly tested, but I think it will fix the bug that
>>>>>>>>> Salvatore reported.
>>>>>>>>> ---
>>>>>>>>>    fs/nfsd/filecache.c | 11 ++++++++++-
>>>>>>>>>    1 file changed, 10 insertions(+), 1 deletion(-)
>>>>>>>>>
>>>>>>>>> diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
>>>>>>>>> index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
>>>>>>>>> --- a/fs/nfsd/filecache.c
>>>>>>>>> +++ b/fs/nfsd/filecache.c
>>>>>>>>> @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
>>>>>>>>>    						struct nfsd_file, nf_gc);
>>>>>>>>>    		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
>>>>>>>>>    		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
>>>>>>>>> +		struct svc_serv *serv;
>>>>>>>>>    
>>>>>>>>>    		spin_lock(&l->lock);
>>>>>>>>>    		list_move_tail(&nf->nf_gc, &l->freeme);
>>>>>>>>>    		spin_unlock(&l->lock);
>>>>>>>>> -		svc_wake_up(nn->nfsd_serv);
>>>>>>>>> +
>>>>>>>>> +		/*
>>>>>>>>> +		 * The filecache laundrette is shut down after the
>>>>>>>>> +		 * nn->nfsd_serv pointer is cleared, but before the
>>>>>>>>> +		 * svc_serv is freed.
>>>>>>>>> +		 */
>>>>>>>>> +		serv = nn->nfsd_serv;
>>>>>>>>
>>>>>>>> I wonder if this should be READ_ONCE() to tell the compiler that we
>>>>>>>> could race with clearing nn->nfsd_serv.  Would the comment still be
>>>>>>>> needed?
>>>>>>>>
>>>>>>>
>>>>>>> I think we need a comment at least. The linkage between the laundrette
>>>>>>> and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
>>>>>>> doesn't convey that well, and is unnecessary here.
>>>>>>
>>>>>> Why do you say "is unnecessary here" ?
>>>>>> If the code were
>>>>>>      if (nn->nfsd_serv)
>>>>>>               svc_wake_up(nn->nfsd_serv);
>>>>>> that would be wrong as nn->nfds_serv could be set to NULL between the
>>>>>> two.
>>>>>> And the C compile is allowed to load the value twice because the C memory
>>>>>> model declares that would have the same effect.
>>>>>> While I doubt it would actually change how the code is compiled, I think
>>>>>> we should have READ_ONCE() here (and I've been wrong before about what
>>>>>> the compiler will actually do).
>>>>>>
>>>>>>
>>>>>
>>>>> It's unnecessary because the outcome of either case is acceptable.
>>>>>
>>>>> When racing with shutdown, either it's NULL and the laundrette won't
>>>>> call svc_wake_up(), or it's non-NULL and it will. In the non-NULL case,
>>>>> the call to svc_wake_up() will be a no-op because the threads are shut
>>>>> down.
>>>>>
>>>>> The vastly common case in this code is that this pointer will be non-
>>>>> NULL, because the server is running (i.e. not racing with shutdown). I
>>>>> don't see the need in making all of those accesses volatile.
>>>>
>>>> One of us is confused.  I hope it isn't me.
>>>>
>>>
>>> It's probably me. I think you have a much better understanding of
>>> compiler design than I do. Still...
>>>
>>>> The hypothetical problem I see is that the C compiler could generate
>>>> code to load the value "nn->nfsd_serv" twice.  The first time it is not
>>>> NULL, the second time it is NULL.
>>>> The first is used for the test, the second is passed to svc_wake_up().
>>>>
>>>> Unlikely though this is, it is possible and READ_ONCE() is designed
>>>> precisely to prevent this.
>>>> To quote from include/asm-generic/rwonce.h it will
>>>>    "Prevent the compiler from merging or refetching reads"
>>>>
>>>> A "volatile" access does not add any cost (in this case).  What it does
>>>> is break any aliasing that the compile might have deduced.
>>>> Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
>>>> won't think it has the result of READ_ONCE(nn->nfsd_serv) in that register.
>>>> And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
>>>> won't decide that it can just read nn->nfsd_serv again.  It MUST keep
>>>> the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not needed
>>>> any more.
>>>
>>> I'm mainly just considering the resulting pointer. There are two
>>> possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
>>> pointer that points to the svc_serv, or it's NULL. The resulting code
>>> can handle either case, so it doesn't seem like adding READ_ONCE() will
>>> create any material difference here.
>>>
>>> Maybe I should ask it this way: What bad outcome could result if we
>>> don't add READ_ONCE() here?
>>
>> Neil just described it. The compiler would generate two load operations,
>> one for the test and one for the function call argument. The first load
>> can retrieve a non-NULL address, and the second a NULL address.
>>
>> I agree a READ_ONCE() is necessary.
>>
>>
> 
> Now I'm confused:
> 
>                  struct svc_serv *serv;
> 
> 		[...]
> 
>                  /*
>                   * The filecache laundrette is shut down after the
>                   * nn->nfsd_serv pointer is cleared, but before the
>                   * svc_serv is freed.
>                   */
>                  serv = nn->nfsd_serv;
>                  if (serv)
>                          svc_wake_up(serv);
> 
> This code is explicitly asking to fetch nn->nfsd_serv into the serv
> variable, and then is testing that copy of the pointer and passing it
> into svc_wake_up().
> 
> How is the compiler allowed to suddenly refetch a NULL pointer into
> serv after testing that serv is non-NULL?

There's nothing here that tells the compiler it is not allowed to
optimize this into two separate fetches if it feels that is better
code. READ_ONCE is what tells the compiler we do not want that re-
organization /ever/.
Chuck Lever Jan. 27, 2025, 2:03 p.m. UTC | #11
On 1/27/25 8:39 AM, Chuck Lever wrote:
> On 1/27/25 8:32 AM, Jeff Layton wrote:
>> On Mon, 2025-01-27 at 08:22 -0500, Chuck Lever wrote:
>>> On 1/27/25 8:07 AM, Jeff Layton wrote:
>>>> On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
>>>>> On Mon, 27 Jan 2025, Jeff Layton wrote:
>>>>>> On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
>>>>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
>>>>>>>> On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
>>>>>>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
>>>>>>>>>> nfsd_file_dispose_list_delayed can be called from the filecache
>>>>>>>>>> laundrette, which is shut down after the nfsd threads are shut 
>>>>>>>>>> down and
>>>>>>>>>> the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL 
>>>>>>>>>> then there
>>>>>>>>>> are no threads to wake.
>>>>>>>>>>
>>>>>>>>>> Ensure that the nn->nfsd_serv pointer is non-NULL before calling
>>>>>>>>>> svc_wake_up in nfsd_file_dispose_list_delayed. This is safe 
>>>>>>>>>> since the
>>>>>>>>>> svc_serv is not freed until after the filecache laundrette is 
>>>>>>>>>> cancelled.
>>>>>>>>>>
>>>>>>>>>> Fixes: ffb402596147 ("nfsd: Don't leave work of closing files 
>>>>>>>>>> to a work queue")
>>>>>>>>>> Reported-by: Salvatore Bonaccorso <carnil@debian.org>
>>>>>>>>>> Closes: https://lore.kernel.org/linux- 
>>>>>>>>>> nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
>>>>>>>>>> Signed-off-by: Jeff Layton <jlayton@kernel.org>
>>>>>>>>>> ---
>>>>>>>>>> This is only lightly tested, but I think it will fix the bug that
>>>>>>>>>> Salvatore reported.
>>>>>>>>>> ---
>>>>>>>>>>    fs/nfsd/filecache.c | 11 ++++++++++-
>>>>>>>>>>    1 file changed, 10 insertions(+), 1 deletion(-)
>>>>>>>>>>
>>>>>>>>>> diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
>>>>>>>>>> index 
>>>>>>>>>> e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
>>>>>>>>>> --- a/fs/nfsd/filecache.c
>>>>>>>>>> +++ b/fs/nfsd/filecache.c
>>>>>>>>>> @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct 
>>>>>>>>>> list_head *dispose)
>>>>>>>>>>                            struct nfsd_file, nf_gc);
>>>>>>>>>>            struct nfsd_net *nn = net_generic(nf->nf_net, 
>>>>>>>>>> nfsd_net_id);
>>>>>>>>>>            struct nfsd_fcache_disposal *l = nn->fcache_disposal;
>>>>>>>>>> +        struct svc_serv *serv;
>>>>>>>>>>            spin_lock(&l->lock);
>>>>>>>>>>            list_move_tail(&nf->nf_gc, &l->freeme);
>>>>>>>>>>            spin_unlock(&l->lock);
>>>>>>>>>> -        svc_wake_up(nn->nfsd_serv);
>>>>>>>>>> +
>>>>>>>>>> +        /*
>>>>>>>>>> +         * The filecache laundrette is shut down after the
>>>>>>>>>> +         * nn->nfsd_serv pointer is cleared, but before the
>>>>>>>>>> +         * svc_serv is freed.
>>>>>>>>>> +         */
>>>>>>>>>> +        serv = nn->nfsd_serv;
>>>>>>>>>
>>>>>>>>> I wonder if this should be READ_ONCE() to tell the compiler 
>>>>>>>>> that we
>>>>>>>>> could race with clearing nn->nfsd_serv.  Would the comment 
>>>>>>>>> still be
>>>>>>>>> needed?
>>>>>>>>>
>>>>>>>>
>>>>>>>> I think we need a comment at least. The linkage between the 
>>>>>>>> laundrette
>>>>>>>> and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
>>>>>>>> doesn't convey that well, and is unnecessary here.
>>>>>>>
>>>>>>> Why do you say "is unnecessary here" ?
>>>>>>> If the code were
>>>>>>>      if (nn->nfsd_serv)
>>>>>>>               svc_wake_up(nn->nfsd_serv);
>>>>>>> that would be wrong as nn->nfds_serv could be set to NULL between 
>>>>>>> the
>>>>>>> two.
>>>>>>> And the C compile is allowed to load the value twice because the 
>>>>>>> C memory
>>>>>>> model declares that would have the same effect.
>>>>>>> While I doubt it would actually change how the code is compiled, 
>>>>>>> I think
>>>>>>> we should have READ_ONCE() here (and I've been wrong before about 
>>>>>>> what
>>>>>>> the compiler will actually do).
>>>>>>>
>>>>>>>
>>>>>>
>>>>>> It's unnecessary because the outcome of either case is acceptable.
>>>>>>
>>>>>> When racing with shutdown, either it's NULL and the laundrette won't
>>>>>> call svc_wake_up(), or it's non-NULL and it will. In the non-NULL 
>>>>>> case,
>>>>>> the call to svc_wake_up() will be a no-op because the threads are 
>>>>>> shut
>>>>>> down.
>>>>>>
>>>>>> The vastly common case in this code is that this pointer will be non-
>>>>>> NULL, because the server is running (i.e. not racing with 
>>>>>> shutdown). I
>>>>>> don't see the need in making all of those accesses volatile.
>>>>>
>>>>> One of us is confused.  I hope it isn't me.
>>>>>
>>>>
>>>> It's probably me. I think you have a much better understanding of
>>>> compiler design than I do. Still...
>>>>
>>>>> The hypothetical problem I see is that the C compiler could generate
>>>>> code to load the value "nn->nfsd_serv" twice.  The first time it is 
>>>>> not
>>>>> NULL, the second time it is NULL.
>>>>> The first is used for the test, the second is passed to svc_wake_up().
>>>>>
>>>>> Unlikely though this is, it is possible and READ_ONCE() is designed
>>>>> precisely to prevent this.
>>>>> To quote from include/asm-generic/rwonce.h it will
>>>>>    "Prevent the compiler from merging or refetching reads"
>>>>>
>>>>> A "volatile" access does not add any cost (in this case).  What it 
>>>>> does
>>>>> is break any aliasing that the compile might have deduced.
>>>>> Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
>>>>> won't think it has the result of READ_ONCE(nn->nfsd_serv) in that 
>>>>> register.
>>>>> And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
>>>>> won't decide that it can just read nn->nfsd_serv again.  It MUST keep
>>>>> the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not 
>>>>> needed
>>>>> any more.
>>>>
>>>> I'm mainly just considering the resulting pointer. There are two
>>>> possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
>>>> pointer that points to the svc_serv, or it's NULL. The resulting code
>>>> can handle either case, so it doesn't seem like adding READ_ONCE() will
>>>> create any material difference here.
>>>>
>>>> Maybe I should ask it this way: What bad outcome could result if we
>>>> don't add READ_ONCE() here?
>>>
>>> Neil just described it. The compiler would generate two load operations,
>>> one for the test and one for the function call argument. The first load
>>> can retrieve a non-NULL address, and the second a NULL address.
>>>
>>> I agree a READ_ONCE() is necessary.
>>>
>>>
>>
>> Now I'm confused:
>>
>>                  struct svc_serv *serv;
>>
>>         [...]
>>
>>                  /*
>>                   * The filecache laundrette is shut down after the
>>                   * nn->nfsd_serv pointer is cleared, but before the
>>                   * svc_serv is freed.
>>                   */
>>                  serv = nn->nfsd_serv;
>>                  if (serv)
>>                          svc_wake_up(serv);
>>
>> This code is explicitly asking to fetch nn->nfsd_serv into the serv
>> variable, and then is testing that copy of the pointer and passing it
>> into svc_wake_up().
>>
>> How is the compiler allowed to suddenly refetch a NULL pointer into
>> serv after testing that serv is non-NULL?
> 
> There's nothing here that tells the compiler it is not allowed to
> optimize this into two separate fetches if it feels that is better
> code. READ_ONCE is what tells the compiler we do not want that re-
> organization /ever/.
> 
> 

Well, I think you can argue that even if the compiler does split this
code into two reads of nn->nfsd_serv, it is guaranteed that the read
value is always the same both times -- I guess that's that the comment
is arguing, yes?

I just wonder what will happen if that guarantee should happen to change
in the future.
Jeff Layton Jan. 27, 2025, 2:34 p.m. UTC | #12
On Mon, 2025-01-27 at 09:03 -0500, Chuck Lever wrote:
> On 1/27/25 8:39 AM, Chuck Lever wrote:
> > On 1/27/25 8:32 AM, Jeff Layton wrote:
> > > On Mon, 2025-01-27 at 08:22 -0500, Chuck Lever wrote:
> > > > On 1/27/25 8:07 AM, Jeff Layton wrote:
> > > > > On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
> > > > > > On Mon, 27 Jan 2025, Jeff Layton wrote:
> > > > > > > On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
> > > > > > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > > > > > On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> > > > > > > > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > > > > > > > nfsd_file_dispose_list_delayed can be called from the filecache
> > > > > > > > > > > laundrette, which is shut down after the nfsd threads are shut 
> > > > > > > > > > > down and
> > > > > > > > > > > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL 
> > > > > > > > > > > then there
> > > > > > > > > > > are no threads to wake.
> > > > > > > > > > > 
> > > > > > > > > > > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > > > > > > > > > > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe 
> > > > > > > > > > > since the
> > > > > > > > > > > svc_serv is not freed until after the filecache laundrette is 
> > > > > > > > > > > cancelled.
> > > > > > > > > > > 
> > > > > > > > > > > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files 
> > > > > > > > > > > to a work queue")
> > > > > > > > > > > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > > > > > > > > > > Closes: https://lore.kernel.org/linux- 
> > > > > > > > > > > nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > > > > > > > > > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > > > > > > > > > ---
> > > > > > > > > > > This is only lightly tested, but I think it will fix the bug that
> > > > > > > > > > > Salvatore reported.
> > > > > > > > > > > ---
> > > > > > > > > > >    fs/nfsd/filecache.c | 11 ++++++++++-
> > > > > > > > > > >    1 file changed, 10 insertions(+), 1 deletion(-)
> > > > > > > > > > > 
> > > > > > > > > > > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > > > > > > > > > > index 
> > > > > > > > > > > e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > > > > > > > > > > --- a/fs/nfsd/filecache.c
> > > > > > > > > > > +++ b/fs/nfsd/filecache.c
> > > > > > > > > > > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct 
> > > > > > > > > > > list_head *dispose)
> > > > > > > > > > >                            struct nfsd_file, nf_gc);
> > > > > > > > > > >            struct nfsd_net *nn = net_generic(nf->nf_net, 
> > > > > > > > > > > nfsd_net_id);
> > > > > > > > > > >            struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > > > > > > > > > > +        struct svc_serv *serv;
> > > > > > > > > > >            spin_lock(&l->lock);
> > > > > > > > > > >            list_move_tail(&nf->nf_gc, &l->freeme);
> > > > > > > > > > >            spin_unlock(&l->lock);
> > > > > > > > > > > -        svc_wake_up(nn->nfsd_serv);
> > > > > > > > > > > +
> > > > > > > > > > > +        /*
> > > > > > > > > > > +         * The filecache laundrette is shut down after the
> > > > > > > > > > > +         * nn->nfsd_serv pointer is cleared, but before the
> > > > > > > > > > > +         * svc_serv is freed.
> > > > > > > > > > > +         */
> > > > > > > > > > > +        serv = nn->nfsd_serv;
> > > > > > > > > > 
> > > > > > > > > > I wonder if this should be READ_ONCE() to tell the compiler 
> > > > > > > > > > that we
> > > > > > > > > > could race with clearing nn->nfsd_serv.  Would the comment 
> > > > > > > > > > still be
> > > > > > > > > > needed?
> > > > > > > > > > 
> > > > > > > > > 
> > > > > > > > > I think we need a comment at least. The linkage between the 
> > > > > > > > > laundrette
> > > > > > > > > and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> > > > > > > > > doesn't convey that well, and is unnecessary here.
> > > > > > > > 
> > > > > > > > Why do you say "is unnecessary here" ?
> > > > > > > > If the code were
> > > > > > > >      if (nn->nfsd_serv)
> > > > > > > >               svc_wake_up(nn->nfsd_serv);
> > > > > > > > that would be wrong as nn->nfds_serv could be set to NULL between 
> > > > > > > > the
> > > > > > > > two.
> > > > > > > > And the C compile is allowed to load the value twice because the 
> > > > > > > > C memory
> > > > > > > > model declares that would have the same effect.
> > > > > > > > While I doubt it would actually change how the code is compiled, 
> > > > > > > > I think
> > > > > > > > we should have READ_ONCE() here (and I've been wrong before about 
> > > > > > > > what
> > > > > > > > the compiler will actually do).
> > > > > > > > 
> > > > > > > > 
> > > > > > > 
> > > > > > > It's unnecessary because the outcome of either case is acceptable.
> > > > > > > 
> > > > > > > When racing with shutdown, either it's NULL and the laundrette won't
> > > > > > > call svc_wake_up(), or it's non-NULL and it will. In the non-NULL 
> > > > > > > case,
> > > > > > > the call to svc_wake_up() will be a no-op because the threads are 
> > > > > > > shut
> > > > > > > down.
> > > > > > > 
> > > > > > > The vastly common case in this code is that this pointer will be non-
> > > > > > > NULL, because the server is running (i.e. not racing with 
> > > > > > > shutdown). I
> > > > > > > don't see the need in making all of those accesses volatile.
> > > > > > 
> > > > > > One of us is confused.  I hope it isn't me.
> > > > > > 
> > > > > 
> > > > > It's probably me. I think you have a much better understanding of
> > > > > compiler design than I do. Still...
> > > > > 
> > > > > > The hypothetical problem I see is that the C compiler could generate
> > > > > > code to load the value "nn->nfsd_serv" twice.  The first time it is 
> > > > > > not
> > > > > > NULL, the second time it is NULL.
> > > > > > The first is used for the test, the second is passed to svc_wake_up().
> > > > > > 
> > > > > > Unlikely though this is, it is possible and READ_ONCE() is designed
> > > > > > precisely to prevent this.
> > > > > > To quote from include/asm-generic/rwonce.h it will
> > > > > >    "Prevent the compiler from merging or refetching reads"
> > > > > > 
> > > > > > A "volatile" access does not add any cost (in this case).  What it 
> > > > > > does
> > > > > > is break any aliasing that the compile might have deduced.
> > > > > > Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
> > > > > > won't think it has the result of READ_ONCE(nn->nfsd_serv) in that 
> > > > > > register.
> > > > > > And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
> > > > > > won't decide that it can just read nn->nfsd_serv again.  It MUST keep
> > > > > > the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not 
> > > > > > needed
> > > > > > any more.
> > > > > 
> > > > > I'm mainly just considering the resulting pointer. There are two
> > > > > possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
> > > > > pointer that points to the svc_serv, or it's NULL. The resulting code
> > > > > can handle either case, so it doesn't seem like adding READ_ONCE() will
> > > > > create any material difference here.
> > > > > 
> > > > > Maybe I should ask it this way: What bad outcome could result if we
> > > > > don't add READ_ONCE() here?
> > > > 
> > > > Neil just described it. The compiler would generate two load operations,
> > > > one for the test and one for the function call argument. The first load
> > > > can retrieve a non-NULL address, and the second a NULL address.
> > > > 
> > > > I agree a READ_ONCE() is necessary.
> > > > 
> > > > 
> > > 
> > > Now I'm confused:
> > > 
> > >                  struct svc_serv *serv;
> > > 
> > >         [...]
> > > 
> > >                  /*
> > >                   * The filecache laundrette is shut down after the
> > >                   * nn->nfsd_serv pointer is cleared, but before the
> > >                   * svc_serv is freed.
> > >                   */
> > >                  serv = nn->nfsd_serv;
> > >                  if (serv)
> > >                          svc_wake_up(serv);
> > > 
> > > This code is explicitly asking to fetch nn->nfsd_serv into the serv
> > > variable, and then is testing that copy of the pointer and passing it
> > > into svc_wake_up().
> > > 
> > > How is the compiler allowed to suddenly refetch a NULL pointer into
> > > serv after testing that serv is non-NULL?
> > 
> > There's nothing here that tells the compiler it is not allowed to
> > optimize this into two separate fetches if it feels that is better
> > code. READ_ONCE is what tells the compiler we do not want that re-
> > organization /ever/.
> > 
> > 
> 
> Well, I think you can argue that even if the compiler does split this
> code into two reads of nn->nfsd_serv, it is guaranteed that the read
> value is always the same both times -- I guess that's that the comment
> is arguing, yes?
> 

Exactly. That's why I didn't just do:

if (nn->nfsd_serv)
	svc_destroy(nn->nfsd_serv);

We just have to ensure that we don't pass a NULL pointer to
svc_destroy() and that should be guaranteed by fetching it into an
interim variable. 

A READ_ONCE() doesn't buy us anything extra in this situation. It
ensures that it doesn't use a cached version of nn->nfsd_serv when it
does the fetch, but using a cached version is harmless here. Either way
will still work.

Plus, if this had access to a cached version of that variable in a
register, it avoided a memory fetch. Given that this should almost
never be NULL, adding READ_ONCE() seems like a waste.

> I just wonder what will happen if that guarantee should happen to change
> in the future.

I think the only way this could break would be if nfsd_destroy_serv()
started calling svc_destroy(&serv) before canceling the laundrette wq
job. Maybe it's worth a comment in there pointing out that dependency.
NeilBrown Jan. 27, 2025, 10:11 p.m. UTC | #13
On Tue, 28 Jan 2025, Jeff Layton wrote:
> On Mon, 2025-01-27 at 09:03 -0500, Chuck Lever wrote:
> > On 1/27/25 8:39 AM, Chuck Lever wrote:
> > > On 1/27/25 8:32 AM, Jeff Layton wrote:
> > > > On Mon, 2025-01-27 at 08:22 -0500, Chuck Lever wrote:
> > > > > On 1/27/25 8:07 AM, Jeff Layton wrote:
> > > > > > On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
> > > > > > > On Mon, 27 Jan 2025, Jeff Layton wrote:
> > > > > > > > On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
> > > > > > > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > > > > > > On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> > > > > > > > > > > On Sun, 26 Jan 2025, Jeff Layton wrote:
> > > > > > > > > > > > nfsd_file_dispose_list_delayed can be called from the filecache
> > > > > > > > > > > > laundrette, which is shut down after the nfsd threads are shut 
> > > > > > > > > > > > down and
> > > > > > > > > > > > the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL 
> > > > > > > > > > > > then there
> > > > > > > > > > > > are no threads to wake.
> > > > > > > > > > > > 
> > > > > > > > > > > > Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> > > > > > > > > > > > svc_wake_up in nfsd_file_dispose_list_delayed. This is safe 
> > > > > > > > > > > > since the
> > > > > > > > > > > > svc_serv is not freed until after the filecache laundrette is 
> > > > > > > > > > > > cancelled.
> > > > > > > > > > > > 
> > > > > > > > > > > > Fixes: ffb402596147 ("nfsd: Don't leave work of closing files 
> > > > > > > > > > > > to a work queue")
> > > > > > > > > > > > Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> > > > > > > > > > > > Closes: https://lore.kernel.org/linux- 
> > > > > > > > > > > > nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> > > > > > > > > > > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > > > > > > > > > > ---
> > > > > > > > > > > > This is only lightly tested, but I think it will fix the bug that
> > > > > > > > > > > > Salvatore reported.
> > > > > > > > > > > > ---
> > > > > > > > > > > >    fs/nfsd/filecache.c | 11 ++++++++++-
> > > > > > > > > > > >    1 file changed, 10 insertions(+), 1 deletion(-)
> > > > > > > > > > > > 
> > > > > > > > > > > > diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> > > > > > > > > > > > index 
> > > > > > > > > > > > e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> > > > > > > > > > > > --- a/fs/nfsd/filecache.c
> > > > > > > > > > > > +++ b/fs/nfsd/filecache.c
> > > > > > > > > > > > @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct 
> > > > > > > > > > > > list_head *dispose)
> > > > > > > > > > > >                            struct nfsd_file, nf_gc);
> > > > > > > > > > > >            struct nfsd_net *nn = net_generic(nf->nf_net, 
> > > > > > > > > > > > nfsd_net_id);
> > > > > > > > > > > >            struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> > > > > > > > > > > > +        struct svc_serv *serv;
> > > > > > > > > > > >            spin_lock(&l->lock);
> > > > > > > > > > > >            list_move_tail(&nf->nf_gc, &l->freeme);
> > > > > > > > > > > >            spin_unlock(&l->lock);
> > > > > > > > > > > > -        svc_wake_up(nn->nfsd_serv);
> > > > > > > > > > > > +
> > > > > > > > > > > > +        /*
> > > > > > > > > > > > +         * The filecache laundrette is shut down after the
> > > > > > > > > > > > +         * nn->nfsd_serv pointer is cleared, but before the
> > > > > > > > > > > > +         * svc_serv is freed.
> > > > > > > > > > > > +         */
> > > > > > > > > > > > +        serv = nn->nfsd_serv;
> > > > > > > > > > > 
> > > > > > > > > > > I wonder if this should be READ_ONCE() to tell the compiler 
> > > > > > > > > > > that we
> > > > > > > > > > > could race with clearing nn->nfsd_serv.  Would the comment 
> > > > > > > > > > > still be
> > > > > > > > > > > needed?
> > > > > > > > > > > 
> > > > > > > > > > 
> > > > > > > > > > I think we need a comment at least. The linkage between the 
> > > > > > > > > > laundrette
> > > > > > > > > > and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> > > > > > > > > > doesn't convey that well, and is unnecessary here.
> > > > > > > > > 
> > > > > > > > > Why do you say "is unnecessary here" ?
> > > > > > > > > If the code were
> > > > > > > > >      if (nn->nfsd_serv)
> > > > > > > > >               svc_wake_up(nn->nfsd_serv);
> > > > > > > > > that would be wrong as nn->nfds_serv could be set to NULL between 
> > > > > > > > > the
> > > > > > > > > two.
> > > > > > > > > And the C compile is allowed to load the value twice because the 
> > > > > > > > > C memory
> > > > > > > > > model declares that would have the same effect.
> > > > > > > > > While I doubt it would actually change how the code is compiled, 
> > > > > > > > > I think
> > > > > > > > > we should have READ_ONCE() here (and I've been wrong before about 
> > > > > > > > > what
> > > > > > > > > the compiler will actually do).
> > > > > > > > > 
> > > > > > > > > 
> > > > > > > > 
> > > > > > > > It's unnecessary because the outcome of either case is acceptable.
> > > > > > > > 
> > > > > > > > When racing with shutdown, either it's NULL and the laundrette won't
> > > > > > > > call svc_wake_up(), or it's non-NULL and it will. In the non-NULL 
> > > > > > > > case,
> > > > > > > > the call to svc_wake_up() will be a no-op because the threads are 
> > > > > > > > shut
> > > > > > > > down.
> > > > > > > > 
> > > > > > > > The vastly common case in this code is that this pointer will be non-
> > > > > > > > NULL, because the server is running (i.e. not racing with 
> > > > > > > > shutdown). I
> > > > > > > > don't see the need in making all of those accesses volatile.
> > > > > > > 
> > > > > > > One of us is confused.  I hope it isn't me.
> > > > > > > 
> > > > > > 
> > > > > > It's probably me. I think you have a much better understanding of
> > > > > > compiler design than I do. Still...
> > > > > > 
> > > > > > > The hypothetical problem I see is that the C compiler could generate
> > > > > > > code to load the value "nn->nfsd_serv" twice.  The first time it is 
> > > > > > > not
> > > > > > > NULL, the second time it is NULL.
> > > > > > > The first is used for the test, the second is passed to svc_wake_up().
> > > > > > > 
> > > > > > > Unlikely though this is, it is possible and READ_ONCE() is designed
> > > > > > > precisely to prevent this.
> > > > > > > To quote from include/asm-generic/rwonce.h it will
> > > > > > >    "Prevent the compiler from merging or refetching reads"
> > > > > > > 
> > > > > > > A "volatile" access does not add any cost (in this case).  What it 
> > > > > > > does
> > > > > > > is break any aliasing that the compile might have deduced.
> > > > > > > Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
> > > > > > > won't think it has the result of READ_ONCE(nn->nfsd_serv) in that 
> > > > > > > register.
> > > > > > > And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
> > > > > > > won't decide that it can just read nn->nfsd_serv again.  It MUST keep
> > > > > > > the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not 
> > > > > > > needed
> > > > > > > any more.
> > > > > > 
> > > > > > I'm mainly just considering the resulting pointer. There are two
> > > > > > possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
> > > > > > pointer that points to the svc_serv, or it's NULL. The resulting code
> > > > > > can handle either case, so it doesn't seem like adding READ_ONCE() will
> > > > > > create any material difference here.
> > > > > > 
> > > > > > Maybe I should ask it this way: What bad outcome could result if we
> > > > > > don't add READ_ONCE() here?
> > > > > 
> > > > > Neil just described it. The compiler would generate two load operations,
> > > > > one for the test and one for the function call argument. The first load
> > > > > can retrieve a non-NULL address, and the second a NULL address.
> > > > > 
> > > > > I agree a READ_ONCE() is necessary.
> > > > > 
> > > > > 
> > > > 
> > > > Now I'm confused:
> > > > 
> > > >                  struct svc_serv *serv;
> > > > 
> > > >         [...]
> > > > 
> > > >                  /*
> > > >                   * The filecache laundrette is shut down after the
> > > >                   * nn->nfsd_serv pointer is cleared, but before the
> > > >                   * svc_serv is freed.
> > > >                   */
> > > >                  serv = nn->nfsd_serv;
> > > >                  if (serv)
> > > >                          svc_wake_up(serv);
> > > > 
> > > > This code is explicitly asking to fetch nn->nfsd_serv into the serv
> > > > variable, and then is testing that copy of the pointer and passing it
> > > > into svc_wake_up().
> > > > 
> > > > How is the compiler allowed to suddenly refetch a NULL pointer into
> > > > serv after testing that serv is non-NULL?
> > > 
> > > There's nothing here that tells the compiler it is not allowed to
> > > optimize this into two separate fetches if it feels that is better
> > > code. READ_ONCE is what tells the compiler we do not want that re-
> > > organization /ever/.
> > > 
> > > 
> > 
> > Well, I think you can argue that even if the compiler does split this
> > code into two reads of nn->nfsd_serv, it is guaranteed that the read
> > value is always the same both times -- I guess that's that the comment
> > is arguing, yes?
> > 
> 
> Exactly. That's why I didn't just do:
> 
> if (nn->nfsd_serv)
> 	svc_destroy(nn->nfsd_serv);
> 
> We just have to ensure that we don't pass a NULL pointer to
> svc_destroy() and that should be guaranteed by fetching it into an
> interim variable. 
> 
> A READ_ONCE() doesn't buy us anything extra in this situation. It
> ensures that it doesn't use a cached version of nn->nfsd_serv when it
> does the fetch, but using a cached version is harmless here. Either way
> will still work.

Yes, READ_ONCE() ensures we don't use a cached version but, as you say,
that is not relevant here.  But that is not the ONLY thing that
READ_ONCE() does.
Remember what I quoted from rwonce.h:

 * Prevent the compiler from merging or refetching reads or writes.
                                     ^^^^^^^^^^^^^

There are 2 things that READ_ONCE() does. 
1/ it avoids "merging" so a cached value won't be used
2/ it avoids "refetching".  THAT is what we need to ensure.

This is more practically relevant in larger functions.
If I had

 serv = nn->nfsd_serv;
 if (serv) {
     /* do lots of other complicated stuff with other
      * fields from nn and probably call some functions, but
      * don't use serv or nn->nfsd_serv
      */
     svc_destroy(serv);
 }

then while compiling that complicated stuff the compiler might find that
it wants to spill the register containing "serv".  But doesn't want to
spill "nn" because that is more actively used.  So instead of spilling
"serv" to the stack it just reuses the register and reloads
nn->nfsd_serv for the final call.  This is perfectly legal in C because
the memory model affirms that memory won't spontaneously change unless
it is marked "volatile" (or unless it is told that memory might have
changed which is that "barrier()" does).

In the particular code in question there isn't any complexity in between
so I cannot imagine the compiler wanting to spill anything.  But maybe
one day the code will be changed.
It is best-practice to use an appropriate accessor for data that might
change in some other thread.  Sometimes that means holding a lock,
sometimes it mean atomic_read or rcu_dereference.  Sometimes it means
READ_ONCE().
Accessing data that can be changed by another thread without something
like that is almost certainly "wrong".

> 
> Plus, if this had access to a cached version of that variable in a
> register, it avoided a memory fetch. Given that this should almost
> never be NULL, adding READ_ONCE() seems like a waste.

But it doesn't have access to a cached version.  In this code, this is
the first use of nn->nfsd_serv.  So the READ_ONCE() can have no
performance cost at all.  If the code were ever changed to add more uses
for nn->nfsd_serv, that READ_ONCE would serve as a loud reminder that
the value can change and so any new accesses should happen after the
READ_ONCE().

Thanks,
NeilBrown
NeilBrown Jan. 27, 2025, 10:16 p.m. UTC | #14
On Tue, 28 Jan 2025, Chuck Lever wrote:
> On 1/27/25 8:39 AM, Chuck Lever wrote:
> > On 1/27/25 8:32 AM, Jeff Layton wrote:
> >> On Mon, 2025-01-27 at 08:22 -0500, Chuck Lever wrote:
> >>> On 1/27/25 8:07 AM, Jeff Layton wrote:
> >>>> On Mon, 2025-01-27 at 11:15 +1100, NeilBrown wrote:
> >>>>> On Mon, 27 Jan 2025, Jeff Layton wrote:
> >>>>>> On Mon, 2025-01-27 at 08:53 +1100, NeilBrown wrote:
> >>>>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
> >>>>>>>> On Sun, 2025-01-26 at 13:39 +1100, NeilBrown wrote:
> >>>>>>>>> On Sun, 26 Jan 2025, Jeff Layton wrote:
> >>>>>>>>>> nfsd_file_dispose_list_delayed can be called from the filecache
> >>>>>>>>>> laundrette, which is shut down after the nfsd threads are shut 
> >>>>>>>>>> down and
> >>>>>>>>>> the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL 
> >>>>>>>>>> then there
> >>>>>>>>>> are no threads to wake.
> >>>>>>>>>>
> >>>>>>>>>> Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> >>>>>>>>>> svc_wake_up in nfsd_file_dispose_list_delayed. This is safe 
> >>>>>>>>>> since the
> >>>>>>>>>> svc_serv is not freed until after the filecache laundrette is 
> >>>>>>>>>> cancelled.
> >>>>>>>>>>
> >>>>>>>>>> Fixes: ffb402596147 ("nfsd: Don't leave work of closing files 
> >>>>>>>>>> to a work queue")
> >>>>>>>>>> Reported-by: Salvatore Bonaccorso <carnil@debian.org>
> >>>>>>>>>> Closes: https://lore.kernel.org/linux- 
> >>>>>>>>>> nfs/7d9f2a8aede4f7ca9935a47e1d405643220d7946.camel@kernel.org/
> >>>>>>>>>> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> >>>>>>>>>> ---
> >>>>>>>>>> This is only lightly tested, but I think it will fix the bug that
> >>>>>>>>>> Salvatore reported.
> >>>>>>>>>> ---
> >>>>>>>>>>    fs/nfsd/filecache.c | 11 ++++++++++-
> >>>>>>>>>>    1 file changed, 10 insertions(+), 1 deletion(-)
> >>>>>>>>>>
> >>>>>>>>>> diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
> >>>>>>>>>> index 
> >>>>>>>>>> e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
> >>>>>>>>>> --- a/fs/nfsd/filecache.c
> >>>>>>>>>> +++ b/fs/nfsd/filecache.c
> >>>>>>>>>> @@ -445,11 +445,20 @@ nfsd_file_dispose_list_delayed(struct 
> >>>>>>>>>> list_head *dispose)
> >>>>>>>>>>                            struct nfsd_file, nf_gc);
> >>>>>>>>>>            struct nfsd_net *nn = net_generic(nf->nf_net, 
> >>>>>>>>>> nfsd_net_id);
> >>>>>>>>>>            struct nfsd_fcache_disposal *l = nn->fcache_disposal;
> >>>>>>>>>> +        struct svc_serv *serv;
> >>>>>>>>>>            spin_lock(&l->lock);
> >>>>>>>>>>            list_move_tail(&nf->nf_gc, &l->freeme);
> >>>>>>>>>>            spin_unlock(&l->lock);
> >>>>>>>>>> -        svc_wake_up(nn->nfsd_serv);
> >>>>>>>>>> +
> >>>>>>>>>> +        /*
> >>>>>>>>>> +         * The filecache laundrette is shut down after the
> >>>>>>>>>> +         * nn->nfsd_serv pointer is cleared, but before the
> >>>>>>>>>> +         * svc_serv is freed.
> >>>>>>>>>> +         */
> >>>>>>>>>> +        serv = nn->nfsd_serv;
> >>>>>>>>>
> >>>>>>>>> I wonder if this should be READ_ONCE() to tell the compiler 
> >>>>>>>>> that we
> >>>>>>>>> could race with clearing nn->nfsd_serv.  Would the comment 
> >>>>>>>>> still be
> >>>>>>>>> needed?
> >>>>>>>>>
> >>>>>>>>
> >>>>>>>> I think we need a comment at least. The linkage between the 
> >>>>>>>> laundrette
> >>>>>>>> and the nfsd_serv being set to NULL is very subtle. A READ_ONCE()
> >>>>>>>> doesn't convey that well, and is unnecessary here.
> >>>>>>>
> >>>>>>> Why do you say "is unnecessary here" ?
> >>>>>>> If the code were
> >>>>>>>      if (nn->nfsd_serv)
> >>>>>>>               svc_wake_up(nn->nfsd_serv);
> >>>>>>> that would be wrong as nn->nfds_serv could be set to NULL between 
> >>>>>>> the
> >>>>>>> two.
> >>>>>>> And the C compile is allowed to load the value twice because the 
> >>>>>>> C memory
> >>>>>>> model declares that would have the same effect.
> >>>>>>> While I doubt it would actually change how the code is compiled, 
> >>>>>>> I think
> >>>>>>> we should have READ_ONCE() here (and I've been wrong before about 
> >>>>>>> what
> >>>>>>> the compiler will actually do).
> >>>>>>>
> >>>>>>>
> >>>>>>
> >>>>>> It's unnecessary because the outcome of either case is acceptable.
> >>>>>>
> >>>>>> When racing with shutdown, either it's NULL and the laundrette won't
> >>>>>> call svc_wake_up(), or it's non-NULL and it will. In the non-NULL 
> >>>>>> case,
> >>>>>> the call to svc_wake_up() will be a no-op because the threads are 
> >>>>>> shut
> >>>>>> down.
> >>>>>>
> >>>>>> The vastly common case in this code is that this pointer will be non-
> >>>>>> NULL, because the server is running (i.e. not racing with 
> >>>>>> shutdown). I
> >>>>>> don't see the need in making all of those accesses volatile.
> >>>>>
> >>>>> One of us is confused.  I hope it isn't me.
> >>>>>
> >>>>
> >>>> It's probably me. I think you have a much better understanding of
> >>>> compiler design than I do. Still...
> >>>>
> >>>>> The hypothetical problem I see is that the C compiler could generate
> >>>>> code to load the value "nn->nfsd_serv" twice.  The first time it is 
> >>>>> not
> >>>>> NULL, the second time it is NULL.
> >>>>> The first is used for the test, the second is passed to svc_wake_up().
> >>>>>
> >>>>> Unlikely though this is, it is possible and READ_ONCE() is designed
> >>>>> precisely to prevent this.
> >>>>> To quote from include/asm-generic/rwonce.h it will
> >>>>>    "Prevent the compiler from merging or refetching reads"
> >>>>>
> >>>>> A "volatile" access does not add any cost (in this case).  What it 
> >>>>> does
> >>>>> is break any aliasing that the compile might have deduced.
> >>>>> Even if the compiler thinks it has "nn->nfsd_serv" in a register, it
> >>>>> won't think it has the result of READ_ONCE(nn->nfsd_serv) in that 
> >>>>> register.
> >>>>> And if it needs the result of a previous READ_ONCE(nn->nfsd_serv) it
> >>>>> won't decide that it can just read nn->nfsd_serv again.  It MUST keep
> >>>>> the result of READ_ONCE(nn->nfsd_serv) somewhere until it is not 
> >>>>> needed
> >>>>> any more.
> >>>>
> >>>> I'm mainly just considering the resulting pointer. There are two
> >>>> possible outcomes to the fetch of nn->nfsd_serv. Either it's a valid
> >>>> pointer that points to the svc_serv, or it's NULL. The resulting code
> >>>> can handle either case, so it doesn't seem like adding READ_ONCE() will
> >>>> create any material difference here.
> >>>>
> >>>> Maybe I should ask it this way: What bad outcome could result if we
> >>>> don't add READ_ONCE() here?
> >>>
> >>> Neil just described it. The compiler would generate two load operations,
> >>> one for the test and one for the function call argument. The first load
> >>> can retrieve a non-NULL address, and the second a NULL address.
> >>>
> >>> I agree a READ_ONCE() is necessary.
> >>>
> >>>
> >>
> >> Now I'm confused:
> >>
> >>                  struct svc_serv *serv;
> >>
> >>         [...]
> >>
> >>                  /*
> >>                   * The filecache laundrette is shut down after the
> >>                   * nn->nfsd_serv pointer is cleared, but before the
> >>                   * svc_serv is freed.
> >>                   */
> >>                  serv = nn->nfsd_serv;
> >>                  if (serv)
> >>                          svc_wake_up(serv);
> >>
> >> This code is explicitly asking to fetch nn->nfsd_serv into the serv
> >> variable, and then is testing that copy of the pointer and passing it
> >> into svc_wake_up().
> >>
> >> How is the compiler allowed to suddenly refetch a NULL pointer into
> >> serv after testing that serv is non-NULL?
> > 
> > There's nothing here that tells the compiler it is not allowed to
> > optimize this into two separate fetches if it feels that is better
> > code. READ_ONCE is what tells the compiler we do not want that re-
> > organization /ever/.
> > 
> > 
> 
> Well, I think you can argue that even if the compiler does split this
> code into two reads of nn->nfsd_serv, it is guaranteed that the read
> value is always the same both times -- I guess that's that the comment
> is arguing, yes?

I don't think that argument can stand.  This code is run from the
filecache laundrette which can still be active when nn->nfsd_serv is set
to NULL.  That is what the comment says: it is only shutdown *after*
->nfsd_serv is cleared.

NeilBrown
Chuck Lever Jan. 28, 2025, 5:07 p.m. UTC | #15
From: Chuck Lever <chuck.lever@oracle.com>

On Sat, 25 Jan 2025 20:13:18 -0500, Jeff Layton wrote:
> nfsd_file_dispose_list_delayed can be called from the filecache
> laundrette, which is shut down after the nfsd threads are shut down and
> the nfsd_serv pointer is cleared. If nn->nfsd_serv is NULL then there
> are no threads to wake.
> 
> Ensure that the nn->nfsd_serv pointer is non-NULL before calling
> svc_wake_up in nfsd_file_dispose_list_delayed. This is safe since the
> svc_serv is not freed until after the filecache laundrette is cancelled.
> 
> [...]

Applied to nfsd-testing, thanks!

Test experience should demonstrate whether more strict memory
ordering semantics are needed.

[1/1] nfsd: validate the nfsd_serv pointer before calling svc_wake_up
      commit: 363683ced1718d66ad54e1bdf52d41d544f783b2

--
Chuck Lever
diff mbox series

Patch

diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index e91c164b5ea21507659904690533a19ca43b1b64..fb2a4469b7a3c077de2dd750f43239b4af6d37b0 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -445,11 +445,20 @@  nfsd_file_dispose_list_delayed(struct list_head *dispose)
 						struct nfsd_file, nf_gc);
 		struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
 		struct nfsd_fcache_disposal *l = nn->fcache_disposal;
+		struct svc_serv *serv;
 
 		spin_lock(&l->lock);
 		list_move_tail(&nf->nf_gc, &l->freeme);
 		spin_unlock(&l->lock);
-		svc_wake_up(nn->nfsd_serv);
+
+		/*
+		 * The filecache laundrette is shut down after the
+		 * nn->nfsd_serv pointer is cleared, but before the
+		 * svc_serv is freed.
+		 */
+		serv = nn->nfsd_serv;
+		if (serv)
+			svc_wake_up(serv);
 	}
 }