From bf0542c4e1f37b3744ee2389f63ab506d24784b1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Dec 2021 15:02:42 +0100 Subject: [PATCH] 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 70ff2eb12d..343cdd136f 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 c3c6d60350..c469651277 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; } -- 2.47.2