FAILED: patch "[PATCH] sunrpc: fix use-after-free in __rpc_clnt_handle_event and" failed to apply to 6.12-stable tree
Subsystems (4)
[email protected]
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 932a8cf6abb2b2f8677b79153a823108d8861fe2
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090317-curled-track-17d1@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 932a8cf6abb2b2f8677b79153a823108d8861fe2 Mon Sep 17 00:00:00 2001
From: Luxiao Xu <[email protected]>
Date: Tue, 7 Jul 2026 13:20:47 +0800
Subject: [PATCH] sunrpc: fix use-after-free in __rpc_clnt_handle_event and
__rpc_clnt_remove_pipedir
Normal client creation goes through rpc_setup_pipedir(), which records clnt->pipefs_sb, but the mount-event path in __rpc_clnt_handle_event() calls rpc_setup_pipedir_sb() directly and never refreshes that field. The umount path also removes the directory without clearing clnt->pipefs_sb.
After a late pipefs mount or any remount, rpc_clnt_remove_pipedir() compares the current superblock against a stale pipefs_sb pointer and skips cleanup, leaving pipefs dentries whose inode private data still points at a freed rpc_clnt, leading to a potential use-after-free during subsequent rpc_info_open() or rpc_show_info() calls.
Fix this by properly updating clnt->pipefs_sb upon mount events and clearing it during unmount or failure paths.
net/sunrpc/clnt.c +17 -5
@@ -96,7 +96,10 @@ static void rpc_unregister_client(struct rpc_clnt *clnt)static void __rpc_clnt_remove_pipedir (struct rpc_clnt *clnt) { -rpc_remove_client_dir (clnt); +if (clnt->pipefs_sb) { +rpc_remove_client_dir (clnt); + clnt->pipefs_sb =NULL ; + } }static void rpc_clnt_remove_pipedir (struct rpc_clnt *clnt)
@@ -177,19 +180,28 @@ static int rpc_clnt_skip_event(struct rpc_clnt *clnt, unsigned long event) }static int __rpc_clnt_handle_event (struct rpc_clnt *clnt,unsigned long event, -struct super_block *sb) +struct super_block *sb) { +int err =0 ; +switch (event) {case RPC_PIPEFS_MOUNT : -return rpc_setup_pipedir_sb (sb, clnt); + clnt->pipefs_sb = sb; + err =rpc_setup_pipedir_sb (sb, clnt); +if (err) + clnt->pipefs_sb =NULL ; +break ;case RPC_PIPEFS_UMOUNT : -__rpc_clnt_remove_pipedir (clnt); +if (clnt->pipefs_sb == sb) { +__rpc_clnt_remove_pipedir (clnt); + clnt->pipefs_sb =NULL ; + }break ;default :printk (KERN_ERR "%s: unknown event: %ld\n" , __func__, event);return -ENOTSUPP ; } -return 0 ; +return err; } static int __rpc_pipefs_event(struct rpc_clnt *clnt, unsigned long event,
Sasha Levin
maintainerPATCH 1/4 rpc_populate(): lift cleanup into callers
From: Al Viro <[email protected]>
[ Upstream commit 8be22c49646e47452e74aa0b97ea50fb04c271ed ]
rpc_populate() is called either from fill_super (where we don't need to remove any files on failure - rpc_kill_sb() will take them all out anyway) or from rpc_mkdir_populate(), where we need to remove the directory we'd been trying to populate along with whatever we'd put into it before we failed. Simpler to combine that into simple_recursive_removal() there.
Note that rpc_pipe is overlocking directories quite a bit - locked parent is no obstacle to finding a child in dcache, so keeping it locked won't prevent userland observing a partially built subtree.
All we need is to follow minimal VFS requirements; it's not as if clients used directory locking for exclusion - tree changes are serialized, but that's done on ->pipefs_sb_lock.
Stable-dep-of: 932a8cf6abb2 ("sunrpc: fix use-after-free in __rpc_clnt_handle_event and __rpc_clnt_remove_pipedir")
--- include/linux/sunrpc/rpc_pipe_fs.h | 2 +- net/sunrpc/clnt.c | 36 ++++++++++++------------------ net/sunrpc/rpc_pipe.c | 26 ++++++++++----------- 3 files changed, 28 insertions(+), 36 deletions(-)
include/linux/sunrpc/rpc_pipe_fs.h +1 -1
@@ -98,7 +98,7 @@ static inline bool rpc_msg_is_inflight(const struct rpc_pipe_msg *msg) { }struct rpc_clnt ; -extern struct dentry *rpc_create_client_dir (struct dentry *,const char *,struct rpc_clnt *); +extern int rpc_create_client_dir (struct dentry *,const char *,struct rpc_clnt *);extern int rpc_remove_client_dir (struct rpc_clnt *);extern void rpc_init_pipe_dir_head (struct rpc_pipe_dir_head *pdh);
net/sunrpc/clnt.c +14 -22
@@ -112,47 +112,46 @@ static void rpc_clnt_remove_pipedir(struct rpc_clnt *clnt) } } -static struct dentry *rpc_setup_pipedir_sb (struct super_block *sb, +static int rpc_setup_pipedir_sb (struct super_block *sb,struct rpc_clnt *clnt) {static uint32_t clntid;const char *dir_name = clnt->cl_program->pipe_dir_name;char name[15 ]; -struct dentry *dir, *dentry; +struct dentry *dir; +int err; dir =rpc_d_lookup_sb (sb, dir_name);if (dir ==NULL ) {pr_info ("RPC: pipefs directory doesn't exist: %s\n" , dir_name); -return dir; +return -ENOENT ; }for (;;) {snprintf (name,sizeof (name),"clnt%x" , (unsigned int )clntid++); name[sizeof (name) -1 ] ='\0' ; - dentry =rpc_create_client_dir (dir, name, clnt); -if (!IS_ERR (dentry)) + err =rpc_create_client_dir (dir, name, clnt); +if (!err)break ; -if (dentry ==ERR_PTR (-EEXIST )) +if (err == -EEXIST )continue ;printk (KERN_INFO "RPC: Couldn't create pipefs entry" -" %s/%s, error %ld\n" , - dir_name, name,PTR_ERR (dentry)); +" %s/%s, error %d\n" , + dir_name, name, err);break ; }dput (dir); -return dentry; +return err; }static int rpc_setup_pipedir (struct super_block *pipefs_sb,struct rpc_clnt *clnt) { -struct dentry *dentry; - clnt->pipefs_sb = pipefs_sb;if (clnt->cl_program->pipe_dir_name !=NULL ) { - dentry =rpc_setup_pipedir_sb (pipefs_sb, clnt); -if (IS_ERR (dentry)) -return PTR_ERR (dentry); +int err =rpc_setup_pipedir_sb (pipefs_sb, clnt); +if (err && err != -ENOENT ) +return err; }return 0 ; }
@@ -180,16 +179,9 @@ static int rpc_clnt_skip_event(struct rpc_clnt *clnt, unsigned long event)static int __rpc_clnt_handle_event (struct rpc_clnt *clnt,unsigned long event,struct super_block *sb) { -struct dentry *dentry; -switch (event) {case RPC_PIPEFS_MOUNT : - dentry =rpc_setup_pipedir_sb (sb, clnt); -if (!dentry) -return -ENOENT ; -if (IS_ERR (dentry)) -return PTR_ERR (dentry); -break ; +return rpc_setup_pipedir_sb (sb, clnt);case RPC_PIPEFS_UMOUNT :__rpc_clnt_remove_pipedir (clnt);break ;
net/sunrpc/rpc_pipe.c +13 -13
@@ -725,7 +725,6 @@ static int rpc_populate(struct dentry *parent,inode_unlock (dir);return 0 ; out_bad: -__rpc_depopulate (parent, files, start, eof);inode_unlock (dir);printk (KERN_WARNING "%s: %s failed to populate directory %pd\n" , __FILE__, __func__, parent);
@@ -749,14 +748,15 @@ static struct dentry *rpc_mkdir_populate(struct dentry *parent, goto out_err;if (populate !=NULL ) { error =populate (dentry, args_populate); -if (error) - goto err_rmdir; +if (error) { +inode_unlock (dir); +simple_recursive_removal (dentry,NULL ); +return ERR_PTR (error); + } } out:inode_unlock (dir);return dentry; -err_rmdir: -__rpc_rmdir (dir, dentry); out_err: dentry =ERR_PTR (error); goto out;
@@ -1046,19 +1046,19 @@ static void rpc_clntdir_depopulate(struct dentry *dentry)* information about the client, together with any "pipes" that may * later be created using rpc_mkpipe(). */ -struct dentry *rpc_create_client_dir (struct dentry *dentry, -const char *name, -struct rpc_clnt *rpc_client) +int rpc_create_client_dir (struct dentry *dentry, +const char *name, +struct rpc_clnt *rpc_client) {struct dentry *ret; ret =rpc_mkdir_populate (dentry, name,0555 ,NULL , rpc_clntdir_populate, rpc_client); -if (!IS_ERR (ret)) { - rpc_client->cl_pipedir_objects.pdh_dentry = ret; -rpc_create_pipe_dir_objects (&rpc_client->cl_pipedir_objects); - } -return ret; +if (IS_ERR (ret)) +return PTR_ERR (ret); + rpc_client->cl_pipedir_objects.pdh_dentry = ret; +rpc_create_pipe_dir_objects (&rpc_client->cl_pipedir_objects); +return 0 ; }/**
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/4 rpc_mkpipe_dentry(): saner calling conventions
From: Al Viro <[email protected]>
[ Upstream commit 19a6314a997f6adde0c100ecf9224d1ab43c9603 ]
Instead of returning a dentry or ERR_PTR(-E...), return 0 and store dentry into pipe->dentry on success and return -E... on failure.
Callers are happier that way...
NOTE: dummy rpc_pipe is getting ->dentry set; we never access that, since we
1) never call rpc_unlink() for it (dentry is taken out by
->kill_sb())
2) never call rpc_queue_upcall() for it (writing to that
sucker fails; no downcalls are ever submitted, so no replies are going to arrive)
IOW, having that ->dentry set (and left dangling) is harmless, if ugly; cleaner solution will take more massage.
Stable-dep-of: 932a8cf6abb2 ("sunrpc: fix use-after-free in __rpc_clnt_handle_event and __rpc_clnt_remove_pipedir")
--- fs/nfs/blocklayout/rpc_pipefs.c | 41 ++++++++++++------------------ fs/nfs/nfs4idmap.c | 8 +----- fs/nfsd/nfs4recover.c | 37 ++++++++++----------------- include/linux/sunrpc/rpc_pipe_fs.h | 2 +- net/sunrpc/auth_gss/auth_gss.c | 7 +---- net/sunrpc/rpc_pipe.c | 31 +++++++++++----------- 6 files changed, 49 insertions(+), 77 deletions(-)
fs/nfs/blocklayout/rpc_pipefs.c +16 -25
@@ -141,17 +141,18 @@ static const struct rpc_pipe_ops bl_upcall_ops = { .destroy_msg = bl_pipe_destroy_msg, }; -static struct dentry *nfs4blocklayout_register_sb (struct super_block *sb, +static int nfs4blocklayout_register_sb (struct super_block *sb,struct rpc_pipe *pipe) { -struct dentry *dir, *dentry; +struct dentry *dir; +int err; dir =rpc_d_lookup_sb (sb,NFS_PIPE_DIRNAME );if (dir ==NULL ) -return ERR_PTR (-ENOENT ); - dentry =rpc_mkpipe_dentry (dir,"blocklayout" ,NULL , pipe); +return -ENOENT ; + err =rpc_mkpipe_dentry (dir,"blocklayout" ,NULL , pipe);dput (dir); -return dentry; +return err; } static void nfs4blocklayout_unregister_sb(struct super_block *sb,
@@ -167,7 +168,6 @@ static int rpc_pipefs_event(struct notifier_block *nb, unsigned long event,struct super_block *sb = ptr;struct net *net = sb->s_fs_info;struct nfs_net *nn =net_generic (net, nfs_net_id); -struct dentry *dentry;int ret =0 ;if (!try_module_get (THIS_MODULE ))
@@ -180,12 +180,7 @@ static int rpc_pipefs_event(struct notifier_block *nb, unsigned long event,switch (event) {case RPC_PIPEFS_MOUNT : - dentry =nfs4blocklayout_register_sb (sb, nn->bl_device_pipe); -if (IS_ERR (dentry)) { - ret =PTR_ERR (dentry); -break ; - } - nn->bl_device_pipe->dentry = dentry; + ret =nfs4blocklayout_register_sb (sb, nn->bl_device_pipe);break ;case RPC_PIPEFS_UMOUNT :if (nn->bl_device_pipe->dentry)
@@ -203,18 +198,17 @@ static struct notifier_block nfs4blocklayout_block = { .notifier_call = rpc_pipefs_event, }; -static struct dentry *nfs4blocklayout_register_net (struct net *net, -struct rpc_pipe *pipe) +static int nfs4blocklayout_register_net (struct net *net,struct rpc_pipe *pipe) {struct super_block *pipefs_sb; -struct dentry *dentry; +int ret; pipefs_sb =rpc_get_sb_net (net);if (!pipefs_sb) -return NULL ; - dentry =nfs4blocklayout_register_sb (pipefs_sb, pipe); +return 0 ; + ret =nfs4blocklayout_register_sb (pipefs_sb, pipe);rpc_put_sb_net (net); -return dentry; +return ret; } static void nfs4blocklayout_unregister_net(struct net *net,
@@ -232,20 +226,17 @@ static void nfs4blocklayout_unregister_net(struct net *net,static int nfs4blocklayout_net_init (struct net *net) {struct nfs_net *nn =net_generic (net, nfs_net_id); -struct dentry *dentry; +int err;mutex_init (&nn->bl_mutex);init_waitqueue_head (&nn->bl_wq); nn->bl_device_pipe =rpc_mkpipe_data (&bl_upcall_ops,0 );if (IS_ERR (nn->bl_device_pipe))return PTR_ERR (nn->bl_device_pipe); - dentry =nfs4blocklayout_register_net (net, nn->bl_device_pipe); -if (IS_ERR (dentry)) { + err =nfs4blocklayout_register_net (net, nn->bl_device_pipe); +if (unlikely (err))rpc_destroy_pipe_data (nn->bl_device_pipe); -return PTR_ERR (dentry); - } - nn->bl_device_pipe->dentry = dentry; -return 0 ; +return err; }static void nfs4blocklayout_net_exit (struct net *net)
fs/nfs/nfs4idmap.c +1 -7
@@ -436,14 +436,8 @@ static int nfs_idmap_pipe_create(struct dentry *dir, struct rpc_pipe_dir_object *pdo) {struct idmap *idmap = pdo->pdo_data; -struct rpc_pipe *pipe = idmap->idmap_pipe; -struct dentry *dentry; - dentry =rpc_mkpipe_dentry (dir,"idmap" , idmap, pipe); -if (IS_ERR (dentry)) -return PTR_ERR (dentry); - pipe->dentry = dentry; -return 0 ; +return rpc_mkpipe_dentry (dir,"idmap" , idmap, idmap->idmap_pipe); }static const struct rpc_pipe_dir_object_ops nfs_idmap_pipe_dir_object_ops = {
fs/nfsd/nfs4recover.c +14 -23
@@ -926,17 +926,18 @@ static const struct rpc_pipe_ops cld_upcall_ops = { .destroy_msg = cld_pipe_destroy_msg, }; -static struct dentry * +static int nfsd4_cld_register_sb (struct super_block *sb,struct rpc_pipe *pipe) { -struct dentry *dir, *dentry; +struct dentry *dir; +int err; dir =rpc_d_lookup_sb (sb,NFSD_PIPE_DIR );if (dir ==NULL ) -return ERR_PTR (-ENOENT ); - dentry =rpc_mkpipe_dentry (dir,NFSD_CLD_PIPE ,NULL , pipe); +return -ENOENT ; + err =rpc_mkpipe_dentry (dir,NFSD_CLD_PIPE ,NULL , pipe);dput (dir); -return dentry; +return err; } static void
@@ -946,18 +947,18 @@ nfsd4_cld_unregister_sb(struct rpc_pipe *pipe)rpc_unlink (pipe->dentry); } -static struct dentry * +static int nfsd4_cld_register_net (struct net *net,struct rpc_pipe *pipe) {struct super_block *sb; -struct dentry *dentry; +int err; sb =rpc_get_sb_net (net);if (!sb) -return NULL ; - dentry =nfsd4_cld_register_sb (sb, pipe); +return 0 ; + err =nfsd4_cld_register_sb (sb, pipe);rpc_put_sb_net (net); -return dentry; +return err; } static void
@@ -977,7 +978,6 @@ static int __nfsd4_init_cld_pipe(struct net *net) {int ret; -struct dentry *dentry;struct nfsd_net *nn =net_generic (net, nfsd_net_id);struct cld_net *cn;
@@ -998,13 +998,10 @@ __nfsd4_init_cld_pipe(struct net *net)spin_lock_init (&cn->cn_lock);INIT_LIST_HEAD (&cn->cn_list); - dentry =nfsd4_cld_register_net (net, cn->cn_pipe); -if (IS_ERR (dentry)) { - ret =PTR_ERR (dentry); + ret =nfsd4_cld_register_net (net, cn->cn_pipe); +if (unlikely (ret)) goto err_destroy_data; - } - cn->cn_pipe->dentry = dentry;#ifdef CONFIG_NFSD_LEGACY_CLIENT_TRACKING cn->cn_has_legacy = false;#endif
@@ -2132,7 +2129,6 @@ rpc_pipefs_event(struct notifier_block *nb, unsigned long event, void *ptr)struct net *net = sb->s_fs_info;struct nfsd_net *nn =net_generic (net, nfsd_net_id);struct cld_net *cn = nn->cld_net; -struct dentry *dentry;int ret =0 ;if (!try_module_get (THIS_MODULE ))
@@ -2145,12 +2141,7 @@ rpc_pipefs_event(struct notifier_block *nb, unsigned long event, void *ptr)switch (event) {case RPC_PIPEFS_MOUNT : - dentry =nfsd4_cld_register_sb (sb, cn->cn_pipe); -if (IS_ERR (dentry)) { - ret =PTR_ERR (dentry); -break ; - } - cn->cn_pipe->dentry = dentry; + ret =nfsd4_cld_register_sb (sb, cn->cn_pipe);break ;case RPC_PIPEFS_UMOUNT :if (cn->cn_pipe->dentry)
include/linux/sunrpc/rpc_pipe_fs.h +1 -1
@@ -127,7 +127,7 @@ extern void rpc_remove_cache_dir(struct dentry *);struct rpc_pipe *rpc_mkpipe_data (const struct rpc_pipe_ops *ops,int flags);void rpc_destroy_pipe_data (struct rpc_pipe *pipe); -extern struct dentry *rpc_mkpipe_dentry (struct dentry *,const char *,void *, +extern int rpc_mkpipe_dentry (struct dentry *,const char *,void *,struct rpc_pipe *);extern int rpc_unlink (struct dentry *);extern int register_rpc_pipefs (void );
net/sunrpc/auth_gss/auth_gss.c +1 -6
@@ -902,13 +902,8 @@ static int gss_pipe_dentry_create(struct dentry *dir, struct rpc_pipe_dir_object *pdo) {struct gss_pipe *p = pdo->pdo_data; -struct dentry *dentry; - dentry =rpc_mkpipe_dentry (dir, p->name, p->clnt, p->pipe); -if (IS_ERR (dentry)) -return PTR_ERR (dentry); - p->pipe->dentry = dentry; -return 0 ; +return rpc_mkpipe_dentry (dir, p->name, p->clnt, p->pipe); }static const struct rpc_pipe_dir_object_ops gss_pipe_dir_object_ops = {
net/sunrpc/rpc_pipe.c +16 -15
@@ -799,7 +799,7 @@ static int rpc_rmdir_depopulate(struct dentry *dentry,* The @private argument passed here will be available to all these methods * from the file pointer, via RPC_I(file_inode(file))->private. */ -struct dentry *rpc_mkpipe_dentry (struct dentry *parent,const char *name, +int rpc_mkpipe_dentry (struct dentry *parent,const char *name,void *private,struct rpc_pipe *pipe) {struct dentry *dentry;
@@ -814,21 +814,19 @@ struct dentry *rpc_mkpipe_dentry(struct dentry *parent, const char *name,inode_lock_nested (dir,I_MUTEX_PARENT ); dentry =__rpc_lookup_create_exclusive (parent, name); -if (IS_ERR (dentry)) - goto out; +if (IS_ERR (dentry)) { +inode_unlock (dir); +return PTR_ERR (dentry); + } err =__rpc_mkpipe_dentry (dir, dentry, umode, &rpc_pipe_fops, private, pipe); -if (err) - goto out_err; -out: +if (unlikely (err)) +pr_warn ("%s() failed to create pipe %pd/%s (errno = %d)\n" , + __func__, parent, name, err); +else + pipe->dentry = dentry;inode_unlock (dir); -return dentry; -out_err: - dentry =ERR_PTR (err); -printk (KERN_WARNING "%s: %s() failed to create pipe %pd/%s (errno = %d)\n" , - __FILE__, __func__, parent, name, - err); - goto out; +return err; }EXPORT_SYMBOL_GPL (rpc_mkpipe_dentry);
@@ -1326,10 +1324,13 @@ rpc_gssd_dummy_populate(struct dentry *root, struct rpc_pipe *pipe_data) goto out; } - pipe_dentry =rpc_mkpipe_dentry (clnt_dentry,"gssd" ,NULL , pipe_data); -if (IS_ERR (pipe_dentry)) { + ret =rpc_mkpipe_dentry (clnt_dentry,"gssd" ,NULL , pipe_data); +if (ret) {__rpc_depopulate (clnt_dentry, gssd_dummy_info_file,0 ,1 );__rpc_depopulate (gssd_dentry, gssd_dummy_clnt_dir,0 ,1 ); + pipe_dentry =ERR_PTR (ret); + }else { + pipe_dentry = pipe_data->dentry; } out:dput (clnt_dentry);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/4 rpc_pipe: don't overdo directory locking
In reply to Sasha LevinFrom: Al Viro <[email protected]>
[ Upstream commit 41a6b9e52b21544c334830bfe68fb78d6677caa0 ]
Don't try to hold directories locked more than VFS requires; lock just before getting a child to be made positive (using simple_start_creating()) and unlock as soon as the child is created. There's no benefit in keeping the parent locked while populating the child - it won't stop dcache lookups anyway.
Stable-dep-of: 932a8cf6abb2 ("sunrpc: fix use-after-free in __rpc_clnt_handle_event and __rpc_clnt_remove_pipedir")
--- net/sunrpc/rpc_pipe.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-)
net/sunrpc/rpc_pipe.c +12 -13
@@ -698,12 +698,14 @@ static int rpc_populate(struct dentry *parent,struct dentry *dentry;int i, err; -inode_lock (dir);for (i = start; i < eof; i++) { +inode_lock (dir); dentry =__rpc_lookup_create_exclusive (parent, files[i].name); err =PTR_ERR (dentry); -if (IS_ERR (dentry)) +if (IS_ERR (dentry)) { +inode_unlock (dir); goto out_bad; + }switch (files[i].mode &S_IFMT ) {default :BUG ();
@@ -712,20 +714,20 @@ static int rpc_populate(struct dentry *parent, files[i].mode, files[i].i_fop, private); +inode_unlock (dir);break ;case S_IFDIR : err =__rpc_mkdir (dir, dentry, files[i].mode,NULL , private); +inode_unlock (dir); }if (err !=0 ) goto out_bad; } -inode_unlock (dir);return 0 ; out_bad: -inode_unlock (dir);printk (KERN_WARNING "%s: %s failed to populate directory %pd\n" , __FILE__, __func__, parent);return err;
@@ -741,25 +743,22 @@ static struct dentry *rpc_mkdir_populate(struct dentry *parent,inode_lock_nested (dir,I_MUTEX_PARENT ); dentry =__rpc_lookup_create_exclusive (parent, name); -if (IS_ERR (dentry)) - goto out; +if (IS_ERR (dentry)) { +inode_unlock (dir); +return dentry; + } error =__rpc_mkdir (dir, dentry, mode,NULL , private); +inode_unlock (dir);if (error !=0 ) - goto out_err; +return ERR_PTR (error);if (populate !=NULL ) { error =populate (dentry, args_populate);if (error) { -inode_unlock (dir);simple_recursive_removal (dentry,NULL );return ERR_PTR (error); } } -out: -inode_unlock (dir);return dentry; -out_err: - dentry =ERR_PTR (error); - goto out; } static int rpc_rmdir_depopulate(struct dentry *dentry,
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/4 sunrpc: fix use-after-free in __rpc_clnt_handle_event and __rpc_clnt_remove_pipedir
In reply to Sasha LevinFrom: Luxiao Xu <[email protected]>
[ Upstream commit 932a8cf6abb2b2f8677b79153a823108d8861fe2 ]
Normal client creation goes through rpc_setup_pipedir(), which records clnt->pipefs_sb, but the mount-event path in __rpc_clnt_handle_event() calls rpc_setup_pipedir_sb() directly and never refreshes that field. The umount path also removes the directory without clearing clnt->pipefs_sb.
After a late pipefs mount or any remount, rpc_clnt_remove_pipedir() compares the current superblock against a stale pipefs_sb pointer and skips cleanup, leaving pipefs dentries whose inode private data still points at a freed rpc_clnt, leading to a potential use-after-free during subsequent rpc_info_open() or rpc_show_info() calls.
Fix this by properly updating clnt->pipefs_sb upon mount events and clearing it during unmount or failure paths.
--- net/sunrpc/clnt.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-)
net/sunrpc/clnt.c +17 -5
@@ -96,7 +96,10 @@ static void rpc_unregister_client(struct rpc_clnt *clnt)static void __rpc_clnt_remove_pipedir (struct rpc_clnt *clnt) { -rpc_remove_client_dir (clnt); +if (clnt->pipefs_sb) { +rpc_remove_client_dir (clnt); + clnt->pipefs_sb =NULL ; + } }static void rpc_clnt_remove_pipedir (struct rpc_clnt *clnt)
@@ -177,19 +180,28 @@ static int rpc_clnt_skip_event(struct rpc_clnt *clnt, unsigned long event) }static int __rpc_clnt_handle_event (struct rpc_clnt *clnt,unsigned long event, -struct super_block *sb) +struct super_block *sb) { +int err =0 ; +switch (event) {case RPC_PIPEFS_MOUNT : -return rpc_setup_pipedir_sb (sb, clnt); + clnt->pipefs_sb = sb; + err =rpc_setup_pipedir_sb (sb, clnt); +if (err) + clnt->pipefs_sb =NULL ; +break ;case RPC_PIPEFS_UMOUNT : -__rpc_clnt_remove_pipedir (clnt); +if (clnt->pipefs_sb == sb) { +__rpc_clnt_remove_pipedir (clnt); + clnt->pipefs_sb =NULL ; + }break ;default :printk (KERN_ERR "%s: unknown event: %ld\n" , __func__, event);return -ENOTSUPP ; } -return 0 ; +return err; } static int __rpc_pipefs_event(struct rpc_clnt *clnt, unsigned long event,
Signature · 1 line
2.53.0