]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
ike-rekey: Support IKE_SA rekeying with multiple key exchanges
authorTobias Brunner <tobias@strongswan.org>
Mon, 6 Apr 2020 15:41:15 +0000 (17:41 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 1 Dec 2023 08:27:44 +0000 (09:27 +0100)
src/libcharon/sa/ikev2/tasks/ike_rekey.c

index fac008715b936c9291c66912e4063e1373524f6c..446d118db7cd9820afd1c7c2fd9f258dbf41e3a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015-2018 Tobias Brunner
+ * Copyright (C) 2015-2020 Tobias Brunner
  * Copyright (C) 2005-2008 Martin Willi
  * Copyright (C) 2005 Jan Hutter
  *
@@ -26,7 +26,6 @@
 #include <processing/jobs/rekey_ike_sa_job.h>
 #include <processing/jobs/initiate_tasks_job.h>
 
-
 typedef struct private_ike_rekey_t private_ike_rekey_t;
 
 /**
@@ -65,14 +64,49 @@ struct private_ike_rekey_t {
        ike_delete_t *ike_delete;
 
        /**
-        * colliding task detected by the task manager
+        * Colliding passive task if any
         */
        private_ike_rekey_t *collision;
 
        /**
-        * TRUE if rekeying can't be handled temporarily
+        * Link value for the current key exchange
+        */
+       chunk_t link;
+
+       /**
+        * State/error flags
         */
-       bool failed_temporarily;
+       enum {
+
+               /**
+                * Set if rekeying can't be handled temporarily.
+                */
+               IKE_REKEY_FAILED_TEMPORARILY = (1<<0),
+
+               /**
+                * Set if the parsed link value was invalid.
+                */
+               IKE_REKEY_LINK_INVALID = (1<<1),
+
+               /**
+                * Set if we use multiple key exchanges and already processed the
+                * CREATE_CHILD_SA response and started sending IKE_FOLLOWUP_KEs.
+                */
+               IKE_REKEY_FOLLOWUP_KE = (1<<2),
+
+               /**
+                * Set if a passive rekeying has completed successfully and we don't
+                * expect any further messages.
+                */
+               IKE_REKEY_DONE = (1<<3),
+
+               /**
+                * Set if we adopted a completed passive task, otherwise we just
+                * reference it.
+                */
+               IKE_REKEY_ADOPTED_PASSIVE = (1<<4),
+
+       } flags;
 };
 
 /**
@@ -161,10 +195,24 @@ METHOD(task_t, process_i_delete, status_t,
        return this->ike_delete->task.process(&this->ike_delete->task, message);
 }
 
+METHOD(task_t, build_i_multi_ke, status_t,
+       private_ike_rekey_t *this, message_t *message)
+{
+       status_t status;
+
+       charon->bus->set_sa(charon->bus, this->new_sa);
+       message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
+       status = this->ike_init->task.build(&this->ike_init->task, message);
+       charon->bus->set_sa(charon->bus, this->ike_sa);
+       this->flags |= IKE_REKEY_FOLLOWUP_KE;
+       return status;
+}
+
 METHOD(task_t, build_i, status_t,
        private_ike_rekey_t *this, message_t *message)
 {
        ike_version_t version;
+       status_t status;
 
        /* create new SA only on first try */
        if (!this->new_sa)
@@ -188,9 +236,9 @@ METHOD(task_t, build_i, status_t,
                this->ike_init = ike_init_create(this->new_sa, TRUE, this->ike_sa);
                this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
        }
-       this->ike_init->task.build(&this->ike_init->task, message);
-
-       return NEED_MORE;
+       status = this->ike_init->task.build(&this->ike_init->task, message);
+       charon->bus->set_sa(charon->bus, this->ike_sa);
+       return status;
 }
 
 /**
@@ -231,25 +279,116 @@ static bool have_half_open_children(private_ike_rekey_t *this)
        return FALSE;
 }
 
+/**
+ * Check if we are actively rekeying and optionally, if we already sent an
+ * IKE_FOLLOWUP_KE message.
+ */
+static bool actively_rekeying(private_ike_rekey_t *this, bool *follow_up_sent)
+{
+       enumerator_t *enumerator;
+       task_t *task;
+       bool found = FALSE;
+
+       enumerator = this->ike_sa->create_task_enumerator(this->ike_sa,
+                                                                                                         TASK_QUEUE_ACTIVE);
+       while (enumerator->enumerate(enumerator, (void**)&task))
+       {
+               if (task->get_type(task) == TASK_IKE_REKEY)
+               {
+                       if (follow_up_sent)
+                       {
+                               private_ike_rekey_t *rekey = (private_ike_rekey_t*)task;
+                               *follow_up_sent = rekey->flags & IKE_REKEY_FOLLOWUP_KE;
+                       }
+                       found = TRUE;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       return found;
+}
+
+/**
+ * Process payloads in a IKE_FOLLOWUP_KE message or a CREATE_CHILD_SA response
+ */
+static void process_link(private_ike_rekey_t *this, message_t *message)
+{
+       notify_payload_t *notify;
+       chunk_t link;
+
+       notify = message->get_notify(message, ADDITIONAL_KEY_EXCHANGE);
+       if (!notify)
+       {
+               DBG1(DBG_IKE, "%N notify missing", notify_type_names,
+                       ADDITIONAL_KEY_EXCHANGE);
+               this->flags |= IKE_REKEY_LINK_INVALID;
+       }
+       else
+       {
+               link = notify->get_notification_data(notify);
+               if (this->initiator)
+               {
+                       chunk_free(&this->link);
+                       this->link = chunk_clone(link);
+               }
+               else if (!chunk_equals_const(this->link, link))
+               {
+                       DBG1(DBG_IKE, "data in %N notify doesn't match", notify_type_names,
+                                ADDITIONAL_KEY_EXCHANGE);
+                       this->flags |= IKE_REKEY_LINK_INVALID;
+               }
+       }
+}
+
+METHOD(task_t, process_r_multi_ke, status_t,
+       private_ike_rekey_t *this, message_t *message)
+{
+       if (message->get_exchange_type(message) != IKE_FOLLOWUP_KE)
+       {
+               return FAILED;
+       }
+       if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
+       {
+               DBG1(DBG_IKE, "peer continued rekeying, but we are deleting");
+               this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
+               return NEED_MORE;
+       }
+
+       charon->bus->set_sa(charon->bus, this->new_sa);
+       process_link(this, message);
+       this->ike_init->task.process(&this->ike_init->task, message);
+       charon->bus->set_sa(charon->bus, this->ike_sa);
+       return NEED_MORE;
+}
+
 METHOD(task_t, process_r, status_t,
        private_ike_rekey_t *this, message_t *message)
 {
+       bool follow_up_sent;
+
        if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
        {
                DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
-               this->failed_temporarily = TRUE;
+               this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
                return NEED_MORE;
        }
        if (this->ike_sa->has_condition(this->ike_sa, COND_REAUTHENTICATING))
        {
                DBG1(DBG_IKE, "peer initiated rekeying, but we are reauthenticating");
-               this->failed_temporarily = TRUE;
+               this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
                return NEED_MORE;
        }
        if (have_half_open_children(this))
        {
                DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open");
-               this->failed_temporarily = TRUE;
+               this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
+               return NEED_MORE;
+       }
+       if (actively_rekeying(this, &follow_up_sent) && follow_up_sent)
+       {
+               DBG1(DBG_IKE, "peer initiated rekeying, but we did too and already "
+                        "sent IKE_FOLLOWUP_KE");
+               this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
                return NEED_MORE;
        }
 
@@ -269,11 +408,16 @@ METHOD(task_t, process_r, status_t,
 METHOD(task_t, build_r, status_t,
        private_ike_rekey_t *this, message_t *message)
 {
-       if (this->failed_temporarily)
+       if (this->flags & IKE_REKEY_FAILED_TEMPORARILY)
        {
                message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
                return SUCCESS;
        }
+       if (this->flags & IKE_REKEY_LINK_INVALID)
+       {
+               message->add_notify(message, TRUE, STATE_NOT_FOUND, chunk_empty);
+               return SUCCESS;
+       }
        if (!this->new_sa)
        {
                /* IKE_SA/a CHILD_SA is in an unacceptable state, deny rekeying */
@@ -282,17 +426,43 @@ METHOD(task_t, build_r, status_t,
        }
 
        charon->bus->set_sa(charon->bus, this->new_sa);
-       if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED)
+       switch (this->ike_init->task.build(&this->ike_init->task, message))
        {
-               this->ike_init->task.destroy(&this->ike_init->task);
-               this->ike_init = NULL;
-               charon->bus->set_sa(charon->bus, this->ike_sa);
-               return SUCCESS;
+               case FAILED:
+                       this->ike_init->task.destroy(&this->ike_init->task);
+                       this->ike_init = NULL;
+                       charon->bus->set_sa(charon->bus, this->ike_sa);
+                       return SUCCESS;
+               case NEED_MORE:
+                       /* additional key exchanges, the value in the notify doesn't really
+                        * matter to us as we have a window size of 1 */
+                       charon->bus->set_sa(charon->bus, this->ike_sa);
+                       if (!this->link.ptr)
+                       {
+                               this->link = chunk_clone(chunk_from_chars(0x42));
+                       }
+                       message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE,
+                                                               this->link);
+                       if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
+                       {
+                               this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
+                       }
+                       this->public.task.process = _process_r_multi_ke;
+                       return NEED_MORE;
+               default:
+                       charon->bus->set_sa(charon->bus, this->ike_sa);
+                       if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
+                       {
+                               this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
+                       }
+                       break;
        }
-       charon->bus->set_sa(charon->bus, this->ike_sa);
 
-       if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
-       {       /* in case of a collision we let the initiating task handle this */
+       this->flags |= IKE_REKEY_DONE;
+
+       /* if we are actively rekeying, we let the initiating task handle this */
+       if (!actively_rekeying(this, NULL))
+       {
                establish_new(this);
                /* make sure the IKE_SA is gone in case the peer fails to delete it */
                lib->scheduler->schedule_job(lib->scheduler, (job_t*)
@@ -303,7 +473,7 @@ METHOD(task_t, build_r, status_t,
 }
 
 /**
- * Conclude any undetected rekey collision.
+ * Conclude any (undetected) rekey collision.
  *
  * If the peer does not detect the collision it will delete this IKE_SA.
  * Depending on when our request reaches the peer and we receive the delete
@@ -311,18 +481,174 @@ METHOD(task_t, build_r, status_t,
  *
  * Returns TRUE if there was a collision, FALSE otherwise.
  */
-static bool conclude_undetected_collision(private_ike_rekey_t *this)
+static bool conclude_collision(private_ike_rekey_t *this, bool maybe_undetected)
 {
-       if (this->collision)
+       if (this->collision &&
+               this->flags & IKE_REKEY_ADOPTED_PASSIVE)
        {
-               DBG1(DBG_IKE, "peer did not notice IKE_SA rekey collision, abort "
-                        "active rekeying");
+               if (maybe_undetected)
+               {
+                       DBG1(DBG_IKE, "peer may not have noticed IKE_SA rekey collision, "
+                                "abort active rekeying");
+               }
                establish_new(this->collision);
                return TRUE;
        }
        return FALSE;
 }
 
+/**
+ * Delete the redundant IKE_SA we created.
+ */
+static void delete_redundant(private_ike_rekey_t *this)
+{
+       host_t *host;
+
+       /* apply host for a proper delete */
+       host = this->ike_sa->get_my_host(this->ike_sa);
+       this->new_sa->set_my_host(this->new_sa, host->clone(host));
+       host = this->ike_sa->get_other_host(this->ike_sa);
+       this->new_sa->set_other_host(this->new_sa, host->clone(host));
+       this->new_sa->set_state(this->new_sa, IKE_REKEYED);
+       if (this->new_sa->delete(this->new_sa, FALSE) == DESTROY_ME)
+       {
+               this->new_sa->destroy(this->new_sa);
+       }
+       else
+       {
+               charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa);
+       }
+       charon->bus->set_sa(charon->bus, this->ike_sa);
+       this->new_sa = NULL;
+}
+
+/**
+ * Check in the redundant IKE_SA created by the peer and wait for its deletion.
+ */
+static void wait_for_redundant_delete(private_ike_rekey_t *this)
+{
+       private_ike_rekey_t *other = this->collision;
+       job_t *job;
+
+       /* peer should delete the SA it created, add a timeout just in case */
+       job = (job_t*)delete_ike_sa_job_create(
+                                                               other->new_sa->get_id(other->new_sa), TRUE);
+       lib->scheduler->schedule_job(lib->scheduler, job, HALF_OPEN_IKE_SA_TIMEOUT);
+       other->new_sa->set_state(other->new_sa, IKE_REKEYED);
+       charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
+       other->new_sa = NULL;
+       charon->bus->set_sa(charon->bus, this->ike_sa);
+}
+
+/**
+ * Remove the passive rekey task that's waiting for IKE_FOLLOWUP_KE requests
+ * that will never come.
+ */
+static void remove_passive_rekey_task(private_ike_rekey_t *this)
+{
+       enumerator_t *enumerator;
+       task_t *task;
+
+       enumerator = this->ike_sa->create_task_enumerator(this->ike_sa,
+                                                                                                         TASK_QUEUE_PASSIVE);
+       while (enumerator->enumerate(enumerator, &task))
+       {
+               if (task->get_type(task) == TASK_IKE_REKEY)
+               {
+                       this->ike_sa->remove_task(this->ike_sa, enumerator);
+                       task->destroy(task);
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+}
+
+/**
+ * Handle any collision as necessary and report back if we lost the
+ * collision and should abort the active task.
+ */
+static bool collision_lost(private_ike_rekey_t *this, bool multi_ke)
+{
+       private_ike_rekey_t *other = this->collision;
+       chunk_t this_nonce, other_nonce;
+
+       if (!this->collision)
+       {
+               return FALSE;
+       }
+
+       this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
+       other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
+
+       /* the SA with the lowest nonce should be deleted (if already complete),
+        * check if we or the peer created that */
+       if (memcmp(this_nonce.ptr, other_nonce.ptr,
+                          min(this_nonce.len, other_nonce.len)) < 0)
+       {
+               if (multi_ke)
+               {
+                       DBG1(DBG_IKE, "IKE_SA rekey collision lost, abort incomplete "
+                                "multi-KE rekeying");
+               }
+               else
+               {
+                       DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant "
+                                "IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa),
+                                this->new_sa->get_unique_id(this->new_sa));
+                       delete_redundant(this);
+               }
+               /* establish the other SA if the passive task is done (i.e. was
+                * single-KE or our response was delayed and the winner continued),
+                * otherwise, we just let it continue independently */
+               conclude_collision(this, FALSE);
+               return TRUE;
+       }
+
+       /* the passive rekeying is complete only if it was single-KE. otherwise,
+        * the peer would either have stopped before sending IKE_FOLLOWUP_KE when it
+        * noticed it lost, or it responded with TEMPORARY_FAILURE to our
+        * CREATE_CHILD_SA request if it already started sending them.
+        * since the task is not completed immediately, we clean up the collision */
+       if (this->flags & IKE_REKEY_ADOPTED_PASSIVE)
+       {
+               if (multi_ke)
+               {
+                       DBG1(DBG_IKE, "IKE_SA rekey collision won, continue with multi-KE "
+                                "rekeying and wait for delete for redundant IKE_SA %s[%d]",
+                                other->new_sa->get_name(other->new_sa),
+                                other->new_sa->get_unique_id(other->new_sa));
+               }
+               else
+               {
+                       DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete for "
+                                "redundant IKE_SA %s[%d]",
+                                other->new_sa->get_name(other->new_sa),
+                                other->new_sa->get_unique_id(other->new_sa));
+               }
+               wait_for_redundant_delete(this);
+               other->public.task.destroy(&other->public.task);
+       }
+       else
+       {
+               /* the peer will not continue with its multi-KE rekeying, so we must
+                * remove the passive task that's waiting for IKE_FOLLOWUP_KEs */
+               if (multi_ke)
+               {
+                       DBG1(DBG_IKE, "IKE_SA rekey collision won, continue with "
+                                "multi-KE rekeying and remove passive %N task",
+                                task_type_names, TASK_IKE_REKEY);
+               }
+               else
+               {
+                       DBG1(DBG_IKE, "IKE_SA rekey collision won, remove passive %N task",
+                                task_type_names, TASK_IKE_REKEY);
+               }
+               remove_passive_rekey_task(this);
+       }
+       this->collision = NULL;
+       return FALSE;
+}
+
 METHOD(task_t, process_i, status_t,
        private_ike_rekey_t *this, message_t *message)
 {
@@ -336,78 +662,66 @@ METHOD(task_t, process_i, status_t,
                                                        this->ike_sa->get_id(this->ike_sa), TRUE));
                return SUCCESS;
        }
+       if (message->get_notify(message, STATE_NOT_FOUND))
+       {
+               DBG1(DBG_IKE, "peer didn't like our %N notify data", notify_type_names,
+                        ADDITIONAL_KEY_EXCHANGE);
+               if (!conclude_collision(this, TRUE))
+               {
+                       schedule_delayed_rekey(this);
+               }
+               return SUCCESS;
+       }
 
+       charon->bus->set_sa(charon->bus, this->new_sa);
        switch (this->ike_init->task.process(&this->ike_init->task, message))
        {
                case FAILED:
+                       charon->bus->set_sa(charon->bus, this->ike_sa);
                        /* rekeying failed, fallback to old SA */
-                       if (!conclude_undetected_collision(this))
+                       if (!conclude_collision(this, TRUE))
                        {
                                schedule_delayed_rekey(this);
                        }
                        return SUCCESS;
                case NEED_MORE:
-                       /* bad KE method, try again */
-                       this->ike_init->task.migrate(&this->ike_init->task, this->new_sa);
-                       return NEED_MORE;
+                       if (message->get_notify(message, INVALID_KE_PAYLOAD))
+                       {       /* bad key exchange mechanism, try again */
+                               this->ike_init->task.migrate(&this->ike_init->task,
+                                                                                        this->new_sa);
+                               charon->bus->set_sa(charon->bus, this->ike_sa);
+                               return NEED_MORE;
+                       }
+                       /* multiple key exchanges, continue with IKE_FOLLOWUP_KE */
+                       process_link(this, message);
+                       charon->bus->set_sa(charon->bus, this->ike_sa);
+                       if (this->flags & IKE_REKEY_LINK_INVALID)
+                       {       /* we can't continue without notify, maybe the peer returns
+                                * one later */
+                               if (!conclude_collision(this, TRUE))
+                               {
+                                       schedule_delayed_rekey(this);
+                               }
+                               return SUCCESS;
+                       }
+                       this->public.task.build = _build_i_multi_ke;
+                       /* there will only be a collision if we process a CREATE_CHILD_SA
+                        * response, later we just respond with TEMPORARY_FAILURE and ignore
+                        * the passive task */
+                       return collision_lost(this, TRUE) ? SUCCESS : NEED_MORE;
                default:
+                       charon->bus->set_sa(charon->bus, this->ike_sa);
                        break;
        }
 
-       if (this->collision)
+       /* there won't be a collision here if this task is for a multi-KE rekeying,
+        * as a collision during CREATE_CHILD_SA was cleaned up above */
+       if (collision_lost(this, FALSE))
        {
-               private_ike_rekey_t *other = this->collision;
-               host_t *host;
-               chunk_t this_nonce, other_nonce;
-
-               this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
-               other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
-
-               /* the SA with the lowest nonce should be deleted, check if we or
-                * the peer created that */
-               if (memcmp(this_nonce.ptr, other_nonce.ptr,
-                                  min(this_nonce.len, other_nonce.len)) < 0)
-               {
-                       DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant "
-                                "IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa),
-                                this->new_sa->get_unique_id(this->new_sa));
-                       /* apply host for a proper delete */
-                       host = this->ike_sa->get_my_host(this->ike_sa);
-                       this->new_sa->set_my_host(this->new_sa, host->clone(host));
-                       host = this->ike_sa->get_other_host(this->ike_sa);
-                       this->new_sa->set_other_host(this->new_sa, host->clone(host));
-                       this->new_sa->set_state(this->new_sa, IKE_REKEYED);
-                       if (this->new_sa->delete(this->new_sa, FALSE) == DESTROY_ME)
-                       {
-                               this->new_sa->destroy(this->new_sa);
-                       }
-                       else
-                       {
-                               charon->ike_sa_manager->checkin(charon->ike_sa_manager,
-                                                                                               this->new_sa);
-                       }
-                       charon->bus->set_sa(charon->bus, this->ike_sa);
-                       this->new_sa = NULL;
-                       establish_new(other);
-                       return SUCCESS;
-               }
-
-               /* peer should delete the SA it created, add a timeout just in case */
-               job_t *job = (job_t*)delete_ike_sa_job_create(
-                                                                       other->new_sa->get_id(other->new_sa), TRUE);
-               lib->scheduler->schedule_job(lib->scheduler, job,
-                                                                        HALF_OPEN_IKE_SA_TIMEOUT);
-               DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete for "
-                        "redundant IKE_SA %s[%d]", other->new_sa->get_name(other->new_sa),
-                        other->new_sa->get_unique_id(other->new_sa));
-               other->new_sa->set_state(other->new_sa, IKE_REKEYED);
-               charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
-               other->new_sa = NULL;
-               charon->bus->set_sa(charon->bus, this->ike_sa);
+               return SUCCESS;
        }
 
        establish_new(this);
-
        /* rekeying successful, delete this IKE_SA using a subtask */
        this->ike_delete = ike_delete_create(this->ike_sa, TRUE);
        this->public.task.build = _build_i_delete;
@@ -437,7 +751,7 @@ METHOD(ike_rekey_t, collide, bool,
        switch (other->get_type(other))
        {
                case TASK_IKE_DELETE:
-                       conclude_undetected_collision(this);
+                       conclude_collision(this, TRUE);
                        break;
                case TASK_IKE_REKEY:
                {
@@ -447,14 +761,22 @@ METHOD(ike_rekey_t, collide, bool,
                        {
                                DBG1(DBG_IKE, "colliding exchange did not result in an IKE_SA, "
                                         "ignore");
+                               if (this->collision == rekey)
+                               {
+                                       this->collision = NULL;
+                               }
                                break;
                        }
-                       if (this->collision)
+                       /* we keep track of the passive exchange in any case, if not
+                        * complete yet, this method might be called again later */
+                       this->collision = rekey;
+                       if (rekey->flags & IKE_REKEY_DONE)
                        {
-                               this->collision->public.task.destroy(&this->collision->public.task);
+                               this->flags |= IKE_REKEY_ADOPTED_PASSIVE;
+                               return TRUE;
                        }
-                       this->collision = rekey;
-                       return TRUE;
+                       DBG1(DBG_IKE, "colliding passive exchange is not yet complete");
+                       break;
                }
                default:
                        /* shouldn't happen */
@@ -481,10 +803,14 @@ static void cleanup(private_ike_rekey_t *this)
        cur_sa = charon->bus->get_sa(charon->bus);
        DESTROY_IF(this->new_sa);
        charon->bus->set_sa(charon->bus, cur_sa);
-       if (this->collision)
+       /* only destroy if the passive task was adopted, otherwise it is still
+        * queued and might get destroyed by the task manager */
+       if (this->collision &&
+               this->flags & IKE_REKEY_ADOPTED_PASSIVE)
        {
                this->collision->public.task.destroy(&this->collision->public.task);
        }
+       chunk_free(&this->link);
 }
 
 METHOD(task_t, migrate, void,
@@ -496,6 +822,7 @@ METHOD(task_t, migrate, void,
        this->new_sa = NULL;
        this->ike_init = NULL;
        this->ike_delete = NULL;
+       this->flags = 0;
 }
 
 METHOD(task_t, destroy, void,