--- /dev/null
+From d13e59aca2e1a167bd79c398ca888bf2d0b432da Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 8 Sep 2025 11:38:33 +1000
+Subject: nfsd: change nfs4_client_to_reclaim() to allocate data
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 4552f4e3f2c96597914f07b060d5c5db84420ddd ]
+
+The calling convention for nfs4_client_to_reclaim() is clumsy in that
+the caller needs to free memory if the function fails. It is much
+cleaner if the function frees its own memory.
+
+This patch changes nfs4_client_to_reclaim() to re-allocate the .data
+fields to be stored in the newly allocated struct nfs4_client_reclaim,
+and to free everything on failure.
+
+__cld_pipe_inprogress_downcall() needs to allocate the data anyway to
+copy it from user-space, so now that data is allocated twice. I think
+that is a small price to pay for a cleaner interface.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 67 +++++++++++++++----------------------------
+ fs/nfsd/nfs4state.c | 22 ++++++++++++--
+ 2 files changed, 42 insertions(+), 47 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index c74c4023770694..8ff61f6184e24d 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -167,24 +167,13 @@ legacy_recdir_name_error(struct nfs4_client *clp, int error)
+
+ static void
+ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
+- const char *dname, int len, struct nfsd_net *nn)
++ char *dname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = strlen(dname), .data = dname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+ struct nfs4_client_reclaim *crp;
+
+- name.data = kmemdup(dname, len, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- return;
+- }
+- name.len = len;
+ crp = nfs4_client_to_reclaim(name, princhash, nn);
+- if (!crp) {
+- kfree(name.data);
+- return;
+- }
+ crp->cr_clp = clp;
+ }
+
+@@ -240,8 +229,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ inode_unlock(d_inode(dir));
+ if (status == 0) {
+ if (nn->in_grace)
+- __nfsd4_create_reclaim_record_grace(clp, dname,
+- HEXDIR_LEN, nn);
++ __nfsd4_create_reclaim_record_grace(clp, dname, nn);
+ vfs_fsync(nn->rec_file, 0);
+ } else {
+ printk(KERN_ERR "NFSD: failed to write recovery record"
+@@ -478,7 +466,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ static int
+ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = HEXDIR_LEN, .data = cname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+ if (strlen(cname) != HEXDIR_LEN - 1) {
+@@ -487,16 +475,7 @@ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kstrdup(cname, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- goto out;
+- }
+- name.len = HEXDIR_LEN;
+- if (!nfs4_client_to_reclaim(name, princhash, nn))
+- kfree(name.data);
+-out:
++ nfs4_client_to_reclaim(name, princhash, nn);
+ return 0;
+ }
+
+@@ -792,6 +771,8 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ {
+ uint8_t cmd, princhashlen;
+ struct xdr_netobj name, princhash = { .len = 0, .data = NULL };
++ char *namecopy __free(kfree) = NULL;
++ char *princhashcopy __free(kfree) = NULL;
+ uint16_t namelen;
+ struct cld_net *cn = nn->cld_net;
+
+@@ -810,20 +791,20 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&ci->cc_name.cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&ci->cc_name.cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ if (get_user(princhashlen, &ci->cc_princhash.cp_len))
+ return -EFAULT;
+ if (princhashlen > 0) {
+- princhash.data = memdup_user(
+- &ci->cc_princhash.cp_data,
+- princhashlen);
+- if (IS_ERR(princhash.data)) {
+- kfree(name.data);
+- return PTR_ERR(princhash.data);
+- }
++ princhashcopy = memdup_user(
++ &ci->cc_princhash.cp_data,
++ princhashlen);
++ if (IS_ERR(princhashcopy))
++ return PTR_ERR(princhashcopy);
++ princhash.data = princhashcopy;
+ princhash.len = princhashlen;
+ } else
+ princhash.len = 0;
+@@ -837,23 +818,21 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&cnm->cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&cnm->cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ }
+ #ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+- memmove(name.data, name.data + 5, name.len);
++ name.data = name.data + 5;
+ cn->cn_has_legacy = true;
+ }
+ #endif
+- if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+- kfree(name.data);
+- kfree(princhash.data);
++ if (!nfs4_client_to_reclaim(name, princhash, nn))
+ return -EFAULT;
+- }
+ return nn->client_tracking_ops->msglen;
+ }
+ return -EFAULT;
+diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
+index 6b34bfac6194bb..c61e1a4ab620cc 100644
+--- a/fs/nfsd/nfs4state.c
++++ b/fs/nfsd/nfs4state.c
+@@ -8037,9 +8037,6 @@ nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
+
+ /*
+ * failure => all reset bets are off, nfserr_no_grace...
+- *
+- * The caller is responsible for freeing name.data if NULL is returned (it
+- * will be freed in nfs4_remove_reclaim_record in the normal case).
+ */
+ struct nfs4_client_reclaim *
+ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+@@ -8048,6 +8045,22 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ unsigned int strhashval;
+ struct nfs4_client_reclaim *crp;
+
++ name.data = kmemdup(name.data, name.len, GFP_KERNEL);
++ if (!name.data) {
++ dprintk("%s: failed to allocate memory for name.data!\n",
++ __func__);
++ return NULL;
++ }
++ if (princhash.len) {
++ princhash.data = kmemdup(princhash.data, princhash.len, GFP_KERNEL);
++ if (!princhash.data) {
++ dprintk("%s: failed to allocate memory for princhash.data!\n",
++ __func__);
++ kfree(name.data);
++ return NULL;
++ }
++ } else
++ princhash.data = NULL;
+ crp = alloc_reclaim();
+ if (crp) {
+ strhashval = clientstr_hashval(name);
+@@ -8059,6 +8072,9 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ crp->cr_princhash.len = princhash.len;
+ crp->cr_clp = NULL;
+ nn->reclaim_str_hashtbl_size++;
++ } else {
++ kfree(name.data);
++ kfree(princhash.data);
+ }
+ return crp;
+ }
+--
+2.53.0
+
--- /dev/null
+From 3c83fd41ed6deb900cfcad7444540c105c0e3c80 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 15 Sep 2025 12:55:13 +1000
+Subject: nfsd: move name lookup out of nfsd4_list_rec_dir()
+
+From: NeilBrown <neilb@ownmail.net>
+
+[ Upstream commit 89bd77cf436bf25e448817a662ebf76515f22863 ]
+
+nfsd4_list_rec_dir() is called with two different callbacks.
+One of the callbacks uses vfs_rmdir() to remove the directory.
+The other doesn't use the dentry at all, just the name.
+
+As only one callback needs the dentry, this patch moves the lookup into
+that callback. This prepares of changes to how directory operations
+are locked.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 4552f4e3f2c9 ("nfsd: change nfs4_client_to_reclaim() to allocate data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 55 ++++++++++++++++++++++---------------------
+ 1 file changed, 28 insertions(+), 27 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 8ea55b2621ee41..c74c4023770694 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -254,7 +254,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ nfs4_reset_creds(original_cred);
+ }
+
+-typedef int (recdir_func)(struct dentry *, struct dentry *, struct nfsd_net *);
++typedef int (recdir_func)(struct dentry *, char *, struct nfsd_net *);
+
+ struct name_list {
+ char name[HEXDIR_LEN];
+@@ -308,23 +308,14 @@ nfsd4_list_rec_dir(recdir_func *f, struct nfsd_net *nn)
+ }
+
+ status = iterate_dir(nn->rec_file, &ctx.ctx);
+- inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+- if (!status) {
+- struct dentry *dentry;
+- dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
+- if (IS_ERR(dentry)) {
+- status = PTR_ERR(dentry);
+- break;
+- }
+- status = f(dir, dentry, nn);
+- dput(dentry);
+- }
++ if (!status)
++ status = f(dir, entry->name, nn);
++
+ list_del(&entry->list);
+ kfree(entry);
+ }
+- inode_unlock(d_inode(dir));
+ nfs4_reset_creds(original_cred);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+@@ -422,18 +413,19 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)
+ }
+
+ static int
+-purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++purge_old(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ int status;
++ struct dentry *child;
+ struct xdr_netobj name;
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -443,10 +435,17 @@ purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
+ if (nfs4_has_reclaimed_state(name, nn))
+ goto out_free;
+
+- status = vfs_rmdir(d_inode(parent), child);
+- if (status)
+- printk("failed to remove client recovery directory %pd\n",
+- child);
++ inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
++ child = lookup_one_len(cname, parent, HEXDIR_LEN - 1);
++ if (!IS_ERR(child)) {
++ status = vfs_rmdir(d_inode(parent), child);
++ if (status)
++ printk("failed to remove client recovery directory %pd\n",
++ child);
++ dput(child);
++ }
++ inode_unlock(d_inode(parent));
++
+ out_free:
+ kfree(name.data);
+ out:
+@@ -477,18 +476,18 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ }
+
+ static int
+-load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ struct xdr_netobj name;
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -843,11 +842,13 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ return PTR_ERR(name.data);
+ name.len = namelen;
+ }
++#ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+ memmove(name.data, name.data + 5, name.len);
+ cn->cn_has_legacy = true;
+ }
++#endif
+ if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+ kfree(name.data);
+ kfree(princhash.data);
+--
+2.53.0
+
nfsd-reset-write-verifier-on-deferred-writeback-erro.patch
userfaultfd-gate-must_wait-writability-check-on-pte_.patch
clk-imx-add-check-for-kcalloc.patch
+nfsd-move-name-lookup-out-of-nfsd4_list_rec_dir.patch
+nfsd-change-nfs4_client_to_reclaim-to-allocate-data.patch
--- /dev/null
+From 2d47b2334b3baecf9a04ccb8563696184d8e22da Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 8 Sep 2025 11:38:33 +1000
+Subject: nfsd: change nfs4_client_to_reclaim() to allocate data
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 4552f4e3f2c96597914f07b060d5c5db84420ddd ]
+
+The calling convention for nfs4_client_to_reclaim() is clumsy in that
+the caller needs to free memory if the function fails. It is much
+cleaner if the function frees its own memory.
+
+This patch changes nfs4_client_to_reclaim() to re-allocate the .data
+fields to be stored in the newly allocated struct nfs4_client_reclaim,
+and to free everything on failure.
+
+__cld_pipe_inprogress_downcall() needs to allocate the data anyway to
+copy it from user-space, so now that data is allocated twice. I think
+that is a small price to pay for a cleaner interface.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 67 +++++++++++++++----------------------------
+ fs/nfsd/nfs4state.c | 22 ++++++++++++--
+ 2 files changed, 42 insertions(+), 47 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 52984348ba3995..d13e512149eede 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -167,24 +167,13 @@ legacy_recdir_name_error(struct nfs4_client *clp, int error)
+
+ static void
+ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
+- const char *dname, int len, struct nfsd_net *nn)
++ char *dname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = strlen(dname), .data = dname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+ struct nfs4_client_reclaim *crp;
+
+- name.data = kmemdup(dname, len, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- return;
+- }
+- name.len = len;
+ crp = nfs4_client_to_reclaim(name, princhash, nn);
+- if (!crp) {
+- kfree(name.data);
+- return;
+- }
+ crp->cr_clp = clp;
+ }
+
+@@ -240,8 +229,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ inode_unlock(d_inode(dir));
+ if (status == 0) {
+ if (nn->in_grace)
+- __nfsd4_create_reclaim_record_grace(clp, dname,
+- HEXDIR_LEN, nn);
++ __nfsd4_create_reclaim_record_grace(clp, dname, nn);
+ vfs_fsync(nn->rec_file, 0);
+ } else {
+ printk(KERN_ERR "NFSD: failed to write recovery record"
+@@ -478,7 +466,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ static int
+ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = HEXDIR_LEN, .data = cname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+ if (strlen(cname) != HEXDIR_LEN - 1) {
+@@ -487,16 +475,7 @@ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kstrdup(cname, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- goto out;
+- }
+- name.len = HEXDIR_LEN;
+- if (!nfs4_client_to_reclaim(name, princhash, nn))
+- kfree(name.data);
+-out:
++ nfs4_client_to_reclaim(name, princhash, nn);
+ return 0;
+ }
+
+@@ -792,6 +771,8 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ {
+ uint8_t cmd, princhashlen;
+ struct xdr_netobj name, princhash = { .len = 0, .data = NULL };
++ char *namecopy __free(kfree) = NULL;
++ char *princhashcopy __free(kfree) = NULL;
+ uint16_t namelen;
+ struct cld_net *cn = nn->cld_net;
+
+@@ -810,20 +791,20 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&ci->cc_name.cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&ci->cc_name.cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ if (get_user(princhashlen, &ci->cc_princhash.cp_len))
+ return -EFAULT;
+ if (princhashlen > 0) {
+- princhash.data = memdup_user(
+- &ci->cc_princhash.cp_data,
+- princhashlen);
+- if (IS_ERR(princhash.data)) {
+- kfree(name.data);
+- return PTR_ERR(princhash.data);
+- }
++ princhashcopy = memdup_user(
++ &ci->cc_princhash.cp_data,
++ princhashlen);
++ if (IS_ERR(princhashcopy))
++ return PTR_ERR(princhashcopy);
++ princhash.data = princhashcopy;
+ princhash.len = princhashlen;
+ } else
+ princhash.len = 0;
+@@ -837,23 +818,21 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&cnm->cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&cnm->cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ }
+ #ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+- memmove(name.data, name.data + 5, name.len);
++ name.data = name.data + 5;
+ cn->cn_has_legacy = true;
+ }
+ #endif
+- if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+- kfree(name.data);
+- kfree(princhash.data);
++ if (!nfs4_client_to_reclaim(name, princhash, nn))
+ return -EFAULT;
+- }
+ return nn->client_tracking_ops->msglen;
+ }
+ return -EFAULT;
+diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
+index 86e3bf53440e38..e6c0b1520973e9 100644
+--- a/fs/nfsd/nfs4state.c
++++ b/fs/nfsd/nfs4state.c
+@@ -8028,9 +8028,6 @@ nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
+
+ /*
+ * failure => all reset bets are off, nfserr_no_grace...
+- *
+- * The caller is responsible for freeing name.data if NULL is returned (it
+- * will be freed in nfs4_remove_reclaim_record in the normal case).
+ */
+ struct nfs4_client_reclaim *
+ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+@@ -8039,6 +8036,22 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ unsigned int strhashval;
+ struct nfs4_client_reclaim *crp;
+
++ name.data = kmemdup(name.data, name.len, GFP_KERNEL);
++ if (!name.data) {
++ dprintk("%s: failed to allocate memory for name.data!\n",
++ __func__);
++ return NULL;
++ }
++ if (princhash.len) {
++ princhash.data = kmemdup(princhash.data, princhash.len, GFP_KERNEL);
++ if (!princhash.data) {
++ dprintk("%s: failed to allocate memory for princhash.data!\n",
++ __func__);
++ kfree(name.data);
++ return NULL;
++ }
++ } else
++ princhash.data = NULL;
+ crp = alloc_reclaim();
+ if (crp) {
+ strhashval = clientstr_hashval(name);
+@@ -8050,6 +8063,9 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ crp->cr_princhash.len = princhash.len;
+ crp->cr_clp = NULL;
+ nn->reclaim_str_hashtbl_size++;
++ } else {
++ kfree(name.data);
++ kfree(princhash.data);
+ }
+ return crp;
+ }
+--
+2.53.0
+
--- /dev/null
+From 49e851bd264042ac2500fe199aac08233f65e463 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 15 Sep 2025 12:55:13 +1000
+Subject: nfsd: move name lookup out of nfsd4_list_rec_dir()
+
+From: NeilBrown <neilb@ownmail.net>
+
+[ Upstream commit 89bd77cf436bf25e448817a662ebf76515f22863 ]
+
+nfsd4_list_rec_dir() is called with two different callbacks.
+One of the callbacks uses vfs_rmdir() to remove the directory.
+The other doesn't use the dentry at all, just the name.
+
+As only one callback needs the dentry, this patch moves the lookup into
+that callback. This prepares of changes to how directory operations
+are locked.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 4552f4e3f2c9 ("nfsd: change nfs4_client_to_reclaim() to allocate data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 55 ++++++++++++++++++++++---------------------
+ 1 file changed, 28 insertions(+), 27 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index b8ea2bdfbcfbf5..52984348ba3995 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -254,7 +254,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ nfs4_reset_creds(original_cred);
+ }
+
+-typedef int (recdir_func)(struct dentry *, struct dentry *, struct nfsd_net *);
++typedef int (recdir_func)(struct dentry *, char *, struct nfsd_net *);
+
+ struct name_list {
+ char name[HEXDIR_LEN];
+@@ -308,23 +308,14 @@ nfsd4_list_rec_dir(recdir_func *f, struct nfsd_net *nn)
+ }
+
+ status = iterate_dir(nn->rec_file, &ctx.ctx);
+- inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+- if (!status) {
+- struct dentry *dentry;
+- dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
+- if (IS_ERR(dentry)) {
+- status = PTR_ERR(dentry);
+- break;
+- }
+- status = f(dir, dentry, nn);
+- dput(dentry);
+- }
++ if (!status)
++ status = f(dir, entry->name, nn);
++
+ list_del(&entry->list);
+ kfree(entry);
+ }
+- inode_unlock(d_inode(dir));
+ nfs4_reset_creds(original_cred);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+@@ -422,18 +413,19 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)
+ }
+
+ static int
+-purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++purge_old(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ int status;
++ struct dentry *child;
+ struct xdr_netobj name;
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -443,10 +435,17 @@ purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
+ if (nfs4_has_reclaimed_state(name, nn))
+ goto out_free;
+
+- status = vfs_rmdir(&init_user_ns, d_inode(parent), child);
+- if (status)
+- printk("failed to remove client recovery directory %pd\n",
+- child);
++ inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
++ child = lookup_one_len(cname, parent, HEXDIR_LEN - 1);
++ if (!IS_ERR(child)) {
++ status = vfs_rmdir(&init_user_ns, d_inode(parent), child);
++ if (status)
++ printk("failed to remove client recovery directory %pd\n",
++ child);
++ dput(child);
++ }
++ inode_unlock(d_inode(parent));
++
+ out_free:
+ kfree(name.data);
+ out:
+@@ -477,18 +476,18 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ }
+
+ static int
+-load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ struct xdr_netobj name;
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -843,11 +842,13 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ return PTR_ERR(name.data);
+ name.len = namelen;
+ }
++#ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+ memmove(name.data, name.data + 5, name.len);
+ cn->cn_has_legacy = true;
+ }
++#endif
+ if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+ kfree(name.data);
+ kfree(princhash.data);
+--
+2.53.0
+
slimbus-qcom-ngd-ctrl-correct-pdr-and-ssr-cleanup-ow.patch
slimbus-convert-to-platform-remove-callback-returnin.patch
slimbus-qcom-ngd-ctrl-register-callbacks-after-creat.patch
+nfsd-move-name-lookup-out-of-nfsd4_list_rec_dir.patch
+nfsd-change-nfs4_client_to_reclaim-to-allocate-data.patch
--- /dev/null
+From 0e6ced058fed27806e5dc705143487c2949129cc Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 8 Sep 2025 11:38:33 +1000
+Subject: nfsd: change nfs4_client_to_reclaim() to allocate data
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 4552f4e3f2c96597914f07b060d5c5db84420ddd ]
+
+The calling convention for nfs4_client_to_reclaim() is clumsy in that
+the caller needs to free memory if the function fails. It is much
+cleaner if the function frees its own memory.
+
+This patch changes nfs4_client_to_reclaim() to re-allocate the .data
+fields to be stored in the newly allocated struct nfs4_client_reclaim,
+and to free everything on failure.
+
+__cld_pipe_inprogress_downcall() needs to allocate the data anyway to
+copy it from user-space, so now that data is allocated twice. I think
+that is a small price to pay for a cleaner interface.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 67 +++++++++++++++----------------------------
+ fs/nfsd/nfs4state.c | 22 ++++++++++++--
+ 2 files changed, 42 insertions(+), 47 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 3ad9da783eb686..4f1f4d2c6bed1f 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -167,24 +167,13 @@ legacy_recdir_name_error(struct nfs4_client *clp, int error)
+
+ static void
+ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
+- const char *dname, int len, struct nfsd_net *nn)
++ char *dname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = strlen(dname), .data = dname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+ struct nfs4_client_reclaim *crp;
+
+- name.data = kmemdup(dname, len, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- return;
+- }
+- name.len = len;
+ crp = nfs4_client_to_reclaim(name, princhash, nn);
+- if (!crp) {
+- kfree(name.data);
+- return;
+- }
+ crp->cr_clp = clp;
+ }
+
+@@ -240,8 +229,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ inode_unlock(d_inode(dir));
+ if (status == 0) {
+ if (nn->in_grace)
+- __nfsd4_create_reclaim_record_grace(clp, dname,
+- HEXDIR_LEN, nn);
++ __nfsd4_create_reclaim_record_grace(clp, dname, nn);
+ vfs_fsync(nn->rec_file, 0);
+ } else {
+ printk(KERN_ERR "NFSD: failed to write recovery record"
+@@ -478,7 +466,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ static int
+ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = HEXDIR_LEN, .data = cname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+ if (strlen(cname) != HEXDIR_LEN - 1) {
+@@ -487,16 +475,7 @@ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kstrdup(cname, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- goto out;
+- }
+- name.len = HEXDIR_LEN;
+- if (!nfs4_client_to_reclaim(name, princhash, nn))
+- kfree(name.data);
+-out:
++ nfs4_client_to_reclaim(name, princhash, nn);
+ return 0;
+ }
+
+@@ -792,6 +771,8 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ {
+ uint8_t cmd, princhashlen;
+ struct xdr_netobj name, princhash = { .len = 0, .data = NULL };
++ char *namecopy __free(kfree) = NULL;
++ char *princhashcopy __free(kfree) = NULL;
+ uint16_t namelen;
+ struct cld_net *cn = nn->cld_net;
+
+@@ -810,20 +791,20 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&ci->cc_name.cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&ci->cc_name.cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ if (get_user(princhashlen, &ci->cc_princhash.cp_len))
+ return -EFAULT;
+ if (princhashlen > 0) {
+- princhash.data = memdup_user(
+- &ci->cc_princhash.cp_data,
+- princhashlen);
+- if (IS_ERR(princhash.data)) {
+- kfree(name.data);
+- return PTR_ERR(princhash.data);
+- }
++ princhashcopy = memdup_user(
++ &ci->cc_princhash.cp_data,
++ princhashlen);
++ if (IS_ERR(princhashcopy))
++ return PTR_ERR(princhashcopy);
++ princhash.data = princhashcopy;
+ princhash.len = princhashlen;
+ } else
+ princhash.len = 0;
+@@ -837,23 +818,21 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&cnm->cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&cnm->cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ }
+ #ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+- memmove(name.data, name.data + 5, name.len);
++ name.data = name.data + 5;
+ cn->cn_has_legacy = true;
+ }
+ #endif
+- if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+- kfree(name.data);
+- kfree(princhash.data);
++ if (!nfs4_client_to_reclaim(name, princhash, nn))
+ return -EFAULT;
+- }
+ return nn->client_tracking_ops->msglen;
+ }
+ return -EFAULT;
+diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
+index ed5cc5b2330aef..f390716ce3ffe7 100644
+--- a/fs/nfsd/nfs4state.c
++++ b/fs/nfsd/nfs4state.c
+@@ -8027,9 +8027,6 @@ nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
+
+ /*
+ * failure => all reset bets are off, nfserr_no_grace...
+- *
+- * The caller is responsible for freeing name.data if NULL is returned (it
+- * will be freed in nfs4_remove_reclaim_record in the normal case).
+ */
+ struct nfs4_client_reclaim *
+ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+@@ -8038,6 +8035,22 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ unsigned int strhashval;
+ struct nfs4_client_reclaim *crp;
+
++ name.data = kmemdup(name.data, name.len, GFP_KERNEL);
++ if (!name.data) {
++ dprintk("%s: failed to allocate memory for name.data!\n",
++ __func__);
++ return NULL;
++ }
++ if (princhash.len) {
++ princhash.data = kmemdup(princhash.data, princhash.len, GFP_KERNEL);
++ if (!princhash.data) {
++ dprintk("%s: failed to allocate memory for princhash.data!\n",
++ __func__);
++ kfree(name.data);
++ return NULL;
++ }
++ } else
++ princhash.data = NULL;
+ crp = alloc_reclaim();
+ if (crp) {
+ strhashval = clientstr_hashval(name);
+@@ -8049,6 +8062,9 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ crp->cr_princhash.len = princhash.len;
+ crp->cr_clp = NULL;
+ nn->reclaim_str_hashtbl_size++;
++ } else {
++ kfree(name.data);
++ kfree(princhash.data);
+ }
+ return crp;
+ }
+--
+2.53.0
+
--- /dev/null
+From 0e9a219fd38ce22467fb9271cecfe30496e461a3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 15 Sep 2025 12:55:13 +1000
+Subject: nfsd: move name lookup out of nfsd4_list_rec_dir()
+
+From: NeilBrown <neilb@ownmail.net>
+
+[ Upstream commit 89bd77cf436bf25e448817a662ebf76515f22863 ]
+
+nfsd4_list_rec_dir() is called with two different callbacks.
+One of the callbacks uses vfs_rmdir() to remove the directory.
+The other doesn't use the dentry at all, just the name.
+
+As only one callback needs the dentry, this patch moves the lookup into
+that callback. This prepares of changes to how directory operations
+are locked.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 4552f4e3f2c9 ("nfsd: change nfs4_client_to_reclaim() to allocate data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 55 ++++++++++++++++++++++---------------------
+ 1 file changed, 28 insertions(+), 27 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index d2505f817d78be..3ad9da783eb686 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -254,7 +254,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ nfs4_reset_creds(original_cred);
+ }
+
+-typedef int (recdir_func)(struct dentry *, struct dentry *, struct nfsd_net *);
++typedef int (recdir_func)(struct dentry *, char *, struct nfsd_net *);
+
+ struct name_list {
+ char name[HEXDIR_LEN];
+@@ -308,23 +308,14 @@ nfsd4_list_rec_dir(recdir_func *f, struct nfsd_net *nn)
+ }
+
+ status = iterate_dir(nn->rec_file, &ctx.ctx);
+- inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+- if (!status) {
+- struct dentry *dentry;
+- dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
+- if (IS_ERR(dentry)) {
+- status = PTR_ERR(dentry);
+- break;
+- }
+- status = f(dir, dentry, nn);
+- dput(dentry);
+- }
++ if (!status)
++ status = f(dir, entry->name, nn);
++
+ list_del(&entry->list);
+ kfree(entry);
+ }
+- inode_unlock(d_inode(dir));
+ nfs4_reset_creds(original_cred);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+@@ -422,18 +413,19 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)
+ }
+
+ static int
+-purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++purge_old(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ int status;
++ struct dentry *child;
+ struct xdr_netobj name;
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -443,10 +435,17 @@ purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
+ if (nfs4_has_reclaimed_state(name, nn))
+ goto out_free;
+
+- status = vfs_rmdir(&init_user_ns, d_inode(parent), child);
+- if (status)
+- printk("failed to remove client recovery directory %pd\n",
+- child);
++ inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
++ child = lookup_one_len(cname, parent, HEXDIR_LEN - 1);
++ if (!IS_ERR(child)) {
++ status = vfs_rmdir(&init_user_ns, d_inode(parent), child);
++ if (status)
++ printk("failed to remove client recovery directory %pd\n",
++ child);
++ dput(child);
++ }
++ inode_unlock(d_inode(parent));
++
+ out_free:
+ kfree(name.data);
+ out:
+@@ -477,18 +476,18 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ }
+
+ static int
+-load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ struct xdr_netobj name;
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -843,11 +842,13 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ return PTR_ERR(name.data);
+ name.len = namelen;
+ }
++#ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+ memmove(name.data, name.data + 5, name.len);
+ cn->cn_has_legacy = true;
+ }
++#endif
+ if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+ kfree(name.data);
+ kfree(princhash.data);
+--
+2.53.0
+
slimbus-qcom-ngd-ctrl-correct-pdr-and-ssr-cleanup-ow.patch
slimbus-convert-to-platform-remove-callback-returnin.patch
slimbus-qcom-ngd-ctrl-register-callbacks-after-creat.patch
+nfsd-move-name-lookup-out-of-nfsd4_list_rec_dir.patch
+nfsd-change-nfs4_client_to_reclaim-to-allocate-data.patch
--- /dev/null
+From 67274988fb4f83f18b35d72c468a214c2564cb6f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 8 Sep 2025 11:38:33 +1000
+Subject: nfsd: change nfs4_client_to_reclaim() to allocate data
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 4552f4e3f2c96597914f07b060d5c5db84420ddd ]
+
+The calling convention for nfs4_client_to_reclaim() is clumsy in that
+the caller needs to free memory if the function fails. It is much
+cleaner if the function frees its own memory.
+
+This patch changes nfs4_client_to_reclaim() to re-allocate the .data
+fields to be stored in the newly allocated struct nfs4_client_reclaim,
+and to free everything on failure.
+
+__cld_pipe_inprogress_downcall() needs to allocate the data anyway to
+copy it from user-space, so now that data is allocated twice. I think
+that is a small price to pay for a cleaner interface.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 67 +++++++++++++++----------------------------
+ fs/nfsd/nfs4state.c | 22 ++++++++++++--
+ 2 files changed, 42 insertions(+), 47 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 4c63f46985c2ea..a8182962a28618 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -168,24 +168,13 @@ legacy_recdir_name_error(struct nfs4_client *clp, int error)
+
+ static void
+ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
+- const char *dname, int len, struct nfsd_net *nn)
++ char *dname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = strlen(dname), .data = dname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+ struct nfs4_client_reclaim *crp;
+
+- name.data = kmemdup(dname, len, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- return;
+- }
+- name.len = len;
+ crp = nfs4_client_to_reclaim(name, princhash, nn);
+- if (!crp) {
+- kfree(name.data);
+- return;
+- }
+ crp->cr_clp = clp;
+ }
+
+@@ -241,8 +230,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ inode_unlock(d_inode(dir));
+ if (status == 0) {
+ if (nn->in_grace)
+- __nfsd4_create_reclaim_record_grace(clp, dname,
+- HEXDIR_LEN, nn);
++ __nfsd4_create_reclaim_record_grace(clp, dname, nn);
+ vfs_fsync(nn->rec_file, 0);
+ } else {
+ printk(KERN_ERR "NFSD: failed to write recovery record"
+@@ -479,7 +467,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ static int
+ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = HEXDIR_LEN, .data = cname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+ if (strlen(cname) != HEXDIR_LEN - 1) {
+@@ -488,16 +476,7 @@ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kstrdup(cname, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- goto out;
+- }
+- name.len = HEXDIR_LEN;
+- if (!nfs4_client_to_reclaim(name, princhash, nn))
+- kfree(name.data);
+-out:
++ nfs4_client_to_reclaim(name, princhash, nn);
+ return 0;
+ }
+
+@@ -796,6 +775,8 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ {
+ uint8_t cmd, princhashlen;
+ struct xdr_netobj name, princhash = { .len = 0, .data = NULL };
++ char *namecopy __free(kfree) = NULL;
++ char *princhashcopy __free(kfree) = NULL;
+ uint16_t namelen;
+
+ if (get_user(cmd, &cmsg->cm_cmd)) {
+@@ -813,20 +794,20 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: invalid namelen (%u)", __func__, namelen);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&ci->cc_name.cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&ci->cc_name.cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ if (get_user(princhashlen, &ci->cc_princhash.cp_len))
+ return -EFAULT;
+ if (princhashlen > 0) {
+- princhash.data = memdup_user(
+- &ci->cc_princhash.cp_data,
+- princhashlen);
+- if (IS_ERR(princhash.data)) {
+- kfree(name.data);
+- return PTR_ERR(princhash.data);
+- }
++ princhashcopy = memdup_user(
++ &ci->cc_princhash.cp_data,
++ princhashlen);
++ if (IS_ERR(princhashcopy))
++ return PTR_ERR(princhashcopy);
++ princhash.data = princhashcopy;
+ princhash.len = princhashlen;
+ } else
+ princhash.len = 0;
+@@ -840,9 +821,10 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: invalid namelen (%u)", __func__, namelen);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&cnm->cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&cnm->cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ }
+ #ifdef CONFIG_NFSD_LEGACY_CLIENT_TRACKING
+@@ -850,15 +832,12 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ struct cld_net *cn = nn->cld_net;
+
+ name.len = name.len - 5;
+- memmove(name.data, name.data + 5, name.len);
++ name.data = name.data + 5;
+ cn->cn_has_legacy = true;
+ }
+ #endif
+- if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+- kfree(name.data);
+- kfree(princhash.data);
++ if (!nfs4_client_to_reclaim(name, princhash, nn))
+ return -EFAULT;
+- }
+ return nn->client_tracking_ops->msglen;
+ }
+ return -EFAULT;
+diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
+index e18d70adcdb903..8c13fa68c08a84 100644
+--- a/fs/nfsd/nfs4state.c
++++ b/fs/nfsd/nfs4state.c
+@@ -8557,9 +8557,6 @@ nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
+
+ /*
+ * failure => all reset bets are off, nfserr_no_grace...
+- *
+- * The caller is responsible for freeing name.data if NULL is returned (it
+- * will be freed in nfs4_remove_reclaim_record in the normal case).
+ */
+ struct nfs4_client_reclaim *
+ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+@@ -8568,6 +8565,22 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ unsigned int strhashval;
+ struct nfs4_client_reclaim *crp;
+
++ name.data = kmemdup(name.data, name.len, GFP_KERNEL);
++ if (!name.data) {
++ dprintk("%s: failed to allocate memory for name.data!\n",
++ __func__);
++ return NULL;
++ }
++ if (princhash.len) {
++ princhash.data = kmemdup(princhash.data, princhash.len, GFP_KERNEL);
++ if (!princhash.data) {
++ dprintk("%s: failed to allocate memory for princhash.data!\n",
++ __func__);
++ kfree(name.data);
++ return NULL;
++ }
++ } else
++ princhash.data = NULL;
+ crp = alloc_reclaim();
+ if (crp) {
+ strhashval = clientstr_hashval(name);
+@@ -8579,6 +8592,9 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ crp->cr_princhash.len = princhash.len;
+ crp->cr_clp = NULL;
+ nn->reclaim_str_hashtbl_size++;
++ } else {
++ kfree(name.data);
++ kfree(princhash.data);
+ }
+ return crp;
+ }
+--
+2.53.0
+
--- /dev/null
+From 58d0a73ee18904cf81bb132b2a0397477df37230 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 15 Sep 2025 12:55:13 +1000
+Subject: nfsd: move name lookup out of nfsd4_list_rec_dir()
+
+From: NeilBrown <neilb@ownmail.net>
+
+[ Upstream commit 89bd77cf436bf25e448817a662ebf76515f22863 ]
+
+nfsd4_list_rec_dir() is called with two different callbacks.
+One of the callbacks uses vfs_rmdir() to remove the directory.
+The other doesn't use the dentry at all, just the name.
+
+As only one callback needs the dentry, this patch moves the lookup into
+that callback. This prepares of changes to how directory operations
+are locked.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 4552f4e3f2c9 ("nfsd: change nfs4_client_to_reclaim() to allocate data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 53 +++++++++++++++++++++----------------------
+ 1 file changed, 26 insertions(+), 27 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 075715998648a0..4c63f46985c2ea 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -255,7 +255,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ nfs4_reset_creds(original_cred);
+ }
+
+-typedef int (recdir_func)(struct dentry *, struct dentry *, struct nfsd_net *);
++typedef int (recdir_func)(struct dentry *, char *, struct nfsd_net *);
+
+ struct name_list {
+ char name[HEXDIR_LEN];
+@@ -309,23 +309,14 @@ nfsd4_list_rec_dir(recdir_func *f, struct nfsd_net *nn)
+ }
+
+ status = iterate_dir(nn->rec_file, &ctx.ctx);
+- inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+- if (!status) {
+- struct dentry *dentry;
+- dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
+- if (IS_ERR(dentry)) {
+- status = PTR_ERR(dentry);
+- break;
+- }
+- status = f(dir, dentry, nn);
+- dput(dentry);
+- }
++ if (!status)
++ status = f(dir, entry->name, nn);
++
+ list_del(&entry->list);
+ kfree(entry);
+ }
+- inode_unlock(d_inode(dir));
+ nfs4_reset_creds(original_cred);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+@@ -423,18 +414,19 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)
+ }
+
+ static int
+-purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++purge_old(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ int status;
++ struct dentry *child;
+ struct xdr_netobj name;
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -444,10 +436,17 @@ purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
+ if (nfs4_has_reclaimed_state(name, nn))
+ goto out_free;
+
+- status = vfs_rmdir(&nop_mnt_idmap, d_inode(parent), child);
+- if (status)
+- printk("failed to remove client recovery directory %pd\n",
+- child);
++ inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
++ child = lookup_one_len(cname, parent, HEXDIR_LEN-1);
++ if (!IS_ERR(child)) {
++ status = vfs_rmdir(&nop_mnt_idmap, d_inode(parent), child);
++ if (status)
++ printk("failed to remove client recovery directory %pd\n",
++ child);
++ dput(child);
++ }
++ inode_unlock(d_inode(parent));
++
+ out_free:
+ kfree(name.data);
+ out:
+@@ -478,18 +477,18 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ }
+
+ static int
+-load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ struct xdr_netobj name;
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+--
+2.53.0
+
nfsv4-flexfiles-reject-zero-filehandle-version-count.patch
locking-rtmutex-make-sure-we-wake-anything-on-the-wake_q-when-we-release-the-lock-wait_lock.patch
apparmor-advertise-the-tcp-fast-open-fix-is-applied.patch
+nfsd-move-name-lookup-out-of-nfsd4_list_rec_dir.patch
+nfsd-change-nfs4_client_to_reclaim-to-allocate-data.patch
--- /dev/null
+From d25ea21a59bd530ca5718e460738e9d755710934 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 8 Sep 2025 11:38:33 +1000
+Subject: nfsd: change nfs4_client_to_reclaim() to allocate data
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 4552f4e3f2c96597914f07b060d5c5db84420ddd ]
+
+The calling convention for nfs4_client_to_reclaim() is clumsy in that
+the caller needs to free memory if the function fails. It is much
+cleaner if the function frees its own memory.
+
+This patch changes nfs4_client_to_reclaim() to re-allocate the .data
+fields to be stored in the newly allocated struct nfs4_client_reclaim,
+and to free everything on failure.
+
+__cld_pipe_inprogress_downcall() needs to allocate the data anyway to
+copy it from user-space, so now that data is allocated twice. I think
+that is a small price to pay for a cleaner interface.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 67 +++++++++++++++----------------------------
+ fs/nfsd/nfs4state.c | 22 ++++++++++++--
+ 2 files changed, 42 insertions(+), 47 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 8ab0b26c1f91ab..8c5b5b8a5c880a 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -147,24 +147,13 @@ legacy_recdir_name_error(struct nfs4_client *clp, int error)
+
+ static void
+ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
+- const char *dname, int len, struct nfsd_net *nn)
++ char *dname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = strlen(dname), .data = dname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+ struct nfs4_client_reclaim *crp;
+
+- name.data = kmemdup(dname, len, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- return;
+- }
+- name.len = len;
+ crp = nfs4_client_to_reclaim(name, princhash, nn);
+- if (!crp) {
+- kfree(name.data);
+- return;
+- }
+ crp->cr_clp = clp;
+ }
+
+@@ -223,8 +212,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ inode_unlock(d_inode(dir));
+ if (status == 0) {
+ if (nn->in_grace)
+- __nfsd4_create_reclaim_record_grace(clp, dname,
+- HEXDIR_LEN, nn);
++ __nfsd4_create_reclaim_record_grace(clp, dname, nn);
+ vfs_fsync(nn->rec_file, 0);
+ } else {
+ printk(KERN_ERR "NFSD: failed to write recovery record"
+@@ -461,7 +449,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ static int
+ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = HEXDIR_LEN, .data = cname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+ if (strlen(cname) != HEXDIR_LEN - 1) {
+@@ -470,16 +458,7 @@ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kstrdup(cname, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- goto out;
+- }
+- name.len = HEXDIR_LEN;
+- if (!nfs4_client_to_reclaim(name, princhash, nn))
+- kfree(name.data);
+-out:
++ nfs4_client_to_reclaim(name, princhash, nn);
+ return 0;
+ }
+
+@@ -777,6 +756,8 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ {
+ uint8_t cmd, princhashlen;
+ struct xdr_netobj name, princhash = { .len = 0, .data = NULL };
++ char *namecopy __free(kfree) = NULL;
++ char *princhashcopy __free(kfree) = NULL;
+ uint16_t namelen;
+
+ if (get_user(cmd, &cmsg->cm_cmd)) {
+@@ -794,20 +775,20 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: invalid namelen (%u)", __func__, namelen);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&ci->cc_name.cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&ci->cc_name.cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ if (get_user(princhashlen, &ci->cc_princhash.cp_len))
+ return -EFAULT;
+ if (princhashlen > 0) {
+- princhash.data = memdup_user(
+- &ci->cc_princhash.cp_data,
+- princhashlen);
+- if (IS_ERR(princhash.data)) {
+- kfree(name.data);
+- return PTR_ERR(princhash.data);
+- }
++ princhashcopy = memdup_user(
++ &ci->cc_princhash.cp_data,
++ princhashlen);
++ if (IS_ERR(princhashcopy))
++ return PTR_ERR(princhashcopy);
++ princhash.data = princhashcopy;
+ princhash.len = princhashlen;
+ } else
+ princhash.len = 0;
+@@ -821,9 +802,10 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: invalid namelen (%u)", __func__, namelen);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&cnm->cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&cnm->cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ }
+ #ifdef CONFIG_NFSD_LEGACY_CLIENT_TRACKING
+@@ -831,15 +813,12 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ struct cld_net *cn = nn->cld_net;
+
+ name.len = name.len - 5;
+- memmove(name.data, name.data + 5, name.len);
++ name.data = name.data + 5;
+ cn->cn_has_legacy = true;
+ }
+ #endif
+- if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+- kfree(name.data);
+- kfree(princhash.data);
++ if (!nfs4_client_to_reclaim(name, princhash, nn))
+ return -EFAULT;
+- }
+ return nn->client_tracking_ops->msglen;
+ }
+ return -EFAULT;
+diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
+index a063a4a83ec22d..a3e6076a3e645e 100644
+--- a/fs/nfsd/nfs4state.c
++++ b/fs/nfsd/nfs4state.c
+@@ -8831,9 +8831,6 @@ nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
+
+ /*
+ * failure => all reset bets are off, nfserr_no_grace...
+- *
+- * The caller is responsible for freeing name.data if NULL is returned (it
+- * will be freed in nfs4_remove_reclaim_record in the normal case).
+ */
+ struct nfs4_client_reclaim *
+ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+@@ -8842,6 +8839,22 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ unsigned int strhashval;
+ struct nfs4_client_reclaim *crp;
+
++ name.data = kmemdup(name.data, name.len, GFP_KERNEL);
++ if (!name.data) {
++ dprintk("%s: failed to allocate memory for name.data!\n",
++ __func__);
++ return NULL;
++ }
++ if (princhash.len) {
++ princhash.data = kmemdup(princhash.data, princhash.len, GFP_KERNEL);
++ if (!princhash.data) {
++ dprintk("%s: failed to allocate memory for princhash.data!\n",
++ __func__);
++ kfree(name.data);
++ return NULL;
++ }
++ } else
++ princhash.data = NULL;
+ crp = alloc_reclaim();
+ if (crp) {
+ strhashval = clientstr_hashval(name);
+@@ -8853,6 +8866,9 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ crp->cr_princhash.len = princhash.len;
+ crp->cr_clp = NULL;
+ nn->reclaim_str_hashtbl_size++;
++ } else {
++ kfree(name.data);
++ kfree(princhash.data);
+ }
+ return crp;
+ }
+--
+2.53.0
+
--- /dev/null
+From b55d1d817836298eedcc5b6512e1cd6827b1f4e9 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 15 Sep 2025 12:55:13 +1000
+Subject: nfsd: move name lookup out of nfsd4_list_rec_dir()
+
+From: NeilBrown <neilb@ownmail.net>
+
+[ Upstream commit 89bd77cf436bf25e448817a662ebf76515f22863 ]
+
+nfsd4_list_rec_dir() is called with two different callbacks.
+One of the callbacks uses vfs_rmdir() to remove the directory.
+The other doesn't use the dentry at all, just the name.
+
+As only one callback needs the dentry, this patch moves the lookup into
+that callback. This prepares of changes to how directory operations
+are locked.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 4552f4e3f2c9 ("nfsd: change nfs4_client_to_reclaim() to allocate data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 54 +++++++++++++++++++++----------------------
+ 1 file changed, 26 insertions(+), 28 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 3ea4b8a8616315..8ab0b26c1f91ab 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -237,7 +237,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ nfs4_reset_creds(original_cred);
+ }
+
+-typedef int (recdir_func)(struct dentry *, struct dentry *, struct nfsd_net *);
++typedef int (recdir_func)(struct dentry *, char *, struct nfsd_net *);
+
+ struct name_list {
+ char name[HEXDIR_LEN];
+@@ -291,24 +291,14 @@ nfsd4_list_rec_dir(recdir_func *f, struct nfsd_net *nn)
+ }
+
+ status = iterate_dir(nn->rec_file, &ctx.ctx);
+- inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+- if (!status) {
+- struct dentry *dentry;
+- dentry = lookup_one(&nop_mnt_idmap,
+- &QSTR(entry->name), dir);
+- if (IS_ERR(dentry)) {
+- status = PTR_ERR(dentry);
+- break;
+- }
+- status = f(dir, dentry, nn);
+- dput(dentry);
+- }
++ if (!status)
++ status = f(dir, entry->name, nn);
++
+ list_del(&entry->list);
+ kfree(entry);
+ }
+- inode_unlock(d_inode(dir));
+ nfs4_reset_creds(original_cred);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+@@ -406,18 +396,19 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)
+ }
+
+ static int
+-purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++purge_old(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ int status;
++ struct dentry *child;
+ struct xdr_netobj name;
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -427,10 +418,17 @@ purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
+ if (nfs4_has_reclaimed_state(name, nn))
+ goto out_free;
+
+- status = vfs_rmdir(&nop_mnt_idmap, d_inode(parent), child);
+- if (status)
+- printk("failed to remove client recovery directory %pd\n",
+- child);
++ inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
++ child = lookup_one(&nop_mnt_idmap, &QSTR(cname), parent);
++ if (!IS_ERR(child)) {
++ status = vfs_rmdir(&nop_mnt_idmap, d_inode(parent), child);
++ if (status)
++ printk("failed to remove client recovery directory %pd\n",
++ child);
++ dput(child);
++ }
++ inode_unlock(d_inode(parent));
++
+ out_free:
+ kfree(name.data);
+ out:
+@@ -461,18 +459,18 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ }
+
+ static int
+-load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ struct xdr_netobj name;
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+--
+2.53.0
+
f2fs-fix-to-do-sanity-check-on-f2fs_get_node_folio_ra.patch
fbdev-fbcon-fix-out-of-bounds-read-in-err_out-of-fbcon_do_set_font.patch
net-sched-dualpi2-fix-gso-backlog-accounting.patch
+nfsd-move-name-lookup-out-of-nfsd4_list_rec_dir.patch
+nfsd-change-nfs4_client_to_reclaim-to-allocate-data.patch
--- /dev/null
+From 6106a9c99a249d36de9e332e01fcb59f50761577 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 8 Sep 2025 11:38:33 +1000
+Subject: nfsd: change nfs4_client_to_reclaim() to allocate data
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 4552f4e3f2c96597914f07b060d5c5db84420ddd ]
+
+The calling convention for nfs4_client_to_reclaim() is clumsy in that
+the caller needs to free memory if the function fails. It is much
+cleaner if the function frees its own memory.
+
+This patch changes nfs4_client_to_reclaim() to re-allocate the .data
+fields to be stored in the newly allocated struct nfs4_client_reclaim,
+and to free everything on failure.
+
+__cld_pipe_inprogress_downcall() needs to allocate the data anyway to
+copy it from user-space, so now that data is allocated twice. I think
+that is a small price to pay for a cleaner interface.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 67 +++++++++++++++----------------------------
+ fs/nfsd/nfs4state.c | 22 ++++++++++++--
+ 2 files changed, 42 insertions(+), 47 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 8b8d7e0131fd1f..f4060d0cec8a5e 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -167,24 +167,13 @@ legacy_recdir_name_error(struct nfs4_client *clp, int error)
+
+ static void
+ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
+- const char *dname, int len, struct nfsd_net *nn)
++ char *dname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = strlen(dname), .data = dname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+ struct nfs4_client_reclaim *crp;
+
+- name.data = kmemdup(dname, len, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- return;
+- }
+- name.len = len;
+ crp = nfs4_client_to_reclaim(name, princhash, nn);
+- if (!crp) {
+- kfree(name.data);
+- return;
+- }
+ crp->cr_clp = clp;
+ }
+
+@@ -240,8 +229,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ inode_unlock(d_inode(dir));
+ if (status == 0) {
+ if (nn->in_grace)
+- __nfsd4_create_reclaim_record_grace(clp, dname,
+- HEXDIR_LEN, nn);
++ __nfsd4_create_reclaim_record_grace(clp, dname, nn);
+ vfs_fsync(nn->rec_file, 0);
+ } else {
+ printk(KERN_ERR "NFSD: failed to write recovery record"
+@@ -478,7 +466,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ static int
+ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+- struct xdr_netobj name;
++ struct xdr_netobj name = { .len = HEXDIR_LEN, .data = cname };
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+ if (strlen(cname) != HEXDIR_LEN - 1) {
+@@ -487,16 +475,7 @@ load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kstrdup(cname, GFP_KERNEL);
+- if (!name.data) {
+- dprintk("%s: failed to allocate memory for name.data!\n",
+- __func__);
+- goto out;
+- }
+- name.len = HEXDIR_LEN;
+- if (!nfs4_client_to_reclaim(name, princhash, nn))
+- kfree(name.data);
+-out:
++ nfs4_client_to_reclaim(name, princhash, nn);
+ return 0;
+ }
+
+@@ -792,6 +771,8 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ {
+ uint8_t cmd, princhashlen;
+ struct xdr_netobj name, princhash = { .len = 0, .data = NULL };
++ char *namecopy __free(kfree) = NULL;
++ char *princhashcopy __free(kfree) = NULL;
+ uint16_t namelen;
+ struct cld_net *cn = nn->cld_net;
+
+@@ -810,20 +791,20 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&ci->cc_name.cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&ci->cc_name.cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ if (get_user(princhashlen, &ci->cc_princhash.cp_len))
+ return -EFAULT;
+ if (princhashlen > 0) {
+- princhash.data = memdup_user(
+- &ci->cc_princhash.cp_data,
+- princhashlen);
+- if (IS_ERR(princhash.data)) {
+- kfree(name.data);
+- return PTR_ERR(princhash.data);
+- }
++ princhashcopy = memdup_user(
++ &ci->cc_princhash.cp_data,
++ princhashlen);
++ if (IS_ERR(princhashcopy))
++ return PTR_ERR(princhashcopy);
++ princhash.data = princhashcopy;
+ princhash.len = princhashlen;
+ } else
+ princhash.len = 0;
+@@ -837,23 +818,21 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ dprintk("%s: namelen should not be zero", __func__);
+ return -EINVAL;
+ }
+- name.data = memdup_user(&cnm->cn_id, namelen);
+- if (IS_ERR(name.data))
+- return PTR_ERR(name.data);
++ namecopy = memdup_user(&cnm->cn_id, namelen);
++ if (IS_ERR(namecopy))
++ return PTR_ERR(namecopy);
++ name.data = namecopy;
+ name.len = namelen;
+ }
+ #ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+- memmove(name.data, name.data + 5, name.len);
++ name.data = name.data + 5;
+ cn->cn_has_legacy = true;
+ }
+ #endif
+- if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+- kfree(name.data);
+- kfree(princhash.data);
++ if (!nfs4_client_to_reclaim(name, princhash, nn))
+ return -EFAULT;
+- }
+ return nn->client_tracking_ops->msglen;
+ }
+ return -EFAULT;
+diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
+index c0dd50b59ad16c..f21878a7f39398 100644
+--- a/fs/nfsd/nfs4state.c
++++ b/fs/nfsd/nfs4state.c
+@@ -8093,9 +8093,6 @@ nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
+
+ /*
+ * failure => all reset bets are off, nfserr_no_grace...
+- *
+- * The caller is responsible for freeing name.data if NULL is returned (it
+- * will be freed in nfs4_remove_reclaim_record in the normal case).
+ */
+ struct nfs4_client_reclaim *
+ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+@@ -8104,6 +8101,22 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ unsigned int strhashval;
+ struct nfs4_client_reclaim *crp;
+
++ name.data = kmemdup(name.data, name.len, GFP_KERNEL);
++ if (!name.data) {
++ dprintk("%s: failed to allocate memory for name.data!\n",
++ __func__);
++ return NULL;
++ }
++ if (princhash.len) {
++ princhash.data = kmemdup(princhash.data, princhash.len, GFP_KERNEL);
++ if (!princhash.data) {
++ dprintk("%s: failed to allocate memory for princhash.data!\n",
++ __func__);
++ kfree(name.data);
++ return NULL;
++ }
++ } else
++ princhash.data = NULL;
+ crp = alloc_reclaim();
+ if (crp) {
+ strhashval = clientstr_hashval(name);
+@@ -8115,6 +8128,9 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
+ crp->cr_princhash.len = princhash.len;
+ crp->cr_clp = NULL;
+ nn->reclaim_str_hashtbl_size++;
++ } else {
++ kfree(name.data);
++ kfree(princhash.data);
+ }
+ return crp;
+ }
+--
+2.53.0
+
--- /dev/null
+From d08281c0218c932d0b2e43a3e6ae01e44151200f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 15 Sep 2025 12:55:13 +1000
+Subject: nfsd: move name lookup out of nfsd4_list_rec_dir()
+
+From: NeilBrown <neilb@ownmail.net>
+
+[ Upstream commit 89bd77cf436bf25e448817a662ebf76515f22863 ]
+
+nfsd4_list_rec_dir() is called with two different callbacks.
+One of the callbacks uses vfs_rmdir() to remove the directory.
+The other doesn't use the dentry at all, just the name.
+
+As only one callback needs the dentry, this patch moves the lookup into
+that callback. This prepares of changes to how directory operations
+are locked.
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 4552f4e3f2c9 ("nfsd: change nfs4_client_to_reclaim() to allocate data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/nfsd/nfs4recover.c | 55 ++++++++++++++++++++++---------------------
+ 1 file changed, 28 insertions(+), 27 deletions(-)
+
+diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
+index 017044c27d381d..8b8d7e0131fd1f 100644
+--- a/fs/nfsd/nfs4recover.c
++++ b/fs/nfsd/nfs4recover.c
+@@ -254,7 +254,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
+ nfs4_reset_creds(original_cred);
+ }
+
+-typedef int (recdir_func)(struct dentry *, struct dentry *, struct nfsd_net *);
++typedef int (recdir_func)(struct dentry *, char *, struct nfsd_net *);
+
+ struct name_list {
+ char name[HEXDIR_LEN];
+@@ -308,23 +308,14 @@ nfsd4_list_rec_dir(recdir_func *f, struct nfsd_net *nn)
+ }
+
+ status = iterate_dir(nn->rec_file, &ctx.ctx);
+- inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+- if (!status) {
+- struct dentry *dentry;
+- dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
+- if (IS_ERR(dentry)) {
+- status = PTR_ERR(dentry);
+- break;
+- }
+- status = f(dir, dentry, nn);
+- dput(dentry);
+- }
++ if (!status)
++ status = f(dir, entry->name, nn);
++
+ list_del(&entry->list);
+ kfree(entry);
+ }
+- inode_unlock(d_inode(dir));
+ nfs4_reset_creds(original_cred);
+
+ list_for_each_entry_safe(entry, tmp, &ctx.names, list) {
+@@ -422,18 +413,19 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)
+ }
+
+ static int
+-purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++purge_old(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ int status;
++ struct dentry *child;
+ struct xdr_netobj name;
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -443,10 +435,17 @@ purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
+ if (nfs4_has_reclaimed_state(name, nn))
+ goto out_free;
+
+- status = vfs_rmdir(&nop_mnt_idmap, d_inode(parent), child);
+- if (status)
+- printk("failed to remove client recovery directory %pd\n",
+- child);
++ inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
++ child = lookup_one_len(cname, parent, HEXDIR_LEN-1);
++ if (!IS_ERR(child)) {
++ status = vfs_rmdir(&nop_mnt_idmap, d_inode(parent), child);
++ if (status)
++ printk("failed to remove client recovery directory %pd\n",
++ child);
++ dput(child);
++ }
++ inode_unlock(d_inode(parent));
++
+ out_free:
+ kfree(name.data);
+ out:
+@@ -477,18 +476,18 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
+ }
+
+ static int
+-load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
++load_recdir(struct dentry *parent, char *cname, struct nfsd_net *nn)
+ {
+ struct xdr_netobj name;
+ struct xdr_netobj princhash = { .len = 0, .data = NULL };
+
+- if (child->d_name.len != HEXDIR_LEN - 1) {
+- printk("%s: illegal name %pd in recovery directory\n",
+- __func__, child);
++ if (strlen(cname) != HEXDIR_LEN - 1) {
++ printk("%s: illegal name %s in recovery directory\n",
++ __func__, cname);
+ /* Keep trying; maybe the others are OK: */
+ return 0;
+ }
+- name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
++ name.data = kstrdup(cname, GFP_KERNEL);
+ if (!name.data) {
+ dprintk("%s: failed to allocate memory for name.data!\n",
+ __func__);
+@@ -843,11 +842,13 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
+ return PTR_ERR(name.data);
+ name.len = namelen;
+ }
++#ifdef CONFIG_NFSD_V4
+ if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+ name.len = name.len - 5;
+ memmove(name.data, name.data + 5, name.len);
+ cn->cn_has_legacy = true;
+ }
++#endif
+ if (!nfs4_client_to_reclaim(name, princhash, nn)) {
+ kfree(name.data);
+ kfree(princhash.data);
+--
+2.53.0
+
nfsv4-flexfiles-reject-zero-filehandle-version-count.patch
nfsd-don-t-reset-the-write-verifier-on-a-commit-eagain.patch
apparmor-advertise-the-tcp-fast-open-fix-is-applied.patch
+nfsd-move-name-lookup-out-of-nfsd4_list_rec_dir.patch
+nfsd-change-nfs4_client_to_reclaim-to-allocate-data.patch