[PATCH v3] nfs: protect nfs41_impl_id by rcu

Li Lingfeng posted 1 patch 1 month ago
fs/nfs/nfs4client.c       |  5 ++++-
fs/nfs/nfs4proc.c         |  5 +++--
fs/nfs/super.c            | 12 +++++++++---
include/linux/nfs_fs_sb.h |  2 +-
include/linux/nfs_xdr.h   |  3 ++-
5 files changed, 19 insertions(+), 8 deletions(-)
[PATCH v3] nfs: protect nfs41_impl_id by rcu
Posted by Li Lingfeng 1 month ago
When performing exchange id call, a new nfs41_impl_id will be allocated to
store some information from server. The pointers to the old and new
nfs41_impl_ids are swapped, and the old one will be freed.

However, UAF may be triggered as follows:

After T2 has got a pointer to the nfs41_impl_id, the nfs41_impl_id is
freed by T1 before it is used.
         T1                                           T2
nfs4_proc_exchange_id
 _nfs4_proc_exchange_id
  nfs4_run_exchange_id
   kzalloc // alloc nfs41_impl_id-B
   rpc_run_task
                                nfs_show_stats
                                 show_implementation_id
                                  impl_id = nfss->nfs_client->cl_implid
                                  // get alloc nfs41_impl_id-A
  swap(clp->cl_implid, resp->impl_id)
  rpc_put_task
   ...
    nfs4_exchange_id_release
     kfree // free nfs41_impl_id-A
                                  impl_id->name // UAF

Fix this issue by using rcu to protect the nfs41_impl_id.

Fixes: 7d2ed9ac22bc ("NFSv4: parse and display server implementation ids")
Signed-off-by: Li Lingfeng <lilingfeng3@huawei.com>
---
v1->v2:
  Free nfs41_impl_id by call_rcu in nfs4_shutdown_client to resolve
  warning.
v2->v3:
  Free nfs41_impl_id by kfree_rcu and check CONFIG_NFS_V4_1 before
  freeing nfs41_impl_id in nfs4_shutdown_client.

 fs/nfs/nfs4client.c       |  5 ++++-
 fs/nfs/nfs4proc.c         |  5 +++--
 fs/nfs/super.c            | 12 +++++++++---
 include/linux/nfs_fs_sb.h |  2 +-
 include/linux/nfs_xdr.h   |  3 ++-
 5 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c
index 83378f69b35e..852a64294fec 100644
--- a/fs/nfs/nfs4client.c
+++ b/fs/nfs/nfs4client.c
@@ -293,7 +293,10 @@ static void nfs4_shutdown_client(struct nfs_client *clp)
 	rpc_destroy_wait_queue(&clp->cl_rpcwaitq);
 	kfree(clp->cl_serverowner);
 	kfree(clp->cl_serverscope);
-	kfree(clp->cl_implid);
+#ifdef CONFIG_NFS_V4_1
+	if (clp->cl_implid)
+		kfree_rcu(clp->cl_implid, __rcu_head);
+#endif
 	kfree(clp->cl_owner_id);
 }
 
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index cd2fbde2e6d7..b6a9bcabb531 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -8884,7 +8884,8 @@ static void nfs4_exchange_id_release(void *data)
 					(struct nfs41_exchange_id_data *)data;
 
 	nfs_put_client(cdata->args.client);
-	kfree(cdata->res.impl_id);
+	if (cdata->res.impl_id)
+		kfree_rcu(cdata->res.impl_id, __rcu_head);
 	kfree(cdata->res.server_scope);
 	kfree(cdata->res.server_owner);
 	kfree(cdata);
@@ -9046,7 +9047,7 @@ static int _nfs4_proc_exchange_id(struct nfs_client *clp, const struct cred *cre
 
 	swap(clp->cl_serverowner, resp->server_owner);
 	swap(clp->cl_serverscope, resp->server_scope);
-	swap(clp->cl_implid, resp->impl_id);
+	resp->impl_id = rcu_replace_pointer(clp->cl_implid, resp->impl_id, 1);
 
 	/* Save the EXCHANGE_ID verifier session trunk tests */
 	memcpy(clp->cl_confirm.data, argp->verifier.data,
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 9723b6c53397..57665a82f12b 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -615,13 +615,19 @@ static void show_pnfs(struct seq_file *m, struct nfs_server *server)
 
 static void show_implementation_id(struct seq_file *m, struct nfs_server *nfss)
 {
-	if (nfss->nfs_client && nfss->nfs_client->cl_implid) {
-		struct nfs41_impl_id *impl_id = nfss->nfs_client->cl_implid;
+	struct nfs_client *clp = nfss->nfs_client;
+	struct nfs41_impl_id *impl_id;
+
+	if (!clp)
+		return;
+	rcu_read_lock();
+	impl_id = rcu_dereference(clp->cl_implid);
+	if (impl_id)
 		seq_printf(m, "\n\timpl_id:\tname='%s',domain='%s',"
 			   "date='%llu,%u'",
 			   impl_id->name, impl_id->domain,
 			   impl_id->date.seconds, impl_id->date.nseconds);
-	}
+	rcu_read_unlock();
 }
 #else
 #if IS_ENABLED(CONFIG_NFS_V4)
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index b804346a9741..fcad3f0ea68b 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -104,7 +104,7 @@ struct nfs_client {
 	bool			cl_preserve_clid;
 	struct nfs41_server_owner *cl_serverowner;
 	struct nfs41_server_scope *cl_serverscope;
-	struct nfs41_impl_id	*cl_implid;
+	struct nfs41_impl_id __rcu *cl_implid;
 	/* nfs 4.1+ state protection modes: */
 	unsigned long		cl_sp4_flags;
 #define NFS_SP4_MACH_CRED_MINIMAL  1	/* Minimal sp4_mach_cred - state ops
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 12d8e47bc5a3..a9d8a58ddb7c 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -1374,6 +1374,7 @@ struct nfs41_impl_id {
 	char				domain[NFS4_OPAQUE_LIMIT + 1];
 	char				name[NFS4_OPAQUE_LIMIT + 1];
 	struct nfstime4			date;
+	struct rcu_head			__rcu_head;
 };
 
 #define MAX_BIND_CONN_TO_SESSION_RETRIES 3
@@ -1397,7 +1398,7 @@ struct nfs41_exchange_id_res {
 	u32				flags;
 	struct nfs41_server_owner	*server_owner;
 	struct nfs41_server_scope	*server_scope;
-	struct nfs41_impl_id		*impl_id;
+	struct nfs41_impl_id __rcu	*impl_id;
 	struct nfs41_state_protection	state_protect;
 };
 
-- 
2.31.1
Re: [PATCH v3] nfs: protect nfs41_impl_id by rcu
Posted by kernel test robot 1 month ago
Hi Li,

kernel test robot noticed the following build warnings:

[auto build test WARNING on trondmy-nfs/linux-next]
[also build test WARNING on linus/master v6.12-rc4 next-20241025]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Li-Lingfeng/nfs-protect-nfs41_impl_id-by-rcu/20241022-194521
base:   git://git.linux-nfs.org/projects/trondmy/linux-nfs.git linux-next
patch link:    https://lore.kernel.org/r/20241022115847.1283892-1-lilingfeng3%40huawei.com
patch subject: [PATCH v3] nfs: protect nfs41_impl_id by rcu
config: alpha-randconfig-r132-20241025 (https://download.01.org/0day-ci/archive/20241025/202410252304.ImkycETw-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 13.3.0
reproduce: (https://download.01.org/0day-ci/archive/20241025/202410252304.ImkycETw-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202410252304.ImkycETw-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> fs/nfs/nfs4proc.c:8876:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected struct callback_head *head @@     got struct callback_head [noderef] __rcu * @@
   fs/nfs/nfs4proc.c:8876:17: sparse:     expected struct callback_head *head
   fs/nfs/nfs4proc.c:8876:17: sparse:     got struct callback_head [noderef] __rcu *
>> fs/nfs/nfs4proc.c:8876:17: sparse: sparse: cast removes address space '__rcu' of expression
>> fs/nfs/nfs4proc.c:8933:31: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected struct nfs41_impl_id [noderef] __rcu *impl_id @@     got void *_res @@
   fs/nfs/nfs4proc.c:8933:31: sparse:     expected struct nfs41_impl_id [noderef] __rcu *impl_id
   fs/nfs/nfs4proc.c:8933:31: sparse:     got void *_res
>> fs/nfs/nfs4proc.c:8973:28: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void const *objp @@     got struct nfs41_impl_id [noderef] __rcu *impl_id @@
   fs/nfs/nfs4proc.c:8973:28: sparse:     expected void const *objp
   fs/nfs/nfs4proc.c:8973:28: sparse:     got struct nfs41_impl_id [noderef] __rcu *impl_id
>> fs/nfs/nfs4proc.c:9038:25: sparse: sparse: incorrect type in initializer (different address spaces) @@     expected struct nfs41_impl_id [noderef] __rcu *__tmp @@     got struct nfs41_impl_id * @@
   fs/nfs/nfs4proc.c:9038:25: sparse:     expected struct nfs41_impl_id [noderef] __rcu *__tmp
   fs/nfs/nfs4proc.c:9038:25: sparse:     got struct nfs41_impl_id *
--
>> fs/nfs/nfs4xdr.c:5788:27: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void * @@     got char [noderef] __rcu * @@
   fs/nfs/nfs4xdr.c:5788:27: sparse:     expected void *
   fs/nfs/nfs4xdr.c:5788:27: sparse:     got char [noderef] __rcu *
   fs/nfs/nfs4xdr.c:5794:27: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void * @@     got char [noderef] __rcu * @@
   fs/nfs/nfs4xdr.c:5794:27: sparse:     expected void *
   fs/nfs/nfs4xdr.c:5794:27: sparse:     got char [noderef] __rcu *
>> fs/nfs/nfs4xdr.c:5800:45: sparse: sparse: incorrect type in argument 2 (different address spaces) @@     expected unsigned long long [usertype] *valp @@     got unsigned long long [noderef] __rcu * @@
   fs/nfs/nfs4xdr.c:5800:45: sparse:     expected unsigned long long [usertype] *valp
   fs/nfs/nfs4xdr.c:5800:45: sparse:     got unsigned long long [noderef] __rcu *
>> fs/nfs/nfs4xdr.c:5801:20: sparse: sparse: dereference of noderef expression
--
>> fs/nfs/nfs4client.c:298:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected struct callback_head *head @@     got struct callback_head [noderef] __rcu * @@
   fs/nfs/nfs4client.c:298:17: sparse:     expected struct callback_head *head
   fs/nfs/nfs4client.c:298:17: sparse:     got struct callback_head [noderef] __rcu *
>> fs/nfs/nfs4client.c:298:17: sparse: sparse: cast removes address space '__rcu' of expression

vim +8876 fs/nfs/nfs4proc.c

  8868	
  8869	static void nfs4_exchange_id_release(void *data)
  8870	{
  8871		struct nfs41_exchange_id_data *cdata =
  8872						(struct nfs41_exchange_id_data *)data;
  8873	
  8874		nfs_put_client(cdata->args.client);
  8875		if (cdata->res.impl_id)
> 8876			kfree_rcu(cdata->res.impl_id, __rcu_head);
  8877		kfree(cdata->res.server_scope);
  8878		kfree(cdata->res.server_owner);
  8879		kfree(cdata);
  8880	}
  8881	
  8882	static const struct rpc_call_ops nfs4_exchange_id_call_ops = {
  8883		.rpc_release = nfs4_exchange_id_release,
  8884	};
  8885	
  8886	/*
  8887	 * _nfs4_proc_exchange_id()
  8888	 *
  8889	 * Wrapper for EXCHANGE_ID operation.
  8890	 */
  8891	static struct rpc_task *
  8892	nfs4_run_exchange_id(struct nfs_client *clp, const struct cred *cred,
  8893				u32 sp4_how, struct rpc_xprt *xprt)
  8894	{
  8895		struct rpc_message msg = {
  8896			.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_EXCHANGE_ID],
  8897			.rpc_cred = cred,
  8898		};
  8899		struct rpc_task_setup task_setup_data = {
  8900			.rpc_client = clp->cl_rpcclient,
  8901			.callback_ops = &nfs4_exchange_id_call_ops,
  8902			.rpc_message = &msg,
  8903			.flags = RPC_TASK_TIMEOUT | RPC_TASK_NO_ROUND_ROBIN,
  8904		};
  8905		struct nfs41_exchange_id_data *calldata;
  8906		int status;
  8907	
  8908		if (!refcount_inc_not_zero(&clp->cl_count))
  8909			return ERR_PTR(-EIO);
  8910	
  8911		status = -ENOMEM;
  8912		calldata = kzalloc(sizeof(*calldata), GFP_NOFS);
  8913		if (!calldata)
  8914			goto out;
  8915	
  8916		nfs4_init_boot_verifier(clp, &calldata->args.verifier);
  8917	
  8918		status = nfs4_init_uniform_client_string(clp);
  8919		if (status)
  8920			goto out_calldata;
  8921	
  8922		calldata->res.server_owner = kzalloc(sizeof(struct nfs41_server_owner),
  8923							GFP_NOFS);
  8924		status = -ENOMEM;
  8925		if (unlikely(calldata->res.server_owner == NULL))
  8926			goto out_calldata;
  8927	
  8928		calldata->res.server_scope = kzalloc(sizeof(struct nfs41_server_scope),
  8929						GFP_NOFS);
  8930		if (unlikely(calldata->res.server_scope == NULL))
  8931			goto out_server_owner;
  8932	
> 8933		calldata->res.impl_id = kzalloc(sizeof(struct nfs41_impl_id), GFP_NOFS);
  8934		if (unlikely(calldata->res.impl_id == NULL))
  8935			goto out_server_scope;
  8936	
  8937		switch (sp4_how) {
  8938		case SP4_NONE:
  8939			calldata->args.state_protect.how = SP4_NONE;
  8940			break;
  8941	
  8942		case SP4_MACH_CRED:
  8943			calldata->args.state_protect = nfs4_sp4_mach_cred_request;
  8944			break;
  8945	
  8946		default:
  8947			/* unsupported! */
  8948			WARN_ON_ONCE(1);
  8949			status = -EINVAL;
  8950			goto out_impl_id;
  8951		}
  8952		if (xprt) {
  8953			task_setup_data.rpc_xprt = xprt;
  8954			task_setup_data.flags |= RPC_TASK_SOFTCONN;
  8955			memcpy(calldata->args.verifier.data, clp->cl_confirm.data,
  8956					sizeof(calldata->args.verifier.data));
  8957		}
  8958		calldata->args.client = clp;
  8959		calldata->args.flags = EXCHGID4_FLAG_SUPP_MOVED_REFER |
  8960		EXCHGID4_FLAG_BIND_PRINC_STATEID;
  8961	#ifdef CONFIG_NFS_V4_1_MIGRATION
  8962		calldata->args.flags |= EXCHGID4_FLAG_SUPP_MOVED_MIGR;
  8963	#endif
  8964		if (test_bit(NFS_CS_PNFS, &clp->cl_flags))
  8965			calldata->args.flags |= EXCHGID4_FLAG_USE_PNFS_DS;
  8966		msg.rpc_argp = &calldata->args;
  8967		msg.rpc_resp = &calldata->res;
  8968		task_setup_data.callback_data = calldata;
  8969	
  8970		return rpc_run_task(&task_setup_data);
  8971	
  8972	out_impl_id:
> 8973		kfree(calldata->res.impl_id);
  8974	out_server_scope:
  8975		kfree(calldata->res.server_scope);
  8976	out_server_owner:
  8977		kfree(calldata->res.server_owner);
  8978	out_calldata:
  8979		kfree(calldata);
  8980	out:
  8981		nfs_put_client(clp);
  8982		return ERR_PTR(status);
  8983	}
  8984	
  8985	/*
  8986	 * _nfs4_proc_exchange_id()
  8987	 *
  8988	 * Wrapper for EXCHANGE_ID operation.
  8989	 */
  8990	static int _nfs4_proc_exchange_id(struct nfs_client *clp, const struct cred *cred,
  8991				u32 sp4_how)
  8992	{
  8993		struct rpc_task *task;
  8994		struct nfs41_exchange_id_args *argp;
  8995		struct nfs41_exchange_id_res *resp;
  8996		unsigned long now = jiffies;
  8997		int status;
  8998	
  8999		task = nfs4_run_exchange_id(clp, cred, sp4_how, NULL);
  9000		if (IS_ERR(task))
  9001			return PTR_ERR(task);
  9002	
  9003		argp = task->tk_msg.rpc_argp;
  9004		resp = task->tk_msg.rpc_resp;
  9005		status = task->tk_status;
  9006		if (status  != 0)
  9007			goto out;
  9008	
  9009		status = nfs4_check_cl_exchange_flags(resp->flags,
  9010				clp->cl_mvops->minor_version);
  9011		if (status  != 0)
  9012			goto out;
  9013	
  9014		status = nfs4_sp4_select_mode(clp, &resp->state_protect);
  9015		if (status != 0)
  9016			goto out;
  9017	
  9018		do_renew_lease(clp, now);
  9019	
  9020		clp->cl_clientid = resp->clientid;
  9021		clp->cl_exchange_flags = resp->flags;
  9022		clp->cl_seqid = resp->seqid;
  9023		/* Client ID is not confirmed */
  9024		if (!(resp->flags & EXCHGID4_FLAG_CONFIRMED_R))
  9025			clear_bit(NFS4_SESSION_ESTABLISHED,
  9026				  &clp->cl_session->session_state);
  9027	
  9028		if (clp->cl_serverscope != NULL &&
  9029		    !nfs41_same_server_scope(clp->cl_serverscope,
  9030					resp->server_scope)) {
  9031			dprintk("%s: server_scope mismatch detected\n",
  9032				__func__);
  9033			set_bit(NFS4CLNT_SERVER_SCOPE_MISMATCH, &clp->cl_state);
  9034		}
  9035	
  9036		swap(clp->cl_serverowner, resp->server_owner);
  9037		swap(clp->cl_serverscope, resp->server_scope);
> 9038		resp->impl_id = rcu_replace_pointer(clp->cl_implid, resp->impl_id, 1);
  9039	
  9040		/* Save the EXCHANGE_ID verifier session trunk tests */
  9041		memcpy(clp->cl_confirm.data, argp->verifier.data,
  9042		       sizeof(clp->cl_confirm.data));
  9043	out:
  9044		trace_nfs4_exchange_id(clp, status);
  9045		rpc_put_task(task);
  9046		return status;
  9047	}
  9048	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki