From 366ac36a1f46607d7fee9c34fffa334799cd415f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 20 May 2021 11:43:39 +0200 Subject: [PATCH 01/47] ike: Don't reset optional CHILD_SA properties when migrating child-creating tasks These are set via methods, which are not called again after migration (e.g. when retrying due to INVALID_KE or when moving queued tasks), so we don't want to clear these values. --- src/libcharon/sa/ikev1/tasks/quick_mode.c | 1 - src/libcharon/sa/ikev2/tasks/child_create.c | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c index f75775357..6138efc97 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -1475,7 +1475,6 @@ METHOD(task_t, migrate, void, this->dh = NULL; this->spi_i = 0; this->spi_r = 0; - this->child = (child_sa_create_t){}; if (!this->initiator) { diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 64dd55167..5a6c1b431 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1876,7 +1876,6 @@ METHOD(task_t, migrate, void, this->ipcomp_received = IPCOMP_NONE; this->other_cpi = 0; this->established = FALSE; - this->child = (child_sa_create_t){}; } METHOD(task_t, destroy, void, From 7f30e1aea2648478ee5823e90c9a6b5be4f27d1e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 19 May 2021 14:16:20 +0200 Subject: [PATCH 02/47] ike-sa: Use a struct to pass optional arguments when initiating CHILD_SAs --- src/charon-nm/nm/nm_service.c | 2 +- .../backend/android_service.c | 2 +- src/libcharon/control/controller.c | 2 +- .../processing/jobs/adopt_children_job.c | 2 +- .../processing/jobs/initiate_tasks_job.c | 2 +- src/libcharon/sa/ike_sa.c | 18 ++++++++--------- src/libcharon/sa/ike_sa.h | 20 ++++++++++++++----- src/libcharon/sa/ikev1/task_manager_v1.c | 17 ++++++++++------ src/libcharon/sa/ikev1/tasks/quick_delete.c | 5 ++++- src/libcharon/sa/ikev2/task_manager_v2.c | 15 ++++++++------ src/libcharon/sa/ikev2/tasks/child_delete.c | 7 ++++--- src/libcharon/sa/ikev2/tasks/child_rekey.c | 7 +++---- src/libcharon/sa/task_manager.h | 8 +++----- src/libcharon/sa/trap_manager.c | 10 ++++++++-- .../tests/suites/test_child_create.c | 4 ++-- src/libcharon/tests/suites/test_childless.c | 10 +++++----- .../tests/suites/test_ike_mid_sync.c | 2 +- .../tests/utils/exchange_test_helper.c | 2 +- 18 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/charon-nm/nm/nm_service.c b/src/charon-nm/nm/nm_service.c index 2d93b2fae..6fae7bf9a 100644 --- a/src/charon-nm/nm/nm_service.c +++ b/src/charon-nm/nm/nm_service.c @@ -903,7 +903,7 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection, * Initiate */ child_cfg->get_ref(child_cfg); - if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) + if (ike_sa->initiate(ike_sa, child_cfg, NULL) != SUCCESS) { charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, ike_sa); diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c index 3e686ac83..a99d039f8 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c @@ -924,7 +924,7 @@ static job_requeue_t initiate(private_android_service_t *this) /* get an additional reference because initiate consumes one */ child_cfg->get_ref(child_cfg); - if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) + if (ike_sa->initiate(ike_sa, child_cfg, NULL) != SUCCESS) { DBG1(DBG_CFG, "failed to initiate tunnel"); charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, diff --git a/src/libcharon/control/controller.c b/src/libcharon/control/controller.c index 46b065e3f..cd25b28fe 100644 --- a/src/libcharon/control/controller.c +++ b/src/libcharon/control/controller.c @@ -473,7 +473,7 @@ METHOD(job_t, initiate_execute, job_requeue_t, } } - if (ike_sa->initiate(ike_sa, listener->child_cfg, 0, NULL, NULL) == SUCCESS) + if (ike_sa->initiate(ike_sa, listener->child_cfg, NULL) == SUCCESS) { if (!listener->logger.callback) { diff --git a/src/libcharon/processing/jobs/adopt_children_job.c b/src/libcharon/processing/jobs/adopt_children_job.c index e2a7f6b20..f969419fd 100644 --- a/src/libcharon/processing/jobs/adopt_children_job.c +++ b/src/libcharon/processing/jobs/adopt_children_job.c @@ -253,7 +253,7 @@ METHOD(job_t, execute, job_requeue_t, task->migrate(task, ike_sa); ike_sa->queue_task(ike_sa, task); } - if (ike_sa->initiate(ike_sa, NULL, 0, NULL, NULL) == DESTROY_ME) + if (ike_sa->initiate(ike_sa, NULL, NULL) == DESTROY_ME) { charon->ike_sa_manager->checkin_and_destroy( charon->ike_sa_manager, ike_sa); diff --git a/src/libcharon/processing/jobs/initiate_tasks_job.c b/src/libcharon/processing/jobs/initiate_tasks_job.c index 001e71fd1..442cd3163 100644 --- a/src/libcharon/processing/jobs/initiate_tasks_job.c +++ b/src/libcharon/processing/jobs/initiate_tasks_job.c @@ -55,7 +55,7 @@ METHOD(job_t, execute, job_requeue_t, this->ike_sa_id); if (ike_sa) { - if (ike_sa->initiate(ike_sa, NULL, 0, NULL, NULL) == DESTROY_ME) + if (ike_sa->initiate(ike_sa, NULL, NULL) == DESTROY_ME) { charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, ike_sa); diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index a891aff49..3ef5e020b 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -1531,8 +1531,7 @@ static void resolve_hosts(private_ike_sa_t *this) } METHOD(ike_sa_t, initiate, status_t, - private_ike_sa_t *this, child_cfg_t *child_cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr) + private_ike_sa_t *this, child_cfg_t *child_cfg, child_init_args_t *args) { bool defer_initiate = FALSE; @@ -1587,8 +1586,7 @@ METHOD(ike_sa_t, initiate, status_t, if (child_cfg) { /* normal IKE_SA with CHILD_SA */ - this->task_manager->queue_child(this->task_manager, child_cfg, reqid, - tsi, tsr); + this->task_manager->queue_child(this->task_manager, child_cfg, args); #ifdef ME if (this->peer_cfg->get_mediated_by(this->peer_cfg)) { @@ -1621,7 +1619,7 @@ METHOD(ike_sa_t, retry_initiate, status_t, if (this->retry_initiate_queued) { this->retry_initiate_queued = FALSE; - return initiate(this, NULL, 0, NULL, NULL); + return initiate(this, NULL, NULL); } return SUCCESS; } @@ -2077,13 +2075,15 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, } if (action == ACTION_RESTART) { + child_init_args_t args = { + .reqid = child_sa->get_reqid(child_sa), + }; child_cfg = child_sa->get_config(child_sa); DBG1(DBG_IKE, "restarting CHILD_SA %s", child_cfg->get_name(child_cfg)); other->task_manager->queue_child(other->task_manager, child_cfg->get_ref(child_cfg), - child_sa->get_reqid(child_sa), - NULL, NULL); + &args); } } enumerator->destroy(enumerator); @@ -2091,7 +2091,7 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, /* adopt any active or queued CHILD-creating tasks */ new->adopt_child_tasks(new, &this->public); - return new->initiate(new, NULL, 0, NULL, NULL); + return new->initiate(new, NULL, NULL); } METHOD(ike_sa_t, reestablish, status_t, @@ -2224,7 +2224,7 @@ METHOD(ike_sa_t, reestablish, status_t, #ifdef ME if (this->peer_cfg->is_mediation(this->peer_cfg)) { - status = new->initiate(new, NULL, 0, NULL, NULL); + status = new->initiate(new, NULL, NULL); } else #endif /* ME */ diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index ada8e89b6..37aac2d38 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -29,6 +29,7 @@ typedef enum ike_condition_t ike_condition_t; typedef enum ike_sa_state_t ike_sa_state_t; typedef enum statistic_t statistic_t; typedef enum update_hosts_flag_t update_hosts_flag_t; +typedef struct child_init_args_t child_init_args_t; typedef struct ike_sa_t ike_sa_t; #include @@ -369,6 +370,18 @@ enum ike_sa_state_t { */ extern enum_name_t *ike_sa_state_names; +/** + * Optional arguments passed when initiating a CHILD_SA. + */ +struct child_init_args_t { + /** Reqid to use for CHILD_SA, 0 to assign automatically */ + uint32_t reqid; + /** Optional source of triggering packet */ + traffic_selector_t *src; + /** Optional destination of triggering packet */ + traffic_selector_t *dst; +}; + /** * Class ike_sa_t representing an IKE_SA. * @@ -787,16 +800,13 @@ struct ike_sa_t { * to the CHILD_SA. * * @param child_cfg child config to create CHILD from - * @param reqid reqid to use for CHILD_SA, 0 assign uniquely - * @param tsi source of triggering packet - * @param tsr destination of triggering packet. + * @param args optional arguments for the CHILD initiation * @return * - SUCCESS if initialization started * - DESTROY_ME if initialization failed */ status_t (*initiate) (ike_sa_t *this, child_cfg_t *child_cfg, - uint32_t reqid, traffic_selector_t *tsi, - traffic_selector_t *tsr); + child_init_args_t *args); /** * Retry initiation of this IKE_SA after it got deferred previously. diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index 8df2862dd..cafeabb84 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -1685,7 +1685,7 @@ METHOD(task_manager_t, queue_ike_reauth, void, enumerator->destroy(enumerator); } - if (new->initiate(new, NULL, 0, NULL, NULL) != DESTROY_ME) + if (new->initiate(new, NULL, NULL) != DESTROY_ME) { charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); @@ -1732,14 +1732,19 @@ METHOD(task_manager_t, queue_mobike, void, } METHOD(task_manager_t, queue_child, void, - private_task_manager_t *this, child_cfg_t *cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr) + private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args) { quick_mode_t *task; - task = quick_mode_create(this->ike_sa, cfg, tsi, tsr); - task->use_reqid(task, reqid); - + if (args) + { + task = quick_mode_create(this->ike_sa, cfg, args->src, args->dst); + task->use_reqid(task, args->reqid); + } + else + { + task = quick_mode_create(this->ike_sa, cfg, NULL, NULL); + } queue_task(this, &task->task); } diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.c b/src/libcharon/sa/ikev1/tasks/quick_delete.c index 7dff247fb..ecfc8dda3 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_delete.c +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.c @@ -149,6 +149,9 @@ static status_t delete_child(private_quick_delete_t *this, if (remote_close) { + child_init_args_t args = { + .reqid = child_sa->get_reqid(child_sa), + }; child_cfg = child_sa->get_config(child_sa); child_cfg->get_ref(child_cfg); @@ -157,7 +160,7 @@ static status_t delete_child(private_quick_delete_t *this, case ACTION_RESTART: child_cfg->get_ref(child_cfg); status = this->ike_sa->initiate(this->ike_sa, child_cfg, - child_sa->get_reqid(child_sa), NULL, NULL); + &args); break; case ACTION_ROUTE: charon->traps->install(charon->traps, diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index f45d074e1..b359c67bb 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -1983,7 +1983,7 @@ static void trigger_mbb_reauth(private_task_manager_t *this) /* suspend online revocation checking until the SA is established */ new->set_condition(new, COND_ONLINE_VALIDATION_SUSPENDED, TRUE); - if (new->initiate(new, NULL, 0, NULL, NULL) != DESTROY_ME) + if (new->initiate(new, NULL, NULL) != DESTROY_ME) { new->queue_task(new, (task_t*)ike_verify_peer_cert_create(new)); new->queue_task(new, (task_t*)ike_reauth_complete_create(new, @@ -2102,15 +2102,18 @@ METHOD(task_manager_t, queue_dpd, void, } METHOD(task_manager_t, queue_child, void, - private_task_manager_t *this, child_cfg_t *cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr) + private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args) { child_create_t *task; - task = child_create_create(this->ike_sa, cfg, FALSE, tsi, tsr); - if (reqid) + if (args) { - task->use_reqid(task, reqid); + task = child_create_create(this->ike_sa, cfg, FALSE, args->src, args->dst); + task->use_reqid(task, args->reqid); + } + else + { + task = child_create_create(this->ike_sa, cfg, FALSE, NULL, NULL); } queue_task(this, &task->task); } diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index f3881ceaa..5ce251b5f 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -312,12 +312,13 @@ static void process_payloads(private_child_delete_t *this, message_t *message) */ static status_t destroy_and_reestablish(private_child_delete_t *this) { + child_init_args_t args = {}; enumerator_t *enumerator; entry_t *entry; child_sa_t *child_sa; child_cfg_t *child_cfg; protocol_id_t protocol; - uint32_t spi, reqid; + uint32_t spi; action_t action; status_t status = SUCCESS; time_t now, expire; @@ -362,9 +363,9 @@ static status_t destroy_and_reestablish(private_child_delete_t *this) /* no delay and no lifetime, destroy it immediately */ } spi = child_sa->get_spi(child_sa, TRUE); - reqid = child_sa->get_reqid(child_sa); child_cfg = child_sa->get_config(child_sa); child_cfg->get_ref(child_cfg); + args.reqid = child_sa->get_reqid(child_sa); action = child_sa->get_close_action(child_sa); this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); @@ -376,7 +377,7 @@ static status_t destroy_and_reestablish(private_child_delete_t *this) case ACTION_RESTART: child_cfg->get_ref(child_cfg); status = this->ike_sa->initiate(this->ike_sa, child_cfg, - reqid, NULL, NULL); + &args); break; case ACTION_ROUTE: charon->traps->install(charon->traps, diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index 37f7ea9bd..cf103e194 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -391,7 +391,7 @@ METHOD(task_t, process_i, status_t, if (message->get_notify(message, CHILD_SA_NOT_FOUND)) { child_cfg_t *child_cfg; - uint32_t reqid; + child_init_args_t args = {}; if (this->collision && this->collision->get_type(this->collision) == TASK_CHILD_DELETE) @@ -406,15 +406,14 @@ METHOD(task_t, process_i, status_t, * that (we could go by name, but that might be tricky e.g. due to * narrowing) */ spi = this->child_sa->get_spi(this->child_sa, TRUE); - reqid = this->child_sa->get_reqid(this->child_sa); protocol = this->child_sa->get_protocol(this->child_sa); child_cfg = this->child_sa->get_config(this->child_sa); child_cfg->get_ref(child_cfg); + args.reqid = this->child_sa->get_reqid(this->child_sa); charon->bus->child_updown(charon->bus, this->child_sa, FALSE); this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); return this->ike_sa->initiate(this->ike_sa, - child_cfg->get_ref(child_cfg), reqid, - NULL, NULL); + child_cfg->get_ref(child_cfg), &args); } if (this->child_create->task.process(&this->child_create->task, diff --git a/src/libcharon/sa/task_manager.h b/src/libcharon/sa/task_manager.h index f9225c56a..8c6496adf 100644 --- a/src/libcharon/sa/task_manager.h +++ b/src/libcharon/sa/task_manager.h @@ -172,12 +172,10 @@ struct task_manager_t { * Queue CHILD_SA establishing tasks. * * @param cfg CHILD_SA config to establish - * @param reqid reqid to use for CHILD_SA - * @param tsi initiator traffic selector, if packet-triggered - * @param tsr responder traffic selector, if packet-triggered + * @param args optional arguments for the initiation */ - void (*queue_child)(task_manager_t *this, child_cfg_t *cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr); + void (*queue_child)(task_manager_t *this, child_cfg_t *cfg, + child_init_args_t *args); /** * Queue CHILD_SA rekeying tasks. diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index f9f78acab..fc78eb441 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -542,17 +542,23 @@ METHOD(trap_manager_t, acquire, void, if (ike_sa) { + child_init_args_t args = { + .reqid = reqid, + .src = src, + .dst = dst, + }; + if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1) { /* in IKEv1, don't prepend the acquiring packet TS, as we only * have a single TS that we can establish in a Quick Mode. */ - src = dst = NULL; + args.src = args.dst = NULL; } this->mutex->lock(this->mutex); acquire->ike_sa = ike_sa; this->mutex->unlock(this->mutex); - if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME) + if (ike_sa->initiate(ike_sa, child, &args) != DESTROY_ME) { charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); } diff --git a/src/libcharon/tests/suites/test_child_create.c b/src/libcharon/tests/suites/test_child_create.c index 20a47f6bf..4f6733a92 100644 --- a/src/libcharon/tests/suites/test_child_create.c +++ b/src/libcharon/tests/suites/test_child_create.c @@ -44,7 +44,7 @@ START_TEST(test_collision_ike_rekey) traffic_selector_create_dynamic(0, 0, 65535)); child_cfg->add_traffic_selector(child_cfg, FALSE, traffic_selector_create_dynamic(0, 0, 65535)); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); assert_child_sa_count(a, 1); assert_hook(); @@ -81,7 +81,7 @@ START_TEST(test_collision_ike_rekey) ck_assert(!exchange_test_helper->sender->dequeue(exchange_test_helper->sender)); assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE); assert_num_tasks(a, 1, TASK_QUEUE_QUEUED); - call_ikesa(a, initiate, NULL, 0, NULL, NULL); + call_ikesa(a, initiate, NULL, NULL); assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE); assert_sa_idle(b); diff --git a/src/libcharon/tests/suites/test_childless.c b/src/libcharon/tests/suites/test_childless.c index 6ac02aad8..931abbb5e 100644 --- a/src/libcharon/tests/suites/test_childless.c +++ b/src/libcharon/tests/suites/test_childless.c @@ -44,7 +44,7 @@ START_TEST(test_regular) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); @@ -115,7 +115,7 @@ START_TEST(test_regular_manual) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, NULL, 0, NULL, NULL); + call_ikesa(a, initiate, NULL, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); @@ -144,7 +144,7 @@ START_TEST(test_regular_manual) assert_sa_idle(a); assert_sa_idle(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* CREATE_CHILD_SA { SA, Ni, KEi, TSi, TSr } --> */ assert_hook_called(child_updown); @@ -192,7 +192,7 @@ START_TEST(test_failure_init) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); @@ -233,7 +233,7 @@ START_TEST(test_failure_resp) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); diff --git a/src/libcharon/tests/suites/test_ike_mid_sync.c b/src/libcharon/tests/suites/test_ike_mid_sync.c index 3776f39e9..cfebf4b5a 100644 --- a/src/libcharon/tests/suites/test_ike_mid_sync.c +++ b/src/libcharon/tests/suites/test_ike_mid_sync.c @@ -493,7 +493,7 @@ START_TEST(test_active) charon->bus->remove_listener(charon->bus, &mid.listener); /* the active task was queued again */ - call_ikesa(a, initiate, NULL, 0, NULL, NULL); + call_ikesa(a, initiate, NULL, NULL); exchange_test_helper->process_message(exchange_test_helper, b, NULL); exchange_test_helper->process_message(exchange_test_helper, a, NULL); send_dpd(b, a); diff --git a/src/libcharon/tests/utils/exchange_test_helper.c b/src/libcharon/tests/utils/exchange_test_helper.c index be55a1387..1448504ab 100644 --- a/src/libcharon/tests/utils/exchange_test_helper.c +++ b/src/libcharon/tests/utils/exchange_test_helper.c @@ -273,7 +273,7 @@ METHOD(exchange_test_helper_t, establish_sa, void, id_i = sa_i->get_id(sa_i); id_r = sa_r->get_id(sa_r); - call_ikesa(sa_i, initiate, child_i, 0, NULL, NULL); + call_ikesa(sa_i, initiate, child_i, NULL); /* IKE_SA_INIT --> */ id_r->set_initiator_spi(id_r, id_i->get_initiator_spi(id_i)); From 9983326b20ee18806668b589898d81e53d7f1a7c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 28 May 2021 13:36:04 +0200 Subject: [PATCH 03/47] kernel-netlink: Read protocol of acquire not from template If a policy with IPComp template triggers an acquire, we get two, one for an IPComp, one for ESP/AH SA. However, the triggering template of the trap policy (where we get the reqid from), will be the same in both acquires, IPComp, which we ignore, so no acquire was actually forwarded. --- src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 339ce2a59..32b685345 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -899,9 +899,10 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, size_t rtasize; traffic_selector_t *src_ts, *dst_ts; uint32_t reqid = 0; - int proto = 0; + uint8_t proto; acquire = NLMSG_DATA(hdr); + proto = acquire->id.proto; rta = XFRM_RTA(hdr, struct xfrm_user_acquire); rtasize = XFRM_PAYLOAD(hdr, struct xfrm_user_acquire); @@ -916,7 +917,6 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, struct xfrm_user_tmpl* tmpl; tmpl = (struct xfrm_user_tmpl*)RTA_DATA(rta); reqid = tmpl->reqid; - proto = tmpl->id.proto; } rta = RTA_NEXT(rta, rtasize); } From 3b699c720f16ee785913ca201d71427159dc7c80 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 16 Dec 2021 13:50:38 +0100 Subject: [PATCH 04/47] kernel-listener: Use a struct to pass data from acquires --- src/charon-tkm/src/ees/ees_callbacks.c | 4 ++- src/libcharon/kernel/kernel_handler.c | 9 +++-- src/libcharon/kernel/kernel_interface.c | 5 ++- src/libcharon/kernel/kernel_interface.h | 5 ++- src/libcharon/kernel/kernel_listener.h | 16 +++++++-- .../kernel_netlink/kernel_netlink_ipsec.c | 11 +++--- .../plugins/kernel_pfkey/kernel_pfkey_ipsec.c | 14 +++++--- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 11 ++++-- src/libcharon/processing/jobs/acquire_job.c | 34 +++++++++---------- src/libcharon/processing/jobs/acquire_job.h | 11 +++--- src/libcharon/sa/trap_manager.c | 17 +++++----- src/libcharon/sa/trap_manager.h | 7 ++-- 12 files changed, 81 insertions(+), 63 deletions(-) diff --git a/src/charon-tkm/src/ees/ees_callbacks.c b/src/charon-tkm/src/ees/ees_callbacks.c index 863f618bc..304f4cac3 100644 --- a/src/charon-tkm/src/ees/ees_callbacks.c +++ b/src/charon-tkm/src/ees/ees_callbacks.c @@ -24,8 +24,10 @@ void charon_esa_acquire(result_type *res, const sp_id_type sp_id) { + kernel_acquire_data_t data = {}; + DBG1(DBG_KNL, "ees: acquire received for reqid %u", sp_id); - charon->kernel->acquire(charon->kernel, sp_id, NULL, NULL); + charon->kernel->acquire(charon->kernel, sp_id, &data); *res = TKM_OK; } diff --git a/src/libcharon/kernel/kernel_handler.c b/src/libcharon/kernel/kernel_handler.c index 006304d5e..4f467efee 100644 --- a/src/libcharon/kernel/kernel_handler.c +++ b/src/libcharon/kernel/kernel_handler.c @@ -53,20 +53,19 @@ static inline protocol_id_t proto_ip2ike(uint8_t protocol) } METHOD(kernel_listener_t, acquire, bool, - private_kernel_handler_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts) + private_kernel_handler_t *this, uint32_t reqid, kernel_acquire_data_t *data) { - if (src_ts && dst_ts) + if (data->src && data->dst) { DBG1(DBG_KNL, "creating acquire job for policy %R === %R with " - "reqid {%u}", src_ts, dst_ts, reqid); + "reqid {%u}", data->src, data->dst, reqid); } else { DBG1(DBG_KNL, "creating acquire job for policy with reqid {%u}", reqid); } lib->processor->queue_job(lib->processor, - (job_t*)acquire_job_create(reqid, src_ts, dst_ts)); + (job_t*)acquire_job_create(reqid, data)); return TRUE; } diff --git a/src/libcharon/kernel/kernel_interface.c b/src/libcharon/kernel/kernel_interface.c index 300796f8e..8f6c21b61 100644 --- a/src/libcharon/kernel/kernel_interface.c +++ b/src/libcharon/kernel/kernel_interface.c @@ -827,7 +827,7 @@ METHOD(kernel_interface_t, remove_listener, void, METHOD(kernel_interface_t, acquire, void, private_kernel_interface_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts) + kernel_acquire_data_t *data) { kernel_listener_t *listener; enumerator_t *enumerator; @@ -835,8 +835,7 @@ METHOD(kernel_interface_t, acquire, void, enumerator = this->listeners->create_enumerator(this->listeners); while (enumerator->enumerate(enumerator, &listener)) { - if (listener->acquire && - !listener->acquire(listener, reqid, src_ts, dst_ts)) + if (listener->acquire && !listener->acquire(listener, reqid, data)) { this->listeners->remove_at(this->listeners, enumerator); } diff --git a/src/libcharon/kernel/kernel_interface.h b/src/libcharon/kernel/kernel_interface.h index 50c1cac53..dbc863736 100644 --- a/src/libcharon/kernel/kernel_interface.h +++ b/src/libcharon/kernel/kernel_interface.h @@ -513,11 +513,10 @@ struct kernel_interface_t { * Raise an acquire event. * * @param reqid reqid of the policy to acquire - * @param src_ts source traffic selector - * @param dst_ts destination traffic selector + * @param data data from the acquire */ void (*acquire)(kernel_interface_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts); + kernel_acquire_data_t *data); /** * Raise an expire event. diff --git a/src/libcharon/kernel/kernel_listener.h b/src/libcharon/kernel/kernel_listener.h index 71868511f..6534921c2 100644 --- a/src/libcharon/kernel/kernel_listener.h +++ b/src/libcharon/kernel/kernel_listener.h @@ -22,12 +22,23 @@ #define KERNEL_LISTENER_H_ typedef struct kernel_listener_t kernel_listener_t; +typedef struct kernel_acquire_data_t kernel_acquire_data_t; #include #include #include #include +/** + * Data received with a kernel's acquire, has to be cloned/copied by listener. + */ +struct kernel_acquire_data_t { + /** Optional source of the triggering packet */ + traffic_selector_t *src; + /** Optional destination of the triggering packet */ + traffic_selector_t *dst; +}; + /** * Interface for components interested in kernel events. * @@ -39,12 +50,11 @@ struct kernel_listener_t { * Hook called if an acquire event for a policy is received. * * @param reqid reqid of the policy to acquire - * @param src_ts source traffic selector - * @param dst_ts destination traffic selector + * @param data data from the acquire * @return TRUE to remain registered, FALSE to unregister */ bool (*acquire)(kernel_listener_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts); + kernel_acquire_data_t *data); /** * Hook called if an expire event for an IPsec SA is received. diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 32b685345..c3c6d6035 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -897,7 +897,7 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, struct xfrm_user_acquire *acquire; struct rtattr *rta; size_t rtasize; - traffic_selector_t *src_ts, *dst_ts; + kernel_acquire_data_t data = {}; uint32_t reqid = 0; uint8_t proto; @@ -930,10 +930,13 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, /* acquire for AH/ESP only, not for IPCOMP */ return; } - src_ts = selector2ts(&acquire->sel, TRUE); - dst_ts = selector2ts(&acquire->sel, FALSE); + data.src = selector2ts(&acquire->sel, TRUE); + data.dst = selector2ts(&acquire->sel, FALSE); - charon->kernel->acquire(charon->kernel, reqid, src_ts, dst_ts); + charon->kernel->acquire(charon->kernel, reqid, &data); + + DESTROY_IF(data.src); + DESTROY_IF(data.dst); } /** diff --git a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index f32c4f83d..3243d1e94 100644 --- a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -1318,8 +1318,8 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* msg) { pfkey_msg_t response; + kernel_acquire_data_t data = {}; uint32_t index, reqid = 0; - traffic_selector_t *src_ts, *dst_ts; policy_entry_t *policy; policy_sa_t *sa; @@ -1363,10 +1363,16 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, this->mutex->unlock(this->mutex); } - src_ts = sadb_address2ts(response.src); - dst_ts = sadb_address2ts(response.dst); + if (reqid) + { + data.src = sadb_address2ts(response.src); + data.dst = sadb_address2ts(response.dst); - charon->kernel->acquire(charon->kernel, reqid, src_ts, dst_ts); + charon->kernel->acquire(charon->kernel, reqid, &data); + + data.src->destroy(data.src); + data.dst->destroy(data.dst); + } } /** diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 19d4f3ef4..36f5a97fc 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1634,6 +1634,7 @@ static u_int hash_trap(trap_t *trap) static void acquire(private_kernel_wfp_ipsec_t *this, UINT64 filter_id, traffic_selector_t *src, traffic_selector_t *dst) { + kernel_acquire_data_t data = {}; uint32_t reqid = 0; trap_t *trap, key = { .filter_id = filter_id, @@ -1649,9 +1650,13 @@ static void acquire(private_kernel_wfp_ipsec_t *this, UINT64 filter_id, if (reqid) { - src = src ? src->clone(src) : NULL; - dst = dst ? dst->clone(dst) : NULL; - charon->kernel->acquire(charon->kernel, reqid, src, dst); + data.src = src ? src->clone(src) : NULL; + data.dst = dst ? dst->clone(dst) : NULL; + + charon->kernel->acquire(charon->kernel, reqid, &data); + + DESTROY_IF(data.src); + DESTROY_IF(data.dst); } } diff --git a/src/libcharon/processing/jobs/acquire_job.c b/src/libcharon/processing/jobs/acquire_job.c index c92c03f8f..0f06fcfcd 100644 --- a/src/libcharon/processing/jobs/acquire_job.c +++ b/src/libcharon/processing/jobs/acquire_job.c @@ -30,34 +30,28 @@ struct private_acquire_job_t { acquire_job_t public; /** - * reqid of the child to rekey + * reqid of the triggered policy */ uint32_t reqid; /** - * acquired source traffic selector + * Data from the acquire */ - traffic_selector_t *src_ts; - - /** - * acquired destination traffic selector - */ - traffic_selector_t *dst_ts; + kernel_acquire_data_t data; }; METHOD(job_t, destroy, void, private_acquire_job_t *this) { - DESTROY_IF(this->src_ts); - DESTROY_IF(this->dst_ts); + DESTROY_IF(this->data.src); + DESTROY_IF(this->data.dst); free(this); } METHOD(job_t, execute, job_requeue_t, private_acquire_job_t *this) { - charon->traps->acquire(charon->traps, this->reqid, - this->src_ts, this->dst_ts); + charon->traps->acquire(charon->traps, this->reqid, &this->data); return JOB_REQUEUE_NONE; } @@ -70,9 +64,7 @@ METHOD(job_t, get_priority, job_priority_t, /* * Described in header */ -acquire_job_t *acquire_job_create(uint32_t reqid, - traffic_selector_t *src_ts, - traffic_selector_t *dst_ts) +acquire_job_t *acquire_job_create(uint32_t reqid, kernel_acquire_data_t *data) { private_acquire_job_t *this; @@ -85,10 +77,18 @@ acquire_job_t *acquire_job_create(uint32_t reqid, }, }, .reqid = reqid, - .src_ts = src_ts, - .dst_ts = dst_ts, + .data = *data, ); + if (this->data.src) + { + this->data.src = this->data.src->clone(this->data.src); + } + if (this->data.dst) + { + this->data.dst = this->data.dst->clone(this->data.dst); + } + return &this->public; } diff --git a/src/libcharon/processing/jobs/acquire_job.h b/src/libcharon/processing/jobs/acquire_job.h index d45f72b46..f6b472397 100644 --- a/src/libcharon/processing/jobs/acquire_job.h +++ b/src/libcharon/processing/jobs/acquire_job.h @@ -24,7 +24,7 @@ typedef struct acquire_job_t acquire_job_t; #include -#include +#include #include /** @@ -42,13 +42,10 @@ struct acquire_job_t { /** * Creates a job of type ACQUIRE. * - * @param reqid reqid of the trapped CHILD_SA to acquire - * @param src_ts source traffic selector - * @param dst_ts destination traffic selector + * @param reqid reqid of the triggered policy + * @param data data from the acquire * @return acquire_job_t object */ -acquire_job_t *acquire_job_create(uint32_t reqid, - traffic_selector_t *src_ts, - traffic_selector_t *dst_ts); +acquire_job_t *acquire_job_create(uint32_t reqid, kernel_acquire_data_t *data); #endif /** REKEY_CHILD_SA_JOB_H_ @}*/ diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index fc78eb441..b00f7edc9 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -423,8 +423,7 @@ METHOD(trap_manager_t, create_enumerator, enumerator_t*, } METHOD(trap_manager_t, acquire, void, - private_trap_manager_t *this, uint32_t reqid, - traffic_selector_t *src, traffic_selector_t *dst) + private_trap_manager_t *this, uint32_t reqid, kernel_acquire_data_t *data) { enumerator_t *enumerator; entry_t *entry, *found = NULL; @@ -454,7 +453,6 @@ METHOD(trap_manager_t, acquire, void, this->lock->unlock(this->lock); return; } - reqid = found->child_sa->get_reqid(found->child_sa); wildcard = found->wildcard; this->mutex->lock(this->mutex); @@ -463,7 +461,7 @@ METHOD(trap_manager_t, acquire, void, * with the same peer */ uint8_t mask; - dst->to_subnet(dst, &host, &mask); + data->dst->to_subnet(data->dst, &host, &mask); if (this->acquires->find_first(this->acquires, acquire_by_dst, (void**)&acquire, host)) { @@ -497,7 +495,8 @@ METHOD(trap_manager_t, acquire, void, this->mutex->unlock(this->mutex); if (ignore) { - DBG1(DBG_CFG, "ignoring acquire, connection attempt pending"); + DBG1(DBG_CFG, "ignoring acquire for reqid %u, connection attempt " + "pending", reqid); this->lock->unlock(this->lock); return; } @@ -521,12 +520,12 @@ METHOD(trap_manager_t, acquire, void, ike_cfg = ike_sa->get_ike_cfg(ike_sa); port = ike_cfg->get_other_port(ike_cfg); - dst->to_subnet(dst, &host, &mask); + data->dst->to_subnet(data->dst, &host, &mask); host->set_port(host, port); ike_sa->set_other_host(ike_sa, host); port = ike_cfg->get_my_port(ike_cfg); - src->to_subnet(src, &host, &mask); + data->src->to_subnet(data->src, &host, &mask); host->set_port(host, port); ike_sa->set_my_host(ike_sa, host); @@ -544,8 +543,8 @@ METHOD(trap_manager_t, acquire, void, { child_init_args_t args = { .reqid = reqid, - .src = src, - .dst = dst, + .src = data->src, + .dst = data->dst, }; if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1) diff --git a/src/libcharon/sa/trap_manager.h b/src/libcharon/sa/trap_manager.h index f226798fc..d9b4906f7 100644 --- a/src/libcharon/sa/trap_manager.h +++ b/src/libcharon/sa/trap_manager.h @@ -64,12 +64,11 @@ struct trap_manager_t { /** * Acquire an SA triggered by an installed trap. * - * @param reqid reqid of the triggering CHILD_SA - * @param src source of the triggering packet - * @param dst destination of the triggering packet + * @param reqid reqid of the triggered policy + * @param data data from the acquire */ void (*acquire)(trap_manager_t *this, uint32_t reqid, - traffic_selector_t *src, traffic_selector_t *dst); + kernel_acquire_data_t *data); /** * Clear any installed trap. From 511033b3da75b2b35a23d6c3ecb3bfaffd691f91 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 20 May 2021 17:55:54 +0200 Subject: [PATCH 05/47] child-create: Just abort CREATE_CHILD_SA request if nonce creation fails No need to send an illegal request with just an error notify. --- src/libcharon/sa/ikev2/tasks/child_create.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 5a6c1b431..399aa1f81 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1094,8 +1094,7 @@ METHOD(task_t, build_i, status_t, case CREATE_CHILD_SA: if (!generate_nonce(this)) { - message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, - chunk_empty); + message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED); return SUCCESS; } if (!this->retry && this->dh_group == MODP_NONE) From a5430e1601fa42147ffb725c2dea3fc5e2b982f1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 20 May 2021 18:11:58 +0200 Subject: [PATCH 06/47] child-create: Abort initiating a duplicate CHILD_SA This could happen if an acquire is triggered while we respond to a CREATE_CHILD_SA request from the peer, or if an acquire is triggered while an IKE_SA (with its existing CHILD_SAs) is reestablished (also with break-before-make reauthentication). Also catches multiple manual initiations. Note that this ignores the traffic selectors from acquires (narrowing to them seems rare in practice anyway). Duplicates can still get created if e.g. both peers initiate them concurrently. --- src/libcharon/sa/ikev2/tasks/child_create.c | 71 +++++++++++++++++++ .../tests/suites/test_child_create.c | 47 ++++++++++++ 2 files changed, 118 insertions(+) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 399aa1f81..dd693c700 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1079,6 +1079,67 @@ static status_t defer_child_sa(private_child_create_t *this) return NOT_SUPPORTED; } +/** + * Compare two CHILD_SA objects for equality + */ +static bool child_sa_equals(child_sa_t *a, child_sa_t *b) +{ + child_cfg_t *cfg = a->get_config(a); + return cfg->equals(cfg, b->get_config(b)) && + /* reqids are allocated based on the final TS, so we can only compare + * them if they are static (i.e. both have them) */ + (!a->get_reqid(a) || !b->get_reqid(b) || + a->get_reqid(a) == b->get_reqid(b)) && + a->get_mark(a, TRUE).value == b->get_mark(b, TRUE).value && + a->get_mark(a, FALSE).value == b->get_mark(b, FALSE).value && + a->get_if_id(a, TRUE) == b->get_if_id(b, TRUE) && + a->get_if_id(a, FALSE) == b->get_if_id(b, FALSE); +} + +/** + * Check if there is a duplicate CHILD_SA already established and we can abort + * initiating this one + */ +static bool check_for_duplicate(private_child_create_t *this) +{ + enumerator_t *enumerator; + child_sa_t *child_sa, *found = NULL; + + enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); + while (enumerator->enumerate(enumerator, (void**)&child_sa)) + { + if (child_sa->get_state(child_sa) == CHILD_INSTALLED && + child_sa_equals(child_sa, this->child_sa)) + { + found = child_sa; + break; + } + } + enumerator->destroy(enumerator); + + if (found) + { + linked_list_t *my_ts, *other_ts; + + my_ts = linked_list_create_from_enumerator( + found->create_ts_enumerator(found, TRUE)); + other_ts = linked_list_create_from_enumerator( + found->create_ts_enumerator(found, FALSE)); + + DBG1(DBG_IKE, "not establishing CHILD_SA %s{%d} due to existing " + "duplicate {%d} with SPIs %.8x_i %.8x_o and TS %#R === %#R", + this->child_sa->get_name(this->child_sa), + this->child_sa->get_unique_id(this->child_sa), + found->get_unique_id(found), + ntohl(found->get_spi(found, TRUE)), + ntohl(found->get_spi(found, FALSE)), my_ts, other_ts); + + my_ts->destroy(my_ts); + other_ts->destroy(other_ts); + } + return found; +} + METHOD(task_t, build_i, status_t, private_child_create_t *this, message_t *message) { @@ -1180,6 +1241,16 @@ METHOD(task_t, build_i, status_t, this->ike_sa->get_other_host(this->ike_sa), this->config, &this->child); + /* check this after creating the object so that its destruction is detected + * by controller and trap manager */ + if (!this->rekey && + message->get_exchange_type(message) == CREATE_CHILD_SA && + check_for_duplicate(this)) + { + message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED); + return SUCCESS; + } + if (this->child.reqid) { DBG0(DBG_IKE, "establishing CHILD_SA %s{%d} reqid %d", diff --git a/src/libcharon/tests/suites/test_child_create.c b/src/libcharon/tests/suites/test_child_create.c index 4f6733a92..07ce07ae9 100644 --- a/src/libcharon/tests/suites/test_child_create.c +++ b/src/libcharon/tests/suites/test_child_create.c @@ -21,6 +21,47 @@ #include #include +/** + * The peers try to create a new CHILD_SA that looks exactly the same + * as the existing one, so it won't get initiated. + */ +START_TEST(test_duplicate) +{ + child_cfg_t *child_cfg; + child_cfg_create_t child = { + .mode = MODE_TUNNEL, + }; + ike_sa_t *a, *b; + + exchange_test_helper->establish_sa(exchange_test_helper, + &a, &b, NULL); + + assert_no_jobs_scheduled(); + assert_hook_not_called(child_updown); + assert_hook_not_called(message); + child_cfg = child_cfg_create("child", &child); + child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP)); + child_cfg->add_traffic_selector(child_cfg, TRUE, + traffic_selector_create_dynamic(0, 0, 65535)); + child_cfg->add_traffic_selector(child_cfg, FALSE, + traffic_selector_create_dynamic(0, 0, 65535)); + child_cfg->get_ref(child_cfg); + call_ikesa(a, initiate, child_cfg, NULL); + assert_child_sa_count(a, 1); + assert_sa_idle(a); + + call_ikesa(b, initiate, child_cfg, NULL); + assert_child_sa_count(b, 1); + assert_sa_idle(b); + assert_hook(); + assert_hook(); + assert_scheduler(); + + call_ikesa(a, destroy); + call_ikesa(b, destroy); +} +END_TEST + /** * One of the peers tries to create a new CHILD_SA while the other concurrently * started to rekey the IKE_SA. TEMPORARY_FAILURE should be returned on both @@ -31,6 +72,8 @@ START_TEST(test_collision_ike_rekey) child_cfg_t *child_cfg; child_cfg_create_t child = { .mode = MODE_TUNNEL, + /* make sure this is not a duplicate of the initial CHILD_SA */ + .mark_out = { .value = 42, .mask = 0xffffffff }, }; ike_sa_t *a, *b; @@ -98,6 +141,10 @@ Suite *child_create_suite_create() s = suite_create("child create"); + tc = tcase_create("initiate duplicate"); + tcase_add_test(tc, test_duplicate); + suite_add_tcase(s, tc); + tc = tcase_create("collisions ike rekey"); tcase_add_test(tc, test_collision_ike_rekey); suite_add_tcase(s, tc); From 927103ece4fc7898cf75b272d316ba237e8585eb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 17 Dec 2021 14:25:44 +0100 Subject: [PATCH 07/47] enum: Allow specifying the name used when none of the flags are set --- src/libcharon/sa/child_sa.c | 1 + src/libstrongswan/tests/suites/test_enum.c | 8 ++++---- src/libstrongswan/utils/enum.c | 11 ++++++----- src/libstrongswan/utils/enum.h | 5 +++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 9b06ebc3c..f87683138 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -42,6 +42,7 @@ ENUM(child_sa_state_names, CHILD_CREATED, CHILD_DESTROYING, ); ENUM_FLAGS(child_sa_outbound_state_names, CHILD_OUTBOUND_REGISTERED, CHILD_OUTBOUND_POLICIES, + "NONE", "REGISTERED", "SA", "POLICIES", diff --git a/src/libstrongswan/tests/suites/test_enum.c b/src/libstrongswan/tests/suites/test_enum.c index 06b2c3ff3..613043e6d 100644 --- a/src/libstrongswan/tests/suites/test_enum.c +++ b/src/libstrongswan/tests/suites/test_enum.c @@ -70,15 +70,15 @@ enum { } test_enum_flags; ENUM_FLAGS(test_enum_flags_names, FLAG1, FLAG5, - "FLAG1", "FLAG2", "FLAG3", "FLAG4", "FLAG5"); + "(unset)", "FLAG1", "FLAG2", "FLAG3", "FLAG4", "FLAG5"); ENUM_FLAGS(test_enum_flags_incomplete_names, FLAG3, FLAG4, - "FLAG3", "FLAG4"); + "(unset)", "FLAG3", "FLAG4"); ENUM_FLAGS(test_enum_flags_null_names, FLAG1, FLAG4, - "FLAG1", NULL, "FLAG3", NULL); + "(unset)", "FLAG1", NULL, "FLAG3", NULL); -ENUM_FLAGS(test_enum_flags_overflow_names, FLAG1, FLAG12, +ENUM_FLAGS(test_enum_flags_overflow_names, FLAG1, FLAG12, "(unset)", "OVERFLOWFLAGLONGNAME1", "OVERFLOWFLAGLONGNAME2", "OVERFLOWFLAGLONGNAME3", "OVERFLOWFLAGLONGNAME4", "OVERFLOWFLAGLONGNAME5", "OVERFLOWFLAGLONGNAME6", "OVERFLOWFLAGLONGNAME7", "OVERFLOWFLAGLONGNAME8", "OVERFLOWFLAGLONGNAME9", diff --git a/src/libstrongswan/utils/enum.c b/src/libstrongswan/utils/enum.c index 25182f93d..e62c75f91 100644 --- a/src/libstrongswan/utils/enum.c +++ b/src/libstrongswan/utils/enum.c @@ -66,16 +66,17 @@ bool enum_from_name_as_int(enum_name_t *e, const char *name, int *val) /** * Get the position of a flag name using offset calculation */ -static int find_flag_pos(u_int val, u_int first) +static int find_flag_pos(u_int first, u_int val) { int offset = 0; - while (val != 0x01) + while (first != 0x01) { - val = val >> 1; + first = first >> 1; offset++; } - return first - offset; + /* skip the first name as that's used if no flag is set */ + return 1 + val - offset; } /** @@ -95,7 +96,7 @@ char *enum_flags_to_string(enum_name_t *e, u_int val, char *buf, size_t len) return buf; } - if (snprintf(buf, len, "(unset)") >= len) + if (snprintf(buf, len, e->names[0]) >= len) { return NULL; } diff --git a/src/libstrongswan/utils/enum.h b/src/libstrongswan/utils/enum.h index 72dea495a..1e723a220 100644 --- a/src/libstrongswan/utils/enum.h +++ b/src/libstrongswan/utils/enum.h @@ -130,13 +130,14 @@ struct enum_name_t { * @param name name of the enum_name list * @param first enum value of the first enum string * @param last enum value of the last enum string + * @param unset name used if no flags are set * @param ... a list of strings */ -#define ENUM_FLAGS(name, first, last, ...) \ +#define ENUM_FLAGS(name, first, last, unset, ...) \ static enum_name_t name##last = {first, last + \ BUILD_ASSERT((__builtin_ffs(last)-__builtin_ffs(first)+1) == \ countof(((char*[]){__VA_ARGS__}))), \ - ENUM_FLAG_MAGIC, { __VA_ARGS__ }}; ENUM_END(name, last) + ENUM_FLAG_MAGIC, { unset, __VA_ARGS__ }}; ENUM_END(name, last) /** * Convert a enum value to its string representation. From 4f4d4021b4ed6a72f6cdecde7b58a2b5e7bfce2d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 16 Dec 2021 16:34:37 +0100 Subject: [PATCH 08/47] ike: Treat action_t as flags so 'start' and 'trap' can be combined While combining the actions could cause duplicates (while the SA is initiated, traffic might trigger the trap and the initiation of another CHILD_SA), the previous commit should avoid most duplicates. If reuse_ikesa is disabled, duplicates can't be prevented, though. --- src/charon-nm/nm/nm_service.c | 4 +- .../backend/android_service.c | 4 +- src/libcharon/config/child_cfg.c | 8 +- src/libcharon/config/child_cfg.h | 10 +- src/libcharon/plugins/stroke/stroke_config.c | 4 +- src/libcharon/plugins/vici/vici_config.c | 200 +++++++++--------- .../processing/jobs/start_action_job.c | 52 ++--- src/libcharon/sa/ike_sa.c | 21 +- src/libcharon/sa/ikev1/tasks/quick_delete.c | 26 ++- src/libcharon/sa/ikev2/tasks/child_delete.c | 22 +- 10 files changed, 175 insertions(+), 176 deletions(-) diff --git a/src/charon-nm/nm/nm_service.c b/src/charon-nm/nm/nm_service.c index 6fae7bf9a..09107a76b 100644 --- a/src/charon-nm/nm/nm_service.c +++ b/src/charon-nm/nm/nm_service.c @@ -635,8 +635,8 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection, }, }, .mode = MODE_TUNNEL, - .dpd_action = ACTION_RESTART, - .close_action = ACTION_RESTART, + .dpd_action = ACTION_START, + .close_action = ACTION_START, }; /** diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c index a99d039f8..77decfd80 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c @@ -801,8 +801,8 @@ static job_requeue_t initiate(private_android_service_t *this) }, }, .mode = MODE_TUNNEL, - .dpd_action = ACTION_RESTART, - .close_action = ACTION_RESTART, + .dpd_action = ACTION_START, + .close_action = ACTION_START, }; char *type, *remote_id; diff --git a/src/libcharon/config/child_cfg.c b/src/libcharon/config/child_cfg.c index d4fb37cdc..9ea6186c3 100644 --- a/src/libcharon/config/child_cfg.c +++ b/src/libcharon/config/child_cfg.c @@ -22,10 +22,10 @@ #include -ENUM(action_names, ACTION_NONE, ACTION_RESTART, - "clear", - "hold", - "restart", +ENUM_FLAGS(action_names, ACTION_TRAP, ACTION_START, + "none", + "trap", + "start", ); /** Default replay window size, if not set using charon.replay_window */ diff --git a/src/libcharon/config/child_cfg.h b/src/libcharon/config/child_cfg.h index b176d67bd..4de978826 100644 --- a/src/libcharon/config/child_cfg.h +++ b/src/libcharon/config/child_cfg.h @@ -40,11 +40,11 @@ typedef struct child_cfg_create_t child_cfg_create_t; */ enum action_t { /** No action */ - ACTION_NONE, - /** Route config to establish or reestablish on demand */ - ACTION_ROUTE, - /** Start or restart config immediately */ - ACTION_RESTART, + ACTION_NONE = 0, + /** Install trap policy to (re-)establish on demand */ + ACTION_TRAP = (1<<0), + /** Start or restart immediately */ + ACTION_START = (1<<1), }; /** diff --git a/src/libcharon/plugins/stroke/stroke_config.c b/src/libcharon/plugins/stroke/stroke_config.c index 175b6b549..a15c9f5a4 100644 --- a/src/libcharon/plugins/stroke/stroke_config.c +++ b/src/libcharon/plugins/stroke/stroke_config.c @@ -1042,9 +1042,9 @@ static action_t map_action(int starter_action) switch (starter_action) { case 2: /* =hold */ - return ACTION_ROUTE; + return ACTION_TRAP; case 3: /* =restart */ - return ACTION_RESTART; + return ACTION_START; default: return ACTION_NONE; } diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index aa6fbafbe..2c570c69b 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1004,10 +1004,10 @@ CALLBACK(parse_action, bool, action_t *out, chunk_t v) { enum_map_t map[] = { - { "start", ACTION_RESTART }, - { "restart", ACTION_RESTART }, - { "route", ACTION_ROUTE }, - { "trap", ACTION_ROUTE }, + { "start", ACTION_START }, + { "restart", ACTION_START }, + { "route", ACTION_TRAP }, + { "trap", ACTION_TRAP }, { "none", ACTION_NONE }, { "clear", ACTION_NONE }, }; @@ -2143,30 +2143,33 @@ CALLBACK(peer_sn, bool, static void run_start_action(private_vici_config_t *this, peer_cfg_t *peer_cfg, child_cfg_t *child_cfg) { - switch (child_cfg->get_start_action(child_cfg)) + action_t action; + + action = child_cfg->get_start_action(child_cfg); + + if (action & ACTION_TRAP) { - case ACTION_RESTART: - DBG1(DBG_CFG, "initiating '%s'", child_cfg->get_name(child_cfg)); - charon->controller->initiate(charon->controller, + DBG1(DBG_CFG, "installing '%s'", child_cfg->get_name(child_cfg)); + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: + charon->shunts->install(charon->shunts, + peer_cfg->get_name(peer_cfg), child_cfg); + /* no need to check for ACTION_START */ + return; + default: + charon->traps->install(charon->traps, peer_cfg, child_cfg); + break; + } + } + + if (action & ACTION_START) + { + DBG1(DBG_CFG, "initiating '%s'", child_cfg->get_name(child_cfg)); + charon->controller->initiate(charon->controller, peer_cfg->get_ref(peer_cfg), child_cfg->get_ref(child_cfg), NULL, NULL, 0, FALSE); - break; - case ACTION_ROUTE: - DBG1(DBG_CFG, "installing '%s'", child_cfg->get_name(child_cfg)); - switch (child_cfg->get_mode(child_cfg)) - { - case MODE_PASS: - case MODE_DROP: - charon->shunts->install(charon->shunts, - peer_cfg->get_name(peer_cfg), child_cfg); - break; - default: - charon->traps->install(charon->traps, peer_cfg, child_cfg); - break; - } - break; - default: - break; } } @@ -2181,100 +2184,101 @@ static void clear_start_action(private_vici_config_t *this, char *peer_name, ike_sa_t *ike_sa; uint32_t id = 0, others; array_t *ids = NULL, *ikeids = NULL; + action_t action; char *name; name = child_cfg->get_name(child_cfg); + action = child_cfg->get_start_action(child_cfg); - switch (child_cfg->get_start_action(child_cfg)) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - enumerator = charon->controller->create_ike_sa_enumerator( + DBG1(DBG_CFG, "uninstalling '%s'", name); + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: + charon->shunts->uninstall(charon->shunts, peer_name, name); + /* no need to check for ACTION_START */ + return; + default: + charon->traps->uninstall(charon->traps, peer_name, name); + break; + } + } + + if (action & ACTION_START) + { + enumerator = charon->controller->create_ike_sa_enumerator( charon->controller, TRUE); - while (enumerator->enumerate(enumerator, &ike_sa)) + while (enumerator->enumerate(enumerator, &ike_sa)) + { + if (!streq(ike_sa->get_name(ike_sa), peer_name)) { - if (!streq(ike_sa->get_name(ike_sa), peer_name)) + continue; + } + others = id = 0; + children = ike_sa->create_child_sa_enumerator(ike_sa); + while (children->enumerate(children, &child_sa)) + { + if (child_sa->get_state(child_sa) != CHILD_DELETING && + child_sa->get_state(child_sa) != CHILD_DELETED) { - continue; + if (streq(name, child_sa->get_name(child_sa))) + { + id = child_sa->get_unique_id(child_sa); + } + else + { + others++; + } } - others = id = 0; + } + children->destroy(children); + + if (!ike_sa->get_child_count(ike_sa) || (id && !others)) + { + /* found no children or only matching, delete IKE_SA */ + id = ike_sa->get_unique_id(ike_sa); + array_insert_create_value(&ikeids, sizeof(id), + ARRAY_TAIL, &id); + } + else + { children = ike_sa->create_child_sa_enumerator(ike_sa); while (children->enumerate(children, &child_sa)) { - if (child_sa->get_state(child_sa) != CHILD_DELETING && - child_sa->get_state(child_sa) != CHILD_DELETED) + if (streq(name, child_sa->get_name(child_sa))) { - if (streq(name, child_sa->get_name(child_sa))) - { - id = child_sa->get_unique_id(child_sa); - } - else - { - others++; - } + id = child_sa->get_unique_id(child_sa); + array_insert_create_value(&ids, sizeof(id), + ARRAY_TAIL, &id); } } children->destroy(children); + } + } + enumerator->destroy(enumerator); - if (!ike_sa->get_child_count(ike_sa) || (id && !others)) - { - /* found no children or only matching, delete IKE_SA */ - id = ike_sa->get_unique_id(ike_sa); - array_insert_create_value(&ikeids, sizeof(id), - ARRAY_TAIL, &id); - } - else - { - children = ike_sa->create_child_sa_enumerator(ike_sa); - while (children->enumerate(children, &child_sa)) - { - if (streq(name, child_sa->get_name(child_sa))) - { - id = child_sa->get_unique_id(child_sa); - array_insert_create_value(&ids, sizeof(id), - ARRAY_TAIL, &id); - } - } - children->destroy(children); - } - } - enumerator->destroy(enumerator); - - if (array_count(ids)) + if (array_count(ids)) + { + while (array_remove(ids, ARRAY_HEAD, &id)) { - while (array_remove(ids, ARRAY_HEAD, &id)) - { - DBG1(DBG_CFG, "closing '%s' #%u", name, id); - charon->controller->terminate_child(charon->controller, - id, NULL, NULL, 0); - } - array_destroy(ids); + DBG1(DBG_CFG, "closing '%s' #%u", name, id); + charon->controller->terminate_child(charon->controller, + id, NULL, NULL, 0); } - if (array_count(ikeids)) + array_destroy(ids); + } + if (array_count(ikeids)) + { + while (array_remove(ikeids, ARRAY_HEAD, &id)) { - while (array_remove(ikeids, ARRAY_HEAD, &id)) - { - DBG1(DBG_CFG, "closing IKE_SA #%u", id); - charon->controller->terminate_ike(charon->controller, id, - FALSE, NULL, NULL, 0); - } - array_destroy(ikeids); + DBG1(DBG_CFG, "closing IKE_SA #%u", id); + charon->controller->terminate_ike(charon->controller, id, + FALSE, NULL, NULL, 0); } - break; - case ACTION_ROUTE: - DBG1(DBG_CFG, "uninstalling '%s'", name); - switch (child_cfg->get_mode(child_cfg)) - { - case MODE_PASS: - case MODE_DROP: - charon->shunts->uninstall(charon->shunts, peer_name, name); - break; - default: - charon->traps->uninstall(charon->traps, peer_name, name); - break; - } - break; - default: - break; + array_destroy(ikeids); + } } } diff --git a/src/libcharon/processing/jobs/start_action_job.c b/src/libcharon/processing/jobs/start_action_job.c index 3a0ed879f..31e154a77 100644 --- a/src/libcharon/processing/jobs/start_action_job.c +++ b/src/libcharon/processing/jobs/start_action_job.c @@ -42,8 +42,7 @@ METHOD(job_t, execute, job_requeue_t, enumerator_t *enumerator, *children; peer_cfg_t *peer_cfg; child_cfg_t *child_cfg; - ipsec_mode_t mode; - char *name; + action_t action; enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, NULL, NULL, NULL, NULL, IKE_ANY); @@ -52,34 +51,39 @@ METHOD(job_t, execute, job_requeue_t, children = peer_cfg->create_child_cfg_enumerator(peer_cfg); while (children->enumerate(children, &child_cfg)) { - name = child_cfg->get_name(child_cfg); - - switch (child_cfg->get_start_action(child_cfg)) + action = child_cfg->get_start_action(child_cfg); + if (action == ACTION_NONE) { - case ACTION_RESTART: - DBG1(DBG_JOB, "start action: initiate '%s'", name); - charon->controller->initiate(charon->controller, - peer_cfg->get_ref(peer_cfg), - child_cfg->get_ref(child_cfg), - NULL, NULL, 0, FALSE); - break; - case ACTION_ROUTE: - DBG1(DBG_JOB, "start action: route '%s'", name); - mode = child_cfg->get_mode(child_cfg); - if (mode == MODE_PASS || mode == MODE_DROP) - { + continue; + } + + DBG1(DBG_JOB, "start action: %N '%s'", action_names, action, + child_cfg->get_name(child_cfg)); + + if (action & ACTION_TRAP) + { + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: charon->shunts->install(charon->shunts, peer_cfg->get_name(peer_cfg), child_cfg); - } - else - { + /* no need to check for ACTION_START */ + continue; + default: charon->traps->install(charon->traps, peer_cfg, child_cfg); - } - break; - case ACTION_NONE: - break; + break; + } + } + + if (action & ACTION_START) + { + charon->controller->initiate(charon->controller, + peer_cfg->get_ref(peer_cfg), + child_cfg->get_ref(child_cfg), + NULL, NULL, 0, FALSE); } } children->destroy(children); diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 3ef5e020b..a9ea02d95 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -2060,7 +2060,7 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, } if (force) { - action = ACTION_RESTART; + action = ACTION_START; } else { /* only restart CHILD_SAs that are configured accordingly */ @@ -2073,7 +2073,7 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, action = child_sa->get_dpd_action(child_sa); } } - if (action == ACTION_RESTART) + if (action & ACTION_START) { child_init_args_t args = { .reqid = child_sa->get_reqid(child_sa), @@ -2150,17 +2150,14 @@ METHOD(ike_sa_t, reestablish, status_t, { action = child_sa->get_dpd_action(child_sa); } - switch (action) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - restart = TRUE; - break; - case ACTION_ROUTE: - charon->traps->install(charon->traps, this->peer_cfg, - child_sa->get_config(child_sa)); - break; - default: - break; + charon->traps->install(charon->traps, this->peer_cfg, + child_sa->get_config(child_sa)); + } + if (action & ACTION_START) + { + restart = TRUE; } } enumerator->destroy(enumerator); diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.c b/src/libcharon/sa/ikev1/tasks/quick_delete.c index ecfc8dda3..9d09ee42f 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_delete.c +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.c @@ -152,23 +152,21 @@ static status_t delete_child(private_quick_delete_t *this, child_init_args_t args = { .reqid = child_sa->get_reqid(child_sa), }; + action_t action; + + action = child_sa->get_close_action(child_sa); child_cfg = child_sa->get_config(child_sa); child_cfg->get_ref(child_cfg); - - switch (child_sa->get_close_action(child_sa)) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - child_cfg->get_ref(child_cfg); - status = this->ike_sa->initiate(this->ike_sa, child_cfg, - &args); - break; - case ACTION_ROUTE: - charon->traps->install(charon->traps, - this->ike_sa->get_peer_cfg(this->ike_sa), - child_cfg); - break; - default: - break; + charon->traps->install(charon->traps, + this->ike_sa->get_peer_cfg(this->ike_sa), + child_cfg); + } + if (action & ACTION_START) + { + child_cfg->get_ref(child_cfg); + status = this->ike_sa->initiate(this->ike_sa, child_cfg, &args); } child_cfg->destroy(child_cfg); } diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index 5ce251b5f..13cbee3f5 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -372,20 +372,16 @@ static status_t destroy_and_reestablish(private_child_delete_t *this) if (entry->check_delete_action) { /* enforce child_cfg policy if deleted passively */ - switch (action) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - child_cfg->get_ref(child_cfg); - status = this->ike_sa->initiate(this->ike_sa, child_cfg, - &args); - break; - case ACTION_ROUTE: - charon->traps->install(charon->traps, - this->ike_sa->get_peer_cfg(this->ike_sa), - child_cfg); - break; - default: - break; + charon->traps->install(charon->traps, + this->ike_sa->get_peer_cfg(this->ike_sa), + child_cfg); + } + if (action & ACTION_START) + { + child_cfg->get_ref(child_cfg); + status = this->ike_sa->initiate(this->ike_sa, child_cfg, &args); } } child_cfg->destroy(child_cfg); From 7137fd96c25bd73af7ba50441994fdcffe7b5ac0 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 17 Dec 2021 15:19:02 +0100 Subject: [PATCH 09/47] enum: Add helper to parse enum flags from strings Individual flag names are separated by |. --- src/libstrongswan/tests/suites/test_enum.c | 73 ++++++++++++++++++++-- src/libstrongswan/utils/enum.c | 49 +++++++++++++++ src/libstrongswan/utils/enum.h | 36 ++++++++++- 3 files changed, 149 insertions(+), 9 deletions(-) diff --git a/src/libstrongswan/tests/suites/test_enum.c b/src/libstrongswan/tests/suites/test_enum.c index 613043e6d..83ff14be8 100644 --- a/src/libstrongswan/tests/suites/test_enum.c +++ b/src/libstrongswan/tests/suites/test_enum.c @@ -20,13 +20,13 @@ /******************************************************************************* * continuous enum */ -enum { +enum test_enum_cont { CONT1, CONT2, CONT3, CONT4, CONT5, -} test_enum_cont; +}; ENUM_BEGIN(test_enum_cont_names, CONT1, CONT5, "CONT1", "CONT2", "CONT3", "CONT4", "CONT5"); @@ -35,13 +35,13 @@ ENUM_END(test_enum_cont_names, CONT5); /******************************************************************************* * split enum */ -enum { +enum test_enum_split { SPLIT1 = 1, SPLIT2, SPLIT3 = 5, SPLIT4, SPLIT5 = 255, -} test_enum_split; +}; ENUM_BEGIN(test_enum_split_names, SPLIT1, SPLIT2, "SPLIT1", "SPLIT2"); @@ -54,7 +54,7 @@ ENUM_END(test_enum_split_names, SPLIT5); /******************************************************************************* * enum flags */ -enum { +enum test_enum_flags { FLAG1 = (1 << 0), FLAG2 = (1 << 1), FLAG3 = (1 << 2), @@ -67,7 +67,7 @@ enum { FLAG10 = (1 << 9), FLAG11 = (1 << 10), FLAG12 = (1 << 11), -} test_enum_flags; +}; ENUM_FLAGS(test_enum_flags_names, FLAG1, FLAG5, "(unset)", "FLAG1", "FLAG2", "FLAG3", "FLAG4", "FLAG5"); @@ -263,6 +263,30 @@ static struct { }, enum_flags_to_string_tests[] = { {-1, NULL}, {6435, NULL}, +}, enum_flags_from_string_tests[] = { + {0, NULL}, + {0, ""}, + {0, "(unset)"}, + {FLAG1, "FLAG1"}, + {FLAG2, "flag2"}, + {FLAG3, "fLaG3"}, + {FLAG4, "FLAG4"}, + {FLAG5, "FLAG5"}, + {FLAG1 | FLAG3, "FLAG1 | FLAG3"}, + {FLAG1 | FLAG3, "flag3|flag1"}, + {FLAG1 | FLAG3, "flag1|flag3 | (unset)"}, + {FLAG1 | FLAG2 | FLAG3 | FLAG4 | FLAG5, "flag1|flag2|flag3|flag4|flag5"}, + {FLAG1 | FLAG2 | FLAG3 | FLAG4 | FLAG5, "flag3|flag4|flag5|flag2|flag1"}, + {FLAG5, "(unset)|flag5"}, + {FLAG1, "FLAG1 | flag1 | flAg1"}, + {-1, "FLAG6"}, + {-1, "flag1 | asdf"}, +}, enum_flags_from_string_noflagenum_tests[] = { + {0, NULL}, + {0, ""}, + {CONT2, "CONT2"}, + {CONT5, "CONT5"}, + {-1, "asdf"}, }; START_TEST(test_enum_printf_hook_cont) @@ -371,6 +395,38 @@ START_TEST(test_enum_flags_to_string_noflagenum) } END_TEST +START_TEST(test_enum_flags_from_string) +{ + enum test_enum_flags val; + + if (enum_flags_from_string(test_enum_flags_names, + enum_flags_from_string_tests[_i].str, &val)) + { + ck_assert_int_eq(enum_flags_from_string_tests[_i].val, val); + } + else + { + ck_assert_int_eq(enum_flags_from_string_tests[_i].val, -1); + } +} +END_TEST + +START_TEST(test_enum_flags_from_string_noflagenum) +{ + enum test_enum_cont val; + + if (enum_flags_from_string(test_enum_cont_names, + enum_flags_from_string_noflagenum_tests[_i].str, &val)) + { + ck_assert_int_eq(enum_flags_from_string_noflagenum_tests[_i].val, val); + } + else + { + ck_assert_int_eq(enum_flags_from_string_noflagenum_tests[_i].val, -1); + } +} +END_TEST + START_TEST(test_enum_printf_hook_width) { char buf[128]; @@ -406,6 +462,11 @@ Suite *enum_suite_create() tcase_add_loop_test(tc, test_enum_flags_to_string_noflagenum, 0, countof(printf_tests_flags_noflagenum)); suite_add_tcase(s, tc); + tc = tcase_create("enum_flags_from_string"); + tcase_add_loop_test(tc, test_enum_flags_from_string, 0, countof(enum_flags_from_string_tests)); + tcase_add_loop_test(tc, test_enum_flags_from_string_noflagenum, 0, countof(enum_flags_from_string_noflagenum_tests)); + suite_add_tcase(s, tc); + tc = tcase_create("enum_printf_hook"); tcase_add_loop_test(tc, test_enum_printf_hook_cont, 0, countof(printf_tests_cont)); tcase_add_loop_test(tc, test_enum_printf_hook_split, 0, countof(printf_tests_split)); diff --git a/src/libstrongswan/utils/enum.c b/src/libstrongswan/utils/enum.c index e62c75f91..79da450f0 100644 --- a/src/libstrongswan/utils/enum.c +++ b/src/libstrongswan/utils/enum.c @@ -17,6 +17,7 @@ #include #include +#include #include #include "enum.h" @@ -135,6 +136,54 @@ char *enum_flags_to_string(enum_name_t *e, u_int val, char *buf, size_t len) return buf; } +/* + * Described in header + */ +bool enum_flags_from_string_as_int(enum_name_t *e, const char *str, u_int *val) +{ + enumerator_t *enumerator; + char *name; + + *val = 0; + + if (!str || !*str) + { + return TRUE; + } + else if (e->next != ENUM_FLAG_MAGIC) + { + return enum_from_name_as_int(e, str, val); + } + + enumerator = enumerator_create_token(str, "|", " "); + while (enumerator->enumerate(enumerator, &name)) + { + u_int flag, i; + bool found = FALSE; + + if (strcaseeq(name, e->names[0])) + { /* accept name used if no flags are set */ + continue; + } + for (i = 1, flag = e->first; flag <= e->last; i++, flag <<= 1) + { + if (e->names[i] && strcaseeq(name, e->names[i])) + { + *val |= flag; + found = TRUE; + break; + } + } + if (!found) + { + enumerator->destroy(enumerator); + return FALSE; + } + } + enumerator->destroy(enumerator); + return TRUE; +} + /** * See header. */ diff --git a/src/libstrongswan/utils/enum.h b/src/libstrongswan/utils/enum.h index 1e723a220..d6f909e15 100644 --- a/src/libstrongswan/utils/enum.h +++ b/src/libstrongswan/utils/enum.h @@ -158,7 +158,7 @@ char *enum_to_name(enum_name_t *e, int val); */ #define enum_from_name(e, name, valp) ({ \ int _val; \ - int _found = enum_from_name_as_int(e, name, &_val); \ + bool _found = enum_from_name_as_int(e, name, &_val); \ if (_found) \ { \ *(valp) = _val; \ @@ -168,13 +168,13 @@ char *enum_to_name(enum_name_t *e, int val); /** * Convert a enum string back to its enum value, integer pointer variant. * - * This variant takes integer pointer only, use enum_from_name() to pass + * This variant takes an integer pointer, use enum_from_name() to pass * enum type pointers for the result. * * @param e enum names for this enum value * @param name name to get enum value for * @param val integer pointer receiving value - * @return TRUE if enum name found, FALSE otherwise + * @return TRUE if all names found, FALSE otherwise */ bool enum_from_name_as_int(enum_name_t *e, const char *name, int *val); @@ -189,6 +189,36 @@ bool enum_from_name_as_int(enum_name_t *e, const char *name, int *val); */ char *enum_flags_to_string(enum_name_t *e, u_int val, char *buf, size_t len); +/** + * Convert a string of flags separated by | to their combined value + * + * @param e enum names for this enum value + * @param str string to get enum value for + * @param valp variable sized pointer receiving value + * @return TRUE if all names found, FALSE otherwise + */ +#define enum_flags_from_string(e, str, valp) ({ \ + u_int _val; \ + bool _found = enum_flags_from_string_as_int(e, str, &_val); \ + if (_found) \ + { \ + *(valp) = _val; \ + } \ + _found; }) + +/** + * Convert a string of flags separated by | to their combined value. + * + * This variant takes an unsigned integer pointer, use enum_flags_from_names() + * to pass enum type pointers for the result. + * + * @param e enum names for this enum value + * @param str string to get enum value for + * @param val integer pointer receiving value + * @return TRUE if enum name found, FALSE otherwise + */ +bool enum_flags_from_string_as_int(enum_name_t *e, const char *str, u_int *val); + /** * printf hook function for enum_names_t. * From c00c5e5a354ecda93d1db58aa6055ebe289f381c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 16 Dec 2021 16:51:04 +0100 Subject: [PATCH 10/47] vici: Make combination of 'trap' and 'start' configurable --- src/libcharon/plugins/vici/vici_config.c | 9 +++++++++ src/swanctl/swanctl.opt | 12 ++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 2c570c69b..a7cd7f9d5 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1011,6 +1011,7 @@ CALLBACK(parse_action, bool, { "none", ACTION_NONE }, { "clear", ACTION_NONE }, }; + char buf[BUF_LEN]; int d; if (parse_map(map, countof(map), &d, v)) @@ -1018,6 +1019,14 @@ CALLBACK(parse_action, bool, *out = d; return TRUE; } + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + if (enum_flags_from_string(action_names, buf, out)) + { + return TRUE; + } return FALSE; } diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index 0ae9d4579..082bf4cbb 100644 --- a/src/swanctl/swanctl.opt +++ b/src/swanctl/swanctl.opt @@ -885,7 +885,8 @@ connections..children..dpd_action = clear Action to perform for this CHILD_SA on DPD timeout. The default _clear_ closes the CHILD_SA and does not take further action. _trap_ installs a trap policy, which will catch matching traffic and tries to re-negotiate - the tunnel on-demand. _restart_ immediately tries to re-negotiate the + the tunnel on-demand (note that this is redundant if **start_action** + includes _trap_). _restart_ immediately tries to re-negotiate the CHILD_SA under a fresh IKE_SA. connections..children..ipcomp = no @@ -1075,19 +1076,22 @@ connections..children..start_action = none The value _trap_ installs a trap policy, which triggers the tunnel as soon as matching traffic has been detected. The value _start_ initiates - the connection actively. + the connection actively. These two modes can be combined with _trap|start_, + to immediately initiate a connection for which trap policies have been + installed. When unloading or replacing a CHILD_SA configuration having a **start_action** different from _none_, the inverse action is performed. Configurations with _start_ get closed, while such with _trap_ get - uninstalled. + uninstalled (both happens for connections with _trap|start_). connections..children..close_action = none Action to perform after a CHILD_SA gets closed (_none_, _trap_, _start_). Action to perform after a CHILD_SA gets closed by the peer. The default of _none_ does not take any action, _trap_ installs a trap policy for the - CHILD_SA. _start_ tries to re-create the CHILD_SA. + CHILD_SA (note that this is redundant if **start_action** includes _trap_). + _start_ tries to immediately re-create the CHILD_SA. **close_action** does not provide any guarantee that the CHILD_SA is kept alive. It acts on explicit close messages only, but not on negotiation From fe5f27336d4df1aebbf3c9d9bd6e1115cc337322 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 17 Dec 2021 17:13:27 +0100 Subject: [PATCH 11/47] configure: Add option to link against libselinux --- configure.ac | 8 ++++++++ scripts/test.sh | 3 ++- src/libstrongswan/Makefile.am | 4 +++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 799a1d170..ac0724612 100644 --- a/configure.ac +++ b/configure.ac @@ -321,6 +321,7 @@ ARG_ENABL_SET([python-eggs], [enable build of provided python eggs.]) ARG_ENABL_SET([python-eggs-install],[enable installation of provided python eggs.]) ARG_ENABL_SET([perl-cpan], [enable build of provided perl CPAN module.]) ARG_ENABL_SET([perl-cpan-install],[enable installation of provided CPAN module.]) +ARG_ENABL_SET([selinux], [enable SELinux support for labeled IPsec.]) ARG_ENABL_SET([tss-trousers], [enable the use of the TrouSerS Trusted Software Stack]) ARG_ENABL_SET([tss-tss2], [enable the use of the TSS 2.0 Trusted Software Stack]) @@ -1248,6 +1249,13 @@ if test x$capabilities = xlibcap; then AC_DEFINE([CAPABILITIES_LIBCAP], [], [have libpcap library]) fi +if test x$selinux = xtrue; then + PKG_CHECK_MODULES(selinux, [libselinux]) + AC_SUBST(selinux_CFLAGS) + AC_SUBST(selinux_LIBS) + AC_DEFINE([USE_SELINUX], [], [build with support for SELinux]) +fi + if test x$integrity_test = xtrue; then AC_MSG_CHECKING([for dladdr()]) AC_COMPILE_IFELSE( diff --git a/scripts/test.sh b/scripts/test.sh index c773b3ef1..15ca7e0e6 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -171,7 +171,8 @@ all|coverage|sonarcloud) DEPS="$DEPS libcurl4-gnutls-dev libsoup2.4-dev libunbound-dev libldns-dev libmysqlclient-dev libsqlite3-dev clearsilver-dev libfcgi-dev libldap2-dev libpcsclite-dev libpam0g-dev binutils-dev libnm-dev - libgcrypt20-dev libjson-c-dev python3-pip libtspi-dev libsystemd-dev" + libgcrypt20-dev libjson-c-dev python3-pip libtspi-dev libsystemd-dev + libselinux1-dev" if [ "$ID" = "ubuntu" -a "$VERSION_ID" = "20.04" ]; then DEPS="$DEPS libiptc-dev" else diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index a12567291..5d230822b 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -132,7 +132,8 @@ library.lo : $(top_builddir)/config.status libstrongswan_la_LIBADD = \ $(DLLIB) $(ATOMICLIB) $(BTLIB) $(SOCKLIB) \ - $(RTLIB) $(BFDLIB) $(UNWINDLIB) + $(RTLIB) $(BFDLIB) $(UNWINDLIB) \ + $(selinux_LIBS) AM_CPPFLAGS = \ -I$(top_srcdir)/src/libstrongswan \ @@ -142,6 +143,7 @@ AM_CPPFLAGS = \ -DSTRONGSWAN_CONF=\"${strongswan_conf}\" AM_CFLAGS = \ + $(selinux_CFLAGS) \ @COVERAGE_CFLAGS@ AM_LDFLAGS = \ From decfe44433363002a0ae645b02cf740654828b6e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 11:23:37 +0100 Subject: [PATCH 12/47] sec-label: Add class to represent security labels In accordance with SELinux, we include the null-terminator in the encoding for now. --- src/libstrongswan/Android.mk | 3 +- src/libstrongswan/Makefile.am | 6 +- src/libstrongswan/selectors/sec_label.c | 197 ++++++++++++++++++++++++ src/libstrongswan/selectors/sec_label.h | 125 +++++++++++++++ 4 files changed, 328 insertions(+), 3 deletions(-) create mode 100644 src/libstrongswan/selectors/sec_label.c create mode 100644 src/libstrongswan/selectors/sec_label.h diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 660382da2..c1001905c 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -41,7 +41,8 @@ networking/streams/stream_tcp.c networking/streams/stream_service_tcp.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \ -selectors/traffic_selector.c settings/settings.c settings/settings_types.c \ +selectors/sec_label.c selectors/traffic_selector.c \ +settings/settings.c settings/settings_types.c \ settings/settings_parser.c settings/settings_lexer.c utils/cpu_feature.c \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 5d230822b..37fe4b1e5 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -39,7 +39,8 @@ networking/streams/stream_tcp.c networking/streams/stream_service_tcp.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \ -selectors/traffic_selector.c settings/settings.c settings/settings_types.c \ +selectors/sec_label.c selectors/traffic_selector.c \ +settings/settings.c settings/settings_types.c \ settings/settings_parser.y settings/settings_lexer.l utils/cpu_feature.c \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ @@ -112,7 +113,8 @@ resolver/resolver.h resolver/resolver_response.h resolver/rr_set.h \ resolver/rr.h resolver/resolver_manager.h \ plugins/plugin_loader.h plugins/plugin.h plugins/plugin_feature.h \ processing/jobs/job.h processing/jobs/callback_job.h processing/processor.h \ -processing/scheduler.h processing/watcher.h selectors/traffic_selector.h \ +processing/scheduler.h processing/watcher.h \ +selectors/sec_label.h selectors/traffic_selector.h \ settings/settings.h settings/settings_parser.h threading/thread_value.h \ threading/thread.h threading/windows/thread.h \ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \ diff --git a/src/libstrongswan/selectors/sec_label.c b/src/libstrongswan/selectors/sec_label.c new file mode 100644 index 000000000..1b85942e9 --- /dev/null +++ b/src/libstrongswan/selectors/sec_label.c @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2021 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define _GNU_SOURCE +#include +#ifdef USE_SELINUX +#include +#endif + +#include "sec_label.h" + +typedef struct private_sec_label_t private_sec_label_t; + +/** + * Private data. + */ +struct private_sec_label_t { + + /** + * Public interface + */ + sec_label_t public; + + /** + * Encoded label value + */ + chunk_t encoding; + + /** + * String representation of the label + */ + char *str; +}; + +static sec_label_t *create_sec_label(chunk_t encoding, char *str); + +METHOD(sec_label_t, get_encoding, chunk_t, + private_sec_label_t *this) +{ + return this->encoding; +} + +METHOD(sec_label_t, get_string, char*, + private_sec_label_t *this) +{ + return this->str; +} + +METHOD(sec_label_t, clone_, sec_label_t*, + private_sec_label_t *this) +{ + return create_sec_label(chunk_clone(this->encoding), strdup(this->str)); +} + +METHOD(sec_label_t, equals, bool, + private_sec_label_t *this, sec_label_t *other_pub) +{ + private_sec_label_t *other = (private_sec_label_t*)other_pub; + + if (!other_pub) + { + return FALSE; + } + return chunk_equals_const(this->encoding, other->encoding); +} + +METHOD(sec_label_t, matches, bool, + private_sec_label_t *this, sec_label_t *other_pub) +{ + if (!other_pub) + { + return FALSE; + } +#ifdef USE_SELINUX + if (is_selinux_enabled()) + { /* if disabled, the following matches anything against anything */ + private_sec_label_t *other = (private_sec_label_t*)other_pub; + return selinux_check_access(other->str, this->str, "association", + "polmatch", NULL) == 0; + } +#endif + return equals(this, other_pub); +} + +METHOD(sec_label_t, hash, u_int, + private_sec_label_t *this, u_int inc) +{ + return chunk_hash_inc(this->encoding, inc); +} + +METHOD(sec_label_t, destroy, void, + private_sec_label_t *this) +{ + chunk_free(&this->encoding); + free(this->str); + free(this); +} + +/** + * Internal constructor, data is adopted + */ +static sec_label_t *create_sec_label(chunk_t encoding, char *str) +{ + private_sec_label_t *this; + + INIT(this, + .public = { + .get_encoding = _get_encoding, + .get_string = _get_string, + .clone = _clone_, + .matches = _matches, + .equals = _equals, + .hash = _hash, + .destroy = _destroy, + }, + .encoding = encoding, + .str = str, + ); + return &this->public; +} + +/* + * Described in header + */ +sec_label_t *sec_label_from_encoding(const chunk_t value) +{ + chunk_t cloned, sanitized = chunk_empty; + char *str; + + if (!value.len || (value.len == 1 && !value.ptr[0])) + { + DBG1(DBG_LIB, "invalid empty security label"); + return NULL; + } + else if (value.ptr[value.len-1]) + { + DBG1(DBG_LIB, "adding null-terminator to security label"); + cloned = chunk_cat("cc", value, chunk_from_chars(0x00)); + } + else + { + cloned = chunk_clone(value); + } + + /* create a sanitized version while ignoring the null-terminator */ + if (!chunk_printable(chunk_create(cloned.ptr, cloned.len-1), &sanitized, '?')) + { +#ifdef USE_SELINUX + /* don't accept labels with non-printable characters if we use SELinux */ + DBG1(DBG_LIB, "invalid security label with non-printable characters %B", + &value); + chunk_free(&sanitized); + chunk_free(&cloned); + return NULL; +#endif + } + if (asprintf(&str, "%.*s", (int)sanitized.len, sanitized.ptr) <= 0) + { + chunk_free(&sanitized); + chunk_free(&cloned); + return NULL; + } + chunk_free(&sanitized); + + return create_sec_label(cloned, str); +} + +/* + * Described in header + */ +sec_label_t *sec_label_from_string(const char *value) +{ + if (!value) + { + return NULL; + } + return sec_label_from_encoding(chunk_create((char*)value, strlen(value)+1)); +} diff --git a/src/libstrongswan/selectors/sec_label.h b/src/libstrongswan/selectors/sec_label.h new file mode 100644 index 000000000..55feec439 --- /dev/null +++ b/src/libstrongswan/selectors/sec_label.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup sec_label sec_label + * @{ @ingroup ipsec + */ + +#ifndef SEC_LABEL_H_ +#define SEC_LABEL_H_ + +typedef struct sec_label_t sec_label_t; + +#include + +/** + * Representation of a security label used on policies/SAs. + * + * For example, with SELinux this could be a value like + * system_u:object_r:ipsec_spd_t:s0. + */ +struct sec_label_t { + + /** + * Return a binary encoding of the security label as used for IKE. + * + * @return binary encoding (internal data) + */ + chunk_t (*get_encoding)(sec_label_t *this); + + /** + * Return a string representation of this security label. + * + * @return string representation (internal data) + */ + char *(*get_string)(sec_label_t *this); + + /** + * Clone this security label. + * + * @return clone of it + */ + sec_label_t *(*clone)(sec_label_t *this); + + /** + * Match two security labels. + * + * For SELinux this checks if this security label permits other in terms + * of association { polmatch }. + * + * @param other security label to match against this + * @return TRUE if matching, FALSE otherwise + */ + bool (*matches)(sec_label_t *this, sec_label_t *other); + + /** + * Compare two security labels for equality. + * + * @param other security label to compare with this + * @return TRUE if equal, FALSE otherwise + */ + bool (*equals)(sec_label_t *this, sec_label_t *other); + + /** + * Create a hash value for the security label. + * + * @param inc optional value for incremental hashing + * @return calculated hash value for the security label + */ + u_int (*hash)(sec_label_t *this, u_int inc); + + /** + * Destroys the object. + */ + void (*destroy)(sec_label_t *this); +}; + +/** + * Try to parse a sec_label_t from the given binary encoding. + * + * @param value encoding to parse + * @return security label instance, NULL if invalid + */ +sec_label_t *sec_label_from_encoding(const chunk_t value); + +/** + * Try to parse a sec_label_t from the given string. + * + * @param value string to parse + * @return security label instance, NULL if invalid + */ +sec_label_t *sec_label_from_string(const char *value); + +/** + * Compare two security labels for equality, accept if both are NULL. + * + * @param a first label + * @param b second label + * @return TRUE if labels are equal or both NULL + */ +static inline bool sec_labels_equal(sec_label_t *a, sec_label_t *b) +{ + return (!a && !b) || (a && a->equals(a, b)); +} + +#endif /** SEC_LABEL_H_ @}*/ From 2dd60e49463bb20db9b20b38dde998fc049672c4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 27 Jan 2022 14:49:39 +0100 Subject: [PATCH 13/47] sec-label: Add enum for security label mode --- src/libstrongswan/selectors/sec_label.c | 31 ++++++++++++++++ src/libstrongswan/selectors/sec_label.h | 47 +++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/libstrongswan/selectors/sec_label.c b/src/libstrongswan/selectors/sec_label.c index 1b85942e9..596e98c03 100644 --- a/src/libstrongswan/selectors/sec_label.c +++ b/src/libstrongswan/selectors/sec_label.c @@ -28,6 +28,12 @@ #include "sec_label.h" +ENUM(sec_label_mode_names, SEC_LABEL_MODE_SYSTEM, SEC_LABEL_MODE_SELINUX, + "system", + "simple", + "selinux", +); + typedef struct private_sec_label_t private_sec_label_t; /** @@ -195,3 +201,28 @@ sec_label_t *sec_label_from_string(const char *value) } return sec_label_from_encoding(chunk_create((char*)value, strlen(value)+1)); } + +/* + * Described in header + */ +bool sec_label_mode_from_string(const char *value, sec_label_mode_t *mode) +{ + sec_label_mode_t def = sec_label_mode_default(); + + return enum_from_name(sec_label_mode_names, value, mode) && + (def == SEC_LABEL_MODE_SELINUX || *mode != SEC_LABEL_MODE_SELINUX); +} + +/* + * Described in header + */ +sec_label_mode_t sec_label_mode_default() +{ +#ifdef USE_SELINUX + if (is_selinux_enabled()) + { + return SEC_LABEL_MODE_SELINUX; + } +#endif + return SEC_LABEL_MODE_SIMPLE; +} diff --git a/src/libstrongswan/selectors/sec_label.h b/src/libstrongswan/selectors/sec_label.h index 55feec439..0392db77f 100644 --- a/src/libstrongswan/selectors/sec_label.h +++ b/src/libstrongswan/selectors/sec_label.h @@ -28,10 +28,41 @@ #ifndef SEC_LABEL_H_ #define SEC_LABEL_H_ +typedef enum sec_label_mode_t sec_label_mode_t; typedef struct sec_label_t sec_label_t; #include +/** + * Mode in which security labels are used. + */ +enum sec_label_mode_t { + + /** + * System default. Simple mode if SELinux is not supported or disabled + * on the system. + */ + SEC_LABEL_MODE_SYSTEM, + + /** + * Simple mode that does establish regular CHILD_SAs, matches labels exactly + * and does not install them in the kernel. + */ + SEC_LABEL_MODE_SIMPLE, + + /** + * SELinux mode where configured labels are installed on (trap) policies, + * labels from acquires/peer on SAs, child-less IKE_SAs are initiated + * if there is no acquire, labels are also matched via polmatch. + */ + SEC_LABEL_MODE_SELINUX, +}; + +/** + * Names for security label modes. + */ +extern enum_name_t *sec_label_mode_names; + /** * Representation of a security label used on policies/SAs. * @@ -122,4 +153,20 @@ static inline bool sec_labels_equal(sec_label_t *a, sec_label_t *b) return (!a && !b) || (a && a->equals(a, b)); } +/** + * Try to parse a security label mode from the given string. + * + * @param value string to parse + * @param mode parsed mode + * @return TRUE if mode is valid (and usable on system) + */ +bool sec_label_mode_from_string(const char *value, sec_label_mode_t *mode); + +/** + * Get the system default security label mode. + * + * @return default mode + */ +sec_label_mode_t sec_label_mode_default(); + #endif /** SEC_LABEL_H_ @}*/ From ebb99484e304950bad51edac11015ea494fa299d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 4 Feb 2022 08:17:54 +0100 Subject: [PATCH 14/47] leak-detective: Whitelist selinux_check_access() --- src/libstrongswan/utils/leak_detective.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index bbf7078dd..96d55f5cc 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -539,6 +539,7 @@ static char *whitelist[] = { "initgroups", "tzset", "_IO_file_doallocate", + "selinux_check_access", /* ignore dlopen, as we do not dlclose to get proper leak reports */ "dlopen", "dlerror", From d71d181d28218299c51486906beecf87ec44e88a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 10 Jan 2022 16:35:38 +0100 Subject: [PATCH 15/47] traffic-selector: Add TS_SECLABEL type --- src/charon-cmd/cmd/cmd_connection.c | 2 ++ src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 2 ++ src/libstrongswan/selectors/traffic_selector.c | 8 +++++--- src/libstrongswan/selectors/traffic_selector.h | 7 ++++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/charon-cmd/cmd/cmd_connection.c b/src/charon-cmd/cmd/cmd_connection.c index 0481d78d4..37d951951 100644 --- a/src/charon-cmd/cmd/cmd_connection.c +++ b/src/charon-cmd/cmd/cmd_connection.c @@ -388,6 +388,8 @@ static child_cfg_t* create_child_cfg(private_cmd_connection_t *this, case TS_IPV6_ADDR_RANGE: has_v6 = TRUE; break; + default: + continue; } child_cfg->add_traffic_selector(child_cfg, FALSE, ts); } diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 36f5a97fc..650e04620 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -820,6 +820,8 @@ static bool install_sps(private_kernel_wfp_ipsec_t *this, case TS_IPV6_ADDR_RANGE: has_v6 = TRUE; break; + default: + continue; } /* inbound policy */ diff --git a/src/libstrongswan/selectors/traffic_selector.c b/src/libstrongswan/selectors/traffic_selector.c index 2735a5cc1..2e69e5ad8 100644 --- a/src/libstrongswan/selectors/traffic_selector.c +++ b/src/libstrongswan/selectors/traffic_selector.c @@ -31,10 +31,12 @@ #define NON_SUBNET_ADDRESS_RANGE 255 -ENUM(ts_type_name, TS_IPV4_ADDR_RANGE, TS_IPV6_ADDR_RANGE, +ENUM_BEGIN(ts_type_name, TS_IPV4_ADDR_RANGE, TS_IPV6_ADDR_RANGE, "TS_IPV4_ADDR_RANGE", - "TS_IPV6_ADDR_RANGE", -); + "TS_IPV6_ADDR_RANGE"); +ENUM_NEXT(ts_type_name, TS_SECLABEL, TS_SECLABEL, TS_IPV6_ADDR_RANGE, + "TS_SECLABEL"); +ENUM_END(ts_type_name, TS_SECLABEL); typedef struct private_traffic_selector_t private_traffic_selector_t; diff --git a/src/libstrongswan/selectors/traffic_selector.h b/src/libstrongswan/selectors/traffic_selector.h index 03f7a6d8c..a13aca106 100644 --- a/src/libstrongswan/selectors/traffic_selector.h +++ b/src/libstrongswan/selectors/traffic_selector.h @@ -50,7 +50,12 @@ enum ts_type_t { * (inclusive). All addresses falling between the two specified * addresses are considered to be within the list. */ - TS_IPV6_ADDR_RANGE = 8 + TS_IPV6_ADDR_RANGE = 8, + + /** + * A security label. + */ + TS_SECLABEL = 10, }; /** From a44de0b9578f28919fbdde80dff3e8b9a2164827 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 10 Jan 2022 17:34:22 +0100 Subject: [PATCH 16/47] traffic-selector-substructure: Add support for TS_SECLABEL Changes how regular address range traffic selectors are parsed as the IKE parser currently doesn't provide sub-type parsing. Also removed a lot of unused method definitions. --- .../payloads/traffic_selector_substructure.c | 211 ++++++++++++------ .../payloads/traffic_selector_substructure.h | 115 ++++------ 2 files changed, 189 insertions(+), 137 deletions(-) diff --git a/src/libcharon/encoding/payloads/traffic_selector_substructure.c b/src/libcharon/encoding/payloads/traffic_selector_substructure.c index febf0c410..c4e5d2b1d 100644 --- a/src/libcharon/encoding/payloads/traffic_selector_substructure.c +++ b/src/libcharon/encoding/payloads/traffic_selector_substructure.c @@ -14,11 +14,33 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "traffic_selector_substructure.h" #include -#include +#include +#include typedef struct private_traffic_selector_substructure_t private_traffic_selector_substructure_t; @@ -48,50 +70,32 @@ struct private_traffic_selector_substructure_t { uint16_t payload_length; /** - * Start port number. + * Port/address range or security label. */ - uint16_t start_port; - - /** - * End port number. - */ - uint16_t end_port; - - /** - * Starting address. - */ - chunk_t starting_address; - - /** - * Ending address. - */ - chunk_t ending_address; + chunk_t ts_data; }; /** - * Encoding rules to parse or generate a TS payload + * Encoding rules to parse or generate a TS payload. + * + * Due to the generic nature of security labels, the actual structure of regular + * TS is not parsed with these rules. * * The defined offsets are the positions in a object of type * private_traffic_selector_substructure_t. */ static encoding_rule_t encodings[] = { /* 1 Byte next ts type*/ - { TS_TYPE, offsetof(private_traffic_selector_substructure_t, ts_type) }, + { U_INT_8, offsetof(private_traffic_selector_substructure_t, ts_type) }, /* 1 Byte IP protocol id*/ { U_INT_8, offsetof(private_traffic_selector_substructure_t, ip_protocol_id) }, /* Length of the whole payload*/ { PAYLOAD_LENGTH,offsetof(private_traffic_selector_substructure_t, payload_length) }, - /* 2 Byte start port*/ - { U_INT_16, offsetof(private_traffic_selector_substructure_t, start_port) }, - /* 2 Byte end port*/ - { U_INT_16, offsetof(private_traffic_selector_substructure_t, end_port) }, - /* starting address is either 4 or 16 byte */ - { ADDRESS, offsetof(private_traffic_selector_substructure_t, starting_address) }, - /* ending address is either 4 or 16 byte */ - { ADDRESS, offsetof(private_traffic_selector_substructure_t, ending_address) } + /* traffic selector data, length is defined in PAYLOAD_LENGTH */ + { CHUNK_DATA, offsetof(private_traffic_selector_substructure_t, ts_data) }, }; -/* +/* Regular traffic selectors for address ranges: 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -107,48 +111,91 @@ static encoding_rule_t encodings[] = { ~ Ending Address* ~ ! ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + * Security labels: + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +---------------+---------------+-------------------------------+ + | TS Type | Reserved | Selector Length | + +---------------+---------------+-------------------------------+ + | | + ~ Security Label* ~ + | | + +---------------------------------------------------------------+ */ +/** + * Parse the data of a regular address range traffic selector. + */ +static bool parse_ts_data(private_traffic_selector_substructure_t *this, + uint16_t *start_port, uint16_t *end_port, + chunk_t *start_addr, chunk_t *end_addr) +{ + bio_reader_t *reader; + int addr_len; + + switch (this->ts_type) + { + case TS_IPV4_ADDR_RANGE: + addr_len = 4; + break; + case TS_IPV6_ADDR_RANGE: + addr_len = 16; + break; + default: + return FALSE; + } + + reader = bio_reader_create(this->ts_data); + if (!reader->read_uint16(reader, start_port) || + !reader->read_uint16(reader, end_port) || + !reader->read_data(reader, addr_len, start_addr) || + !reader->read_data(reader, addr_len, end_addr) || + reader->remaining(reader) > 0) + { + reader->destroy(reader); + return FALSE; + } + reader->destroy(reader); + return TRUE; +} + METHOD(payload_t, verify, status_t, private_traffic_selector_substructure_t *this) { - if (this->start_port > this->end_port) - { - /* OPAQUE ports are the only exception */ - if (this->start_port != 0xffff && this->end_port != 0) - { - return FAILED; - } - } switch (this->ts_type) { case TS_IPV4_ADDR_RANGE: - { - if ((this->starting_address.len != 4) || - (this->ending_address.len != 4)) - { - /* ipv4 address must be 4 bytes long */ - return FAILED; - } - break; - } case TS_IPV6_ADDR_RANGE: { - if ((this->starting_address.len != 16) || - (this->ending_address.len != 16)) + uint16_t start_port, end_port; + chunk_t start_addr, end_addr; + + if (!parse_ts_data(this, &start_port, &end_port, &start_addr, + &end_addr)) { - /* ipv6 address must be 16 bytes long */ return FAILED; } + if (start_port > end_port) + { + /* OPAQUE ports are the only exception */ + if (start_port != 0xffff && end_port != 0) + { + return FAILED; + } + } break; } + case TS_SECLABEL: + if (!this->ts_data.len) + { + return FAILED; + } + break; default: - { - /* not supported ts type */ - return FAILED; - } + /* unsupported TS type, just ignored later */ + break; } - return SUCCESS; } @@ -162,7 +209,7 @@ METHOD(payload_t, get_encoding_rules, int, METHOD(payload_t, get_header_length, int, private_traffic_selector_substructure_t *this) { - return 8; + return 4; } METHOD(payload_t, get_type, payload_type_t, @@ -191,17 +238,32 @@ METHOD(payload_t, get_length, size_t, METHOD(traffic_selector_substructure_t, get_traffic_selector, traffic_selector_t*, private_traffic_selector_substructure_t *this) { + uint16_t start_port, end_port; + chunk_t start_addr, end_addr; + + if (!parse_ts_data(this, &start_port, &end_port, &start_addr, &end_addr)) + { + return NULL; + } return traffic_selector_create_from_bytes( this->ip_protocol_id, this->ts_type, - this->starting_address, this->start_port, - this->ending_address, this->end_port); + start_addr, start_port, end_addr, end_port); +} + +METHOD(traffic_selector_substructure_t, get_sec_label, sec_label_t*, + private_traffic_selector_substructure_t *this) +{ + if (this->ts_type != TS_SECLABEL) + { + return NULL; + } + return sec_label_from_encoding(this->ts_data); } METHOD2(payload_t, traffic_selector_substructure_t, destroy, void, private_traffic_selector_substructure_t *this) { - free(this->starting_address.ptr); - free(this->ending_address.ptr); + free(this->ts_data.ptr); free(this); } @@ -225,6 +287,7 @@ traffic_selector_substructure_t *traffic_selector_substructure_create() .destroy = _destroy, }, .get_traffic_selector = _get_traffic_selector, + .get_sec_label = _get_sec_label, .destroy = _destroy, }, .payload_length = get_header_length(this), @@ -241,16 +304,34 @@ traffic_selector_substructure_t *traffic_selector_substructure_create_from_traff traffic_selector_t *ts) { private_traffic_selector_substructure_t *this; + bio_writer_t *writer; this = (private_traffic_selector_substructure_t*)traffic_selector_substructure_create(); this->ts_type = ts->get_type(ts); this->ip_protocol_id = ts->get_protocol(ts); - this->start_port = ts->get_from_port(ts); - this->end_port = ts->get_to_port(ts); - this->starting_address = chunk_clone(ts->get_from_address(ts)); - this->ending_address = chunk_clone(ts->get_to_address(ts)); - this->payload_length = get_header_length(this) + - this->ending_address.len + this->starting_address.len; + writer = bio_writer_create(this->ts_type == TS_IPV4_ADDR_RANGE ? 12 : 36); + writer->write_uint16(writer, ts->get_from_port(ts)); + writer->write_uint16(writer, ts->get_to_port(ts)); + writer->write_data(writer, ts->get_from_address(ts)); + writer->write_data(writer, ts->get_to_address(ts)); + this->ts_data = writer->extract_buf(writer); + this->payload_length += this->ts_data.len; + writer->destroy(writer); + return &this->public; +} + +/* + * Described in header + */ +traffic_selector_substructure_t *traffic_selector_substructure_create_from_sec_label( + sec_label_t *label) +{ + private_traffic_selector_substructure_t *this; + + this = (private_traffic_selector_substructure_t*)traffic_selector_substructure_create(); + this->ts_type = TS_SECLABEL; + this->ts_data = chunk_clone(label->get_encoding(label)); + this->payload_length += this->ts_data.len; return &this->public; } diff --git a/src/libcharon/encoding/payloads/traffic_selector_substructure.h b/src/libcharon/encoding/payloads/traffic_selector_substructure.h index d9698f86f..a22c7a935 100644 --- a/src/libcharon/encoding/payloads/traffic_selector_substructure.h +++ b/src/libcharon/encoding/payloads/traffic_selector_substructure.h @@ -13,6 +13,27 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ /** * @defgroup traffic_selector_substructure traffic_selector_substructure @@ -27,6 +48,7 @@ typedef struct traffic_selector_substructure_t traffic_selector_substructure_t; #include #include #include +#include #include /** @@ -35,88 +57,29 @@ typedef struct traffic_selector_substructure_t traffic_selector_substructure_t; * The TRAFFIC SELECTOR format is described in RFC section 3.13.1. */ struct traffic_selector_substructure_t { + /** * The payload_t interface. */ payload_t payload_interface; /** - * Get the type of Traffic selector. + * Get a traffic_selector_t from this substructure if possible. * - * @return type of traffic selector + * @warning the returned object must be destroyed after use * + * @return contained traffic_selector_t (NULL if type mismatch) */ - ts_type_t (*get_ts_type) (traffic_selector_substructure_t *this); + traffic_selector_t *(*get_traffic_selector)(traffic_selector_substructure_t *this); /** - * Set the type of Traffic selector. + * Get a sec_label_t from this substructure if possible. * - * @param ts_type type of traffic selector + * @warning the returned object must be destroyed after use + * + * @return contained sec_label_t (NULL if type mismatch) */ - void (*set_ts_type) (traffic_selector_substructure_t *this, - ts_type_t ts_type); - - /** - * Get the IP protocol ID of Traffic selector. - * - * @return type of traffic selector - * - */ - uint8_t (*get_protocol_id) (traffic_selector_substructure_t *this); - - /** - * Set the IP protocol ID of Traffic selector - * - * @param protocol_id protocol ID of traffic selector - */ - void (*set_protocol_id) (traffic_selector_substructure_t *this, - uint8_t protocol_id); - - /** - * Get the start port and address as host_t object. - * - * Returned host_t object has to get destroyed by the caller. - * - * @return start host as host_t object - * - */ - host_t *(*get_start_host) (traffic_selector_substructure_t *this); - - /** - * Set the start port and address as host_t object. - * - * @param start_host start host as host_t object - */ - void (*set_start_host) (traffic_selector_substructure_t *this, - host_t *start_host); - - /** - * Get the end port and address as host_t object. - * - * Returned host_t object has to get destroyed by the caller. - * - * @return end host as host_t object - * - */ - host_t *(*get_end_host) (traffic_selector_substructure_t *this); - - /** - * Set the end port and address as host_t object. - * - * @param end_host end host as host_t object - */ - void (*set_end_host) (traffic_selector_substructure_t *this, - host_t *end_host); - - /** - * Get a traffic_selector_t from this substructure. - * - * @warning traffic_selector_t must be destroyed after usage. - * - * @return contained traffic_selector_t - */ - traffic_selector_t *(*get_traffic_selector) ( - traffic_selector_substructure_t *this); + sec_label_t *(*get_sec_label)(traffic_selector_substructure_t *this); /** * Destroys an traffic_selector_substructure_t object. @@ -134,13 +97,21 @@ struct traffic_selector_substructure_t { traffic_selector_substructure_t *traffic_selector_substructure_create(void); /** - * Creates an initialized traffic selector substructure using - * the values from a traffic_selector_t. + * Creates a traffic selector substructure based on a traffic_selector_t. * - * @param traffic_selector traffic_selector_t to use for initialization + * @param traffic_selector data to use * @return traffic_selector_substructure_t object */ traffic_selector_substructure_t *traffic_selector_substructure_create_from_traffic_selector( traffic_selector_t *traffic_selector); +/** + * Creates a traffic selector substructure based on a sec_label_t. + * + * @param label data to use + * @return traffic_selector_substructure_t object + */ +traffic_selector_substructure_t *traffic_selector_substructure_create_from_sec_label( + sec_label_t *label); + #endif /** TRAFFIC_SELECTOR_SUBSTRUCTURE_H_ @}*/ From f4c0ec0b713b4f11ee9b436f8fa2499b30530859 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 10 Jan 2022 17:39:56 +0100 Subject: [PATCH 17/47] ts-payload: Add support for TS of type TS_SECLABEL The security labels can be retrieved in a separate list from the regular traffic selectors. We currently only plan to support a single security label ourselves, so when generating we don't expect a list. --- src/conftest/hooks/pretend_auth.c | 4 +- src/libcharon/encoding/payloads/ts_payload.c | 49 +++++++++++++++++--- src/libcharon/encoding/payloads/ts_payload.h | 20 ++++++-- src/libcharon/sa/ikev2/tasks/child_create.c | 4 +- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/conftest/hooks/pretend_auth.c b/src/conftest/hooks/pretend_auth.c index 5a86c5392..94b0ae4c1 100644 --- a/src/conftest/hooks/pretend_auth.c +++ b/src/conftest/hooks/pretend_auth.c @@ -305,12 +305,12 @@ static void process_auth_response(private_pretend_auth_t *this, if (this->tsi) { message->add_payload(message, (payload_t*) - ts_payload_create_from_traffic_selectors(TRUE, this->tsi)); + ts_payload_create_from_traffic_selectors(TRUE, this->tsi, NULL)); } if (this->tsr) { message->add_payload(message, (payload_t*) - ts_payload_create_from_traffic_selectors(FALSE, this->tsr)); + ts_payload_create_from_traffic_selectors(FALSE, this->tsr, NULL)); } } diff --git a/src/libcharon/encoding/payloads/ts_payload.c b/src/libcharon/encoding/payloads/ts_payload.c index 2862b6acf..3ea1aaab7 100644 --- a/src/libcharon/encoding/payloads/ts_payload.c +++ b/src/libcharon/encoding/payloads/ts_payload.c @@ -218,10 +218,11 @@ METHOD(ts_payload_t, set_initiator, void, this->is_initiator = is_initiator; } -METHOD(ts_payload_t, get_traffic_selectors, linked_list_t*, - private_ts_payload_t *this) +/** + * Get a list of either traffic selectors or labels + */ +static linked_list_t *get_list(private_ts_payload_t *this, bool labels) { - traffic_selector_t *ts; enumerator_t *enumerator; traffic_selector_substructure_t *subst; linked_list_t *list; @@ -230,14 +231,42 @@ METHOD(ts_payload_t, get_traffic_selectors, linked_list_t*, enumerator = this->substrs->create_enumerator(this->substrs); while (enumerator->enumerate(enumerator, &subst)) { - ts = subst->get_traffic_selector(subst); - list->insert_last(list, ts); + if (labels) + { + sec_label_t *label = subst->get_sec_label(subst); + + if (label) + { + list->insert_last(list, label); + } + } + else + { + traffic_selector_t *ts = subst->get_traffic_selector(subst); + + if (ts) + { + list->insert_last(list, ts); + } + } } enumerator->destroy(enumerator); return list; } +METHOD(ts_payload_t, get_traffic_selectors, linked_list_t*, + private_ts_payload_t *this) +{ + return get_list(this, FALSE); +} + +METHOD(ts_payload_t, get_sec_labels, linked_list_t*, + private_ts_payload_t *this) +{ + return get_list(this, TRUE); +} + METHOD2(payload_t, ts_payload_t, destroy, void, private_ts_payload_t *this) { @@ -267,6 +296,7 @@ ts_payload_t *ts_payload_create(bool is_initiator) .get_initiator = _get_initiator, .set_initiator = _set_initiator, .get_traffic_selectors = _get_traffic_selectors, + .get_sec_labels = _get_sec_labels, .destroy = _destroy, }, .next_payload = PL_NONE, @@ -281,7 +311,8 @@ ts_payload_t *ts_payload_create(bool is_initiator) * Described in header */ ts_payload_t *ts_payload_create_from_traffic_selectors(bool is_initiator, - linked_list_t *traffic_selectors) + linked_list_t *traffic_selectors, + sec_label_t *label) { enumerator_t *enumerator; traffic_selector_t *ts; @@ -297,6 +328,12 @@ ts_payload_t *ts_payload_create_from_traffic_selectors(bool is_initiator, this->substrs->insert_last(this->substrs, subst); } enumerator->destroy(enumerator); + + if (label) + { + subst = traffic_selector_substructure_create_from_sec_label(label); + this->substrs->insert_last(this->substrs, subst); + } compute_length(this); return &this->public; diff --git a/src/libcharon/encoding/payloads/ts_payload.h b/src/libcharon/encoding/payloads/ts_payload.h index 8b7824849..7038bb26b 100644 --- a/src/libcharon/encoding/payloads/ts_payload.h +++ b/src/libcharon/encoding/payloads/ts_payload.h @@ -63,11 +63,20 @@ struct ts_payload_t { /** * Get a list of nested traffic selectors as traffic_selector_t. * - * Resulting list and its traffic selectors must be destroyed after usage + * Resulting list and its traffic selectors must be destroyed after use. * * @return list of traffic selectors */ - linked_list_t *(*get_traffic_selectors) (ts_payload_t *this); + linked_list_t *(*get_traffic_selectors)(ts_payload_t *this); + + /** + * Get a list of security labels as sec_label_t. + * + * Resulting list and its security labels must be destroyed after use. + * + * @return list of security labels + */ + linked_list_t *(*get_sec_labels)(ts_payload_t *this); /** * Destroys an ts_payload_t object. @@ -84,13 +93,16 @@ struct ts_payload_t { ts_payload_t *ts_payload_create(bool is_initiator); /** - * Creates ts_payload with a list of traffic_selector_t + * Creates ts_payload with a list of traffic_selector_t and an optional security + * label. * * @param is_initiator TRUE for TSi, FALSE for TSr payload type * @param traffic_selectors list of traffic selectors to include + * @param label optional security label to include * @return ts_payload_t object */ ts_payload_t *ts_payload_create_from_traffic_selectors(bool is_initiator, - linked_list_t *traffic_selectors); + linked_list_t *traffic_selectors, + sec_label_t *label); #endif /** TS_PAYLOAD_H_ @}*/ diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index dd693c700..83f3c7328 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -891,9 +891,9 @@ static bool build_payloads(private_child_create_t *this, message_t *message) } /* add TSi/TSr payloads */ - ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi); + ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi, NULL); message->add_payload(message, (payload_t*)ts_payload); - ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr); + ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr, NULL); message->add_payload(message, (payload_t*)ts_payload); /* add a notify if we are not in tunnel mode */ From c4e2b7617f471ad6b88318106eb1f71729dae7ed Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 13 Jan 2022 14:26:06 +0100 Subject: [PATCH 18/47] encoding: Remove unused TS_TYPE and ADDRESS encodings --- src/libcharon/encoding/generator.c | 4 ---- src/libcharon/encoding/parser.c | 23 ------------------ src/libcharon/encoding/payloads/encodings.c | 2 -- src/libcharon/encoding/payloads/encodings.h | 26 --------------------- 4 files changed, 55 deletions(-) diff --git a/src/libcharon/encoding/generator.c b/src/libcharon/encoding/generator.c index f898c87bd..4bf0ab0cf 100644 --- a/src/libcharon/encoding/generator.c +++ b/src/libcharon/encoding/generator.c @@ -205,7 +205,6 @@ static void generate_u_int_type(private_generator_t *this, case U_INT_4: number_of_bits = 4; break; - case TS_TYPE: case RESERVED_BYTE: case SPI_SIZE: case U_INT_8: @@ -281,7 +280,6 @@ static void generate_u_int_type(private_generator_t *this, } break; } - case TS_TYPE: case RESERVED_BYTE: case SPI_SIZE: case U_INT_8: @@ -478,7 +476,6 @@ METHOD(generator_t, generate_payload, void, case IKE_SPI: case RESERVED_BYTE: case SPI_SIZE: - case TS_TYPE: case ATTRIBUTE_TYPE: case ATTRIBUTE_LENGTH: generate_u_int_type(this, rules[i].type, rules[i].offset); @@ -491,7 +488,6 @@ METHOD(generator_t, generate_payload, void, this->header_length_offset = get_offset(this); generate_u_int_type(this, U_INT_32, rules[i].offset); break; - case ADDRESS: case SPI: case CHUNK_DATA: case ENCRYPTED_DATA: diff --git a/src/libcharon/encoding/parser.c b/src/libcharon/encoding/parser.c index de66ad467..6e2e175d8 100644 --- a/src/libcharon/encoding/parser.c +++ b/src/libcharon/encoding/parser.c @@ -368,7 +368,6 @@ METHOD(parser_t, parse_payload, status_t, payload_t *pld; void *output; int payload_length = 0, spi_size = 0, attribute_length = 0, header_length; - uint16_t ts_type = 0; bool attribute_format = FALSE; int rule_number, rule_count; encoding_rule_t *rule; @@ -588,28 +587,6 @@ METHOD(parser_t, parse_payload, status_t, } break; } - case TS_TYPE: - { - if (!parse_uint8(this, rule_number, output + rule->offset)) - { - pld->destroy(pld); - return PARSE_ERROR; - } - ts_type = *(uint8_t*)(output + rule->offset); - break; - } - case ADDRESS: - { - int address_length = (ts_type == TS_IPV4_ADDR_RANGE) ? 4 : 16; - - if (!parse_chunk(this, rule_number, output + rule->offset, - address_length)) - { - pld->destroy(pld); - return PARSE_ERROR; - } - break; - } default: { DBG1(DBG_ENC, " no rule to parse rule %d %N", diff --git a/src/libcharon/encoding/payloads/encodings.c b/src/libcharon/encoding/payloads/encodings.c index e81122e87..580925c27 100644 --- a/src/libcharon/encoding/payloads/encodings.c +++ b/src/libcharon/encoding/payloads/encodings.c @@ -34,8 +34,6 @@ ENUM(encoding_type_names, U_INT_4, ENCRYPTED_DATA, "ATTRIBUTE_LENGTH_OR_VALUE", "ATTRIBUTE_LENGTH", "ATTRIBUTE_VALUE", - "TS_TYPE", - "ADDRESS", "CHUNK_DATA", "IKE_SPI", "ENCRYPTED_DATA", diff --git a/src/libcharon/encoding/payloads/encodings.h b/src/libcharon/encoding/payloads/encodings.h index fb0e9ca3d..e5ea62c72 100644 --- a/src/libcharon/encoding/payloads/encodings.h +++ b/src/libcharon/encoding/payloads/encodings.h @@ -255,32 +255,6 @@ enum encoding_type_t { */ ATTRIBUTE_VALUE, - /** - * Representing a Traffic selector type field. - * - * When generating it must be changed from host to network order. - * The value is read from the associated data struct. - * The current write position is moved 16 bit forward afterwards. - * - * When parsing it must be changed from network to host order. - * The value is written to the associated data struct. - * The current read pointer is moved 16 bit forward afterwards. - */ - TS_TYPE, - - /** - * Representing an address field in a traffic selector. - * - * Depending on the last field of type TS_TYPE - * this field is either 4 or 16 byte long. - * - * When generating the content of the chunk pointing to - * is written. - * - * When parsing 4 or 16 bytes are read and written into the chunk pointing to. - */ - ADDRESS, - /** * Representing a variable length byte field. */ From bf0542c4e1f37b3744ee2389f63ab506d24784b1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 15:02:42 +0100 Subject: [PATCH 19/47] kernel-netlink: Add support for optional security label on SAs and policies --- src/libcharon/kernel/kernel_ipsec.h | 5 + .../kernel_netlink/kernel_netlink_ipsec.c | 148 ++++++++++++++---- 2 files changed, 120 insertions(+), 33 deletions(-) diff --git a/src/libcharon/kernel/kernel_ipsec.h b/src/libcharon/kernel/kernel_ipsec.h index 70ff2eb12..343cdd136 100644 --- a/src/libcharon/kernel/kernel_ipsec.h +++ b/src/libcharon/kernel/kernel_ipsec.h @@ -38,6 +38,7 @@ typedef struct kernel_ipsec_query_policy_t kernel_ipsec_query_policy_t; #include #include #include +#include #include #include @@ -97,6 +98,8 @@ struct kernel_ipsec_add_sa_t { hw_offload_t hw_offload; /** Mark the SA should apply to packets after processing */ mark_t mark; + /** Security label to match or apply */ + sec_label_t *label; /** TRUE to use Extended Sequence Numbers */ bool esn; /** TRUE to copy the DF bit to the outer IPv4 header in tunnel mode */ @@ -160,6 +163,8 @@ struct kernel_ipsec_policy_id_t { uint32_t if_id; /** Network interface restricting policy */ char *interface; + /** Security label restricting policy */ + sec_label_t *label; }; /** diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index c3c6d6035..c46965127 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -577,6 +577,9 @@ struct policy_entry_t { /** Optional interface ID */ uint32_t if_id; + /** Optional security label */ + sec_label_t *label; + /** Associated route installed for this policy */ route_entry_t *route; @@ -609,6 +612,7 @@ static void policy_entry_destroy(private_kernel_netlink_ipsec_t *this, policy->direction, this); policy->used_by->destroy(policy->used_by); } + DESTROY_IF(policy->label); free(policy); } @@ -618,8 +622,15 @@ static void policy_entry_destroy(private_kernel_netlink_ipsec_t *this, static u_int policy_hash(policy_entry_t *key) { chunk_t chunk = chunk_from_thing(key->sel); - return chunk_hash_inc(chunk, chunk_hash_inc(chunk_from_thing(key->mark), + u_int hash; + + hash = chunk_hash_inc(chunk, chunk_hash_inc(chunk_from_thing(key->mark), chunk_hash(chunk_from_thing(key->if_id)))); + if (key->label) + { + hash = key->label->hash(key->label, hash); + } + return hash; } /** @@ -630,7 +641,8 @@ static bool policy_equals(policy_entry_t *key, policy_entry_t *other_key) return memeq(&key->sel, &other_key->sel, sizeof(struct xfrm_selector)) && key->mark == other_key->mark && key->if_id == other_key->if_id && - key->direction == other_key->direction; + key->direction == other_key->direction && + sec_labels_equal(key->label, other_key->label); } /** @@ -1286,6 +1298,47 @@ static bool add_mark(struct nlmsghdr *hdr, int buflen, mark_t mark) return TRUE; } +/** + * Format the security label for debug messages + */ +static void format_label(char *buf, int buflen, sec_label_t *label) +{ + if (label) + { + snprintf(buf, buflen, " (ctx %s)", label->get_string(label)); + } +} + +/** + * Add a security label to message if required + */ +static bool add_label(struct nlmsghdr *hdr, int buflen, sec_label_t *label) +{ + if (label) + { +#ifdef USE_SELINUX + struct xfrm_user_sec_ctx *ctx; + chunk_t enc = label->get_encoding(label); + int len = sizeof(*ctx) + enc.len; + + ctx = netlink_reserve(hdr, buflen, XFRMA_SEC_CTX, len); + if (!ctx) + { + return FALSE; + } + /* this attribute for some reason duplicates the generic header */ + ctx->exttype = XFRMA_SEC_CTX; + ctx->len = len; + + ctx->ctx_doi = XFRM_SC_DOI_LSM; + ctx->ctx_alg = XFRM_SC_ALG_SELINUX; + ctx->ctx_len = enc.len; + memcpy((void*)(ctx + 1), enc.ptr, enc.len); +#endif + } + return TRUE; +} + /** * Add a uint32 attribute to message */ @@ -1874,6 +1927,11 @@ METHOD(kernel_ipsec_t, add_sa, status_t, goto failed; } + if (!add_label(hdr, sizeof(request), data->label)) + { + goto failed; + } + if (ipcomp == IPCOMP_NONE && (data->mark.value | data->mark.mask)) { if (!add_uint32(hdr, sizeof(request), XFRMA_SET_MARK, @@ -2747,7 +2805,9 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, } tmpl->reqid = ipsec->cfg.reqid; tmpl->id.proto = protos[i].proto; - if (policy->direction == POLICY_OUT) + /* in order to match SAs with all matching labels, we can't have the + * SPI in the template */ + if (policy->direction == POLICY_OUT && !policy->label) { tmpl->id.spi = protos[i].spi; } @@ -2781,6 +2841,11 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, policy_change_done(this, policy); return FAILED; } + if (!add_label(hdr, sizeof(request), policy->label)) + { + policy_change_done(this, policy); + return FAILED; + } this->mutex->unlock(this->mutex); status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr); @@ -2826,7 +2891,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t, policy_sa_t *assigned_sa, *current_sa; enumerator_t *enumerator; bool found = FALSE, update = TRUE; - char markstr[32] = ""; + char markstr[32] = "", labelstr[128] = ""; uint32_t cur_priority = 0; int use_count; @@ -2835,10 +2900,12 @@ METHOD(kernel_ipsec_t, add_policy, status_t, .sel = ts2selector(id->src_ts, id->dst_ts, id->interface), .mark = id->mark.value & id->mark.mask, .if_id = id->if_id, + .label = id->label ? id->label->clone(id->label) : NULL, .direction = id->dir, .reqid = data->sa->reqid, ); format_mark(markstr, sizeof(markstr), id->mark); + format_label(labelstr, sizeof(labelstr), id->label); /* find the policy, which matches EXACTLY */ this->mutex->lock(this->mutex); @@ -2848,18 +2915,18 @@ METHOD(kernel_ipsec_t, add_policy, status_t, if (current->reqid && data->sa->reqid && current->reqid != data->sa->reqid) { - DBG1(DBG_CFG, "unable to install policy %R === %R %N%s for reqid " + DBG1(DBG_CFG, "unable to install policy %R === %R %N%s%s for reqid " "%u, the same policy for reqid %u exists", id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, - data->sa->reqid, current->reqid); + labelstr, data->sa->reqid, current->reqid); policy_entry_destroy(this, policy); this->mutex->unlock(this->mutex); return INVALID_STATE; } /* use existing policy */ - DBG2(DBG_KNL, "policy %R === %R %N%s already exists, increasing " + DBG2(DBG_KNL, "policy %R === %R %N%s%s already exists, increasing " "refcount", id->src_ts, id->dst_ts, policy_dir_names, id->dir, - markstr); + markstr, labelstr); policy_entry_destroy(this, policy); policy = current; found = TRUE; @@ -2923,9 +2990,9 @@ METHOD(kernel_ipsec_t, add_policy, status_t, { /* we don't update the policy if the priority is lower than that of * the currently installed one */ policy_change_done(this, policy); - DBG2(DBG_KNL, "not updating policy %R === %R %N%s [priority %u, " + DBG2(DBG_KNL, "not updating policy %R === %R %N%s%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, - id->dir, markstr, cur_priority, use_count); + id->dir, markstr, labelstr, cur_priority, use_count); return SUCCESS; } policy->reqid = assigned_sa->sa->cfg.reqid; @@ -2935,15 +3002,16 @@ METHOD(kernel_ipsec_t, add_policy, status_t, found = TRUE; } - DBG2(DBG_KNL, "%s policy %R === %R %N%s [priority %u, refcount %d]", + DBG2(DBG_KNL, "%s policy %R === %R %N%s%s [priority %u, refcount %d]", found ? "updating" : "adding", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr, assigned_sa->priority, use_count); + policy_dir_names, id->dir, markstr, labelstr, assigned_sa->priority, + use_count); if (add_policy_internal(this, policy, assigned_sa, found) != SUCCESS) { - DBG1(DBG_KNL, "unable to %s policy %R === %R %N%s", + DBG1(DBG_KNL, "unable to %s policy %R === %R %N%s%s", found ? "update" : "add", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr); + policy_dir_names, id->dir, markstr, labelstr); return FAILED; } return SUCCESS; @@ -2958,13 +3026,14 @@ METHOD(kernel_ipsec_t, query_policy, status_t, struct xfrm_userpolicy_id *policy_id; struct xfrm_userpolicy_info *policy = NULL; size_t len; - char markstr[32] = ""; + char markstr[32] = "", labelstr[128] = ""; memset(&request, 0, sizeof(request)); format_mark(markstr, sizeof(markstr), id->mark); + format_label(labelstr, sizeof(labelstr), id->label); - DBG2(DBG_KNL, "querying policy %R === %R %N%s", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr); + DBG2(DBG_KNL, "querying policy %R === %R %N%s%s", id->src_ts, id->dst_ts, + policy_dir_names, id->dir, markstr, labelstr); hdr = &request.hdr; hdr->nlmsg_flags = NLM_F_REQUEST; @@ -2983,6 +3052,10 @@ METHOD(kernel_ipsec_t, query_policy, status_t, { return FAILED; } + if (!add_label(hdr, sizeof(request), id->label)) + { + return FAILED; + } if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS) { @@ -3054,20 +3127,22 @@ METHOD(kernel_ipsec_t, del_policy, status_t, .if_id = id->if_id, .cfg = *data->sa, }; - char markstr[32] = ""; + char markstr[32] = "", labelstr[128] = ""; int use_count; status_t status = SUCCESS; format_mark(markstr, sizeof(markstr), id->mark); + format_label(labelstr, sizeof(labelstr), id->label); - DBG2(DBG_KNL, "deleting policy %R === %R %N%s", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr); + DBG2(DBG_KNL, "deleting policy %R === %R %N%s%s", id->src_ts, id->dst_ts, + policy_dir_names, id->dir, markstr, labelstr); /* create a policy */ memset(&policy, 0, sizeof(policy_entry_t)); policy.sel = ts2selector(id->src_ts, id->dst_ts, id->interface); policy.mark = id->mark.value & id->mark.mask; policy.if_id = id->if_id; + policy.label = id->label; policy.direction = id->dir; /* find the policy */ @@ -3075,8 +3150,9 @@ METHOD(kernel_ipsec_t, del_policy, status_t, current = this->policies->get(this->policies, &policy); if (!current) { - DBG1(DBG_KNL, "deleting policy %R === %R %N%s failed, not found", - id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr); + DBG1(DBG_KNL, "deleting policy %R === %R %N%s%s failed, not found", + id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, + labelstr); this->mutex->unlock(this->mutex); return NOT_FOUND; } @@ -3089,7 +3165,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, current->waiting--; /* remove mapping to SA by reqid and priority */ - auto_priority = get_priority(current, data->prio,id->interface); + auto_priority = get_priority(current, data->prio, id->interface); priority = this->get_priority ? this->get_priority(id, data) : data->manual_prio; priority = priority ?: auto_priority; @@ -3121,22 +3197,23 @@ METHOD(kernel_ipsec_t, del_policy, status_t, if (!is_installed) { /* no need to update as the policy was not installed for this SA */ policy_change_done(this, current); - DBG2(DBG_KNL, "not updating policy %R === %R %N%s [priority %u, " + DBG2(DBG_KNL, "not updating policy %R === %R %N%s%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, - id->dir, markstr, cur_priority, use_count); + id->dir, markstr, labelstr, cur_priority, use_count); return SUCCESS; } current->used_by->get_first(current->used_by, (void**)&mapping); current->reqid = mapping->sa->cfg.reqid; - DBG2(DBG_KNL, "updating policy %R === %R %N%s [priority %u, " + DBG2(DBG_KNL, "updating policy %R === %R %N%s%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, id->dir, - markstr, mapping->priority, use_count); + markstr, labelstr, mapping->priority, use_count); if (add_policy_internal(this, current, mapping, TRUE) != SUCCESS) { - DBG1(DBG_KNL, "unable to update policy %R === %R %N%s", - id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr); + DBG1(DBG_KNL, "unable to update policy %R === %R %N%s%s", + id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, + labelstr); return FAILED; } return SUCCESS; @@ -3163,6 +3240,11 @@ METHOD(kernel_ipsec_t, del_policy, status_t, policy_change_done(this, current); return FAILED; } + if (!add_label(hdr, sizeof(request), id->label)) + { + policy_change_done(this, current); + return FAILED; + } if (current->route) { @@ -3173,16 +3255,16 @@ METHOD(kernel_ipsec_t, del_policy, status_t, route->pass) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with policy " - "%R === %R %N%s", id->src_ts, id->dst_ts, policy_dir_names, - id->dir, markstr); + "%R === %R %N%s%s", id->src_ts, id->dst_ts, policy_dir_names, + id->dir, markstr, labelstr); } } this->mutex->unlock(this->mutex); if (this->socket_xfrm->send_ack(this->socket_xfrm, hdr) != SUCCESS) { - DBG1(DBG_KNL, "unable to delete policy %R === %R %N%s", id->src_ts, - id->dst_ts, policy_dir_names, id->dir, markstr); + DBG1(DBG_KNL, "unable to delete policy %R === %R %N%s%s", id->src_ts, + id->dst_ts, policy_dir_names, id->dir, markstr, labelstr); status = FAILED; } From ef72ac88c32e11ab39840636fc567b658a5cc5b7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 13:49:56 +0100 Subject: [PATCH 20/47] child-cfg: Add optional security label and mode --- src/libcharon/config/child_cfg.c | 32 +++++++++++++++++++++++++++++++- src/libcharon/config/child_cfg.h | 18 ++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/libcharon/config/child_cfg.c b/src/libcharon/config/child_cfg.c index 9ea6186c3..92a1ed502 100644 --- a/src/libcharon/config/child_cfg.c +++ b/src/libcharon/config/child_cfg.c @@ -143,6 +143,16 @@ struct private_child_cfg_t { */ mark_t set_mark_out; + /** + * Optional security label for policies + */ + sec_label_t *label; + + /** + * Optional label mode for policies + */ + sec_label_mode_t label_mode; + /** * Traffic Flow Confidentiality padding, if enabled */ @@ -522,6 +532,18 @@ METHOD(child_cfg_t, get_set_mark, mark_t, return inbound ? this->set_mark_in : this->set_mark_out; } +METHOD(child_cfg_t, get_label, sec_label_t*, + private_child_cfg_t *this) +{ + return this->label; +} + +METHOD(child_cfg_t, get_label_mode, sec_label_mode_t, + private_child_cfg_t *this) +{ + return this->label_mode; +} + METHOD(child_cfg_t, get_tfc, uint32_t, private_child_cfg_t *this) { @@ -607,7 +629,9 @@ METHOD(child_cfg_t, equals, bool, this->hw_offload == other->hw_offload && this->copy_dscp == other->copy_dscp && streq(this->updown, other->updown) && - streq(this->interface, other->interface); + streq(this->interface, other->interface) && + sec_labels_equal(this->label, other->label) && + this->label_mode == other->label_mode; } METHOD(child_cfg_t, get_ref, child_cfg_t*, @@ -625,6 +649,7 @@ METHOD(child_cfg_t, destroy, void, this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy)); this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy)); + DESTROY_IF(this->label); free(this->updown); free(this->interface); free(this->name); @@ -659,6 +684,8 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data) .get_if_id = _get_if_id, .get_mark = _get_mark, .get_set_mark = _get_set_mark, + .get_label = _get_label, + .get_label_mode = _get_label_mode, .get_tfc = _get_tfc, .get_manual_prio = _get_manual_prio, .get_interface = _get_interface, @@ -685,6 +712,9 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data) .mark_out = data->mark_out, .set_mark_in = data->set_mark_in, .set_mark_out = data->set_mark_out, + .label = data->label ? data->label->clone(data->label) : NULL, + .label_mode = data->label_mode != SEC_LABEL_MODE_SYSTEM ? + data->label_mode : sec_label_mode_default(), .lifetime = data->lifetime, .inactivity = data->inactivity, .tfc = data->tfc, diff --git a/src/libcharon/config/child_cfg.h b/src/libcharon/config/child_cfg.h index 4de978826..15c52b7bc 100644 --- a/src/libcharon/config/child_cfg.h +++ b/src/libcharon/config/child_cfg.h @@ -247,6 +247,20 @@ struct child_cfg_t { */ mark_t (*get_set_mark)(child_cfg_t *this, bool inbound); + /** + * Optional security label to be configured on policies. + * + * @return label or NULL + */ + sec_label_t *(*get_label)(child_cfg_t *this); + + /** + * Get the mode in which the security label is used. + * + * @return label mode (never SEC_LABEL_MODE_SYSTEM) + */ + sec_label_mode_t (*get_label_mode)(child_cfg_t *this); + /** * Get the TFC padding value to use for CHILD_SA. * @@ -367,6 +381,10 @@ struct child_cfg_create_t { mark_t set_mark_in; /** Optional outbound mark the SA should apply to traffic */ mark_t set_mark_out; + /** Optional security label configured on policies (cloned) */ + sec_label_t *label; + /** Optional security label mode */ + sec_label_mode_t label_mode; /** Mode to propose for CHILD_SA */ ipsec_mode_t mode; /** TFC padding size, 0 to disable, -1 to pad to PMTU */ From 63d4bd071ff2c9b08f41d2db71d95148f2b8876b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 11 Jan 2022 10:15:53 +0100 Subject: [PATCH 21/47] child-cfg: Add method to select a security label --- src/libcharon/config/child_cfg.c | 80 ++++++++++++++++++++++++++++++++ src/libcharon/config/child_cfg.h | 21 +++++++++ 2 files changed, 101 insertions(+) diff --git a/src/libcharon/config/child_cfg.c b/src/libcharon/config/child_cfg.c index 92a1ed502..862ec69ea 100644 --- a/src/libcharon/config/child_cfg.c +++ b/src/libcharon/config/child_cfg.c @@ -544,6 +544,85 @@ METHOD(child_cfg_t, get_label_mode, sec_label_mode_t, return this->label_mode; } +METHOD(child_cfg_t, select_label, bool, + private_child_cfg_t *this, linked_list_t *labels, bool log, + sec_label_t **label, bool *exact_out) +{ + enumerator_t *enumerator; + sec_label_t *current, *match = NULL; + bool exact = FALSE; + + if (labels && labels->get_count(labels)) + { + if (!this->label) + { + DBG2(DBG_CFG, "peer proposed a security label, but none expected"); + return FALSE; + } + if (log) + { + DBG2(DBG_CFG, "selecting security label matching '%s':", + this->label->get_string(this->label)); + } + enumerator = labels->create_enumerator(labels); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (this->label->equals(this->label, current)) + { + if (log) + { + DBG2(DBG_CFG, " %s => matches exactly", + current->get_string(current)); + } + match = current; + exact = TRUE; + break; + } + else if (this->label_mode == SEC_LABEL_MODE_SELINUX && + this->label->matches(this->label, current)) + { + if (log) + { + DBG2(DBG_CFG, " %s => matches%s", + current->get_string(current), match ? ", ignored" : ""); + } + /* return the first match if we don't find an exact one */ + if (!match) + { + match = current; + } + } + else if (log) + { + DBG2(DBG_CFG, " %s => no match", current->get_string(current)); + } + } + enumerator->destroy(enumerator); + if (!match) + { + DBG2(DBG_CFG, "none of the proposed security labels match the " + "configured label '%s'", this->label->get_string(this->label)); + return FALSE; + } + } + else if (this->label) + { + DBG2(DBG_CFG, "peer didn't propose any security labels, we expect one " + "matching '%s'", this->label->get_string(this->label)); + return FALSE; + } + + if (label) + { + *label = match; + } + if (exact_out) + { + *exact_out = exact; + } + return TRUE; +} + METHOD(child_cfg_t, get_tfc, uint32_t, private_child_cfg_t *this) { @@ -686,6 +765,7 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data) .get_set_mark = _get_set_mark, .get_label = _get_label, .get_label_mode = _get_label_mode, + .select_label = _select_label, .get_tfc = _get_tfc, .get_manual_prio = _get_manual_prio, .get_interface = _get_interface, diff --git a/src/libcharon/config/child_cfg.h b/src/libcharon/config/child_cfg.h index 15c52b7bc..710596d89 100644 --- a/src/libcharon/config/child_cfg.h +++ b/src/libcharon/config/child_cfg.h @@ -261,6 +261,27 @@ struct child_cfg_t { */ sec_label_mode_t (*get_label_mode)(child_cfg_t *this); + /** + * Select a security label from the given list that matches the configured + * label. + * + * This fails under the following conditions: + * - a label is configured but no labels are provided + * - no label is configured but at least one label is provided + * - the configured and provided labels don't match + * + * If no label is configured and none are provided, that's considered a + * success and label will be set to NULL. + * + * @param labels list of labels to match + * @param log FALSE to avoid logging details about the selection + * @param label[out] selected label or NULL if no label necessary + * @param exact[out] TRUE if there was an exact match + * @return FALSE on failure + */ + bool (*select_label)(child_cfg_t *this, linked_list_t *labels, bool log, + sec_label_t **label, bool *exact); + /** * Get the TFC padding value to use for CHILD_SA. * From 03729958db9ed7e9a68daa1be308740ba7a4c323 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 10 Jan 2022 18:31:34 +0100 Subject: [PATCH 22/47] peer-cfg: Consider security labels when selecting child configs --- src/libcharon/config/peer_cfg.c | 42 +++++++++++++++------ src/libcharon/config/peer_cfg.h | 11 ++++-- src/libcharon/sa/ikev1/tasks/quick_mode.c | 2 +- src/libcharon/sa/ikev2/tasks/child_create.c | 5 ++- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/libcharon/config/peer_cfg.c b/src/libcharon/config/peer_cfg.c index 2350ad501..1f2c5f109 100644 --- a/src/libcharon/config/peer_cfg.c +++ b/src/libcharon/config/peer_cfg.c @@ -386,13 +386,28 @@ METHOD(peer_cfg_t, create_child_cfg_enumerator, enumerator_t*, /** * Check how good a list of TS matches a given child config */ -static int get_ts_match(child_cfg_t *cfg, bool local, - linked_list_t *sup_list, linked_list_t *hosts) +static u_int get_ts_match(child_cfg_t *cfg, bool local, + linked_list_t *sup_list, linked_list_t *hosts, + linked_list_t *sup_labels) { linked_list_t *cfg_list; enumerator_t *sup_enum, *cfg_enum; traffic_selector_t *sup_ts, *cfg_ts, *subset; - int match = 0, round; + sec_label_t *label; + u_int match = 0, round; + bool exact = FALSE; + + if (cfg->select_label(cfg, sup_labels, TRUE, &label, &exact)) + { + if (label) + { + match += exact ? 500 : 100; + } + } + else + { /* label config doesn't match, no need to check TS */ + return match; + } /* fetch configured TS list, narrowing dynamic TS */ cfg_list = cfg->get_traffic_selectors(cfg, local, NULL, hosts, TRUE); @@ -432,24 +447,29 @@ static int get_ts_match(child_cfg_t *cfg, bool local, METHOD(peer_cfg_t, select_child_cfg, child_cfg_t*, private_peer_cfg_t *this, linked_list_t *my_ts, linked_list_t *other_ts, - linked_list_t *my_hosts, linked_list_t *other_hosts) + linked_list_t *my_hosts, linked_list_t *other_hosts, + linked_list_t *my_labels, linked_list_t *other_labels) { child_cfg_t *current, *found = NULL; enumerator_t *enumerator; - int best = 0; + u_int best = 0; DBG2(DBG_CFG, "looking for a child config for %#R === %#R", my_ts, other_ts); enumerator = create_child_cfg_enumerator(this); while (enumerator->enumerate(enumerator, ¤t)) { - int my_prio, other_prio; + u_int my_prio, other_prio; - my_prio = get_ts_match(current, TRUE, my_ts, my_hosts); - other_prio = get_ts_match(current, FALSE, other_ts, other_hosts); - - if (my_prio && other_prio) + my_prio = get_ts_match(current, TRUE, my_ts, my_hosts, my_labels); + if (!my_prio) { - DBG2(DBG_CFG, " candidate \"%s\" with prio %d+%d", + continue; + } + other_prio = get_ts_match(current, FALSE, other_ts, other_hosts, + other_labels); + if (other_prio) + { + DBG2(DBG_CFG, " candidate \"%s\" with prio %u+%u", current->get_name(current), my_prio, other_prio); if (my_prio + other_prio > best) { diff --git a/src/libcharon/config/peer_cfg.h b/src/libcharon/config/peer_cfg.h index b80e809c2..da3d6a0cc 100644 --- a/src/libcharon/config/peer_cfg.h +++ b/src/libcharon/config/peer_cfg.h @@ -174,17 +174,20 @@ struct peer_cfg_t { enumerator_t* (*create_child_cfg_enumerator) (peer_cfg_t *this); /** - * Select a CHILD config from traffic selectors. + * Select a CHILD config from received traffic selectors. * * @param my_ts TS for local side * @param other_ts TS for remote side * @param my_hosts hosts to narrow down dynamic TS for local side * @param other_hosts hosts to narrow down dynamic TS for remote side - * @return selected CHILD config, or NULL if no match found + * @param my_labels optional local security labels + * @param other_labels optional remove security labels + * @return selected CHILD config, or NULL if no match found */ - child_cfg_t* (*select_child_cfg) (peer_cfg_t *this, + child_cfg_t* (*select_child_cfg)(peer_cfg_t *this, linked_list_t *my_ts, linked_list_t *other_ts, - linked_list_t *my_hosts, linked_list_t *other_hosts); + linked_list_t *my_hosts, linked_list_t *other_hosts, + linked_list_t *my_labels, linked_list_t *other_labels); /** * Add an authentication config to the peer configuration. diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c index 6138efc97..5e4bf8620 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -1096,7 +1096,7 @@ METHOD(task_t, process_r, status_t, hostsi = get_dynamic_hosts(this->ike_sa, FALSE); hostsr = get_dynamic_hosts(this->ike_sa, TRUE); this->config = peer_cfg->select_child_cfg(peer_cfg, tsr, tsi, - hostsr, hostsi); + hostsr, hostsi, NULL, NULL); hostsi->destroy(hostsi); hostsr->destroy(hostsr); if (this->config) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 83f3c7328..092ac72a6 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1414,7 +1414,7 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this) listi = get_dynamic_hosts(this->ike_sa, FALSE); child_cfg = peer_cfg->select_child_cfg(peer_cfg, tsr ?: this->tsr, tsi ?: this->tsi, - listr, listi); + listr, listi, NULL, NULL); if ((tsi || tsr) && child_cfg && child_cfg->get_mode(child_cfg) != MODE_TRANSPORT) { @@ -1426,7 +1426,8 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this) { /* no match for the substituted NAT selectors, try it without */ child_cfg = peer_cfg->select_child_cfg(peer_cfg, - this->tsr, this->tsi, listr, listi); + this->tsr, this->tsi, + listr, listi, NULL, NULL); } listr->destroy(listr); listi->destroy(listi); From 6cb83c305e6a51d25c9c8f9e99f611d23fdf7097 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 15:11:37 +0100 Subject: [PATCH 23/47] kernel-interface: Optionally consider security label when allocating reqids --- src/libcharon/kernel/kernel_interface.c | 81 +++++++++++++++++-------- src/libcharon/kernel/kernel_interface.h | 8 ++- src/libcharon/sa/child_sa.c | 6 +- 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/libcharon/kernel/kernel_interface.c b/src/libcharon/kernel/kernel_interface.c index 8f6c21b61..01c814ab5 100644 --- a/src/libcharon/kernel/kernel_interface.c +++ b/src/libcharon/kernel/kernel_interface.c @@ -208,6 +208,8 @@ typedef struct { uint32_t if_id_in; /** outbound interface ID used for SA */ uint32_t if_id_out; + /** security label */ + sec_label_t *label; /** local traffic selectors */ array_t *local; /** remote traffic selectors */ @@ -221,19 +223,49 @@ static void reqid_entry_destroy(reqid_entry_t *entry) { array_destroy_offset(entry->local, offsetof(traffic_selector_t, destroy)); array_destroy_offset(entry->remote, offsetof(traffic_selector_t, destroy)); + DESTROY_IF(entry->label); free(entry); } +/** + * Hash the shared data of reqid entries + */ +static u_int entry_hash_shared(reqid_entry_t *entry) +{ + u_int hash; + + hash = chunk_hash_inc(chunk_from_thing(entry->mark_in), + chunk_hash_inc(chunk_from_thing(entry->mark_out), + chunk_hash_inc(chunk_from_thing(entry->if_id_in), + chunk_hash(chunk_from_thing(entry->if_id_out))))); + if (entry->label) + { + hash = entry->label->hash(entry->label, hash); + } + return hash; +} + +/** + * Compare the shared properties of reqid entries + */ +static bool entry_equals_shared(reqid_entry_t *a, reqid_entry_t *b) +{ + return a->mark_in.value == b->mark_in.value && + a->mark_in.mask == b->mark_in.mask && + a->mark_out.value == b->mark_out.value && + a->mark_out.mask == b->mark_out.mask && + a->if_id_in == b->if_id_in && + a->if_id_out == b->if_id_out && + sec_labels_equal(a->label, b->label); +} + /** * Hashtable hash function for reqid entries using reqid as key */ static u_int hash_reqid(reqid_entry_t *entry) { return chunk_hash_inc(chunk_from_thing(entry->reqid), - chunk_hash_inc(chunk_from_thing(entry->mark_in), - chunk_hash_inc(chunk_from_thing(entry->mark_out), - chunk_hash_inc(chunk_from_thing(entry->if_id_in), - chunk_hash(chunk_from_thing(entry->if_id_out)))))); + entry_hash_shared(entry)); } /** @@ -241,13 +273,11 @@ static u_int hash_reqid(reqid_entry_t *entry) */ static bool equals_reqid(reqid_entry_t *a, reqid_entry_t *b) { - return a->reqid == b->reqid && - a->mark_in.value == b->mark_in.value && - a->mark_in.mask == b->mark_in.mask && - a->mark_out.value == b->mark_out.value && - a->mark_out.mask == b->mark_out.mask && - a->if_id_in == b->if_id_in && - a->if_id_out == b->if_id_out; + if (a->reqid == b->reqid) + { + return entry_equals_shared(a, b); + } + return FALSE; } /** @@ -273,11 +303,9 @@ static u_int hash_ts_array(array_t *array, u_int hash) */ static u_int hash_reqid_by_ts(reqid_entry_t *entry) { - return hash_ts_array(entry->local, hash_ts_array(entry->remote, - chunk_hash_inc(chunk_from_thing(entry->mark_in), - chunk_hash_inc(chunk_from_thing(entry->mark_out), - chunk_hash_inc(chunk_from_thing(entry->if_id_in), - chunk_hash(chunk_from_thing(entry->if_id_out))))))); + return hash_ts_array(entry->local, + hash_ts_array(entry->remote, + entry_hash_shared(entry))); } /** @@ -311,14 +339,12 @@ static bool ts_array_equals(array_t *a, array_t *b) */ static bool equals_reqid_by_ts(reqid_entry_t *a, reqid_entry_t *b) { - return ts_array_equals(a->local, b->local) && - ts_array_equals(a->remote, b->remote) && - a->mark_in.value == b->mark_in.value && - a->mark_in.mask == b->mark_in.mask && - a->mark_out.value == b->mark_out.value && - a->mark_out.mask == b->mark_out.mask && - a->if_id_in == b->if_id_in && - a->if_id_out == b->if_id_out; + if (ts_array_equals(a->local, b->local) && + ts_array_equals(a->remote, b->remote)) + { + return entry_equals_shared(a, b); + } + return FALSE; } /** @@ -346,7 +372,7 @@ METHOD(kernel_interface_t, alloc_reqid, status_t, private_kernel_interface_t *this, linked_list_t *local_ts, linked_list_t *remote_ts, mark_t mark_in, mark_t mark_out, uint32_t if_id_in, uint32_t if_id_out, - uint32_t *reqid) + sec_label_t *label, uint32_t *reqid) { static uint32_t counter = 0; reqid_entry_t *entry = NULL, *tmpl; @@ -359,6 +385,7 @@ METHOD(kernel_interface_t, alloc_reqid, status_t, .mark_out = mark_out, .if_id_in = if_id_in, .if_id_out = if_id_out, + .label = label ? label->clone(label) : NULL, .reqid = *reqid, ); @@ -404,7 +431,8 @@ METHOD(kernel_interface_t, alloc_reqid, status_t, METHOD(kernel_interface_t, release_reqid, status_t, private_kernel_interface_t *this, uint32_t reqid, - mark_t mark_in, mark_t mark_out, uint32_t if_id_in, uint32_t if_id_out) + mark_t mark_in, mark_t mark_out, uint32_t if_id_in, uint32_t if_id_out, + sec_label_t *label) { reqid_entry_t *entry, tmpl = { .reqid = reqid, @@ -412,6 +440,7 @@ METHOD(kernel_interface_t, release_reqid, status_t, .mark_out = mark_out, .if_id_in = if_id_in, .if_id_out = if_id_out, + .label = label, }; this->mutex->lock(this->mutex); diff --git a/src/libcharon/kernel/kernel_interface.h b/src/libcharon/kernel/kernel_interface.h index dbc863736..fd5685347 100644 --- a/src/libcharon/kernel/kernel_interface.h +++ b/src/libcharon/kernel/kernel_interface.h @@ -145,13 +145,15 @@ struct kernel_interface_t { * @param mark_out outbound mark on SA * @param if_id_in inbound interface ID on SA * @param if_id_out outbound interface ID on SA + * @param label security label (usually the one on the policy, not SA) * @param reqid allocated reqid * @return SUCCESS if reqid allocated */ status_t (*alloc_reqid)(kernel_interface_t *this, linked_list_t *local_ts, linked_list_t *remote_ts, mark_t mark_in, mark_t mark_out, uint32_t if_id_in, - uint32_t if_id_out, uint32_t *reqid); + uint32_t if_id_out, sec_label_t *label, + uint32_t *reqid); /** * Release a previously allocated reqid. @@ -161,11 +163,13 @@ struct kernel_interface_t { * @param mark_out outbound mark on SA * @param if_id_in inbound interface ID on SA * @param if_id_out outbound interface ID on SA + * @param label security label (usually the one on the policy, not SA) * @return SUCCESS if reqid released */ status_t (*release_reqid)(kernel_interface_t *this, uint32_t reqid, mark_t mark_in, mark_t mark_out, - uint32_t if_id_in, uint32_t if_id_out); + uint32_t if_id_in, uint32_t if_id_out, + sec_label_t *label); /** * Add an SA to the SAD. diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index f87683138..3c4c1d439 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -856,7 +856,7 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr, { status = charon->kernel->alloc_reqid(charon->kernel, my_ts, other_ts, this->mark_in, this->mark_out, this->if_id_in, - this->if_id_out, &this->reqid); + this->if_id_out, NULL, &this->reqid); if (status != SUCCESS) { my_ts->destroy(my_ts); @@ -1258,7 +1258,7 @@ METHOD(child_sa_t, install_policies, status_t, status = charon->kernel->alloc_reqid( charon->kernel, my_ts_list, other_ts_list, this->mark_in, this->mark_out, this->if_id_in, - this->if_id_out, &this->reqid); + this->if_id_out, NULL, &this->reqid); my_ts_list->destroy(my_ts_list); other_ts_list->destroy(other_ts_list); if (status != SUCCESS) @@ -1744,7 +1744,7 @@ METHOD(child_sa_t, destroy, void, { if (charon->kernel->release_reqid(charon->kernel, this->reqid, this->mark_in, this->mark_out, - this->if_id_in, this->if_id_out) != SUCCESS) + this->if_id_in, this->if_id_out, NULL) != SUCCESS) { DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid); } From 139d655817e202857270c46077cd028297ccbdb1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 15:28:07 +0100 Subject: [PATCH 24/47] child-sa: Add support for security labels In SELinux mode we install the configured label on the policies and the negotiated one on the SAs. This is how it usually is configured where the policy/configuration has a generic context and the SAs will get the actual context of the flows assigned (the latter matches the former, so flows match the policies but will trigger an acquire if no matching SA exists). In the simple mode we don't pass the label to the kernel and to avoid duplicate policy errors we also don't use it to acquire unique reqids. --- src/libcharon/sa/child_sa.c | 97 ++++++++++++++++++++++++++++++++++--- src/libcharon/sa/child_sa.h | 13 ++++- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 3c4c1d439..2004b1bfa 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -179,6 +179,11 @@ struct private_child_sa_t { */ mark_t mark_out; + /** + * Security label + */ + sec_label_t *label; + /** * absolute time when rekeying is scheduled */ @@ -298,6 +303,46 @@ static inline mark_t mark_in_sa(private_child_sa_t *this) return (mark_t){}; } +/** + * Possible uses for security labels + */ +typedef enum { + LABEL_USE_REQID, + LABEL_USE_POLICY, + LABEL_USE_SA, +} label_use_t; + +/** + * Returns the security label for either policies, SAs or reqids. + */ +static inline sec_label_t *label_for(private_child_sa_t *this, label_use_t use) +{ + /* For SELinux we use the configured label for policies and reqid but the + * negotiated one for the SAs. That's because the label on the policies is + * usually a generic one that matches specific labels on flows, which will + * trigger an acquire if no matching SA with that label exists yet. + * When that SA is later installed, we want to avoid having to install + * policies in the kernel that will never get used, so we use the configured + * label again. + * Note that while the labels don't have to be equal, they are both either + * NULL or defined. + */ + if (this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX) + { + switch (use) + { + case LABEL_USE_REQID: + case LABEL_USE_POLICY: + return this->config->get_label(this->config); + case LABEL_USE_SA: + return this->label; + } + } + /* for the simple label mode we don't pass labels to the kernel, so we don't + * use it to acquire unique reqids either */ + return NULL; +} + METHOD(child_sa_t, get_name, char*, private_child_sa_t *this) { @@ -638,6 +683,7 @@ static bool update_usetime(private_child_sa_t *this, bool inbound) .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_query_policy_t query = {}; @@ -665,6 +711,7 @@ static bool update_usetime(private_child_sa_t *this, bool inbound) .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_query_policy_t query = {}; @@ -736,6 +783,12 @@ METHOD(child_sa_t, get_if_id, uint32_t, return inbound ? this->if_id_in : this->if_id_out; } +METHOD(child_sa_t, get_label, sec_label_t*, + private_child_sa_t *this) +{ + return this->label ?: this->config->get_label(this->config); +} + METHOD(child_sa_t, get_lifetime, time_t, private_child_sa_t *this, bool hard) { @@ -856,7 +909,8 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr, { status = charon->kernel->alloc_reqid(charon->kernel, my_ts, other_ts, this->mark_in, this->mark_out, this->if_id_in, - this->if_id_out, NULL, &this->reqid); + this->if_id_out, label_for(this, LABEL_USE_REQID), + &this->reqid); if (status != SUCCESS) { my_ts->destroy(my_ts); @@ -920,6 +974,7 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr, .copy_df = !this->config->has_option(this->config, OPT_NO_COPY_DF), .copy_ecn = !this->config->has_option(this->config, OPT_NO_COPY_ECN), .copy_dscp = this->config->get_copy_dscp(this->config), + .label = label_for(this, LABEL_USE_SA), .initiator = initiator, .inbound = inbound, .update = update, @@ -1017,6 +1072,7 @@ static status_t install_policies_inbound(private_child_sa_t *this, .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t in_policy = { .type = type, @@ -1053,6 +1109,7 @@ static status_t install_policies_outbound(private_child_sa_t *this, .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t out_policy = { .type = type, @@ -1126,6 +1183,7 @@ static void del_policies_inbound(private_child_sa_t *this, .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t in_policy = { .type = type, @@ -1161,6 +1219,7 @@ static void del_policies_outbound(private_child_sa_t *this, .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t out_policy = { .type = type, @@ -1258,7 +1317,8 @@ METHOD(child_sa_t, install_policies, status_t, status = charon->kernel->alloc_reqid( charon->kernel, my_ts_list, other_ts_list, this->mark_in, this->mark_out, this->if_id_in, - this->if_id_out, NULL, &this->reqid); + this->if_id_out, label_for(this, LABEL_USE_REQID), + &this->reqid); my_ts_list->destroy(my_ts_list); other_ts_list->destroy(other_ts_list); if (status != SUCCESS) @@ -1312,15 +1372,36 @@ METHOD(child_sa_t, install_policies, status_t, return status; } +/** + * Check if we can install the outbound SA immediately. + * + * If the kernel supports installing SPIs with policies, we can do so as it + * will only be used once we update the policies. + * + * However, if we use labels with SELinux, we can't as we don't set SPIs + * on the policy in order to match SAs with other labels that match the generic + * label that's used on the policies. + */ +static bool install_outbound_immediately(private_child_sa_t *this) +{ + if (charon->kernel->get_features(charon->kernel) & KERNEL_POLICY_SPI) + { + if (this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX) + { + return !this->label; + } + return TRUE; + } + return FALSE; +} + METHOD(child_sa_t, register_outbound, status_t, private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi, uint16_t cpi, bool tfcv3) { status_t status; - /* if the kernel supports installing SPIs with policies we install the - * SA immediately as it will only be used once we update the policies */ - if (charon->kernel->get_features(charon->kernel) & KERNEL_POLICY_SPI) + if (install_outbound_immediately(this)) { status = install_internal(this, encr, integ, spi, cpi, FALSE, FALSE, tfcv3); @@ -1744,7 +1825,8 @@ METHOD(child_sa_t, destroy, void, { if (charon->kernel->release_reqid(charon->kernel, this->reqid, this->mark_in, this->mark_out, - this->if_id_in, this->if_id_out, NULL) != SUCCESS) + this->if_id_in, this->if_id_out, + label_for(this, LABEL_USE_REQID)) != SUCCESS) { DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid); } @@ -1755,6 +1837,7 @@ METHOD(child_sa_t, destroy, void, this->my_addr->destroy(this->my_addr); this->other_addr->destroy(this->other_addr); DESTROY_IF(this->proposal); + DESTROY_IF(this->label); this->config->destroy(this->config); chunk_clear(&this->encr_r); chunk_clear(&this->integ_r); @@ -1827,6 +1910,7 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, .get_usestats = _get_usestats, .get_mark = _get_mark, .get_if_id = _get_if_id, + .get_label = _get_label, .has_encap = _has_encap, .get_ipcomp = _get_ipcomp, .set_ipcomp = _set_ipcomp, @@ -1864,6 +1948,7 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, .mark_out = config->get_mark(config, FALSE), .if_id_in = config->get_if_id(config, TRUE) ?: data->if_id_in_def, .if_id_out = config->get_if_id(config, FALSE) ?: data->if_id_out_def, + .label = data->label ? data->label->clone(data->label) : NULL, .install_time = time_monotonic(NULL), .policies_fwd_out = config->has_option(config, OPT_FWD_OUT_POLICIES), ); diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h index 8a2939efd..cf0504347 100644 --- a/src/libcharon/sa/child_sa.h +++ b/src/libcharon/sa/child_sa.h @@ -369,6 +369,15 @@ struct child_sa_t { */ uint32_t (*get_if_id)(child_sa_t *this, bool inbound); + /** + * Get the security label used with this CHILD_SA. + * + * This might be different than the configured label. + * + * @return security label used with this CHILD_SA + */ + sec_label_t *(*get_label)(child_sa_t *this); + /** * Create an enumerator over traffic selectors of one side. * @@ -533,6 +542,8 @@ struct child_sa_create_t { /** Optional default outbound interface ID, if neither if_id_out, nor config * sets one */ uint32_t if_id_out_def; + /** Optional security label to apply on SAs (cloned) */ + sec_label_t *label; /** TRUE to enable UDP encapsulation (NAT traversal) */ bool encap; }; @@ -546,7 +557,7 @@ struct child_sa_create_t { * @param data data for this CHILD_SA * @return child_sa_t object */ -child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, +child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, child_sa_create_t *data); #endif /** CHILD_SA_H_ @}*/ From 7a8d110863e2398aa5743fe767c7b6d7d5495f56 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 15:58:46 +0100 Subject: [PATCH 25/47] child-create: Consider security label when comparing CHILD_SAs --- src/libcharon/sa/ikev2/tasks/child_create.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 092ac72a6..389ffb2d7 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1093,7 +1093,8 @@ static bool child_sa_equals(child_sa_t *a, child_sa_t *b) a->get_mark(a, TRUE).value == b->get_mark(b, TRUE).value && a->get_mark(a, FALSE).value == b->get_mark(b, FALSE).value && a->get_if_id(a, TRUE) == b->get_if_id(b, TRUE) && - a->get_if_id(a, FALSE) == b->get_if_id(b, FALSE); + a->get_if_id(a, FALSE) == b->get_if_id(b, FALSE) && + sec_labels_equal(a->get_label(a), b->get_label(b)); } /** From f9b895b49f4911adf12de73bb4e4abd6f9d5dbb2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Feb 2022 10:40:16 +0100 Subject: [PATCH 26/47] child-create: Add support to handle security labels With SELinux and without a specific label from an acquire, we abort establishing the CHILD_SA (for the first one we prefer a childless IKE_SA, but since that's a separate extension, we fall back to letting the initial CHILD_SA fail as we won't propose a label). If trap policies are not installed already (e.g. because it's impossible to do so like as responder for roadwarriors), this will require installing them dynamically once the IKE_SA is established. --- src/libcharon/sa/ikev2/tasks/child_create.c | 172 ++++++++++++++++++-- src/libcharon/sa/ikev2/tasks/child_create.h | 7 + 2 files changed, 167 insertions(+), 12 deletions(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 389ffb2d7..ddfe8cf05 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -84,15 +84,25 @@ struct private_child_create_t { proposal_t *proposal; /** - * traffic selectors for initiators side + * traffic selectors for initiator side */ linked_list_t *tsi; /** - * traffic selectors for responders side + * traffic selectors for responder side */ linked_list_t *tsr; + /** + * labels for initiator side + */ + linked_list_t *labels_i; + + /** + * labels for responder side + */ + linked_list_t *labels_r; + /** * source of triggering packet */ @@ -210,6 +220,7 @@ static void schedule_delayed_retry(private_child_create_t *this) task->use_reqid(task, this->child.reqid); task->use_marks(task, this->child.mark_in, this->child.mark_out); task->use_if_ids(task, this->child.if_id_in, this->child.if_id_out); + task->use_label(task, this->child.label); DBG1(DBG_IKE, "creating CHILD_SA failed, trying again in %d seconds", retry); @@ -891,9 +902,11 @@ static bool build_payloads(private_child_create_t *this, message_t *message) } /* add TSi/TSr payloads */ - ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi, NULL); + ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi, + this->child.label); message->add_payload(message, (payload_t*)ts_payload); - ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr, NULL); + ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr, + this->child.label); message->add_payload(message, (payload_t*)ts_payload); /* add a notify if we are not in tunnel mode */ @@ -1039,10 +1052,12 @@ static void process_payloads(private_child_create_t *this, message_t *message) case PLV2_TS_INITIATOR: ts_payload = (ts_payload_t*)payload; this->tsi = ts_payload->get_traffic_selectors(ts_payload); + this->labels_i = ts_payload->get_sec_labels(ts_payload); break; case PLV2_TS_RESPONDER: ts_payload = (ts_payload_t*)payload; this->tsr = ts_payload->get_traffic_selectors(ts_payload); + this->labels_r = ts_payload->get_sec_labels(ts_payload); break; case PLV2_NOTIFY: handle_notify(this, (notify_payload_t*)payload); @@ -1054,6 +1069,16 @@ static void process_payloads(private_child_create_t *this, message_t *message) enumerator->destroy(enumerator); } +/** + * Check if we have only the generic label available when using SELinux and not + * a specific one from an acquire. + */ +static bool generic_label_only(private_child_create_t *this) +{ + return this->config->get_label(this->config) && !this->child.label && + this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX; +} + /** * Check if we should defer the creation of this CHILD_SA until after the * IKE_SA has been established childless. @@ -1061,17 +1086,23 @@ static void process_payloads(private_child_create_t *this, message_t *message) static status_t defer_child_sa(private_child_create_t *this) { ike_cfg_t *ike_cfg; + childless_t policy; ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + policy = ike_cfg->childless(ike_cfg); if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_CHILDLESS)) { - if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE) + /* with SELinux, we prefer not to create a CHILD_SA when we only have + * the generic label available. if the peer does not support it, + * creating the SA will most likely fail */ + if (policy == CHILDLESS_FORCE || + generic_label_only(this)) { return NEED_MORE; } } - else if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE) + else if (policy == CHILDLESS_FORCE) { DBG1(DBG_IKE, "peer does not support childless IKE_SA initiation"); return DESTROY_ME; @@ -1099,7 +1130,7 @@ static bool child_sa_equals(child_sa_t *a, child_sa_t *b) /** * Check if there is a duplicate CHILD_SA already established and we can abort - * initiating this one + * initiating this one. */ static bool check_for_duplicate(private_child_create_t *this) { @@ -1141,6 +1172,26 @@ static bool check_for_duplicate(private_child_create_t *this) return found; } +/** + * Check if this is an attempt to create an SA with generic label and should + * be aborted. + */ +static bool check_for_generic_label(private_child_create_t *this) +{ + if (generic_label_only(this)) + { + sec_label_t *label; + + label = this->config->get_label(this->config); + DBG1(DBG_IKE, "not establishing CHILD_SA %s{%d} with generic " + "label '%s'", this->child_sa->get_name(this->child_sa), + this->child_sa->get_unique_id(this->child_sa), + label->get_string(label)); + return TRUE; + } + return FALSE; +} + METHOD(task_t, build_i, status_t, private_child_create_t *this, message_t *message) { @@ -1231,6 +1282,22 @@ METHOD(task_t, build_i, status_t, this->tsr->insert_first(this->tsr, this->packet_tsr->clone(this->packet_tsr)); } + + if (!generic_label_only(this) && !this->child.label) + { /* in the simple label mode we propose the configured label as we + * won't have labels from acquires */ + this->child.label = this->config->get_label(this->config); + if (this->child.label) + { + this->child.label = this->child.label->clone(this->child.label); + } + } + if (this->child.label) + { + DBG2(DBG_CFG, "proposing security label '%s'", + this->child.label->get_string(this->child.label)); + } + this->proposals = this->config->get_proposals(this->config, this->dh_group == MODP_NONE); this->mode = this->config->get_mode(this->config); @@ -1246,7 +1313,7 @@ METHOD(task_t, build_i, status_t, * by controller and trap manager */ if (!this->rekey && message->get_exchange_type(message) == CREATE_CHILD_SA && - check_for_duplicate(this)) + (check_for_generic_label(this) || check_for_duplicate(this))) { message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED); return SUCCESS; @@ -1414,8 +1481,8 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this) listr = get_dynamic_hosts(this->ike_sa, TRUE); listi = get_dynamic_hosts(this->ike_sa, FALSE); child_cfg = peer_cfg->select_child_cfg(peer_cfg, - tsr ?: this->tsr, tsi ?: this->tsi, - listr, listi, NULL, NULL); + tsr ?: this->tsr, tsi ?: this->tsi, + listr, listi, this->labels_r, this->labels_i); if ((tsi || tsr) && child_cfg && child_cfg->get_mode(child_cfg) != MODE_TRANSPORT) { @@ -1427,8 +1494,8 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this) { /* no match for the substituted NAT selectors, try it without */ child_cfg = peer_cfg->select_child_cfg(peer_cfg, - this->tsr, this->tsi, - listr, listi, NULL, NULL); + this->tsr, this->tsi, + listr, listi, this->labels_r, this->labels_i); } listr->destroy(listr); listi->destroy(listi); @@ -1470,6 +1537,49 @@ static status_t handle_childless(private_child_create_t *this) return NOT_SUPPORTED; } +/** + * Select a security label. + * + * We already know that the proposed labels match the selected config, just make + * sure that the proposed/returned labels are the same. + */ +static bool select_label(private_child_create_t *this) +{ + sec_label_t *li, *lr; + + if (!this->config->select_label(this->config, this->labels_i, FALSE, &li, NULL) || + !this->config->select_label(this->config, this->labels_r, FALSE, &lr, NULL)) + { /* sanity check */ + return FALSE; + } + + if (li) + { + if (!li->equals(li, lr)) + { + DBG1(DBG_CHD, "security labels in TSi and TSr don't match"); + return FALSE; + } + else if (!this->child.label) + { + this->child.label = li->clone(li); + } + else if (!this->child.label->equals(this->child.label, li)) + { + DBG1(DBG_CHD, "returned security label '%s' doesn't match proposed " + "'%s'", li->get_string(li), + this->child.label->get_string(this->child.label)); + return FALSE; + } + } + if (this->child.label) + { + DBG1(DBG_CFG, "selected security label: %s", + this->child.label->get_string(this->child.label)); + } + return TRUE; +} + METHOD(task_t, build_r, status_t, private_child_create_t *this, message_t *message) { @@ -1577,6 +1687,13 @@ METHOD(task_t, build_r, status_t, } enumerator->destroy(enumerator); + if (!select_label(this)) + { + message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty); + handle_child_sa_failure(this, message); + return SUCCESS; + } + this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE); this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE); this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY); @@ -1834,6 +1951,12 @@ METHOD(task_t, process_i, status_t, return delete_failed_sa(this); } + if (!select_label(this)) + { + handle_child_sa_failure(this, message); + return delete_failed_sa(this); + } + if (select_and_install(this, no_dh, ike_auth) == SUCCESS) { if (!this->rekey) @@ -1869,6 +1992,13 @@ METHOD(child_create_t, use_if_ids, void, this->child.if_id_out = out; } +METHOD(child_create_t, use_label, void, + private_child_create_t *this, sec_label_t *label) +{ + DESTROY_IF(this->child.label); + this->child.label = label ? label->clone(label) : NULL; +} + METHOD(child_create_t, use_dh_group, void, private_child_create_t *this, diffie_hellman_group_t dh_group) { @@ -1921,6 +2051,14 @@ METHOD(task_t, migrate, void, { this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); } + if (this->labels_i) + { + this->labels_i->destroy_offset(this->labels_i, offsetof(sec_label_t, destroy)); + } + if (this->labels_r) + { + this->labels_r->destroy_offset(this->labels_r, offsetof(sec_label_t, destroy)); + } DESTROY_IF(this->child_sa); DESTROY_IF(this->proposal); DESTROY_IF(this->nonceg); @@ -1963,6 +2101,14 @@ METHOD(task_t, destroy, void, { this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); } + if (this->labels_i) + { + this->labels_i->destroy_offset(this->labels_i, offsetof(sec_label_t, destroy)); + } + if (this->labels_r) + { + this->labels_r->destroy_offset(this->labels_r, offsetof(sec_label_t, destroy)); + } if (!this->established) { DESTROY_IF(this->child_sa); @@ -1977,6 +2123,7 @@ METHOD(task_t, destroy, void, } DESTROY_IF(this->config); DESTROY_IF(this->nonceg); + DESTROY_IF(this->child.label); free(this); } @@ -1997,6 +2144,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, .use_reqid = _use_reqid, .use_marks = _use_marks, .use_if_ids = _use_if_ids, + .use_label = _use_label, .use_dh_group = _use_dh_group, .task = { .get_type = _get_type, diff --git a/src/libcharon/sa/ikev2/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index eae1f3532..40e25d89a 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -68,6 +68,13 @@ struct child_create_t { */ void (*use_if_ids)(child_create_t *this, uint32_t in, uint32_t out); + /** + * Use specific security label, overriding configuration. + * + * @param label security label + */ + void (*use_label)(child_create_t *this, sec_label_t *label); + /** * Initially propose a specific DH group to override configuration. * From 9619b1e757bec8276c67c07235816faaec15bf5d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 3 Feb 2022 14:46:59 +0100 Subject: [PATCH 27/47] child-sa: Support dynamically updating trap policies --- src/libcharon/sa/child_sa.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 2004b1bfa..0956c4024 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -1636,13 +1636,16 @@ METHOD(child_sa_t, update, status_t, ipsec_sa_cfg_t my_sa, other_sa; enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; + policy_priority_t priority; uint32_t manual_prio; status_t state; bool outbound; prepare_sa_cfg(this, &my_sa, &other_sa); manual_prio = this->config->get_manual_prio(this->config); - outbound = (this->outbound_state & CHILD_OUTBOUND_POLICIES); + priority = this->trap ? POLICY_PRIORITY_ROUTED + : POLICY_PRIORITY_DEFAULT; + outbound = (this->outbound_state & CHILD_OUTBOUND_POLICIES) || this->trap; enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) @@ -1657,7 +1660,7 @@ METHOD(child_sa_t, update, status_t, /* remove old policies */ del_policies_internal(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, - POLICY_PRIORITY_DEFAULT, manual_prio, outbound); + priority, manual_prio, outbound); } enumerator->destroy(enumerator); @@ -1674,7 +1677,7 @@ METHOD(child_sa_t, update, status_t, { install_policies_internal(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, - POLICY_PRIORITY_DEFAULT, manual_prio, outbound); + priority, manual_prio, outbound); } else { @@ -1702,7 +1705,7 @@ METHOD(child_sa_t, update, status_t, /* reinstall updated policies */ install_policies_internal(this, me, other, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, - POLICY_PRIORITY_DEFAULT, manual_prio, outbound); + priority, manual_prio, outbound); } /* remove the drop policy */ if (outbound) From 1f060357f4f580401b677d615b4badb1470c48be Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 3 Feb 2022 17:08:58 +0100 Subject: [PATCH 28/47] kernel-wfp: Use new UDP ports in update_sa() --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 650e04620..b4673ee24 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -2226,8 +2226,8 @@ METHOD(kernel_ipsec_t, update_sa, status_t, { /* inbound entry, do update */ sa_id = entry->sa_id; - ports.localUdpEncapPort = entry->local->get_port(entry->local); - ports.remoteUdpEncapPort = entry->remote->get_port(entry->remote); + ports.localUdpEncapPort = data->new_dst->get_port(data->new_dst); + ports.remoteUdpEncapPort = data->new_src->get_port(data->new_src); } this->mutex->unlock(this->mutex); From 1b3af3e37d7ee00b1f43abd41127dd1126e0afd2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 3 Feb 2022 17:10:12 +0100 Subject: [PATCH 29/47] kernel-interface: Add support to change the reqid in update_sa() --- src/libcharon/kernel/kernel_ipsec.h | 2 ++ src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c | 5 +++++ src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c | 6 ++++++ src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/src/libcharon/kernel/kernel_ipsec.h b/src/libcharon/kernel/kernel_ipsec.h index 343cdd136..748dd433f 100644 --- a/src/libcharon/kernel/kernel_ipsec.h +++ b/src/libcharon/kernel/kernel_ipsec.h @@ -130,6 +130,8 @@ struct kernel_ipsec_update_sa_t { bool encap; /** TRUE to enable UDP encapsulation */ bool new_encap; + /** New reqid, or 0 if unchanged */ + uint32_t new_reqid; }; /** diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index c46965127..a24e79205 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -2329,6 +2329,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t, kernel_ipsec_update_sa_t ipcomp = { .new_src = data->new_src, .new_dst = data->new_dst, + .new_reqid = data->new_reqid, }; update_sa(this, &ipcomp_id, &ipcomp); } @@ -2417,6 +2418,10 @@ METHOD(kernel_ipsec_t, update_sa, status_t, sa = NLMSG_DATA(hdr); memcpy(sa, NLMSG_DATA(out_hdr), sizeof(struct xfrm_usersa_info)); sa->family = data->new_dst->get_family(data->new_dst); + if (data->new_reqid) + { + sa->reqid = data->new_reqid; + } if (!id->src->ip_equals(id->src, data->new_src)) { diff --git a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index 3243d1e94..30688a427 100644 --- a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -1960,6 +1960,12 @@ METHOD(kernel_ipsec_t, update_sa, status_t, size_t len; status_t status = FAILED; + if (data->new_reqid) + { + DBG1(DBG_KNL, "unable to update SAD entry with SPI %.8x: reqid " + "change is not supported", ntohl(id->spi)); + return NOT_SUPPORTED; + } #ifndef SADB_X_EXT_NEW_ADDRESS_SRC /* we can't update the SA if any of the ip addresses have changed. * that's because we can't use SADB_UPDATE and by deleting and readding the diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index b4673ee24..3eb178599 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -2280,6 +2280,10 @@ METHOD(kernel_ipsec_t, update_sa, status_t, key.dst = entry->osa.dst; this->osas->remove(this->osas, &key); + if (data->new_reqid) + { + entry->reqid = data->new_reqid; + } entry->local->destroy(entry->local); entry->remote->destroy(entry->remote); entry->local = data->new_dst->clone(data->new_dst); From 96ecc39cd00f90175209606ebeabb8f7185afb1c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 4 Feb 2022 09:37:37 +0100 Subject: [PATCH 30/47] kernel-netlink: Allow reqid updates for policies again This was originally added with 1551d8b13d14 ("kernel-netlink: reject policy refcount if the reqid differs"). Since then we added code to allocate constant reqids for the same TS, which pretty much avoids the previous issues. However, the reqid might have to be changed due to MOBIKE updates. And because reqids are allocated for a complete set of traffic selectors and not individual pairs, this can create a problem with drop policies as those will use the old reqid (they are installed with the same priority, reqid etc. to replace the actual IPsec policies), while unmodified replacement policies will use the new one. A similar issue exists for CHILD_SAs with SELinux contexts as those all use duplicate policies (same generic label) but can't all be updated concurrently. --- .../kernel_netlink/kernel_netlink_ipsec.c | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index a24e79205..3cb54a00e 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -2916,19 +2916,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t, this->mutex->lock(this->mutex); current = this->policies->get(this->policies, policy); if (current) - { - if (current->reqid && data->sa->reqid && - current->reqid != data->sa->reqid) - { - DBG1(DBG_CFG, "unable to install policy %R === %R %N%s%s for reqid " - "%u, the same policy for reqid %u exists", - id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, - labelstr, data->sa->reqid, current->reqid); - policy_entry_destroy(this, policy); - this->mutex->unlock(this->mutex); - return INVALID_STATE; - } - /* use existing policy */ + { /* use existing policy */ DBG2(DBG_KNL, "policy %R === %R %N%s%s already exists, increasing " "refcount", id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, labelstr); @@ -3000,7 +2988,13 @@ METHOD(kernel_ipsec_t, add_policy, status_t, id->dir, markstr, labelstr, cur_priority, use_count); return SUCCESS; } - policy->reqid = assigned_sa->sa->cfg.reqid; + if (policy->reqid != assigned_sa->sa->cfg.reqid) + { + DBG1(DBG_CFG, "updating reqid for policy %R === %R %N%s%s from %u " + "to %u", id->src_ts, id->dst_ts, policy_dir_names, id->dir, + markstr, labelstr, policy->reqid, assigned_sa->sa->cfg.reqid); + policy->reqid = assigned_sa->sa->cfg.reqid; + } if (this->policy_update) { @@ -3208,7 +3202,13 @@ METHOD(kernel_ipsec_t, del_policy, status_t, return SUCCESS; } current->used_by->get_first(current->used_by, (void**)&mapping); - current->reqid = mapping->sa->cfg.reqid; + if (current->reqid != mapping->sa->cfg.reqid) + { + DBG1(DBG_CFG, "updating reqid for policy %R === %R %N%s%s from %u " + "to %u", id->src_ts, id->dst_ts, policy_dir_names, id->dir, + markstr, labelstr, current->reqid, mapping->sa->cfg.reqid); + current->reqid = mapping->sa->cfg.reqid; + } DBG2(DBG_KNL, "updating policy %R === %R %N%s%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, id->dir, From 8f4cfe96691e7cff55f9c9c5d2b55ee242f94551 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 3 Feb 2022 17:10:46 +0100 Subject: [PATCH 31/47] child-sa: Allocate a new reqid if dynamic traffic selectors are updated If update_sa() is called and dynamic traffic selectors are changed using new addresses, this might cause issues if we continue to use a reqid that doesn't match the updated traffic selectors. For instance, if the initiator then uses make-before-break reauth from the new IP. It's also a particular problem in the SELinux case where multiple CHILD_SAs with specific labels all share the same (trap) policy with generic label. However, SAs created after the update would not match due to the new reqid. --- src/libcharon/sa/child_sa.c | 228 +++++++++++++++++++++++++++--------- 1 file changed, 170 insertions(+), 58 deletions(-) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 0956c4024..eb583944f 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -558,8 +558,12 @@ METHOD(enumerator_t, policy_destroy, void, free(this); } -METHOD(child_sa_t, create_policy_enumerator, enumerator_t*, - private_child_sa_t *this) +/** + * Create an enumerator over two lists of traffic selectors, returning all the + * pairs of traffic selectors from the first and second list. + */ +static enumerator_t *create_policy_enumerator_internal(array_t *my_ts, + array_t *other_ts) { policy_enumerator_t *e; @@ -569,15 +573,21 @@ METHOD(child_sa_t, create_policy_enumerator, enumerator_t*, .venumerate = _policy_enumerate, .destroy = _policy_destroy, }, - .mine = array_create_enumerator(this->my_ts), - .other = array_create_enumerator(this->other_ts), - .array = this->other_ts, + .mine = array_create_enumerator(my_ts), + .other = array_create_enumerator(other_ts), + .array = other_ts, .ts = NULL, ); return &e->public; } +METHOD(child_sa_t, create_policy_enumerator, enumerator_t*, + private_child_sa_t *this) +{ + return create_policy_enumerator_internal(this->my_ts, this->other_ts); +} + /** * update the cached usebytes * returns SUCCESS if the usebytes have changed, FAILED if not or no SPIs @@ -1119,6 +1129,7 @@ static status_t install_policies_outbound(private_child_sa_t *this, .dst = other_addr, .sa = other_sa, }; + uint32_t reqid = other_sa->reqid; status_t status = SUCCESS; status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy); @@ -1142,7 +1153,7 @@ static status_t install_policies_outbound(private_child_sa_t *this, status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy); /* reset the reqid for any other further policies */ - other_sa->reqid = this->reqid; + other_sa->reqid = reqid; } return status; } @@ -1229,6 +1240,7 @@ static void del_policies_outbound(private_child_sa_t *this, .dst = other_addr, .sa = other_sa, }; + uint32_t reqid = other_sa->reqid; charon->kernel->del_policy(charon->kernel, &out_id, &out_policy); @@ -1241,7 +1253,7 @@ static void del_policies_outbound(private_child_sa_t *this, out_policy.prio = POLICY_PRIORITY_ROUTED; } charon->kernel->del_policy(charon->kernel, &out_id, &out_policy); - other_sa->reqid = this->reqid; + other_sa->reqid = reqid; } } @@ -1299,28 +1311,38 @@ METHOD(child_sa_t, set_policies, void, array_sort(this->other_ts, (void*)traffic_selector_cmp, NULL); } +/** + * Allocate a reqid for the given local and remote traffic selectors. + */ +static status_t alloc_reqid(private_child_sa_t *this, array_t *my_ts, + array_t *other_ts, uint32_t *reqid) +{ + linked_list_t *my_ts_list, *other_ts_list; + status_t status; + + my_ts_list = linked_list_create_from_enumerator(array_create_enumerator(my_ts)); + other_ts_list = linked_list_create_from_enumerator(array_create_enumerator(other_ts)); + status = charon->kernel->alloc_reqid( + charon->kernel, my_ts_list, other_ts_list, + this->mark_in, this->mark_out, this->if_id_in, + this->if_id_out, label_for(this, LABEL_USE_REQID), + reqid); + my_ts_list->destroy(my_ts_list); + other_ts_list->destroy(other_ts_list); + return status; +} + METHOD(child_sa_t, install_policies, status_t, private_child_sa_t *this) { enumerator_t *enumerator; - linked_list_t *my_ts_list, *other_ts_list; traffic_selector_t *my_ts, *other_ts; status_t status = SUCCESS; bool install_outbound = FALSE; if (!this->reqid_allocated && !this->static_reqid) { - my_ts_list = linked_list_create_from_enumerator( - array_create_enumerator(this->my_ts)); - other_ts_list = linked_list_create_from_enumerator( - array_create_enumerator(this->other_ts)); - status = charon->kernel->alloc_reqid( - charon->kernel, my_ts_list, other_ts_list, - this->mark_in, this->mark_out, this->if_id_in, - this->if_id_out, label_for(this, LABEL_USE_REQID), - &this->reqid); - my_ts_list->destroy(my_ts_list); - other_ts_list->destroy(other_ts_list); + status = alloc_reqid(this, this->my_ts, this->other_ts, &this->reqid); if (status != SUCCESS) { return status; @@ -1554,7 +1576,7 @@ CALLBACK(reinstall_vip, void, * Update addresses and encap state of IPsec SAs in the kernel */ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, - bool encap) + bool encap, uint32_t reqid) { /* update our (initiator) SA */ if (this->my_spi && this->inbound_installed) @@ -1573,6 +1595,7 @@ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, .new_dst = me, .encap = this->encap, .new_encap = encap, + .new_reqid = reqid, }; if (charon->kernel->update_sa(charon->kernel, &id, &sa) == NOT_SUPPORTED) @@ -1598,6 +1621,7 @@ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, .new_dst = other, .encap = this->encap, .new_encap = encap, + .new_reqid = reqid, }; if (charon->kernel->update_sa(charon->kernel, &id, &sa) == NOT_SUPPORTED) @@ -1609,6 +1633,30 @@ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, return SUCCESS; } +/** + * Fill the second list with copies of the given traffic selectors updating + * dynamic traffic selectors based on the given addresses. + */ +static void update_ts(host_t *old_host, host_t *new_host, array_t *old_list, + array_t *new_list) +{ + enumerator_t *enumerator; + traffic_selector_t *old_ts, *new_ts; + + enumerator = array_create_enumerator(old_list); + while (enumerator->enumerate(enumerator, &old_ts)) + { + new_ts = old_ts->clone(old_ts); + if (new_ts->is_host(new_ts, old_host)) + { + new_ts->set_address(new_ts, new_host); + } + array_insert(new_list, ARRAY_TAIL, new_ts); + } + enumerator->destroy(enumerator); + array_sort(new_list, (void*)traffic_selector_cmp, NULL); +} + METHOD(child_sa_t, update, status_t, private_child_sa_t *this, host_t *me, host_t *other, linked_list_t *vips, bool encap) @@ -1636,8 +1684,9 @@ METHOD(child_sa_t, update, status_t, ipsec_sa_cfg_t my_sa, other_sa; enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; + array_t *new_my_ts = NULL, *new_other_ts = NULL; policy_priority_t priority; - uint32_t manual_prio; + uint32_t manual_prio, new_reqid = 0; status_t state; bool outbound; @@ -1650,6 +1699,16 @@ METHOD(child_sa_t, update, status_t, enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { + if (!new_my_ts && !me->ip_equals(me, this->my_addr) && + my_ts->is_host(my_ts, this->my_addr)) + { + new_my_ts = array_create(0, 0); + } + if (!new_other_ts && !other->ip_equals(other, this->other_addr) && + other_ts->is_host(other_ts, this->other_addr)) + { + new_other_ts = array_create(0, 0); + } /* install drop policy to avoid traffic leaks, acquires etc. */ if (outbound) { @@ -1664,14 +1723,61 @@ METHOD(child_sa_t, update, status_t, } enumerator->destroy(enumerator); + if (new_my_ts) + { + update_ts(this->my_addr, me, this->my_ts, new_my_ts); + } + if (new_other_ts) + { + update_ts(this->other_addr, other, this->other_ts, new_other_ts); + } + if (this->reqid_allocated && (new_my_ts || new_other_ts)) + { + /* if we allocated a reqid with the previous TS, we have to get a + * new one that matches the updated TS */ + if (alloc_reqid(this, new_my_ts ?: this->my_ts, + new_other_ts ?: this->other_ts, &new_reqid) != SUCCESS) + { + DBG1(DBG_CHD, "allocating new reqid for updated SA failed"); + } + } + /* update the IPsec SAs */ - state = update_sas(this, me, other, encap); + state = update_sas(this, me, other, encap, new_reqid); + + /* install new/updated policies only if we were able to update the + * SAs, otherwise we reinstall the old policies further below */ + if (state != NOT_SUPPORTED) + { + /* we reinstall the virtual IP to handle interface roaming + * correctly */ + if (vips) + { + vips->invoke_function(vips, reinstall_vip, me); + } + if (new_reqid) + { + my_sa.reqid = other_sa.reqid = new_reqid; + } + enumerator = create_policy_enumerator_internal( + new_my_ts ?: this->my_ts, + new_other_ts ?: this->other_ts); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + { + install_policies_internal(this, me, other, my_ts, other_ts, + &my_sa, &other_sa, POLICY_IPSEC, + priority, manual_prio, outbound); + } + enumerator->destroy(enumerator); + if (new_reqid) + { + my_sa.reqid = other_sa.reqid = this->reqid; + } + } enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { - traffic_selector_t *old_my_ts = NULL, *old_other_ts = NULL; - /* reinstall the previous policies if we can't update the SAs */ if (state == NOT_SUPPORTED) { @@ -1679,58 +1785,64 @@ METHOD(child_sa_t, update, status_t, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, priority, manual_prio, outbound); } - else - { - /* check if we have to update a "dynamic" traffic selector */ - if (!me->ip_equals(me, this->my_addr) && - my_ts->is_host(my_ts, this->my_addr)) - { - old_my_ts = my_ts->clone(my_ts); - my_ts->set_address(my_ts, me); - } - if (!other->ip_equals(other, this->other_addr) && - other_ts->is_host(other_ts, this->other_addr)) - { - old_other_ts = other_ts->clone(other_ts); - other_ts->set_address(other_ts, other); - } - - /* we reinstall the virtual IP to handle interface roaming - * correctly */ - if (vips) - { - vips->invoke_function(vips, reinstall_vip, me); - } - - /* reinstall updated policies */ - install_policies_internal(this, me, other, my_ts, other_ts, - &my_sa, &other_sa, POLICY_IPSEC, - priority, manual_prio, outbound); - } /* remove the drop policy */ if (outbound) { del_policies_outbound(this, this->my_addr, this->other_addr, - old_my_ts ?: my_ts, old_other_ts ?: other_ts, - &my_sa, &other_sa, POLICY_DROP, + my_ts, other_ts, &my_sa, &other_sa, POLICY_DROP, POLICY_PRIORITY_DEFAULT, manual_prio); } - - DESTROY_IF(old_my_ts); - DESTROY_IF(old_other_ts); } enumerator->destroy(enumerator); if (state == NOT_SUPPORTED) { + if (new_reqid && + charon->kernel->release_reqid(charon->kernel, + new_reqid, this->mark_in, this->mark_out, + this->if_id_in, this->if_id_out, + label_for(this, LABEL_USE_REQID)) != SUCCESS) + { + DBG1(DBG_CHD, "releasing reqid %u failed", new_reqid); + } + array_destroy_offset(new_my_ts, + offsetof(traffic_selector_t, destroy)); + array_destroy_offset(new_other_ts, + offsetof(traffic_selector_t, destroy)); set_state(this, old); return NOT_SUPPORTED; } + if (new_reqid) + { + if (charon->kernel->release_reqid(charon->kernel, + this->reqid, this->mark_in, this->mark_out, + this->if_id_in, this->if_id_out, + label_for(this, LABEL_USE_REQID)) != SUCCESS) + { + DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid); + } + DBG1(DBG_CHD, "replaced reqid %u with reqid %u for updated " + "CHILD_SA %s{%d}", this->reqid, new_reqid, get_name(this), + this->unique_id); + this->reqid = new_reqid; + } + if (new_my_ts) + { + array_destroy_offset(this->my_ts, + offsetof(traffic_selector_t, destroy)); + this->my_ts = new_my_ts; + } + if (new_other_ts) + { + array_destroy_offset(this->other_ts, + offsetof(traffic_selector_t, destroy)); + this->other_ts = new_other_ts; + } } else if (!transport_proxy_mode) { - if (update_sas(this, me, other, encap) == NOT_SUPPORTED) + if (update_sas(this, me, other, encap, 0) == NOT_SUPPORTED) { set_state(this, old); return NOT_SUPPORTED; From 3c65cf64563c6ff67f632ff4a8310ce02498c459 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 1 Feb 2022 20:17:50 +0100 Subject: [PATCH 32/47] trap-manager: Add facility to install externally managed trap policies This allows managing trap policies outside of the trap manager. We'll use this to create trap policies with generic labels if trap policies can't (or won't) be used (e.g. as responder for roadwarriors). --- src/libcharon/sa/trap_manager.c | 151 +++++++++++++++++++++++++------- src/libcharon/sa/trap_manager.h | 27 +++++- 2 files changed, 146 insertions(+), 32 deletions(-) diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index b00f7edc9..7b6e2abdf 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -106,6 +106,8 @@ typedef struct { child_sa_t *child_sa; /** TRUE in case of wildcard Transport Mode SA */ bool wildcard; + /** TRUE for CHILD_SAs that are externally managed */ + bool external; } entry_t; /** @@ -125,7 +127,10 @@ typedef struct { */ static void destroy_entry(entry_t *this) { - this->child_sa->destroy(this->child_sa); + if (!this->external) + { + this->child_sa->destroy(this->child_sa); + } this->peer_cfg->destroy(this->peer_cfg); free(this->name); free(this); @@ -183,6 +188,41 @@ static bool dynamic_remote_ts(child_cfg_t *child) return found; } +/** + * Install the given trap + */ +static status_t install_trap(child_sa_t *child_sa, linked_list_t *local, + linked_list_t *remote) +{ + linked_list_t *my_ts, *other_ts, *proposals; + proposal_t *proposal; + child_cfg_t *child; + protocol_id_t proto = PROTO_ESP; + + child = child_sa->get_config(child_sa); + + my_ts = child->get_traffic_selectors(child, TRUE, NULL, local, FALSE); + other_ts = child->get_traffic_selectors(child, FALSE, NULL, remote, FALSE); + + /* we don't know the finally negotiated protocol (ESP|AH), we install + * the SA with the protocol of the first proposal */ + proposals = child->get_proposals(child, TRUE); + if (proposals->get_first(proposals, (void**)&proposal) == SUCCESS) + { + proto = proposal->get_protocol(proposal); + } + proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); + + child_sa->set_protocol(child_sa, proto); + child_sa->set_mode(child_sa, child->get_mode(child)); + + child_sa->set_policies(child_sa, my_ts, other_ts); + my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); + other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + + return child_sa->install_policies(child_sa); +} + METHOD(trap_manager_t, install, bool, private_trap_manager_t *this, peer_cfg_t *peer, child_cfg_t *child) { @@ -190,12 +230,9 @@ METHOD(trap_manager_t, install, bool, ike_cfg_t *ike_cfg; child_sa_t *child_sa; host_t *me, *other; - linked_list_t *my_ts, *other_ts, *list; + linked_list_t *local, *remote; enumerator_t *enumerator; status_t status; - linked_list_t *proposals; - proposal_t *proposal; - protocol_id_t proto = PROTO_ESP; bool result = FALSE, wildcard = FALSE; /* try to resolve addresses */ @@ -254,13 +291,14 @@ METHOD(trap_manager_t, install, bool, enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { - if (streq(entry->name, child->get_name(child)) && + if (!entry->external && + streq(entry->name, child->get_name(child)) && streq(entry->peer_cfg->get_name(entry->peer_cfg), peer->get_name(peer))) { found = entry; if (entry->child_sa) - { /* replace it with an updated version, if already installed */ + { /* replace it with an updated version if already installed */ this->traps->remove_at(this->traps, enumerator); } break; @@ -302,28 +340,13 @@ METHOD(trap_manager_t, install, bool, }; child_sa = child_sa_create(me, other, child, &child_data); - list = linked_list_create_with_items(me, NULL); - my_ts = child->get_traffic_selectors(child, TRUE, NULL, list, FALSE); - list->destroy_offset(list, offsetof(host_t, destroy)); + local = linked_list_create_with_items(me, NULL); + remote = linked_list_create_with_items(other, NULL); - list = linked_list_create_with_items(other, NULL); - other_ts = child->get_traffic_selectors(child, FALSE, NULL, list, FALSE); - list->destroy_offset(list, offsetof(host_t, destroy)); + status = install_trap(child_sa, local, remote); - /* We don't know the finally negotiated protocol (ESP|AH), we install - * the SA with the protocol of the first proposal */ - proposals = child->get_proposals(child, TRUE); - if (proposals->get_first(proposals, (void**)&proposal) == SUCCESS) - { - proto = proposal->get_protocol(proposal); - } - proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); - child_sa->set_protocol(child_sa, proto); - child_sa->set_mode(child_sa, child->get_mode(child)); - child_sa->set_policies(child_sa, my_ts, other_ts); - status = child_sa->install_policies(child_sa); - my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); - other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + local->destroy_offset(local, offsetof(host_t, destroy)); + remote->destroy_offset(remote, offsetof(host_t, destroy)); if (status != SUCCESS) { DBG1(DBG_CFG, "installing trap failed"); @@ -366,8 +389,71 @@ METHOD(trap_manager_t, uninstall, bool, enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { - if (streq(entry->name, child) && - (!peer || streq(peer, entry->peer_cfg->get_name(entry->peer_cfg)))) + if (!entry->external && + streq(entry->name, child) && + (!peer || streq(peer, entry->peer_cfg->get_name(entry->peer_cfg)))) + { + this->traps->remove_at(this->traps, enumerator); + found = entry; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + if (!found) + { + return FALSE; + } + destroy_entry(found); + return TRUE; +} + +METHOD(trap_manager_t, install_external, bool, + private_trap_manager_t *this, peer_cfg_t *peer, child_sa_t *child, + linked_list_t *local, linked_list_t *remote) +{ + entry_t *entry; + + this->lock->write_lock(this->lock); + if (this->installing == INSTALL_DISABLED) + { /* flush() has been called */ + this->lock->unlock(this->lock); + return FALSE; + } + + INIT(entry, + .name = strdup(child->get_name(child)), + .peer_cfg = peer->get_ref(peer), + .child_sa = child, + .external = TRUE, + ); + this->traps->insert_first(this->traps, entry); + this->lock->unlock(this->lock); + + if (install_trap(child, local, remote) != SUCCESS) + { + DBG1(DBG_CFG, "installing trap failed"); + this->lock->write_lock(this->lock); + this->traps->remove(this->traps, entry, NULL); + this->lock->unlock(this->lock); + destroy_entry(entry); + return FALSE; + } + return TRUE; +} + +METHOD(trap_manager_t, remove_external, bool, + private_trap_manager_t *this, child_sa_t *child) +{ + enumerator_t *enumerator; + entry_t *entry, *found = NULL; + + this->lock->write_lock(this->lock); + enumerator = this->traps->create_enumerator(this->traps); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->external && entry->child_sa == child) { this->traps->remove_at(this->traps, enumerator); found = entry; @@ -396,8 +482,9 @@ CALLBACK(trap_filter, bool, while (orig->enumerate(orig, &entry)) { - if (!entry->child_sa) - { /* skip entries that are currently being installed */ + if (!entry->child_sa || entry->external) + { /* skip entries that are currently being installed or are managed + * externally */ continue; } if (peer_cfg) @@ -678,6 +765,8 @@ trap_manager_t *trap_manager_create(void) .public = { .install = _install, .uninstall = _uninstall, + .install_external = _install_external, + .remove_external = _remove_external, .create_enumerator = _create_enumerator, .acquire = _acquire, .flush = _flush, diff --git a/src/libcharon/sa/trap_manager.h b/src/libcharon/sa/trap_manager.h index d9b4906f7..60dbc2f12 100644 --- a/src/libcharon/sa/trap_manager.h +++ b/src/libcharon/sa/trap_manager.h @@ -25,6 +25,7 @@ #include #include #include +#include typedef struct trap_manager_t trap_manager_t; @@ -55,7 +56,31 @@ struct trap_manager_t { bool (*uninstall)(trap_manager_t *this, char *peer, char *child); /** - * Create an enumerator over all installed traps. + * Install and register an externally managed trap policy using the two + * lists of local and remote addresses when deriving traffic selectors. + * + * @param peer peer configuration to register + * @param child CHILD_SA to install and register + * @param local list of local addresses (virtual or physical) + * @param remote list of remote addresses (virtual or physical) + * @return TRUE if successfully installed and registered + */ + bool (*install_external)(trap_manager_t *this, peer_cfg_t *peer, + child_sa_t *child, linked_list_t *local, + linked_list_t *remote); + + /** + * Remove and uninstall a previously registered externally managed trap + * policy. + * + * @param child CHILD_SA to remove + * @return TRUE if successfully removed + */ + bool (*remove_external)(trap_manager_t *this, child_sa_t *child); + + /** + * Create an enumerator over all installed traps (does not include + * externally managed trap policies). * * @return enumerator over (peer_cfg_t, child_sa_t) */ From 065685dde710e55e1f713dff7f0d61064391c442 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Feb 2022 15:45:57 +0100 Subject: [PATCH 33/47] ike-sa: Add helper to determine an IKE_SA's dynamic hosts --- src/libcharon/sa/ike_sa.c | 49 +++++++++++++++ src/libcharon/sa/ike_sa.h | 14 ++++- src/libcharon/sa/ikev2/tasks/child_create.c | 66 ++------------------- 3 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index a9ea02d95..9bd351d60 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -3261,3 +3261,52 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, } return &this->public; } + +/** + * Check if we have a an address pool configured. + */ +static bool have_pool(private_ike_sa_t *this) +{ + enumerator_t *enumerator; + bool found = FALSE; + + if (this->peer_cfg) + { + enumerator = this->peer_cfg->create_pool_enumerator(this->peer_cfg); + found = enumerator->enumerate(enumerator, NULL); + enumerator->destroy(enumerator); + } + return found; +} + +/* + * Described in header + */ +linked_list_t *ike_sa_get_dynamic_hosts(ike_sa_t *ike_sa, bool local) +{ + private_ike_sa_t *this = (private_ike_sa_t*)ike_sa; + enumerator_t *enumerator; + linked_list_t *list; + host_t *host; + + list = linked_list_create(); + enumerator = create_virtual_ip_enumerator(this, local); + while (enumerator->enumerate(enumerator, &host)) + { + list->insert_last(list, host); + } + enumerator->destroy(enumerator); + + if (!list->get_count(list)) + { /* no virtual IPs assigned */ + if (local) + { + list->insert_last(list, this->my_host); + } + else if (!have_pool(this)) + { /* use remote host only if we don't have a pool configured */ + list->insert_last(list, this->other_host); + } + } + return list; +} diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index 37aac2d38..8fc9a3cdd 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -1248,7 +1248,7 @@ struct ike_sa_t { * @param ike_sa_id ike_sa_id_t to associate with new IKE_SA/ISAKMP_SA * @param initiator TRUE to create this IKE_SA as initiator * @param version IKE version of this SA - * @return ike_sa_t object + * @return ike_sa_t object */ ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, ike_version_t version); @@ -1257,8 +1257,18 @@ ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, * Check if the given IKE_SA can be reauthenticated actively or if config * parameters or the authentication method prevent it. * - * @return TRUE if active reauthentication is possible + * @param this IKE_SA to check + * @return TRUE if active reauthentication is possible */ bool ike_sa_can_reauthenticate(ike_sa_t *this); +/** + * Get hosts, virtual or physical, for deriving dynamic traffic selectors. + * + * @param this IKE_SA to retrieve addresses from + * @param local TRUE to get local hosts + * @return list of hosts (internal objects) + */ +linked_list_t *ike_sa_get_dynamic_hosts(ike_sa_t *this, bool local); + #endif /** IKE_SA_H_ @}*/ diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index ddfe8cf05..555854598 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -369,62 +369,6 @@ static void schedule_inactivity_timeout(private_child_create_t *this) } } -/** - * Check if we have a an address pool configured - */ -static bool have_pool(ike_sa_t *ike_sa) -{ - enumerator_t *enumerator; - peer_cfg_t *peer_cfg; - char *pool; - bool found = FALSE; - - peer_cfg = ike_sa->get_peer_cfg(ike_sa); - if (peer_cfg) - { - enumerator = peer_cfg->create_pool_enumerator(peer_cfg); - if (enumerator->enumerate(enumerator, &pool)) - { - found = TRUE; - } - enumerator->destroy(enumerator); - } - return found; -} - -/** - * Get hosts to use for dynamic traffic selectors - */ -static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local) -{ - enumerator_t *enumerator; - linked_list_t *list; - host_t *host; - - list = linked_list_create(); - enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, local); - while (enumerator->enumerate(enumerator, &host)) - { - list->insert_last(list, host); - } - enumerator->destroy(enumerator); - - if (list->get_count(list) == 0) - { /* no virtual IPs assigned */ - if (local) - { - host = ike_sa->get_my_host(ike_sa); - list->insert_last(list, host); - } - else if (!have_pool(ike_sa)) - { /* use host only if we don't have a pool configured */ - host = ike_sa->get_other_host(ike_sa); - list->insert_last(list, host); - } - } - return list; -} - /** * Substitute any host address with NATed address in traffic selector */ @@ -479,7 +423,7 @@ static linked_list_t* narrow_ts(private_child_create_t *this, bool local, ike_condition_t cond; cond = local ? COND_NAT_HERE : COND_NAT_THERE; - hosts = get_dynamic_hosts(this->ike_sa, local); + hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local); if (this->mode == MODE_TRANSPORT && this->ike_sa->has_condition(this->ike_sa, cond)) @@ -1262,12 +1206,12 @@ METHOD(task_t, build_i, status_t, else { /* no virtual IPs configured */ list->destroy(list); - list = get_dynamic_hosts(this->ike_sa, TRUE); + list = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE); this->tsi = this->config->get_traffic_selectors(this->config, TRUE, NULL, list, TRUE); list->destroy(list); } - list = get_dynamic_hosts(this->ike_sa, FALSE); + list = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE); this->tsr = this->config->get_traffic_selectors(this->config, FALSE, NULL, list, TRUE); list->destroy(list); @@ -1478,8 +1422,8 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this) tsr = get_ts_if_nat_transport(this, TRUE, this->tsr); tsi = get_ts_if_nat_transport(this, FALSE, this->tsi); - listr = get_dynamic_hosts(this->ike_sa, TRUE); - listi = get_dynamic_hosts(this->ike_sa, FALSE); + listr = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE); + listi = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE); child_cfg = peer_cfg->select_child_cfg(peer_cfg, tsr ?: this->tsr, tsi ?: this->tsi, listr, listi, this->labels_r, this->labels_i); From b00a4e778f9e9afe3d3bfbb1daa3463096d4c50f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Feb 2022 15:51:06 +0100 Subject: [PATCH 34/47] selinux: Add plugin to install trap policies with generic labels After establishing an IKE_SA, we check if any of its child configs define generic SELinux labels and install trap policies for them if necessary narrowed to the current (virtual) IPs. --- configure.ac | 3 + src/libcharon/Makefile.am | 7 + src/libcharon/plugins/selinux/Makefile.am | 18 ++ .../plugins/selinux/selinux_listener.c | 261 ++++++++++++++++++ .../plugins/selinux/selinux_listener.h | 56 ++++ .../plugins/selinux/selinux_plugin.c | 106 +++++++ .../plugins/selinux/selinux_plugin.h | 49 ++++ 7 files changed, 500 insertions(+) create mode 100644 src/libcharon/plugins/selinux/Makefile.am create mode 100644 src/libcharon/plugins/selinux/selinux_listener.c create mode 100644 src/libcharon/plugins/selinux/selinux_listener.h create mode 100644 src/libcharon/plugins/selinux/selinux_plugin.c create mode 100644 src/libcharon/plugins/selinux/selinux_plugin.h diff --git a/configure.ac b/configure.ac index ac0724612..86e50a366 100644 --- a/configure.ac +++ b/configure.ac @@ -1524,6 +1524,7 @@ ADD_PLUGIN([kernel-iph], [c charon]) ADD_PLUGIN([kernel-pfkey], [c charon starter nm cmd]) ADD_PLUGIN([kernel-pfroute], [c charon starter nm cmd]) ADD_PLUGIN([kernel-netlink], [c charon starter nm cmd]) +ADD_PLUGIN([selinux], [c charon starter nm cmd]) ADD_PLUGIN([resolve], [c charon cmd]) ADD_PLUGIN([save-keys], [c]) ADD_PLUGIN([socket-default], [c charon nm cmd]) @@ -1770,6 +1771,7 @@ AM_CONDITIONAL(USE_RESOLVE, test x$resolve = xtrue) AM_CONDITIONAL(USE_ATTR, test x$attr = xtrue) AM_CONDITIONAL(USE_ATTR_SQL, test x$attr_sql = xtrue) AM_CONDITIONAL(USE_COUNTERS, test x$counters = xtrue) +AM_CONDITIONAL(USE_SELINUX, test x$selinux = xtrue) # other options # --------------- @@ -2073,6 +2075,7 @@ AC_CONFIG_FILES([ src/libcharon/plugins/resolve/Makefile src/libcharon/plugins/attr/Makefile src/libcharon/plugins/attr_sql/Makefile + src/libcharon/plugins/selinux/Makefile src/libcharon/tests/Makefile src/libtpmtss/Makefile src/libtpmtss/plugins/tpm/Makefile diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index 46346a44d..fb6de8ebd 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -726,6 +726,13 @@ if MONOLITHIC endif endif +if USE_SELINUX + SUBDIRS += plugins/selinux +if MONOLITHIC + libcharon_la_LIBADD += plugins/selinux/libstrongswan-selinux.la +endif +endif + if USE_ATTR_SQL SUBDIRS += plugins/attr_sql if MONOLITHIC diff --git a/src/libcharon/plugins/selinux/Makefile.am b/src/libcharon/plugins/selinux/Makefile.am new file mode 100644 index 000000000..8bf821b5e --- /dev/null +++ b/src/libcharon/plugins/selinux/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libcharon + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-selinux.la +else +plugin_LTLIBRARIES = libstrongswan-selinux.la +endif + +libstrongswan_selinux_la_SOURCES = \ + selinux_plugin.h selinux_plugin.c \ + selinux_listener.h selinux_listener.c + +libstrongswan_selinux_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/selinux/selinux_listener.c b/src/libcharon/plugins/selinux/selinux_listener.c new file mode 100644 index 000000000..bce309236 --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_listener.c @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "selinux_listener.h" + +#include +#include +#include + +typedef struct private_selinux_listener_t private_selinux_listener_t; + +/** + * Private data. + */ +struct private_selinux_listener_t { + + /** + * Public interface. + */ + selinux_listener_t public; + + /** + * IKE_SAs with attached trap policies, ike_sa_id_t => entry_t. + */ + hashtable_t *sas; +}; + +/** + * Entry to keep track of trap policies. + */ +typedef struct { + + /** + * IKE_SA ID. + */ + ike_sa_id_t *id; + + /** + * Installed trap policies. + */ + array_t *traps; + +} entry_t; + +/** + * Destroy the given entry. + */ +static void destroy_entry(entry_t *entry) +{ + entry->id->destroy(entry->id); + array_destroy(entry->traps); + free(entry); +} + +/** + * Hashtable hash function + */ +static u_int hash(const void *key) +{ + ike_sa_id_t *id = (ike_sa_id_t*)key; + uint64_t spi_i = id->get_initiator_spi(id), + spi_r = id->get_responder_spi(id); + return chunk_hash_inc(chunk_from_thing(spi_i), + chunk_hash(chunk_from_thing(spi_r))); +} + +/** + * Hashtable equals function + */ +static bool equals(const void *a_pub, const void *b) +{ + ike_sa_id_t *a = (ike_sa_id_t*)a_pub; + return a->equals(a, (ike_sa_id_t*)b); +} + +/** + * Install a trap policy for the generic SELinux label. + */ +static bool install_generic_trap(ike_sa_t *ike_sa, child_sa_t *child_sa) +{ + linked_list_t *local, *remote; + sec_label_t *label; + bool success; + + label = child_sa->get_label(child_sa); + DBG1(DBG_IKE, "installing trap %s{%d} with generic security label '%s'", + child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa), + label->get_string(label)); + + local = ike_sa_get_dynamic_hosts(ike_sa, TRUE); + remote = ike_sa_get_dynamic_hosts(ike_sa, FALSE); + success = charon->traps->install_external(charon->traps, + ike_sa->get_peer_cfg(ike_sa), + child_sa, local, remote); + local->destroy(local); + remote->destroy(remote); + return success; +} + +METHOD(listener_t, ike_updown, bool, + private_selinux_listener_t *this, ike_sa_t *ike_sa, bool up) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg; + child_sa_t *child_sa; + entry_t *entry; + + if (up) + { + child_sa_create_t child = { + .if_id_in_def = ike_sa->get_if_id(ike_sa, TRUE), + .if_id_out_def = ike_sa->get_if_id(ike_sa, FALSE), + }; + + INIT(entry, + .id = ike_sa->get_id(ike_sa), + ); + entry->id = entry->id->clone(entry->id); + + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); + while (enumerator->enumerate(enumerator, &child_cfg)) + { + if (child_cfg->get_label(child_cfg) && + child_cfg->get_label_mode(child_cfg) == SEC_LABEL_MODE_SELINUX) + { + child_sa = child_sa_create(ike_sa->get_my_host(ike_sa), + ike_sa->get_other_host(ike_sa), + child_cfg, &child); + if (install_generic_trap(ike_sa, child_sa)) + { + array_insert_create(&entry->traps, ARRAY_TAIL, child_sa); + } + else + { + child_sa->destroy(child_sa); + } + } + } + enumerator->destroy(enumerator); + + if (array_count(entry->traps)) + { + this->sas->put(this->sas, entry->id, entry); + } + else + { + destroy_entry(entry); + } + } + else + { + entry = this->sas->remove(this->sas, ike_sa->get_id(ike_sa)); + if (entry) + { + while (array_remove(entry->traps, ARRAY_TAIL, &child_sa)) + { + sec_label_t *label = child_sa->get_label(child_sa); + + DBG1(DBG_IKE, "uninstalling trap %s{%d} with generic security " + "label '%s'", child_sa->get_name(child_sa), + child_sa->get_unique_id(child_sa), + label->get_string(label)); + charon->traps->remove_external(charon->traps, child_sa); + child_sa->destroy(child_sa); + } + destroy_entry(entry); + } + } + return TRUE; +} + +METHOD(listener_t, ike_rekey, bool, + private_selinux_listener_t *this, ike_sa_t *old, ike_sa_t *new) +{ + entry_t *entry; + + entry = this->sas->remove(this->sas, old->get_id(old)); + if (entry) + { + entry->id->destroy(entry->id); + entry->id = new->get_id(new); + entry->id = entry->id->clone(entry->id); + this->sas->put(this->sas, entry->id, entry); + } + return TRUE; +} + +METHOD(listener_t, ike_update, bool, + private_selinux_listener_t *this, ike_sa_t *ike_sa, + host_t *local, host_t *remote) +{ + entry_t *entry; + child_sa_t *child_sa; + linked_list_t *vips; + int i; + + entry = this->sas->get(this->sas, ike_sa->get_id(ike_sa)); + if (entry) + { + vips = linked_list_create_from_enumerator( + ike_sa->create_virtual_ip_enumerator(ike_sa, local)); + for (i = 0; i < array_count(entry->traps); i++) + { + array_get(entry->traps, i, &child_sa); + child_sa->update(child_sa, local, remote, vips, + ike_sa->has_condition(ike_sa, COND_NAT_ANY)); + } + vips->destroy(vips); + } + return TRUE; +} + +METHOD(selinux_listener_t, destroy, void, + private_selinux_listener_t *this) +{ + this->sas->destroy(this->sas); + free(this); +} + +/* + * Described in header + */ +selinux_listener_t *selinux_listener_create() +{ + private_selinux_listener_t *this; + + INIT(this, + .public = { + .listener = { + .ike_updown = _ike_updown, + .ike_rekey = _ike_rekey, + .ike_update = _ike_update, + }, + .destroy = _destroy, + }, + .sas = hashtable_create(hash, equals, 32), + ); + + return &this->public; +} diff --git a/src/libcharon/plugins/selinux/selinux_listener.h b/src/libcharon/plugins/selinux/selinux_listener.h new file mode 100644 index 000000000..31deed214 --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_listener.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup selinux_listener selinux_listener + * @{ @ingroup selinux + */ + +#ifndef SELINUX_LISTENER_H_ +#define SELINUX_LISTENER_H_ + +#include + +typedef struct selinux_listener_t selinux_listener_t; + +/** + * Listener to manage trap policies for generic SELinux labels. + */ +struct selinux_listener_t { + + /** + * Implements listener_t interface. + */ + listener_t listener; + + /** + * Destroy a selinux_listener_t. + */ + void (*destroy)(selinux_listener_t *this); +}; + +/** + * Create a listener instance. + */ +selinux_listener_t *selinux_listener_create(); + +#endif /** SELINUX_LISTENER_H_ @}*/ diff --git a/src/libcharon/plugins/selinux/selinux_plugin.c b/src/libcharon/plugins/selinux/selinux_plugin.c new file mode 100644 index 000000000..80919ea25 --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_plugin.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "selinux_plugin.h" +#include "selinux_listener.h" + +#include + +typedef struct private_selinux_plugin_t private_selinux_plugin_t; + +/** + * Private data + */ +struct private_selinux_plugin_t { + + /** + * Public interface + */ + selinux_plugin_t public; + + /** + * Listener + */ + selinux_listener_t *listener; +}; + +METHOD(plugin_t, get_name, char*, + private_selinux_plugin_t *this) +{ + return "selinux"; +} + +/** + * Register handler + */ +static bool plugin_cb(private_selinux_plugin_t *this, + plugin_feature_t *feature, bool reg, void *cb_data) +{ + if (reg) + { + charon->bus->add_listener(charon->bus, &this->listener->listener); + } + else + { + charon->bus->remove_listener(charon->bus, &this->listener->listener); + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_selinux_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL), + PLUGIN_PROVIDE(CUSTOM, "selinux"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_selinux_plugin_t *this) +{ + this->listener->destroy(this->listener); + free(this); +} + +/* + * Described in header + */ +plugin_t *selinux_plugin_create() +{ + private_selinux_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + .listener = selinux_listener_create(), + ); + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/selinux/selinux_plugin.h b/src/libcharon/plugins/selinux/selinux_plugin.h new file mode 100644 index 000000000..46d413a9e --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_plugin.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup selinux selinux + * @ingroup cplugins + * + * @defgroup selinux_plugin selinux_plugin + * @{ @ingroup selinux + */ + +#ifndef SELINUX_PLUGIN_H_ +#define SELINUX_PLUGIN_H_ + +#include + +typedef struct selinux_plugin_t selinux_plugin_t; + +/** + * Plugin managing trap policies with generic SELinux labels. + */ +struct selinux_plugin_t { + + /** + * Implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** SELINUX_PLUGIN_H_ @}*/ From 92f54e55e98a3adb52ef556e84e033c831ce70ac Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 16:10:19 +0100 Subject: [PATCH 35/47] child-rekey: Maintain security label during rekeying --- src/libcharon/sa/ikev2/tasks/child_rekey.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index cf103e194..29ae6de67 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -210,6 +210,8 @@ METHOD(task_t, build_i, status_t, this->child_create->use_if_ids(this->child_create, this->child_sa->get_if_id(this->child_sa, TRUE), this->child_sa->get_if_id(this->child_sa, FALSE)); + this->child_create->use_label(this->child_create, + this->child_sa->get_label(this->child_sa)); if (this->child_create->task.build(&this->child_create->task, message) != NEED_MORE) @@ -272,6 +274,8 @@ METHOD(task_t, build_r, status_t, this->child_create->use_if_ids(this->child_create, this->child_sa->get_if_id(this->child_sa, TRUE), this->child_sa->get_if_id(this->child_sa, FALSE)); + this->child_create->use_label(this->child_create, + this->child_sa->get_label(this->child_sa)); config = this->child_sa->get_config(this->child_sa); this->child_create->set_config(this->child_create, config->get_ref(config)); this->child_create->task.build(&this->child_create->task, message); From 3f3838d1f949497226af9258c28e325ec318fedb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 16:16:00 +0100 Subject: [PATCH 36/47] ike-sa: Accept optional security label when initiating CHILD_SAs --- src/libcharon/sa/ike_sa.c | 1 + src/libcharon/sa/ike_sa.h | 2 ++ src/libcharon/sa/ikev2/task_manager_v2.c | 1 + src/libcharon/sa/ikev2/tasks/child_delete.c | 6 ++++++ src/libcharon/sa/ikev2/tasks/child_rekey.c | 12 ++++++++++-- 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 9bd351d60..b7db06946 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -2077,6 +2077,7 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, { child_init_args_t args = { .reqid = child_sa->get_reqid(child_sa), + .label = child_sa->get_label(child_sa), }; child_cfg = child_sa->get_config(child_sa); DBG1(DBG_IKE, "restarting CHILD_SA %s", diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index 8fc9a3cdd..8e4549258 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -380,6 +380,8 @@ struct child_init_args_t { traffic_selector_t *src; /** Optional destination of triggering packet */ traffic_selector_t *dst; + /** Optional security label of triggering packet */ + sec_label_t *label; }; /** diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index b359c67bb..555fb86c7 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -2110,6 +2110,7 @@ METHOD(task_manager_t, queue_child, void, { task = child_create_create(this->ike_sa, cfg, FALSE, args->src, args->dst); task->use_reqid(task, args->reqid); + task->use_label(task, args->label); } else { diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index 13cbee3f5..b570a36e0 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -366,6 +366,11 @@ static status_t destroy_and_reestablish(private_child_delete_t *this) child_cfg = child_sa->get_config(child_sa); child_cfg->get_ref(child_cfg); args.reqid = child_sa->get_reqid(child_sa); + args.label = child_sa->get_label(child_sa); + if (args.label) + { + args.label = args.label->clone(args.label); + } action = child_sa->get_close_action(child_sa); this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); @@ -385,6 +390,7 @@ static status_t destroy_and_reestablish(private_child_delete_t *this) } } child_cfg->destroy(child_cfg); + DESTROY_IF(args.label); if (status != SUCCESS) { break; diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index 29ae6de67..37b05c943 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -396,6 +396,7 @@ METHOD(task_t, process_i, status_t, { child_cfg_t *child_cfg; child_init_args_t args = {}; + status_t status; if (this->collision && this->collision->get_type(this->collision) == TASK_CHILD_DELETE) @@ -414,10 +415,17 @@ METHOD(task_t, process_i, status_t, child_cfg = this->child_sa->get_config(this->child_sa); child_cfg->get_ref(child_cfg); args.reqid = this->child_sa->get_reqid(this->child_sa); + args.label = this->child_sa->get_label(this->child_sa); + if (args.label) + { + args.label = args.label->clone(args.label); + } charon->bus->child_updown(charon->bus, this->child_sa, FALSE); this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); - return this->ike_sa->initiate(this->ike_sa, - child_cfg->get_ref(child_cfg), &args); + status = this->ike_sa->initiate(this->ike_sa, + child_cfg->get_ref(child_cfg), &args); + DESTROY_IF(args.label); + return status; } if (this->child_create->task.process(&this->child_create->task, From 52a05d7f6c39c2a794d4ece6cd32d46f08a5bd6c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 21 Dec 2021 11:22:36 +0100 Subject: [PATCH 37/47] kernel-interface: Optionally pass security label with an acquire --- src/libcharon/kernel/kernel_listener.h | 2 ++ src/libcharon/processing/jobs/acquire_job.c | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libcharon/kernel/kernel_listener.h b/src/libcharon/kernel/kernel_listener.h index 6534921c2..226b32f1e 100644 --- a/src/libcharon/kernel/kernel_listener.h +++ b/src/libcharon/kernel/kernel_listener.h @@ -37,6 +37,8 @@ struct kernel_acquire_data_t { traffic_selector_t *src; /** Optional destination of the triggering packet */ traffic_selector_t *dst; + /** Optional security label of the triggering packet */ + sec_label_t *label; }; /** diff --git a/src/libcharon/processing/jobs/acquire_job.c b/src/libcharon/processing/jobs/acquire_job.c index 0f06fcfcd..5142bb297 100644 --- a/src/libcharon/processing/jobs/acquire_job.c +++ b/src/libcharon/processing/jobs/acquire_job.c @@ -45,6 +45,7 @@ METHOD(job_t, destroy, void, { DESTROY_IF(this->data.src); DESTROY_IF(this->data.dst); + DESTROY_IF(this->data.label); free(this); } @@ -88,7 +89,10 @@ acquire_job_t *acquire_job_create(uint32_t reqid, kernel_acquire_data_t *data) { this->data.dst = this->data.dst->clone(this->data.dst); } + if (this->data.label) + { + this->data.label = this->data.label->clone(this->data.label); + } return &this->public; } - From 81c362829e5fb2a95edbebcb9bed89c20edf7380 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 21 Dec 2021 11:26:12 +0100 Subject: [PATCH 38/47] kernel-handler: Log security label received with acquire --- src/libcharon/kernel/kernel_handler.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libcharon/kernel/kernel_handler.c b/src/libcharon/kernel/kernel_handler.c index 4f467efee..2f881d746 100644 --- a/src/libcharon/kernel/kernel_handler.c +++ b/src/libcharon/kernel/kernel_handler.c @@ -55,14 +55,22 @@ static inline protocol_id_t proto_ip2ike(uint8_t protocol) METHOD(kernel_listener_t, acquire, bool, private_kernel_handler_t *this, uint32_t reqid, kernel_acquire_data_t *data) { + char buf[BUF_LEN] = ""; + + if (data->label) + { + snprintf(buf, sizeof(buf), ", label {%s}", + data->label->get_string(data->label)); + } if (data->src && data->dst) { DBG1(DBG_KNL, "creating acquire job for policy %R === %R with " - "reqid {%u}", data->src, data->dst, reqid); + "reqid {%u}%s", data->src, data->dst, reqid, buf); } else { - DBG1(DBG_KNL, "creating acquire job for policy with reqid {%u}", reqid); + DBG1(DBG_KNL, "creating acquire job for policy with reqid {%u}%s", + reqid, buf); } lib->processor->queue_job(lib->processor, (job_t*)acquire_job_create(reqid, data)); From 0a673794d857a8410db99bda7dbed1c7cfd78925 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 21 Dec 2021 11:26:50 +0100 Subject: [PATCH 39/47] trap-manager: Add support to handle acquires with security labels --- src/libcharon/sa/trap_manager.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index 7b6e2abdf..e45c8ff3f 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -120,6 +120,8 @@ typedef struct { uint32_t reqid; /** destination address (wildcard case) */ host_t *dst; + /** security label, if any */ + sec_label_t *label; } acquire_t; /** @@ -142,6 +144,7 @@ static void destroy_entry(entry_t *this) static void destroy_acquire(acquire_t *this) { DESTROY_IF(this->dst); + DESTROY_IF(this->label); free(this); } @@ -149,9 +152,10 @@ CALLBACK(acquire_by_reqid, bool, acquire_t *this, va_list args) { uint32_t reqid; + sec_label_t *label; - VA_ARGS_VGET(args, reqid); - return this->reqid == reqid; + VA_ARGS_VGET(args, reqid, label); + return this->reqid == reqid && sec_labels_equal(this->label, label); } CALLBACK(acquire_by_dst, bool, @@ -567,7 +571,7 @@ METHOD(trap_manager_t, acquire, void, else { if (this->acquires->find_first(this->acquires, acquire_by_reqid, - (void**)&acquire, reqid)) + (void**)&acquire, reqid, data->label)) { ignore = TRUE; } @@ -575,6 +579,7 @@ METHOD(trap_manager_t, acquire, void, { INIT(acquire, .reqid = reqid, + .label = data->label ? data->label->clone(data->label) : NULL, ); this->acquires->insert_last(this->acquires, acquire); } @@ -632,6 +637,7 @@ METHOD(trap_manager_t, acquire, void, .reqid = reqid, .src = data->src, .dst = data->dst, + .label = data->label, }; if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1) @@ -692,6 +698,11 @@ static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa, { continue; } + else if (!sec_labels_equal(acquire->label, + child_sa->get_label(child_sa))) + { + continue; + } } this->acquires->remove_at(this->acquires, enumerator); destroy_acquire(acquire); From f52e565ad31fe57ada343ade78ea4108a49f8720 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 21 Dec 2021 11:33:06 +0100 Subject: [PATCH 40/47] kernel-netlink: Forward labels from acquires --- .../kernel_netlink/kernel_netlink_ipsec.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 3cb54a00e..ef9ba35f7 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -910,6 +910,7 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, struct rtattr *rta; size_t rtasize; kernel_acquire_data_t data = {}; + chunk_t label = chunk_empty; uint32_t reqid = 0; uint8_t proto; @@ -926,10 +927,21 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, if (rta->rta_type == XFRMA_TMPL) { - struct xfrm_user_tmpl* tmpl; - tmpl = (struct xfrm_user_tmpl*)RTA_DATA(rta); + struct xfrm_user_tmpl* tmpl = RTA_DATA(rta); reqid = tmpl->reqid; } +#ifdef USE_SELINUX + if (rta->rta_type == XFRMA_SEC_CTX) + { + struct xfrm_user_sec_ctx *ctx = RTA_DATA(rta); + + if (ctx->ctx_doi == XFRM_SC_DOI_LSM && + ctx->ctx_alg == XFRM_SC_ALG_SELINUX) + { + label = chunk_create((void*)(ctx + 1), ctx->ctx_len); + } + } +#endif rta = RTA_NEXT(rta, rtasize); } switch (proto) @@ -940,15 +952,18 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, break; default: /* acquire for AH/ESP only, not for IPCOMP */ + return; } data.src = selector2ts(&acquire->sel, TRUE); data.dst = selector2ts(&acquire->sel, FALSE); + data.label = label.len ? sec_label_from_encoding(label) : NULL; charon->kernel->acquire(charon->kernel, reqid, &data); DESTROY_IF(data.src); DESTROY_IF(data.dst); + DESTROY_IF(data.label); } /** From 7cf6f29ac5eac7d0759dc98101134d42796a2008 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 21 Dec 2021 11:56:32 +0100 Subject: [PATCH 41/47] vici: Make security labels and mode configurable --- src/libcharon/plugins/vici/vici_config.c | 38 ++++++++++++++++++++++++ src/swanctl/swanctl.opt | 28 +++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index a7cd7f9d5..3a783b822 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -563,6 +563,9 @@ static void log_child_data(child_data_t *data, char *name) cfg->set_mark_in.value, cfg->set_mark_in.mask); DBG2(DBG_CFG, " set_mark_out = %u/%u", cfg->set_mark_out.value, cfg->set_mark_out.mask); + DBG2(DBG_CFG, " label = %s", + cfg->label ? cfg->label->get_string(cfg->label) : NULL); + DBG2(DBG_CFG, " label_mode = %N", sec_label_mode_names, cfg->label_mode); DBG2(DBG_CFG, " inactivity = %llu", cfg->inactivity); DBG2(DBG_CFG, " proposals = %#P", data->proposals); DBG2(DBG_CFG, " local_ts = %#R", data->local_ts); @@ -585,6 +588,7 @@ static void free_child_data(child_data_t *data) offsetof(traffic_selector_t, destroy)); data->remote_ts->destroy_offset(data->remote_ts, offsetof(traffic_selector_t, destroy)); + DESTROY_IF(data->cfg.label); free(data->cfg.updown); free(data->cfg.interface); } @@ -1270,6 +1274,38 @@ CALLBACK(parse_if_id, bool, return if_id_from_string(buf, out); } +/** + * Parse security label + */ +CALLBACK(parse_label, bool, + sec_label_t **out, chunk_t v) +{ + char buf[BUF_LEN]; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + *out = sec_label_from_string(buf); + return *out != NULL; +} + +/** + * Parse security label mode + */ +CALLBACK(parse_label_mode, bool, + sec_label_mode_t *out, chunk_t v) +{ + char buf[BUF_LEN]; + + if (!vici_stringify(v, buf, sizeof(buf)) || + !sec_label_mode_from_string(buf, out)) + { + return FALSE; + } + return TRUE; +} + /** * Parse TFC padding option */ @@ -1769,6 +1805,8 @@ CALLBACK(child_kv, bool, { "copy_dscp", parse_copy_dscp, &child->cfg.copy_dscp }, { "if_id_in", parse_if_id, &child->cfg.if_id_in }, { "if_id_out", parse_if_id, &child->cfg.if_id_out }, + { "label", parse_label, &child->cfg.label }, + { "label_mode", parse_label_mode, &child->cfg.label_mode }, }; return parse_rules(rules, countof(rules), name, value, diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index 082bf4cbb..553831eca 100644 --- a/src/swanctl/swanctl.opt +++ b/src/swanctl/swanctl.opt @@ -1011,6 +1011,34 @@ connections..children..if_id_out = 0 The daemon will not install routes for CHILD_SAs that have this option set. +connections..children..label + Optional security label (e.g. SELinux context), IKEv2 only. Refer to + **label_mode** for details on how labels are processed. + +connections..children..label_mode = system + Security label mode (_system_, _simple_ or _selinux_), IKEv2 only. + + Defines the mode in which the configured security label is used. The default + value of _system_ selects _selinux_ if strongSwan was built with SELinux + support and SELinux is enabled by the kernel, otherwise, _simple_ will be + selected. + + If set to _simple_, the label will be used as is as an additional + identifier/selector on the IKEv2 level when negotiating CHILD_SAs and + selecting configs, labels are not installed in the kernel and received + labels have to match exactly. + + If set to _selinux_, which is only allowed if SELinux is usable on the + system, the configured label is expected to be a generic context (e.g. + _system_u:object_r:ipsec_spd_t:s0_) for which flows, whose context match it + via association:polmatch, will trigger an acquire if no SA exists yet for + the flow's specific context. The configured label is installed on (trap) + policies, so this should generally be combined with _trap_ in + **start_action**. However, if the connection is initiated directly, + without acquire, a childless IKE_SA is established and appropriate trap + policies are installed on both ends. Labels received from peers are accepted + if they match the configured label via association:polmatch. + connections..children..tfc_padding = 0 Traffic Flow Confidentiality padding. From 2994347d1862e2419dc758b5f245c235e6c6881e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 21 Dec 2021 12:10:34 +0100 Subject: [PATCH 42/47] vici: Report security label on CHILD_SA, policies and configs --- src/libcharon/plugins/vici/README.md | 3 +++ src/libcharon/plugins/vici/vici_query.c | 27 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md index 11c6e8166..5226bd349 100644 --- a/src/libcharon/plugins/vici/README.md +++ b/src/libcharon/plugins/vici/README.md @@ -817,6 +817,7 @@ command. mark-mask-out = if-id-in = if-id-out = + label = encr-alg = encr-keysize = integ-alg = @@ -854,6 +855,7 @@ _list-policies_ command. child = ike = mode = + label = local-ts = [ ] @@ -903,6 +905,7 @@ _list-conns_ command. children = { * = { mode = + label = rekey_time = rekey_bytes = rekey_packets = diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index fb65b1447..4164269b3 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -150,6 +150,29 @@ static void list_mode(vici_builder_t *b, child_sa_t *child, child_cfg_t *cfg) } } +/** + * List the security label of a CHILD_SA or config + */ +static void list_label(vici_builder_t *b, child_sa_t *child, child_cfg_t *cfg) +{ + sec_label_t *label = NULL; + chunk_t enc; + + if (child) + { + label = child->get_label(child); + } + else if (cfg) + { + label = cfg->get_label(cfg); + } + if (label) + { + enc = label->get_encoding(label); + b->add_kv(b, "label", "%+B", &enc); + } +} + /** * List IPsec-related details about a CHILD_SA */ @@ -275,6 +298,7 @@ static void list_child(private_vici_query_t *this, vici_builder_t *b, state = child->get_state(child); b->add_kv(b, "state", "%N", child_sa_state_names, state); list_mode(b, child, NULL); + list_label(b, child, NULL); switch (state) { @@ -576,6 +600,7 @@ static void raise_policy(private_vici_query_t *this, u_int id, char *ike, b->add_kv(b, "ike", "%s", ike); list_mode(b, child, NULL); + list_label(b, child, NULL); b->begin_list(b, "local-ts"); enumerator = child->create_ts_enumerator(child, TRUE); @@ -624,6 +649,7 @@ static void raise_policy_cfg(private_vici_query_t *this, u_int id, char *ike, } list_mode(b, NULL, cfg); + list_label(b, NULL, cfg); b->begin_list(b, "local-ts"); list = cfg->get_traffic_selectors(cfg, TRUE, NULL, NULL, FALSE); @@ -930,6 +956,7 @@ CALLBACK(list_conns, vici_message_t*, b->begin_section(b, child_cfg->get_name(child_cfg)); list_mode(b, NULL, child_cfg); + list_label(b, NULL, child_cfg); lft = child_cfg->get_lifetime(child_cfg, FALSE); b->add_kv(b, "rekey_time", "%"PRIu64, lft->time.rekey); From b67f7fb861144e3b707374df2be8f13d04f39eb9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 21 Dec 2021 12:18:21 +0100 Subject: [PATCH 43/47] swanctl: Report labels in --list-* commands --- src/swanctl/commands/list_conns.c | 2 ++ src/swanctl/commands/list_pols.c | 2 ++ src/swanctl/commands/list_sas.c | 3 ++- src/swanctl/swanctl.c | 22 ++++++++++++++++++++++ src/swanctl/swanctl.h | 9 +++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/swanctl/commands/list_conns.c b/src/swanctl/commands/list_conns.c index f00fb54fd..e7a8db0ce 100644 --- a/src/swanctl/commands/list_conns.c +++ b/src/swanctl/commands/list_conns.c @@ -20,6 +20,7 @@ #include #include +#include "swanctl.h" #include "command.h" #include @@ -135,6 +136,7 @@ CALLBACK(children_sn, int, } printf("\n"); + print_label(" label: ", child->get(child, "label")); printf(" local: %s\n", child->get(child, "local-ts")); printf(" remote: %s\n", child->get(child, "remote-ts")); diff --git a/src/swanctl/commands/list_pols.c b/src/swanctl/commands/list_pols.c index f2ae22172..b238b224b 100644 --- a/src/swanctl/commands/list_pols.c +++ b/src/swanctl/commands/list_pols.c @@ -18,6 +18,7 @@ #include #include "command.h" +#include "swanctl.h" #include @@ -85,6 +86,7 @@ CALLBACK(policies, int, ret = vici_parse_cb(res, NULL, policy_values, policy_list, pol); printf("%s, %s\n", name, pol->get(pol, "mode")); + print_label(" label: ", pol->get(pol, "label")); printf(" local: %s\n", pol->get(pol, "local-ts")); printf(" remote: %s\n", pol->get(pol, "remote-ts")); diff --git a/src/swanctl/commands/list_sas.c b/src/swanctl/commands/list_sas.c index ca2436dd2..48c06fd61 100644 --- a/src/swanctl/commands/list_sas.c +++ b/src/swanctl/commands/list_sas.c @@ -43,6 +43,7 @@ #include #include "command.h" +#include "swanctl.h" #include @@ -81,7 +82,6 @@ CALLBACK(sa_values, int, return 0; } - CALLBACK(sa_list, int, hashtable_t *sa, vici_res_t *res, char *name, void *value, int len) { @@ -227,6 +227,7 @@ CALLBACK(child_sas, int, } printf("\n"); + print_label(" label ", child->get(child, "label")); printf(" local %s\n", child->get(child, "local-ts")); printf(" remote %s\n", child->get(child, "remote-ts")); } diff --git a/src/swanctl/swanctl.c b/src/swanctl/swanctl.c index cfc82f9d7..dc27646bd 100644 --- a/src/swanctl/swanctl.c +++ b/src/swanctl/swanctl.c @@ -22,6 +22,7 @@ #include #include +#include /* * Described in header @@ -59,6 +60,27 @@ settings_t *load_swanctl_conf(char *file) return cfg; } +/* + * Described in header + */ +void print_label(const char *prefix, const char *value) +{ + sec_label_t *label; + chunk_t encoding; + + if (value) + { + encoding = chunk_from_hex(chunk_from_str((char*)value), NULL); + label = sec_label_from_encoding(encoding); + if (label) + { + printf("%s%s\n", prefix, label->get_string(label)); + label->destroy(label); + } + chunk_free(&encoding); + } +} + /** * Cleanup library atexit() */ diff --git a/src/swanctl/swanctl.h b/src/swanctl/swanctl.h index 70f70d179..c7f7a6c43 100644 --- a/src/swanctl/swanctl.h +++ b/src/swanctl/swanctl.h @@ -111,4 +111,13 @@ extern char *swanctl_dir; */ settings_t *load_swanctl_conf(char *file); +/** + * Print the given security label (hex-encoded) on a separate line with the + * given prefix. + * + * @param prefix prefix to print before the string-version of the label + * @param value hex-encoded security label + */ +void print_label(const char *prefix, const char *value); + #endif /** SWANCTL_H_ @}*/ From 994d9d37d475820d3468cfe9656ab8efe314fb73 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Jun 2021 14:29:55 +0200 Subject: [PATCH 44/47] testing: Copy comments to test log --- testing/do-tests | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/testing/do-tests b/testing/do-tests index ace12b86b..1e6a83a37 100755 --- a/testing/do-tests +++ b/testing/do-tests @@ -406,12 +406,16 @@ do echo -e "\nPRE-TEST\n" >> $CONSOLE_LOG 2>&1 eval `awk -F "::" '{ - if ($1 !~ /^#.*/ && $2 != "") - { - printf("echo \"$(print_time)%s# %s\"; ", $1, $2) - printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) - printf("echo;\n") - } + if ($0 ~ /^#.*/) + { + printf("echo \"%s\"; ", $0); + } + else if ($2 != "") + { + printf("echo \"$(print_time)%s# %s\"; ", $1, $2) + printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) + printf("echo;\n") + } }' $TESTDIR/pretest.dat` >> $CONSOLE_LOG 2>&1 @@ -443,7 +447,12 @@ do command=$2 pattern=$3 hit=$4 - if (host ~ /^#.*/ || command == "") + if ($0 ~ /^#.*/) + { + printf("echo \"%s\"; ", $0); + next + } + else if (command == "") { next } @@ -766,12 +775,16 @@ do echo -e "\nPOST-TEST\n" >> $CONSOLE_LOG 2>&1 eval `awk -F "::" '{ - if ($1 !~ /^#.*/ && $2 != "") - { - printf("echo \"$(print_time)%s# %s\"; ", $1, $2) - printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) - printf("echo;\n") - } + if ($0 ~ /^#.*/) + { + printf("echo \"%s\"; ", $0); + } + else if ($2 != "") + { + printf("echo \"$(print_time)%s# %s\"; ", $1, $2) + printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) + printf("echo;\n") + } }' $TESTDIR/posttest.dat` >> $CONSOLE_LOG 2>&1 ########################################################################## From b1c7fac768becf70a3cdf8e84542704e4507bdd2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Jun 2021 14:37:39 +0200 Subject: [PATCH 45/47] vici: Add options to only return specific CHILD_SAs in list-sas() --- src/libcharon/plugins/vici/README.md | 6 ++++-- src/libcharon/plugins/vici/vici_query.c | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md index 5226bd349..6b75711fa 100644 --- a/src/libcharon/plugins/vici/README.md +++ b/src/libcharon/plugins/vici/README.md @@ -277,7 +277,7 @@ Terminates an SA while streaming _control-log_ events. { child = ike = - child-id = + child-id = ike-id = force = @@ -300,7 +300,7 @@ Initiate the rekeying of an SA. { child = ike = - child-id = + child-id = ike-id = reauth = } => { @@ -361,6 +361,8 @@ events. noblock = ike = ike-id = + child = + child-id = } => { # completes after streaming list-sa events } diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index 4164269b3..ff2d36b21 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -529,15 +529,16 @@ CALLBACK(list_sas, vici_message_t*, ike_sa_t *ike_sa; child_sa_t *child_sa; time_t now; - char *ike; - u_int ike_id; + char *ike, *child; + u_int ike_id, child_id; bool bl; char buf[BUF_LEN]; - bl = request->get_str(request, NULL, "noblock") == NULL; ike = request->get_str(request, NULL, "ike"); ike_id = request->get_int(request, 0, "ike-id"); + child = request->get_str(request, NULL, "child"); + child_id = request->get_int(request, 0, "child-id"); isas = charon->controller->create_ike_sa_enumerator(charon->controller, bl); while (isas->enumerate(isas, &ike_sa)) @@ -562,6 +563,15 @@ CALLBACK(list_sas, vici_message_t*, csas = ike_sa->create_child_sa_enumerator(ike_sa); while (csas->enumerate(csas, &child_sa)) { + if (child && !streq(child, child_sa->get_name(child_sa))) + { + continue; + } + if (child_id && child_sa->get_unique_id(child_sa) != child_id) + { + continue; + } + snprintf(buf, sizeof(buf), "%s-%u", child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa)); b->begin_section(b, buf); From 955b526be806dc384cbb16ce82e042532d4ed9fe Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Jun 2021 14:46:03 +0200 Subject: [PATCH 46/47] swanctl: Add options to filter CHILD_SAs in --list-sas --- src/swanctl/commands/list_sas.c | 23 ++++++++++++++++++++--- src/swanctl/commands/terminate.c | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/swanctl/commands/list_sas.c b/src/swanctl/commands/list_sas.c index 48c06fd61..0a7005ce2 100644 --- a/src/swanctl/commands/list_sas.c +++ b/src/swanctl/commands/list_sas.c @@ -373,8 +373,8 @@ static int list_sas(vici_conn_t *conn) vici_res_t *res; bool noblock = FALSE; command_format_options_t format = COMMAND_FORMAT_NONE; - char *arg, *ike = NULL; - int ike_id = 0, ret; + char *arg, *ike = NULL, *child = NULL; + int ike_id = 0, child_id = 0, ret; while (TRUE) { @@ -388,6 +388,12 @@ static int list_sas(vici_conn_t *conn) case 'I': ike_id = atoi(arg); continue; + case 'c': + child = arg; + continue; + case 'C': + child_id = atoi(arg); + continue; case 'n': noblock = TRUE; continue; @@ -419,6 +425,14 @@ static int list_sas(vici_conn_t *conn) { vici_add_key_valuef(req, "ike-id", "%d", ike_id); } + if (child) + { + vici_add_key_valuef(req, "child", "%s", child); + } + if (child_id) + { + vici_add_key_valuef(req, "child-id", "%d", child_id); + } if (noblock) { vici_add_key_valuef(req, "noblock", "yes"); @@ -490,11 +504,14 @@ static void __attribute__ ((constructor))reg() { command_register((command_t) { list_sas, 'l', "list-sas", "list currently active IKE_SAs", - {"[--raw|--pretty]"}, + {"[--ike |--ike-id ] [--child |--child-id ]", + "[--raw|--pretty]"}, { {"help", 'h', 0, "show usage information"}, {"ike", 'i', 1, "filter IKE_SAs by name"}, {"ike-id", 'I', 1, "filter IKE_SAs by unique identifier"}, + {"child", 'c', 1, "filter CHILD_SAs by name"}, + {"child-id", 'C', 1, "filter CHILD_SAs by unique identifier"}, {"noblock", 'n', 0, "don't wait for IKE_SAs in use"}, {"raw", 'r', 0, "dump raw response message"}, {"pretty", 'P', 0, "dump raw response message in pretty print"}, diff --git a/src/swanctl/commands/terminate.c b/src/swanctl/commands/terminate.c index 2309843b2..21ec1c8fe 100644 --- a/src/swanctl/commands/terminate.c +++ b/src/swanctl/commands/terminate.c @@ -156,7 +156,7 @@ static void __attribute__ ((constructor))reg() {"help", 'h', 0, "show usage information"}, {"child", 'c', 1, "terminate by CHILD_SA name"}, {"ike", 'i', 1, "terminate by IKE_SA name"}, - {"child-id", 'C', 1, "terminate by CHILD_SA reqid"}, + {"child-id", 'C', 1, "terminate by CHILD_SA unique identifier"}, {"ike-id", 'I', 1, "terminate by IKE_SA unique identifier"}, {"force", 'f', 0, "terminate IKE_SA without waiting, unless timeout is set"}, {"timeout", 't', 1, "timeout in seconds before detaching"}, From 6910347d1aba509ac7085938124b2b7c9f240064 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 14 Apr 2022 14:13:07 +0200 Subject: [PATCH 47/47] testing: Add DSCP scenario that uses simple labels Instead of creating two IKE_SAs with different identities, this scenario uses simple labels to select the correct child config. --- .../tests/ikev2/net2net-dscp/description.txt | 11 ++++++ testing/tests/ikev2/net2net-dscp/evaltest.dat | 10 +++++ .../hosts/moon/etc/strongswan.conf | 14 +++++++ .../hosts/moon/etc/swanctl/swanctl.conf | 38 +++++++++++++++++++ .../hosts/sun/etc/strongswan.conf | 15 ++++++++ .../hosts/sun/etc/swanctl/swanctl.conf | 38 +++++++++++++++++++ testing/tests/ikev2/net2net-dscp/posttest.dat | 8 ++++ testing/tests/ikev2/net2net-dscp/pretest.dat | 16 ++++++++ testing/tests/ikev2/net2net-dscp/test.conf | 25 ++++++++++++ 9 files changed, 175 insertions(+) create mode 100644 testing/tests/ikev2/net2net-dscp/description.txt create mode 100644 testing/tests/ikev2/net2net-dscp/evaltest.dat create mode 100644 testing/tests/ikev2/net2net-dscp/hosts/moon/etc/strongswan.conf create mode 100755 testing/tests/ikev2/net2net-dscp/hosts/moon/etc/swanctl/swanctl.conf create mode 100644 testing/tests/ikev2/net2net-dscp/hosts/sun/etc/strongswan.conf create mode 100755 testing/tests/ikev2/net2net-dscp/hosts/sun/etc/swanctl/swanctl.conf create mode 100644 testing/tests/ikev2/net2net-dscp/posttest.dat create mode 100644 testing/tests/ikev2/net2net-dscp/pretest.dat create mode 100644 testing/tests/ikev2/net2net-dscp/test.conf diff --git a/testing/tests/ikev2/net2net-dscp/description.txt b/testing/tests/ikev2/net2net-dscp/description.txt new file mode 100644 index 000000000..0108bcb14 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/description.txt @@ -0,0 +1,11 @@ +In order to support Differentiated Services (DiffServ), two parallel IPsec +connections between the subnets behind the gateways moon and sun are +set up. Using XFRM marks one IPsec SA is designated for Best Effort (BE) +traffic and the second SA for Expedited Forwarding (EF) traffic. To guarantee +that the CHILD_SA with the correct mark is selected on the responder side, labels +are used and negotiated via IKEv2. The authentication is based on X.509 certificates. +

+Upon the successful establishment of the IPsec tunnel, the updown script automatically +inserts iptables-based firewall rules that let pass the tunneled traffic +In order to test both tunnel and firewall, client alice behind gateway moon +pings client bob located behind gateway sun. diff --git a/testing/tests/ikev2/net2net-dscp/evaltest.dat b/testing/tests/ikev2/net2net-dscp/evaltest.dat new file mode 100644 index 000000000..313891ef9 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/evaltest.dat @@ -0,0 +1,10 @@ +moon::swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_MOON local-port=500 local-id=moon.strongswan.org remote-host=PH_IP_SUN remote-port=500 remote-id=sun.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-be.*state=INSTALLED mode=TUNNEL label=647363702d626500.*ESP.*mark-in=0000000a mark-out=0000000a encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/16] remote-ts=\[10.2.0.0/16]::YES +moon::swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_MOON local-port=500 local-id=moon.strongswan.org remote-host=PH_IP_SUN remote-port=500 remote-id=sun.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-ef.*state=INSTALLED mode=TUNNEL label=647363702d656600.*ESP.*mark-in=00000014 mark-out=00000014 encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/16] remote-ts=\[10.2.0.0/16]::YES +sun:: swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_SUN local-port=500 local-id=sun.strongswan.org remote-host=PH_IP_MOON remote-port=500 remote-id=moon.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-be.*state=INSTALLED mode=TUNNEL label=647363702d626500.*ESP.*mark-in=0000000a mark-out=0000000a encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.0/16]::YES +sun:: swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_SUN local-port=500 local-id=sun.strongswan.org remote-host=PH_IP_MOON remote-port=500 remote-id=moon.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-ef.*state=INSTALLED mode=TUNNEL label=647363702d656600.*ESP.*mark-in=00000014 mark-out=00000014 encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.0/16]::YES +alice::ping -c 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::YES +venus::ping -c 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::YES +moon::swanctl --list-sas --child net-be --raw 2> /dev/null::child-sas.*net-be.*packets-in=1.*packets-out=1::YES +moon::swanctl --list-sas --child net-ef --raw 2> /dev/null::child-sas.*net-ef.*packets-in=1.*packets-out=1::YES +sun::tcpdump::IP moon.strongswan.org > sun.strongswan.org: ESP::YES +sun::tcpdump::IP sun.strongswan.org > moon.strongswan.org: ESP::YES diff --git a/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/strongswan.conf b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/strongswan.conf new file mode 100644 index 000000000..4fe92b6ec --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/strongswan.conf @@ -0,0 +1,14 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes sha1 sha2 hmac pem pkcs1 x509 revocation curve25519 gmp curl kernel-netlink socket-default updown vici + syslog { + daemon { + knl = 2 + } + } +} diff --git a/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/swanctl/swanctl.conf b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/swanctl/swanctl.conf new file mode 100755 index 000000000..d6c2b3003 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/swanctl/swanctl.conf @@ -0,0 +1,38 @@ +connections { + + dscp { + local_addrs = PH_IP_MOON + remote_addrs = PH_IP_SUN + + local { + auth = pubkey + certs = moonCert.pem + id = moon.strongswan.org + } + remote { + auth = pubkey + id = sun.strongswan.org + } + children { + net-be { + local_ts = 10.1.0.0/16 + remote_ts = 10.2.0.0/16 + + mark_in = 10 + mark_out = 10 + label = dscp-be + + updown = /usr/local/libexec/ipsec/_updown iptables + esp_proposals = aes128gcm128-x25519 + } + net-ef : connections.dscp.children.net-be { + mark_in = 20 + mark_out = 20 + label = dscp-ef + } + } + version = 2 + mobike = no + proposals = aes128-sha256-x25519 + } +} diff --git a/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/strongswan.conf b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/strongswan.conf new file mode 100644 index 000000000..f7a83f3a4 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/strongswan.conf @@ -0,0 +1,15 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes sha1 sha2 hmac pem pkcs1 x509 revocation curve25519 gmp curl kernel-netlink socket-default updown vici + multiple_authentication = no + syslog { + daemon { + knl = 2 + } + } +} diff --git a/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/swanctl/swanctl.conf b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/swanctl/swanctl.conf new file mode 100755 index 000000000..6e69f2a3c --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/swanctl/swanctl.conf @@ -0,0 +1,38 @@ +connections { + + dscp { + local_addrs = PH_IP_SUN + remote_addrs = PH_IP_MOON + + local { + auth = pubkey + certs = sunCert.pem + id = sun.strongswan.org + } + remote { + auth = pubkey + id = moon.strongswan.org + } + children { + net-be { + local_ts = 10.2.0.0/16 + remote_ts = 10.1.0.0/16 + + mark_in = 10 + mark_out = 10 + label = dscp-be + + updown = /usr/local/libexec/ipsec/_updown iptables + esp_proposals = aes128gcm128-x25519 + } + net-ef : connections.dscp.children.net-be { + mark_in = 20 + mark_out = 20 + label = dscp-ef + } + } + version = 2 + mobike = no + proposals = aes128-sha256-x25519 + } +} diff --git a/testing/tests/ikev2/net2net-dscp/posttest.dat b/testing/tests/ikev2/net2net-dscp/posttest.dat new file mode 100644 index 000000000..8c9a5f9ba --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/posttest.dat @@ -0,0 +1,8 @@ +moon::systemctl stop strongswan +sun::systemctl stop strongswan +moon::iptables-restore < /etc/iptables.flush +sun::iptables-restore < /etc/iptables.flush +alice::iptables -t mangle -F OUTPUT +venus::iptables -t mangle -F OUTPUT +bob::iptables -t mangle -F OUTPUT + diff --git a/testing/tests/ikev2/net2net-dscp/pretest.dat b/testing/tests/ikev2/net2net-dscp/pretest.dat new file mode 100644 index 000000000..2d5d7ba37 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/pretest.dat @@ -0,0 +1,16 @@ +moon::iptables-restore < /etc/iptables.rules +sun::iptables-restore < /etc/iptables.rules +alice::iptables -t mangle -A OUTPUT -p icmp -j DSCP --set-dscp-class BE +venus::iptables -t mangle -A OUTPUT -p icmp -j DSCP --set-dscp-class EF +moon::iptables -t mangle -A PREROUTING -m dscp --dscp-class BE -j MARK --set-mark 10 +moon::iptables -t mangle -A PREROUTING -m dscp --dscp-class EF -j MARK --set-mark 20 +bob::iptables -t mangle -A OUTPUT -d PH_IP_ALICE -p icmp -j DSCP --set-dscp-class BE +bob::iptables -t mangle -A OUTPUT -d PH_IP_VENUS -p icmp -j DSCP --set-dscp-class EF +sun::iptables -t mangle -A PREROUTING -m dscp --dscp-class BE -j MARK --set-mark 10 +sun::iptables -t mangle -A PREROUTING -m dscp --dscp-class EF -j MARK --set-mark 20 +sun::systemctl start strongswan +moon::systemctl start strongswan +sun::expect-connection dscp +moon::expect-connection dscp +moon::swanctl --initiate --child net-be +moon::swanctl --initiate --child net-ef diff --git a/testing/tests/ikev2/net2net-dscp/test.conf b/testing/tests/ikev2/net2net-dscp/test.conf new file mode 100644 index 000000000..7f82c02f1 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/test.conf @@ -0,0 +1,25 @@ +#!/bin/bash +# +# This configuration file provides information on the +# guest instances used for this test + +# All guest instances that are required for this test +# +VIRTHOSTS="alice venus moon winnetou sun bob" + +# Corresponding block diagram +# +DIAGRAM="a-v-m-w-s-b.png" + +# Guest instances on which tcpdump is to be started +# +TCPDUMPHOSTS="sun" + +# Guest instances on which IPsec is started +# Used for IPsec logging purposes +# +IPSECHOSTS="moon sun" + +# charon controlled by swanctl +# +SWANCTL=1