SUNRPC: Avoid deep recursion in rpc_release_client
In cases where an rpc client has a parent hierarchy, then rpc_free_client may end up calling rpc_release_client() on the parent, thus recursing back into rpc_free_client. If the hierarchy is deep enough, then we can get into situations where the stack simply overflows. The fix is to have rpc_release_client() loop so that it can take care of the parent rpc client hierarchy without needing to recurse. Reported-by: Jeff Layton <jlayton@redhat.com> Reported-by: Weston Andros Adamson <dros@netapp.com> Reported-by: Bruce Fields <bfields@fieldses.org> Link: http://lkml.kernel.org/r/2C73011F-0939-434C-9E4D-13A1EB1403D7@netapp.com Cc: stable@vger.kernel.org Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
This commit is contained in:
parent
a6b31d18b0
commit
d07ba8422f
|
@ -750,14 +750,16 @@ EXPORT_SYMBOL_GPL(rpc_shutdown_client);
|
||||||
/*
|
/*
|
||||||
* Free an RPC client
|
* Free an RPC client
|
||||||
*/
|
*/
|
||||||
static void
|
static struct rpc_clnt *
|
||||||
rpc_free_client(struct rpc_clnt *clnt)
|
rpc_free_client(struct rpc_clnt *clnt)
|
||||||
{
|
{
|
||||||
|
struct rpc_clnt *parent = NULL;
|
||||||
|
|
||||||
dprintk_rcu("RPC: destroying %s client for %s\n",
|
dprintk_rcu("RPC: destroying %s client for %s\n",
|
||||||
clnt->cl_program->name,
|
clnt->cl_program->name,
|
||||||
rcu_dereference(clnt->cl_xprt)->servername);
|
rcu_dereference(clnt->cl_xprt)->servername);
|
||||||
if (clnt->cl_parent != clnt)
|
if (clnt->cl_parent != clnt)
|
||||||
rpc_release_client(clnt->cl_parent);
|
parent = clnt->cl_parent;
|
||||||
rpc_clnt_remove_pipedir(clnt);
|
rpc_clnt_remove_pipedir(clnt);
|
||||||
rpc_unregister_client(clnt);
|
rpc_unregister_client(clnt);
|
||||||
rpc_free_iostats(clnt->cl_metrics);
|
rpc_free_iostats(clnt->cl_metrics);
|
||||||
|
@ -766,18 +768,17 @@ rpc_free_client(struct rpc_clnt *clnt)
|
||||||
rpciod_down();
|
rpciod_down();
|
||||||
rpc_free_clid(clnt);
|
rpc_free_clid(clnt);
|
||||||
kfree(clnt);
|
kfree(clnt);
|
||||||
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Free an RPC client
|
* Free an RPC client
|
||||||
*/
|
*/
|
||||||
static void
|
static struct rpc_clnt *
|
||||||
rpc_free_auth(struct rpc_clnt *clnt)
|
rpc_free_auth(struct rpc_clnt *clnt)
|
||||||
{
|
{
|
||||||
if (clnt->cl_auth == NULL) {
|
if (clnt->cl_auth == NULL)
|
||||||
rpc_free_client(clnt);
|
return rpc_free_client(clnt);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note: RPCSEC_GSS may need to send NULL RPC calls in order to
|
* Note: RPCSEC_GSS may need to send NULL RPC calls in order to
|
||||||
|
@ -788,7 +789,8 @@ rpc_free_auth(struct rpc_clnt *clnt)
|
||||||
rpcauth_release(clnt->cl_auth);
|
rpcauth_release(clnt->cl_auth);
|
||||||
clnt->cl_auth = NULL;
|
clnt->cl_auth = NULL;
|
||||||
if (atomic_dec_and_test(&clnt->cl_count))
|
if (atomic_dec_and_test(&clnt->cl_count))
|
||||||
rpc_free_client(clnt);
|
return rpc_free_client(clnt);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -799,10 +801,13 @@ rpc_release_client(struct rpc_clnt *clnt)
|
||||||
{
|
{
|
||||||
dprintk("RPC: rpc_release_client(%p)\n", clnt);
|
dprintk("RPC: rpc_release_client(%p)\n", clnt);
|
||||||
|
|
||||||
if (list_empty(&clnt->cl_tasks))
|
do {
|
||||||
wake_up(&destroy_wait);
|
if (list_empty(&clnt->cl_tasks))
|
||||||
if (atomic_dec_and_test(&clnt->cl_count))
|
wake_up(&destroy_wait);
|
||||||
rpc_free_auth(clnt);
|
if (!atomic_dec_and_test(&clnt->cl_count))
|
||||||
|
break;
|
||||||
|
clnt = rpc_free_auth(clnt);
|
||||||
|
} while (clnt != NULL);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(rpc_release_client);
|
EXPORT_SYMBOL_GPL(rpc_release_client);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue