jail: Improve locking when removing prisons

Change the flow of prison_deref() so it doesn't let go of allprison_lock
until it's completely done using it (except for a possible drop as part
of an upgrade on its first try).

Differential Revision:	https://reviews.freebsd.org/D28458
MFC after:	3 days
This commit is contained in:
Jamie Gritton 2021-02-20 14:38:58 -08:00
parent a8e431e153
commit 6e1d1bfcac

View File

@ -2793,11 +2793,17 @@ prison_complete(void *context, int pending)
static void
prison_deref(struct prison *pr, int flags)
{
struct prison *ppr, *tpr;
struct prisonlist freeprison;
struct prison *rpr, *tpr;
int lastref, lasturef;
TAILQ_INIT(&freeprison);
if (!(flags & PD_LOCKED))
mtx_lock(&pr->pr_mtx);
/*
* Release this prison as requested, which may cause its parent
* to be released, and then maybe its grandparent, etc.
*/
for (;;) {
if (flags & PD_DEUREF) {
KASSERT(refcount_load(&pr->pr_uref) > 0,
@ -2840,56 +2846,63 @@ prison_deref(struct prison *pr, int flags)
mtx_unlock(&pr->pr_mtx);
}
/* If the prison still has references, nothing else to do. */
if (!lastref) {
if (flags & PD_LIST_SLOCKED)
sx_sunlock(&allprison_lock);
else if (flags & PD_LIST_XLOCKED)
sx_xunlock(&allprison_lock);
return;
}
if (!lastref)
break;
if (flags & PD_LIST_SLOCKED) {
if (!sx_try_upgrade(&allprison_lock)) {
sx_sunlock(&allprison_lock);
sx_xlock(&allprison_lock);
}
flags &= ~PD_LIST_SLOCKED;
} else if (!(flags & PD_LIST_XLOCKED))
sx_xlock(&allprison_lock);
flags |= PD_LIST_XLOCKED;
TAILQ_REMOVE(&allprison, pr, pr_list);
LIST_REMOVE(pr, pr_sibling);
ppr = pr->pr_parent;
for (tpr = ppr; tpr != NULL; tpr = tpr->pr_parent)
TAILQ_INSERT_TAIL(&freeprison, pr, pr_list);
for (tpr = pr->pr_parent; tpr != NULL; tpr = tpr->pr_parent)
tpr->pr_childcount--;
sx_xunlock(&allprison_lock);
#ifdef VIMAGE
if (pr->pr_vnet != ppr->pr_vnet)
vnet_destroy(pr->pr_vnet);
#endif
if (pr->pr_root != NULL)
vrele(pr->pr_root);
mtx_destroy(&pr->pr_mtx);
#ifdef INET
free(pr->pr_ip4, M_PRISON);
#endif
#ifdef INET6
free(pr->pr_ip6, M_PRISON);
#endif
if (pr->pr_cpuset != NULL)
cpuset_rel(pr->pr_cpuset);
osd_jail_exit(pr);
#ifdef RACCT
if (racct_enable)
prison_racct_detach(pr);
#endif
free(pr, M_PRISON);
/* Removing a prison frees a reference on its parent. */
pr = ppr;
pr = pr->pr_parent;
mtx_lock(&pr->pr_mtx);
flags = PD_DEREF | PD_DEUREF;
flags |= PD_DEREF | PD_DEUREF;
}
/* Release all the prison locks. */
if (flags & PD_LIST_SLOCKED)
sx_sunlock(&allprison_lock);
else if (flags & PD_LIST_XLOCKED)
sx_xunlock(&allprison_lock);
/*
* Finish removing any unreferenced prisons, which couldn't happen
* while allprison_lock was held (to avoid a LOR on vrele).
*/
TAILQ_FOREACH_SAFE(rpr, &freeprison, pr_list, tpr) {
#ifdef VIMAGE
if (rpr->pr_vnet != rpr->pr_parent->pr_vnet)
vnet_destroy(rpr->pr_vnet);
#endif
if (rpr->pr_root != NULL)
vrele(rpr->pr_root);
mtx_destroy(&rpr->pr_mtx);
#ifdef INET
free(rpr->pr_ip4, M_PRISON);
#endif
#ifdef INET6
free(rpr->pr_ip6, M_PRISON);
#endif
if (rpr->pr_cpuset != NULL)
cpuset_rel(rpr->pr_cpuset);
osd_jail_exit(rpr);
#ifdef RACCT
if (racct_enable)
prison_racct_detach(rpr);
#endif
free(rpr, M_PRISON);
}
}