Commit ed2849d3 authored by NeilBrown's avatar NeilBrown Committed by J. Bruce Fields

sunrpc: prevent use-after-free on clearing XPT_BUSY

When an xprt is created, it has a refcount of 1, and XPT_BUSY is set.
The refcount is *not* owned by the thread that created the xprt
(as is clear from the fact that creators never put the reference).
Rather, it is owned by the absence of XPT_DEAD.  Once XPT_DEAD is set,
(And XPT_BUSY is clear) that initial reference is dropped and the xprt
can be freed.

So when a creator clears XPT_BUSY it is dropping its only reference and
so must not touch the xprt again.

However svc_recv, after calling ->xpo_accept (and so getting an XPT_BUSY
reference on a new xprt), calls svc_xprt_recieved.  This clears
XPT_BUSY and then svc_xprt_enqueue - this last without owning a reference.
This is dangerous and has been seen to leave svc_xprt_enqueue working
with an xprt containing garbage.

So we need to hold an extra counted reference over that call to
svc_xprt_received.

For safety, any time we clear XPT_BUSY and then use the xprt again, we
first get a reference, and the put it again afterwards.

Note that svc_close_all does not need this extra protection as there are
no threads running, and the final free can only be called asynchronously
from such a thread.
Signed-off-by: default avatarNeilBrown <neilb@suse.de>
Cc: stable@kernel.org
Signed-off-by: default avatarJ. Bruce Fields <bfields@redhat.com>
parent cf7d7e5a
...@@ -212,6 +212,7 @@ int svc_create_xprt(struct svc_serv *serv, const char *xprt_name, ...@@ -212,6 +212,7 @@ int svc_create_xprt(struct svc_serv *serv, const char *xprt_name,
spin_lock(&svc_xprt_class_lock); spin_lock(&svc_xprt_class_lock);
list_for_each_entry(xcl, &svc_xprt_class_list, xcl_list) { list_for_each_entry(xcl, &svc_xprt_class_list, xcl_list) {
struct svc_xprt *newxprt; struct svc_xprt *newxprt;
unsigned short newport;
if (strcmp(xprt_name, xcl->xcl_name)) if (strcmp(xprt_name, xcl->xcl_name))
continue; continue;
...@@ -230,8 +231,9 @@ int svc_create_xprt(struct svc_serv *serv, const char *xprt_name, ...@@ -230,8 +231,9 @@ int svc_create_xprt(struct svc_serv *serv, const char *xprt_name,
spin_lock_bh(&serv->sv_lock); spin_lock_bh(&serv->sv_lock);
list_add(&newxprt->xpt_list, &serv->sv_permsocks); list_add(&newxprt->xpt_list, &serv->sv_permsocks);
spin_unlock_bh(&serv->sv_lock); spin_unlock_bh(&serv->sv_lock);
newport = svc_xprt_local_port(newxprt);
clear_bit(XPT_BUSY, &newxprt->xpt_flags); clear_bit(XPT_BUSY, &newxprt->xpt_flags);
return svc_xprt_local_port(newxprt); return newport;
} }
err: err:
spin_unlock(&svc_xprt_class_lock); spin_unlock(&svc_xprt_class_lock);
...@@ -425,8 +427,13 @@ void svc_xprt_received(struct svc_xprt *xprt) ...@@ -425,8 +427,13 @@ void svc_xprt_received(struct svc_xprt *xprt)
{ {
BUG_ON(!test_bit(XPT_BUSY, &xprt->xpt_flags)); BUG_ON(!test_bit(XPT_BUSY, &xprt->xpt_flags));
xprt->xpt_pool = NULL; xprt->xpt_pool = NULL;
/* As soon as we clear busy, the xprt could be closed and
* 'put', so we need a reference to call svc_xprt_enqueue with:
*/
svc_xprt_get(xprt);
clear_bit(XPT_BUSY, &xprt->xpt_flags); clear_bit(XPT_BUSY, &xprt->xpt_flags);
svc_xprt_enqueue(xprt); svc_xprt_enqueue(xprt);
svc_xprt_put(xprt);
} }
EXPORT_SYMBOL_GPL(svc_xprt_received); EXPORT_SYMBOL_GPL(svc_xprt_received);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment