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(-)
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
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
© 2016 - 2026 Red Hat, Inc.