kernel-netlink: Add support for optional security label on SAs and policies

This commit is contained in:
Tobias Brunner 2021-12-20 15:02:42 +01:00
parent c4e2b7617f
commit bf0542c4e1
2 changed files with 120 additions and 33 deletions

View 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;
};
/**

View 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;
}