From f9b895b49f4911adf12de73bb4e4abd6f9d5dbb2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Feb 2022 10:40:16 +0100 Subject: [PATCH] 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 389ffb2d75..ddfe8cf05e 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 eae1f3532f..40e25d89a8 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. * -- 2.47.2