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: NeilBrown <neilb@suse.de> Cc: stable@kernel.org Signed-off-by: J. Bruce Fields <bfields@redhat.com>
This commit is contained in:
parent
cf7d7e5a19
commit
ed2849d3ec
|
@ -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,
|
||||||
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)
|
||||||
{
|
{
|
||||||
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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue