All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 0/2] NFS: fix RCU and tracing pointer safety
@ 2026-04-08 16:14 Sean Chang
  2026-04-08 16:14 ` [PATCH v1 1/2] NFS: fix RCU safety in nfs_compare_super_address Sean Chang
  2026-04-08 16:14 ` [PATCH v1 2/2] NFS: use unsigned long for req field in nfs_page_class Sean Chang
  0 siblings, 2 replies; 7+ messages in thread
From: Sean Chang @ 2026-04-08 16:14 UTC (permalink / raw)
  To: trondmy, anna; +Cc: linux-nfs, linux-kernel, Sean Chang

This series addresses two Sparse static analysis warnings in the NFS 
client. 

The first patch fixes an RCU-unsafe dereference when comparing 
superblock addresses by adding the necessary RCU read lock and 
dereference wrappers.

The second patch resolves a "noderef" warning in the tracing 
infrastructure by changing a pointer field to an unsigned long, 
ensuring we aren't incorrectly marking private trace-buffer 
pointers as dereferenceable.

Sean Chang (2):
  NFS: fix RCU safety in nfs_compare_super_address
  NFS: use unsigned long for req field in nfs_page_class

 fs/nfs/nfstrace.h |  6 +++---
 fs/nfs/super.c    | 32 ++++++++++++++++++++++----------
 2 files changed, 25 insertions(+), 13 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v1 1/2] NFS: fix RCU safety in nfs_compare_super_address
  2026-04-08 16:14 [PATCH v1 0/2] NFS: fix RCU and tracing pointer safety Sean Chang
@ 2026-04-08 16:14 ` Sean Chang
  2026-04-10 15:09   ` Benjamin Coddington
  2026-04-08 16:14 ` [PATCH v1 2/2] NFS: use unsigned long for req field in nfs_page_class Sean Chang
  1 sibling, 1 reply; 7+ messages in thread
From: Sean Chang @ 2026-04-08 16:14 UTC (permalink / raw)
  To: trondmy, anna; +Cc: linux-nfs, linux-kernel, Sean Chang

The cl_xprt pointer in struct rpc_clnt is marked as __rcu. Accessing
it directly in nfs_compare_super_address() without RCU protection is
unsafe and triggers Sparse warnings about dereferencing noderef
expressions.

Fix this by wrapping the access with rcu_read_lock() and using
rcu_dereference() to safely retrieve the transport pointer. This
ensures the xprt remains valid during the comparison of network
namespaces and addresses, preventing potential use-after-free during
concurrent transport updates.

Signed-off-by: Sean Chang <seanwascoding@gmail.com>
---
 fs/nfs/super.c | 32 ++++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 7a318581f85b..071337f9ea37 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -1166,43 +1166,55 @@ static int nfs_set_super(struct super_block *s, struct fs_context *fc)
 static int nfs_compare_super_address(struct nfs_server *server1,
 				     struct nfs_server *server2)
 {
+	struct rpc_xprt *xprt1, *xprt2;
 	struct sockaddr *sap1, *sap2;
-	struct rpc_xprt *xprt1 = server1->client->cl_xprt;
-	struct rpc_xprt *xprt2 = server2->client->cl_xprt;
+	int ret = 0;
+
+	rcu_read_lock();
+
+	xprt1 = rcu_dereference(server1->client->cl_xprt);
+	xprt2 = rcu_dereference(server2->client->cl_xprt);
+
+	if (!xprt1 || !xprt2)
+		goto out;
 
 	if (!net_eq(xprt1->xprt_net, xprt2->xprt_net))
-		return 0;
+		goto out;
 
 	sap1 = (struct sockaddr *)&server1->nfs_client->cl_addr;
 	sap2 = (struct sockaddr *)&server2->nfs_client->cl_addr;
 
 	if (sap1->sa_family != sap2->sa_family)
-		return 0;
+		goto out;
 
 	switch (sap1->sa_family) {
 	case AF_INET: {
 		struct sockaddr_in *sin1 = (struct sockaddr_in *)sap1;
 		struct sockaddr_in *sin2 = (struct sockaddr_in *)sap2;
 		if (sin1->sin_addr.s_addr != sin2->sin_addr.s_addr)
-			return 0;
+			goto out;
 		if (sin1->sin_port != sin2->sin_port)
-			return 0;
+			goto out;
 		break;
 	}
 	case AF_INET6: {
 		struct sockaddr_in6 *sin1 = (struct sockaddr_in6 *)sap1;
 		struct sockaddr_in6 *sin2 = (struct sockaddr_in6 *)sap2;
 		if (!ipv6_addr_equal(&sin1->sin6_addr, &sin2->sin6_addr))
-			return 0;
+			goto out;
 		if (sin1->sin6_port != sin2->sin6_port)
-			return 0;
+			goto out;
 		break;
 	}
 	default:
-		return 0;
+		goto out;
 	}
 
-	return 1;
+	ret = 1;
+
+out:
+	rcu_read_unlock();
+	return ret;
 }
 
 static int nfs_compare_userns(const struct nfs_server *old,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v1 2/2] NFS: use unsigned long for req field in nfs_page_class
  2026-04-08 16:14 [PATCH v1 0/2] NFS: fix RCU and tracing pointer safety Sean Chang
  2026-04-08 16:14 ` [PATCH v1 1/2] NFS: fix RCU safety in nfs_compare_super_address Sean Chang
@ 2026-04-08 16:14 ` Sean Chang
  2026-04-10 15:23   ` Benjamin Coddington
  1 sibling, 1 reply; 7+ messages in thread
From: Sean Chang @ 2026-04-08 16:14 UTC (permalink / raw)
  To: trondmy, anna; +Cc: linux-nfs, linux-kernel, Sean Chang

The nfs_page_class tracepoint used a pointer for the req field. This
caused Sparse to complain about dereferencing a pointer marked as
__private within the trace ring buffer context.

Change the field type to unsigned long to store the address of the
request without dereferencing it. Update TP_printk to use 0x%lx for
consistent hexadecimal output, allowing for unique identification of
requests across the trace log.

Signed-off-by: Sean Chang <seanwascoding@gmail.com>
---
 fs/nfs/nfstrace.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h
index 9f9ce4a565ea..4150bbd99cfa 100644
--- a/fs/nfs/nfstrace.h
+++ b/fs/nfs/nfstrace.h
@@ -1496,7 +1496,7 @@ DECLARE_EVENT_CLASS(nfs_page_class,
 			__field(dev_t, dev)
 			__field(u32, fhandle)
 			__field(u64, fileid)
-			__field(const struct nfs_page *__private, req)
+			__field(unsigned long, req)
 			__field(loff_t, offset)
 			__field(unsigned int, count)
 			__field(unsigned long, flags)
@@ -1509,14 +1509,14 @@ DECLARE_EVENT_CLASS(nfs_page_class,
 			__entry->dev = inode->i_sb->s_dev;
 			__entry->fileid = nfsi->fileid;
 			__entry->fhandle = nfs_fhandle_hash(&nfsi->fh);
-			__entry->req = req;
+			__entry->req = (unsigned long)req;
 			__entry->offset = req_offset(req);
 			__entry->count = req->wb_bytes;
 			__entry->flags = req->wb_flags;
 		),
 
 		TP_printk(
-			"fileid=%02x:%02x:%llu fhandle=0x%08x req=%p offset=%lld count=%u flags=%s",
+			"fileid=%02x:%02x:%llu fhandle=0x%08x req=0x%lx offset=%lld count=%u flags=%s",
 			MAJOR(__entry->dev), MINOR(__entry->dev),
 			(unsigned long long)__entry->fileid, __entry->fhandle,
 			__entry->req, __entry->offset, __entry->count,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v1 1/2] NFS: fix RCU safety in nfs_compare_super_address
  2026-04-08 16:14 ` [PATCH v1 1/2] NFS: fix RCU safety in nfs_compare_super_address Sean Chang
@ 2026-04-10 15:09   ` Benjamin Coddington
  2026-04-14 16:12     ` Sean Chang
  0 siblings, 1 reply; 7+ messages in thread
From: Benjamin Coddington @ 2026-04-10 15:09 UTC (permalink / raw)
  To: Sean Chang; +Cc: trondmy, anna, linux-nfs, linux-kernel

On 8 Apr 2026, at 12:14, Sean Chang wrote:

> The cl_xprt pointer in struct rpc_clnt is marked as __rcu. Accessing
> it directly in nfs_compare_super_address() without RCU protection is
> unsafe and triggers Sparse warnings about dereferencing noderef
> expressions.
>
> Fix this by wrapping the access with rcu_read_lock() and using
> rcu_dereference() to safely retrieve the transport pointer. This
> ensures the xprt remains valid during the comparison of network
> namespaces and addresses, preventing potential use-after-free during
> concurrent transport updates.
>
> Signed-off-by: Sean Chang <seanwascoding@gmail.com>

Fixes: 7e3fcf61abde ("nfs: don't share mounts between network namespaces")

> ---
>  fs/nfs/super.c | 32 ++++++++++++++++++++++----------
>  1 file changed, 22 insertions(+), 10 deletions(-)
>
> diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> index 7a318581f85b..071337f9ea37 100644
> --- a/fs/nfs/super.c
> +++ b/fs/nfs/super.c
> @@ -1166,43 +1166,55 @@ static int nfs_set_super(struct super_block *s, struct fs_context *fc)
>  static int nfs_compare_super_address(struct nfs_server *server1,
>  				     struct nfs_server *server2)
>  {
> +	struct rpc_xprt *xprt1, *xprt2;
>  	struct sockaddr *sap1, *sap2;
> -	struct rpc_xprt *xprt1 = server1->client->cl_xprt;
> -	struct rpc_xprt *xprt2 = server2->client->cl_xprt;
> +	int ret = 0;
> +
> +	rcu_read_lock();
> +
> +	xprt1 = rcu_dereference(server1->client->cl_xprt);
> +	xprt2 = rcu_dereference(server2->client->cl_xprt);
> +
> +	if (!xprt1 || !xprt2)
> +		goto out;

^^ I'm not sure that's a great test, the rpc_xprt objects are refcounted.
These might not be null but you could still race with a freeing path?

However, I think you might just be safe inside the RCU section here because
rpc_switch_client_transport() uses synchronize_rcu() before xprt_put(old).
I didn't audit all the freeing paths.

>  	if (!net_eq(xprt1->xprt_net, xprt2->xprt_net))
> -		return 0;
> +		goto out;

Probably safe to drop the RCU protection scope after this point.  No need to
hold it over all the other checks..

Ben

>
>  	sap1 = (struct sockaddr *)&server1->nfs_client->cl_addr;
>  	sap2 = (struct sockaddr *)&server2->nfs_client->cl_addr;
>
>  	if (sap1->sa_family != sap2->sa_family)
> -		return 0;
> +		goto out;
>
>  	switch (sap1->sa_family) {
>  	case AF_INET: {
>  		struct sockaddr_in *sin1 = (struct sockaddr_in *)sap1;
>  		struct sockaddr_in *sin2 = (struct sockaddr_in *)sap2;
>  		if (sin1->sin_addr.s_addr != sin2->sin_addr.s_addr)
> -			return 0;
> +			goto out;
>  		if (sin1->sin_port != sin2->sin_port)
> -			return 0;
> +			goto out;
>  		break;
>  	}
>  	case AF_INET6: {
>  		struct sockaddr_in6 *sin1 = (struct sockaddr_in6 *)sap1;
>  		struct sockaddr_in6 *sin2 = (struct sockaddr_in6 *)sap2;
>  		if (!ipv6_addr_equal(&sin1->sin6_addr, &sin2->sin6_addr))
> -			return 0;
> +			goto out;
>  		if (sin1->sin6_port != sin2->sin6_port)
> -			return 0;
> +			goto out;
>  		break;
>  	}
>  	default:
> -		return 0;
> +		goto out;
>  	}
>
> -	return 1;
> +	ret = 1;
> +
> +out:
> +	rcu_read_unlock();
> +	return ret;
>  }
>
>  static int nfs_compare_userns(const struct nfs_server *old,
> -- 
> 2.34.1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v1 2/2] NFS: use unsigned long for req field in nfs_page_class
  2026-04-08 16:14 ` [PATCH v1 2/2] NFS: use unsigned long for req field in nfs_page_class Sean Chang
@ 2026-04-10 15:23   ` Benjamin Coddington
  2026-04-14  9:14     ` Sean Chang
  0 siblings, 1 reply; 7+ messages in thread
From: Benjamin Coddington @ 2026-04-10 15:23 UTC (permalink / raw)
  To: Sean Chang; +Cc: trondmy, anna, linux-nfs, linux-kernel, Jeff Layton

Hi Sean,

On 8 Apr 2026, at 12:14, Sean Chang wrote:

> The nfs_page_class tracepoint used a pointer for the req field. This
> caused Sparse to complain about dereferencing a pointer marked as
> __private within the trace ring buffer context.
>
> Change the field type to unsigned long to store the address of the
> request without dereferencing it. Update TP_printk to use 0x%lx for
> consistent hexadecimal output, allowing for unique identification of
> requests across the trace log.

Probably we don't want to bypass the %p formatting because some
configurations use it to obfuscate kernel pointers.

I think in this context the __private annotation is incorrect.  We deref the
nfs_page pointer only in TP_fast_assign() which runs at the call site, and
the TP_printk only outputs the pointer value.

cc: Jeff

Ben

>
> Signed-off-by: Sean Chang <seanwascoding@gmail.com>
> ---
>  fs/nfs/nfstrace.h | 6 +++---
>  1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h
> index 9f9ce4a565ea..4150bbd99cfa 100644
> --- a/fs/nfs/nfstrace.h
> +++ b/fs/nfs/nfstrace.h
> @@ -1496,7 +1496,7 @@ DECLARE_EVENT_CLASS(nfs_page_class,
>  			__field(dev_t, dev)
>  			__field(u32, fhandle)
>  			__field(u64, fileid)
> -			__field(const struct nfs_page *__private, req)
> +			__field(unsigned long, req)
>  			__field(loff_t, offset)
>  			__field(unsigned int, count)
>  			__field(unsigned long, flags)
> @@ -1509,14 +1509,14 @@ DECLARE_EVENT_CLASS(nfs_page_class,
>  			__entry->dev = inode->i_sb->s_dev;
>  			__entry->fileid = nfsi->fileid;
>  			__entry->fhandle = nfs_fhandle_hash(&nfsi->fh);
> -			__entry->req = req;
> +			__entry->req = (unsigned long)req;
>  			__entry->offset = req_offset(req);
>  			__entry->count = req->wb_bytes;
>  			__entry->flags = req->wb_flags;
>  		),
>
>  		TP_printk(
> -			"fileid=%02x:%02x:%llu fhandle=0x%08x req=%p offset=%lld count=%u flags=%s",
> +			"fileid=%02x:%02x:%llu fhandle=0x%08x req=0x%lx offset=%lld count=%u flags=%s",
>  			MAJOR(__entry->dev), MINOR(__entry->dev),
>  			(unsigned long long)__entry->fileid, __entry->fhandle,
>  			__entry->req, __entry->offset, __entry->count,
> -- 
> 2.34.1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v1 2/2] NFS: use unsigned long for req field in nfs_page_class
  2026-04-10 15:23   ` Benjamin Coddington
@ 2026-04-14  9:14     ` Sean Chang
  0 siblings, 0 replies; 7+ messages in thread
From: Sean Chang @ 2026-04-14  9:14 UTC (permalink / raw)
  To: Benjamin Coddington; +Cc: trondmy, anna, linux-nfs, linux-kernel, Jeff Layton

On Fri, Apr 10, 2026 at 11:23 PM Benjamin Coddington
<ben.coddington@hammerspace.com> wrote:
>
> Hi Sean,
>
> On 8 Apr 2026, at 12:14, Sean Chang wrote:
>
> > The nfs_page_class tracepoint used a pointer for the req field. This
> > caused Sparse to complain about dereferencing a pointer marked as
> > __private within the trace ring buffer context.
> >
> > Change the field type to unsigned long to store the address of the
> > request without dereferencing it. Update TP_printk to use 0x%lx for
> > consistent hexadecimal output, allowing for unique identification of
> > requests across the trace log.
>
> Probably we don't want to bypass the %p formatting because some
> configurations use it to obfuscate kernel pointers.
>
> I think in this context the __private annotation is incorrect.  We deref the
> nfs_page pointer only in TP_fast_assign() which runs at the call site, and
> the TP_printk only outputs the pointer value.
>

Hi Ben,

Thanks for pointing this out.

I realize that using unsigned long results in printing the raw pointer
address, which bypasses the intended %p formatting.

You are right that the __private annotation is not appropriate here,
since the req pointer is only dereferenced in TP_fast_assign() at the
call site and only printed from the trace buffer context.

I will rework the patch to remove __private and keep the pointer type
with %p formatting.

Best Regards,
Sean

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v1 1/2] NFS: fix RCU safety in nfs_compare_super_address
  2026-04-10 15:09   ` Benjamin Coddington
@ 2026-04-14 16:12     ` Sean Chang
  0 siblings, 0 replies; 7+ messages in thread
From: Sean Chang @ 2026-04-14 16:12 UTC (permalink / raw)
  To: Benjamin Coddington; +Cc: trondmy, anna, linux-nfs, linux-kernel

On Fri, Apr 10, 2026 at 11:09 PM Benjamin Coddington
<ben.coddington@hammerspace.com> wrote:
>
> On 8 Apr 2026, at 12:14, Sean Chang wrote:
>
> > The cl_xprt pointer in struct rpc_clnt is marked as __rcu. Accessing
> > it directly in nfs_compare_super_address() without RCU protection is
> > unsafe and triggers Sparse warnings about dereferencing noderef
> > expressions.
> >
> > Fix this by wrapping the access with rcu_read_lock() and using
> > rcu_dereference() to safely retrieve the transport pointer. This
> > ensures the xprt remains valid during the comparison of network
> > namespaces and addresses, preventing potential use-after-free during
> > concurrent transport updates.
> >
> > Signed-off-by: Sean Chang <seanwascoding@gmail.com>
>
> Fixes: 7e3fcf61abde ("nfs: don't share mounts between network namespaces")
>
> > ---
> >  fs/nfs/super.c | 32 ++++++++++++++++++++++----------
> >  1 file changed, 22 insertions(+), 10 deletions(-)
> >
> > diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> > index 7a318581f85b..071337f9ea37 100644
> > --- a/fs/nfs/super.c
> > +++ b/fs/nfs/super.c
> > @@ -1166,43 +1166,55 @@ static int nfs_set_super(struct super_block *s, struct fs_context *fc)
> >  static int nfs_compare_super_address(struct nfs_server *server1,
> >                                    struct nfs_server *server2)
> >  {
> > +     struct rpc_xprt *xprt1, *xprt2;
> >       struct sockaddr *sap1, *sap2;
> > -     struct rpc_xprt *xprt1 = server1->client->cl_xprt;
> > -     struct rpc_xprt *xprt2 = server2->client->cl_xprt;
> > +     int ret = 0;
> > +
> > +     rcu_read_lock();
> > +
> > +     xprt1 = rcu_dereference(server1->client->cl_xprt);
> > +     xprt2 = rcu_dereference(server2->client->cl_xprt);
> > +
> > +     if (!xprt1 || !xprt2)
> > +             goto out;
>
> ^^ I'm not sure that's a great test, the rpc_xprt objects are refcounted.
> These might not be null but you could still race with a freeing path?
>
> However, I think you might just be safe inside the RCU section here because
> rpc_switch_client_transport() uses synchronize_rcu() before xprt_put(old).
> I didn't audit all the freeing paths.
>

Thanks for the insightful feedback and for auditing the
rpc_switch_client_transport path!

You're absolutely right that synchronize_rcu() provides
a safety net during transport switches. However, to ensure
robustness across all potential freeing paths (including
those not yet fully audited) and to address the race you
mentioned, I've updated the patch to check the xprt state:

+        if (!xprt1 || !xprt2 ||
+            !test_bit(XPRT_CONNECTED, &xprt1->state) ||
+            !test_bit(XPRT_CONNECTED, &xprt2->state))
+                goto out_unlock;

The XPRT_CONNECTED check ensures that the transport
is not only memory-safe (via RCU) but also logically active.

Since the RPC layer clears this bit before initiating the teardown
of an rpc_xprt, this prevents accessing xprt_net on an object
that is on the freeing path.

> >       if (!net_eq(xprt1->xprt_net, xprt2->xprt_net))
> > -             return 0;
> > +             goto out;
>
> Probably safe to drop the RCU protection scope after this point.  No need to
> hold it over all the other checks..
>

Agreed. The RCU protection is indeed only necessary for fetching
and comparing the network namespaces from the rpc_xprt objects.
I will move rcu_read_unlock() to immediately after the net_eq check
in v2 to keep the critical section as small as possible.
Thanks!

Best Regards,
Sean

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-04-14 16:12 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-08 16:14 [PATCH v1 0/2] NFS: fix RCU and tracing pointer safety Sean Chang
2026-04-08 16:14 ` [PATCH v1 1/2] NFS: fix RCU safety in nfs_compare_super_address Sean Chang
2026-04-10 15:09   ` Benjamin Coddington
2026-04-14 16:12     ` Sean Chang
2026-04-08 16:14 ` [PATCH v1 2/2] NFS: use unsigned long for req field in nfs_page_class Sean Chang
2026-04-10 15:23   ` Benjamin Coddington
2026-04-14  9:14     ` Sean Chang

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.