ike: Track unprocessed initial IKE messages like half-open IKE_SAs

This should make the DoS limits (cookie_threshold[_ip] and block_threshold)
more accurate so that it won't be possible to create lots of jobs from
spoofed IP addresses before half-open IKE_SAs are actually created from
these jobs to enforce those limits.

Note that retransmits are tracked as half-open SAs until they are
processed/dismissed as the check only happens in checkout_by_message().

Increasing the count in process_message_job_create() avoids issues with
missing calls to track_init() before calling checkout_by_message() (e.g.
when processing fragmented IKEv1 messages, which are reinjected via a
process message job).
This commit is contained in:
Tobias Brunner 2021-06-04 18:11:46 +02:00
parent d8104b7c69
commit b866ee88bf
4 changed files with 78 additions and 23 deletions

View File

@ -28,7 +28,8 @@ charon.accept_unencrypted_mainmode_messages = no
example, some SonicWall boxes). example, some SonicWall boxes).
charon.block_threshold = 5 charon.block_threshold = 5
Maximum number of half-open IKE_SAs for a single peer IP. Maximum number of half-open IKE_SAs (including unprocessed IKE_SA_INITs)
for a single peer IP.
charon.cert_cache = yes charon.cert_cache = yes
Whether relations in validated certificate chains should be cached in Whether relations in validated certificate chains should be cached in
@ -70,11 +71,12 @@ charon.close_ike_on_child_failure = no
Close the IKE_SA if setup of the CHILD_SA along with IKE_AUTH failed. Close the IKE_SA if setup of the CHILD_SA along with IKE_AUTH failed.
charon.cookie_threshold = 30 charon.cookie_threshold = 30
Number of half-open IKE_SAs that activate the cookie mechanism. Number of half-open IKE_SAs (including unprocessed IKE_SA_INITs) that
activate the cookie mechanism.
charon.cookie_threshold_ip = 3 charon.cookie_threshold_ip = 3
Number of half-open IKE_SAs for a single peer IP that activate the cookie Number of half-open IKE_SAs (including unprocessed IKE_SA_INITs) for a
mechanism. single peer IP that activate the cookie mechanism.
charon.crypto_test.bench = no charon.crypto_test.bench = no
Benchmark crypto algorithms and order them by efficiency. Benchmark crypto algorithms and order them by efficiency.

View File

@ -133,5 +133,22 @@ process_message_job_t *process_message_job_create(message_t *message)
.message = message, .message = message,
); );
if (message->get_request(message) &&
message->get_exchange_type(message) == IKE_SA_INIT)
{
charon->ike_sa_manager->track_init(charon->ike_sa_manager,
message->get_source(message));
}
if (message->get_exchange_type(message) == ID_PROT ||
message->get_exchange_type(message) == AGGRESSIVE)
{
ike_sa_id_t *id = message->get_ike_sa_id(message);
if (id->get_responder_spi(id) == 0)
{
charon->ike_sa_manager->track_init(charon->ike_sa_manager,
message->get_source(message));
}
}
return &(this->public); return &(this->public);
} }

View File

@ -786,17 +786,16 @@ static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry,
/** /**
* Put a half-open SA into the hash table. * Put a half-open SA into the hash table.
*/ */
static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry) static void put_half_open(private_ike_sa_manager_t *this, host_t *ip,
bool initiator)
{ {
table_item_t *item; table_item_t *item;
u_int row, segment; u_int row, segment;
rwlock_t *lock; rwlock_t *lock;
ike_sa_id_t *ike_id;
half_open_t *half_open; half_open_t *half_open;
chunk_t addr; chunk_t addr;
ike_id = entry->ike_sa_id; addr = ip->get_address(ip);
addr = entry->other->get_address(entry->other);
row = chunk_hash(addr) & this->table_mask; row = chunk_hash(addr) & this->table_mask;
segment = row & this->segment_mask; segment = row & this->segment_mask;
lock = this->half_open_segments[segment].lock; lock = this->half_open_segments[segment].lock;
@ -826,7 +825,7 @@ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry)
} }
half_open->count++; half_open->count++;
ref_get(&this->half_open_count); ref_get(&this->half_open_count);
if (!ike_id->is_initiator(ike_id)) if (!initiator)
{ {
half_open->count_responder++; half_open->count_responder++;
ref_get(&this->half_open_count_responder); ref_get(&this->half_open_count_responder);
@ -838,16 +837,15 @@ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry)
/** /**
* Remove a half-open SA from the hash table. * Remove a half-open SA from the hash table.
*/ */
static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry) static void remove_half_open(private_ike_sa_manager_t *this, host_t *ip,
bool initiator)
{ {
table_item_t *item, *prev = NULL; table_item_t *item, *prev = NULL;
u_int row, segment; u_int row, segment;
rwlock_t *lock; rwlock_t *lock;
ike_sa_id_t *ike_id;
chunk_t addr; chunk_t addr;
ike_id = entry->ike_sa_id; addr = ip->get_address(ip);
addr = entry->other->get_address(entry->other);
row = chunk_hash(addr) & this->table_mask; row = chunk_hash(addr) & this->table_mask;
segment = row & this->segment_mask; segment = row & this->segment_mask;
lock = this->half_open_segments[segment].lock; lock = this->half_open_segments[segment].lock;
@ -859,7 +857,7 @@ static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry)
if (chunk_equals(addr, half_open->other)) if (chunk_equals(addr, half_open->other))
{ {
if (!ike_id->is_initiator(ike_id)) if (!initiator)
{ {
half_open->count_responder--; half_open->count_responder--;
ignore_result(ref_put(&this->half_open_count_responder)); ignore_result(ref_put(&this->half_open_count_responder));
@ -905,7 +903,7 @@ static u_int create_and_put_entry(private_ike_sa_manager_t *this,
{ {
(*entry)->half_open = TRUE; (*entry)->half_open = TRUE;
(*entry)->other = other->clone(other); (*entry)->other = other->clone(other);
put_half_open(this, *entry); put_half_open(this, (*entry)->other, ike_sa_id->is_initiator(ike_sa_id));
} }
return put_entry(this, *entry); return put_entry(this, *entry);
} }
@ -1314,7 +1312,7 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
ike_sa_t *ike_sa = NULL; ike_sa_t *ike_sa = NULL;
ike_sa_id_t *id; ike_sa_id_t *id;
ike_version_t ike_version; ike_version_t ike_version;
bool is_init = FALSE; bool is_init = FALSE, untrack_half_open = FALSE;
id = message->get_ike_sa_id(message); id = message->get_ike_sa_id(message);
/* clone the IKE_SA ID so we can modify the initiator flag */ /* clone the IKE_SA ID so we can modify the initiator flag */
@ -1359,6 +1357,7 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
uint64_t our_spi; uint64_t our_spi;
chunk_t hash; chunk_t hash;
untrack_half_open = TRUE;
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
if (!hasher || !get_init_hash(hasher, message, &hash)) if (!hasher || !get_init_hash(hasher, message, &hash))
{ {
@ -1386,6 +1385,10 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
entry->ike_sa_id = id; entry->ike_sa_id = id;
entry->processing = get_message_id_or_hash(message); entry->processing = get_message_id_or_hash(message);
entry->init_hash = hash; entry->init_hash = hash;
entry->half_open = TRUE;
entry->other = message->get_source(message);
entry->other = entry->other->clone(entry->other);
untrack_half_open = FALSE;
segment = put_entry(this, entry); segment = put_entry(this, entry);
entry->checked_out = thread_current(); entry->checked_out = thread_current();
@ -1426,6 +1429,9 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
/* it looks like we already handled this init message to some degree */ /* it looks like we already handled this init message to some degree */
id->set_responder_spi(id, our_spi); id->set_responder_spi(id, our_spi);
chunk_free(&hash); chunk_free(&hash);
/* untrack the duplicate before waiting for the checkout */
remove_half_open(this, message->get_source(message), FALSE);
untrack_half_open = FALSE;
} }
if (get_entry_by_id(this, id, &entry, &segment) == SUCCESS) if (get_entry_by_id(this, id, &entry, &segment) == SUCCESS)
@ -1464,6 +1470,10 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
id->destroy(id); id->destroy(id);
out: out:
if (untrack_half_open)
{
remove_half_open(this, message->get_source(message), FALSE);
}
charon->bus->set_sa(charon->bus, ike_sa); charon->bus->set_sa(charon->bus, ike_sa);
if (!ike_sa) if (!ike_sa)
{ {
@ -1886,15 +1896,18 @@ METHOD(ike_sa_manager_t, checkin, void,
{ {
/* not half open anymore */ /* not half open anymore */
entry->half_open = FALSE; entry->half_open = FALSE;
remove_half_open(this, entry); remove_half_open(this, entry->other,
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
} }
else if (entry->half_open && !other->ip_equals(other, entry->other)) else if (entry->half_open && !other->ip_equals(other, entry->other))
{ {
/* the other host's IP has changed, we must update the hash table */ /* the other host's IP has changed, we must update the hash table */
remove_half_open(this, entry); remove_half_open(this, entry->other,
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
DESTROY_IF(entry->other); DESTROY_IF(entry->other);
entry->other = other->clone(other); entry->other = other->clone(other);
put_half_open(this, entry); put_half_open(this, entry->other,
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
} }
else if (!entry->half_open && else if (!entry->half_open &&
ike_sa->get_state(ike_sa) == IKE_CONNECTING) ike_sa->get_state(ike_sa) == IKE_CONNECTING)
@ -1902,7 +1915,8 @@ METHOD(ike_sa_manager_t, checkin, void,
/* this is a new half-open SA */ /* this is a new half-open SA */
entry->half_open = TRUE; entry->half_open = TRUE;
entry->other = other->clone(other); entry->other = other->clone(other);
put_half_open(this, entry); put_half_open(this, entry->other,
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
} }
entry->condvar->signal(entry->condvar); entry->condvar->signal(entry->condvar);
} }
@ -2007,7 +2021,8 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void,
if (entry->half_open) if (entry->half_open)
{ {
remove_half_open(this, entry); remove_half_open(this, entry->other,
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
} }
if (entry->my_id && entry->other_id) if (entry->my_id && entry->other_id)
{ {
@ -2318,6 +2333,12 @@ METHOD(ike_sa_manager_t, get_half_open_count, u_int,
return count; return count;
} }
METHOD(ike_sa_manager_t, track_init, void,
private_ike_sa_manager_t *this, host_t *ip)
{
put_half_open(this, ip, FALSE);
}
METHOD(ike_sa_manager_t, set_spi_cb, void, METHOD(ike_sa_manager_t, set_spi_cb, void,
private_ike_sa_manager_t *this, spi_cb_t callback, void *data) private_ike_sa_manager_t *this, spi_cb_t callback, void *data)
{ {
@ -2342,7 +2363,8 @@ static void destroy_all_entries(private_ike_sa_manager_t *this)
charon->bus->set_sa(charon->bus, entry->ike_sa); charon->bus->set_sa(charon->bus, entry->ike_sa);
if (entry->half_open) if (entry->half_open)
{ {
remove_half_open(this, entry); remove_half_open(this, entry->other,
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
} }
if (entry->my_id && entry->other_id) if (entry->my_id && entry->other_id)
{ {
@ -2494,6 +2516,7 @@ ike_sa_manager_t *ike_sa_manager_create()
.checkin_and_destroy = _checkin_and_destroy, .checkin_and_destroy = _checkin_and_destroy,
.get_count = _get_count, .get_count = _get_count,
.get_half_open_count = _get_half_open_count, .get_half_open_count = _get_half_open_count,
.track_init = _track_init,
.flush = _flush, .flush = _flush,
.set_spi_cb = _set_spi_cb, .set_spi_cb = _set_spi_cb,
.destroy = _destroy, .destroy = _destroy,

View File

@ -85,6 +85,16 @@ struct ike_sa_manager_t {
*/ */
ike_sa_t* (*checkout) (ike_sa_manager_t* this, ike_sa_id_t *sa_id); ike_sa_t* (*checkout) (ike_sa_manager_t* this, ike_sa_id_t *sa_id);
/**
* Track an initial IKE message as responder by increasing the number of
* half-open IKE_SAs.
*
* @note It's expected that checkout_by_message() is called afterwards.
*
* @param ip IP of sender
*/
void (*track_init)(ike_sa_manager_t *this, host_t *ip);
/** /**
* Checkout an IKE_SA by a message. * Checkout an IKE_SA by a message.
* *
@ -97,10 +107,13 @@ struct ike_sa_manager_t {
* retransmission. If so, we have to drop the message, we would * retransmission. If so, we have to drop the message, we would
* create another unneeded IKE_SA for each retransmitted packet. * create another unneeded IKE_SA for each retransmitted packet.
* *
* A call to checkout_by_message() returns a (maybe new created) IKE_SA. * A call to checkout_by_message() returns a (maybe newly created) IKE_SA.
* If processing the message does not make sense (for the reasons above), * If processing the message does not make sense (for the reasons above),
* NULL is returned. * NULL is returned.
* *
* @note For initial IKE messages, track_init() has to be called before
* calling this.
*
* @param ike_sa_id the SA identifier, will be updated * @param ike_sa_id the SA identifier, will be updated
* @returns * @returns
* - checked out/created IKE_SA * - checked out/created IKE_SA