]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
kernel-netlink: Add support for optional security label on SAs and policies
authorTobias Brunner <tobias@strongswan.org>
Mon, 20 Dec 2021 14:02:42 +0000 (15:02 +0100)
committerTobias Brunner <tobias@strongswan.org>
Thu, 14 Apr 2022 16:42:01 +0000 (18:42 +0200)
src/libcharon/kernel/kernel_ipsec.h
src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c

index 70ff2eb12d2625f8d732a217d2975eff97dab089..343cdd136f268578486d27f0fc2a700cebf7959b 100644 (file)
@@ -38,6 +38,7 @@ typedef struct kernel_ipsec_query_policy_t kernel_ipsec_query_policy_t;
 #include <networking/host.h>
 #include <ipsec/ipsec_types.h>
 #include <selectors/traffic_selector.h>
+#include <selectors/sec_label.h>
 #include <plugins/plugin.h>
 #include <kernel/kernel_interface.h>
 
@@ -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;
 };
 
 /**
index c3c6d60350b4520eaa3feb86c411a42b0481f398..c4696512778b3fe30d5723ab5e9962d815c97708 100644 (file)
@@ -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;
        }