Fix deadlock in trap_manager_t during acquire.
Also fixes a TOCTOU issue regarding the use of entry_t.pending.
The deadlock was caused because the rwlock was being locked while
waiting for an IKE_SA. Triggering the deadlock was a bit tricky, here
is the description by Thomas Egerer (the reporter of this issue):
"
The deadlock occurs when the following happens (in the given order):
a) an IKE_SA is built and a thread is processing the IKE_AUTH request,
which can take a bit longer when a smartcard is involved. This
causes the ike_sa_manager to lock a particular IKE_SA exclusively.
b) an acquire is triggered which causes the rwlock in the trap_manager
to be read-locked, the subsequent call to
ike_sa_manager->checkout_by_config has to wait until a) unlocks
it's ike_sa.
c) a child_cfg contained in the peer_cfg belonging to the ike_sa
a) has locked is routed causes the child_configs contained
in the peer config to be locked by c) while the actual routing
code within trap_manager tries to writelock it's rwlock.
That's about it. As soon as a) finishes authentication of the peer
and tries to find a matching child sa it will try to lock the child
configs of the peer config which is not possible since it has been
locked by c).
Thread | Resource locked | Resource desired
-------+--------------------------------+--------------------------------
(a) | ike_sa in ike_sa_manager | child_cfgs of peer_cfg
| |
(b) | rwlock in trap-manager (read) | ike_sa in ike_sa_manager
| |
(c) | child_cfgs of peer_cfg | rwlock in trap-manager (write)
"
With this patch thread (b) now does not hold the lock while waiting for
the IKE_SA. Thus (c) can get the write lock, and (a) can subsequently
lock the mutex in the peer_cfg which then finally allows (b) to checkout
the IKE_SA.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2011 Tobias Brunner
|
||||||
* Copyright (C) 2009 Martin Willi
|
* Copyright (C) 2009 Martin Willi
|
||||||
* Hochschule fuer Technik Rapperswil
|
* Hochschule fuer Technik Rapperswil
|
||||||
*
|
*
|
||||||
@@ -74,8 +75,10 @@ typedef struct {
|
|||||||
peer_cfg_t *peer_cfg;
|
peer_cfg_t *peer_cfg;
|
||||||
/** ref to instanciated CHILD_SA */
|
/** ref to instanciated CHILD_SA */
|
||||||
child_sa_t *child_sa;
|
child_sa_t *child_sa;
|
||||||
|
/** TRUE if an acquire is pending */
|
||||||
|
bool pending;
|
||||||
/** pending IKE_SA connecting upon acquire */
|
/** pending IKE_SA connecting upon acquire */
|
||||||
ike_sa_t *pending;
|
ike_sa_t *ike_sa;
|
||||||
} entry_t;
|
} entry_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -170,10 +173,10 @@ METHOD(trap_manager_t, install, u_int32_t,
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqid = child_sa->get_reqid(child_sa);
|
reqid = child_sa->get_reqid(child_sa);
|
||||||
entry = malloc_thing(entry_t);
|
INIT(entry,
|
||||||
entry->child_sa = child_sa;
|
.child_sa = child_sa,
|
||||||
entry->peer_cfg = peer->get_ref(peer);
|
.peer_cfg = peer->get_ref(peer),
|
||||||
entry->pending = NULL;
|
);
|
||||||
|
|
||||||
this->lock->write_lock(this->lock);
|
this->lock->write_lock(this->lock);
|
||||||
this->traps->insert_last(this->traps, entry);
|
this->traps->insert_last(this->traps, entry);
|
||||||
@@ -263,35 +266,46 @@ METHOD(trap_manager_t, acquire, void,
|
|||||||
if (!found)
|
if (!found)
|
||||||
{
|
{
|
||||||
DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d",reqid);
|
DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d",reqid);
|
||||||
|
this->lock->unlock(this->lock);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (found->pending)
|
if (!cas_bool(&found->pending, FALSE, TRUE))
|
||||||
{
|
{
|
||||||
DBG1(DBG_CFG, "ignoring acquire, connection attempt pending");
|
DBG1(DBG_CFG, "ignoring acquire, connection attempt pending");
|
||||||
|
this->lock->unlock(this->lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
peer = found->peer_cfg->get_ref(found->peer_cfg);
|
||||||
|
child = found->child_sa->get_config(found->child_sa);
|
||||||
|
child = child->get_ref(child);
|
||||||
|
reqid = found->child_sa->get_reqid(found->child_sa);
|
||||||
|
/* don't hold the lock while checking out the IKE_SA */
|
||||||
|
this->lock->unlock(this->lock);
|
||||||
|
|
||||||
|
ike_sa = charon->ike_sa_manager->checkout_by_config(
|
||||||
|
charon->ike_sa_manager, peer);
|
||||||
|
if (ike_sa->get_peer_cfg(ike_sa) == NULL)
|
||||||
|
{
|
||||||
|
ike_sa->set_peer_cfg(ike_sa, peer);
|
||||||
|
}
|
||||||
|
if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME)
|
||||||
|
{
|
||||||
|
/* make sure the entry is still there */
|
||||||
|
this->lock->read_lock(this->lock);
|
||||||
|
if (this->traps->find_first(this->traps, NULL,
|
||||||
|
(void**)&found) == SUCCESS)
|
||||||
|
{
|
||||||
|
found->ike_sa = ike_sa;
|
||||||
|
}
|
||||||
|
this->lock->unlock(this->lock);
|
||||||
|
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
child = found->child_sa->get_config(found->child_sa);
|
charon->ike_sa_manager->checkin_and_destroy(
|
||||||
peer = found->peer_cfg;
|
charon->ike_sa_manager, ike_sa);
|
||||||
ike_sa = charon->ike_sa_manager->checkout_by_config(
|
|
||||||
charon->ike_sa_manager, peer);
|
|
||||||
if (ike_sa->get_peer_cfg(ike_sa) == NULL)
|
|
||||||
{
|
|
||||||
ike_sa->set_peer_cfg(ike_sa, peer);
|
|
||||||
}
|
|
||||||
child->get_ref(child);
|
|
||||||
reqid = found->child_sa->get_reqid(found->child_sa);
|
|
||||||
if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME)
|
|
||||||
{
|
|
||||||
found->pending = ike_sa;
|
|
||||||
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
charon->ike_sa_manager->checkin_and_destroy(
|
|
||||||
charon->ike_sa_manager, ike_sa);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this->lock->unlock(this->lock);
|
peer->destroy(peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -307,7 +321,7 @@ static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa,
|
|||||||
enumerator = this->traps->create_enumerator(this->traps);
|
enumerator = this->traps->create_enumerator(this->traps);
|
||||||
while (enumerator->enumerate(enumerator, &entry))
|
while (enumerator->enumerate(enumerator, &entry))
|
||||||
{
|
{
|
||||||
if (entry->pending != ike_sa)
|
if (entry->ike_sa != ike_sa)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -316,7 +330,8 @@ static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa,
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
entry->pending = NULL;
|
entry->ike_sa = NULL;
|
||||||
|
entry->pending = FALSE;
|
||||||
}
|
}
|
||||||
enumerator->destroy(enumerator);
|
enumerator->destroy(enumerator);
|
||||||
this->lock->unlock(this->lock);
|
this->lock->unlock(this->lock);
|
||||||
|
|||||||
Reference in New Issue
Block a user