Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+618
View File
@@ -0,0 +1,618 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Implementation of the access vector table type.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
/* Updated: Frank Mayer <mayerf@tresys.com> and
* Karl MacMillan <kmacmillan@tresys.com>
* Added conditional policy language extensions
* Copyright (C) 2003 Tresys Technology, LLC
*
* Updated: Yuichi Nakamura <ynakam@hitachisoft.jp>
* Tuned number of hash slots for avtab to reduce memory usage
*/
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include "avtab.h"
#include "policydb.h"
#include "hash.h"
static struct kmem_cache *avtab_node_cachep __ro_after_init;
static struct kmem_cache *avtab_xperms_cachep __ro_after_init;
static inline u32 avtab_hash(const struct avtab_key *keyp, u32 mask)
{
return av_hash((u32)keyp->target_class, (u32)keyp->target_type,
(u32)keyp->source_type, mask);
}
static struct avtab_node *avtab_insert_node(struct avtab *h,
struct avtab_node **dst,
const struct avtab_key *key,
const struct avtab_datum *datum)
{
struct avtab_node *newnode;
struct avtab_extended_perms *xperms;
newnode = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL);
if (newnode == NULL)
return NULL;
newnode->key = *key;
if (key->specified & AVTAB_XPERMS) {
xperms = kmem_cache_zalloc(avtab_xperms_cachep, GFP_KERNEL);
if (xperms == NULL) {
kmem_cache_free(avtab_node_cachep, newnode);
return NULL;
}
*xperms = *(datum->u.xperms);
newnode->datum.u.xperms = xperms;
} else {
newnode->datum.u.data = datum->u.data;
}
newnode->next = *dst;
*dst = newnode;
h->nel++;
return newnode;
}
static int avtab_node_cmp(const struct avtab_key *key1,
const struct avtab_key *key2)
{
u16 specified = key1->specified & ~(AVTAB_ENABLED | AVTAB_ENABLED_OLD);
if (key1->source_type == key2->source_type &&
key1->target_type == key2->target_type &&
key1->target_class == key2->target_class &&
(specified & key2->specified))
return 0;
if (key1->source_type < key2->source_type)
return -1;
if (key1->source_type == key2->source_type &&
key1->target_type < key2->target_type)
return -1;
if (key1->source_type == key2->source_type &&
key1->target_type == key2->target_type &&
key1->target_class < key2->target_class)
return -1;
return 1;
}
static int avtab_insert(struct avtab *h, const struct avtab_key *key,
const struct avtab_datum *datum)
{
u32 hvalue;
struct avtab_node *prev, *cur, *newnode;
int cmp;
if (!h || !h->nslot || h->nel == U32_MAX)
return -EINVAL;
hvalue = avtab_hash(key, h->mask);
for (prev = NULL, cur = h->htable[hvalue]; cur;
prev = cur, cur = cur->next) {
cmp = avtab_node_cmp(key, &cur->key);
/* extended perms may not be unique */
if (cmp == 0 && !(key->specified & AVTAB_XPERMS))
return -EEXIST;
if (cmp <= 0)
break;
}
newnode = avtab_insert_node(h, prev ? &prev->next : &h->htable[hvalue],
key, datum);
if (!newnode)
return -ENOMEM;
return 0;
}
/* Unlike avtab_insert(), this function allow multiple insertions of the same
* key/specified mask into the table, as needed by the conditional avtab.
* It also returns a pointer to the node inserted.
*/
struct avtab_node *avtab_insert_nonunique(struct avtab *h,
const struct avtab_key *key,
const struct avtab_datum *datum)
{
u32 hvalue;
struct avtab_node *prev, *cur;
int cmp;
if (!h || !h->nslot || h->nel == U32_MAX)
return NULL;
hvalue = avtab_hash(key, h->mask);
for (prev = NULL, cur = h->htable[hvalue]; cur;
prev = cur, cur = cur->next) {
cmp = avtab_node_cmp(key, &cur->key);
if (cmp <= 0)
break;
}
return avtab_insert_node(h, prev ? &prev->next : &h->htable[hvalue],
key, datum);
}
/* This search function returns a node pointer, and can be used in
* conjunction with avtab_search_next_node()
*/
struct avtab_node *avtab_search_node(struct avtab *h,
const struct avtab_key *key)
{
u32 hvalue;
struct avtab_node *cur;
int cmp;
if (!h || !h->nslot)
return NULL;
hvalue = avtab_hash(key, h->mask);
for (cur = h->htable[hvalue]; cur; cur = cur->next) {
cmp = avtab_node_cmp(key, &cur->key);
if (cmp == 0)
return cur;
if (cmp < 0)
break;
}
return NULL;
}
struct avtab_node *avtab_search_node_next(struct avtab_node *node,
u16 specified)
{
struct avtab_key tmp_key;
struct avtab_node *cur;
int cmp;
if (!node)
return NULL;
tmp_key = node->key;
tmp_key.specified = specified;
for (cur = node->next; cur; cur = cur->next) {
cmp = avtab_node_cmp(&tmp_key, &cur->key);
if (cmp == 0)
return cur;
if (cmp < 0)
break;
}
return NULL;
}
void avtab_destroy(struct avtab *h)
{
u32 i;
struct avtab_node *cur, *temp;
if (!h)
return;
for (i = 0; i < h->nslot; i++) {
cur = h->htable[i];
while (cur) {
temp = cur;
cur = cur->next;
if (temp->key.specified & AVTAB_XPERMS)
kmem_cache_free(avtab_xperms_cachep,
temp->datum.u.xperms);
kmem_cache_free(avtab_node_cachep, temp);
}
}
kvfree(h->htable);
h->htable = NULL;
h->nel = 0;
h->nslot = 0;
h->mask = 0;
}
void avtab_init(struct avtab *h)
{
h->htable = NULL;
h->nel = 0;
h->nslot = 0;
h->mask = 0;
}
static int avtab_alloc_common(struct avtab *h, u32 nslot)
{
if (!nslot)
return 0;
h->htable = kvcalloc(nslot, sizeof(void *), GFP_KERNEL);
if (!h->htable)
return -ENOMEM;
h->nslot = nslot;
h->mask = nslot - 1;
return 0;
}
int avtab_alloc(struct avtab *h, u32 nrules)
{
int rc;
u32 nslot = 0;
if (nrules != 0) {
nslot = nrules > 3 ? rounddown_pow_of_two(nrules / 2) : 2;
if (nslot > MAX_AVTAB_HASH_BUCKETS)
nslot = MAX_AVTAB_HASH_BUCKETS;
rc = avtab_alloc_common(h, nslot);
if (rc)
return rc;
}
pr_debug("SELinux: %d avtab hash slots, %d rules.\n", nslot, nrules);
return 0;
}
int avtab_alloc_dup(struct avtab *new, const struct avtab *orig)
{
return avtab_alloc_common(new, orig->nslot);
}
#ifdef CONFIG_SECURITY_SELINUX_DEBUG
void avtab_hash_eval(struct avtab *h, const char *tag)
{
u32 i, chain_len, slots_used, max_chain_len;
unsigned long long chain2_len_sum;
struct avtab_node *cur;
slots_used = 0;
max_chain_len = 0;
chain2_len_sum = 0;
for (i = 0; i < h->nslot; i++) {
cur = h->htable[i];
if (cur) {
slots_used++;
chain_len = 0;
while (cur) {
chain_len++;
cur = cur->next;
}
if (chain_len > max_chain_len)
max_chain_len = chain_len;
chain2_len_sum +=
(unsigned long long)chain_len * chain_len;
}
}
pr_debug("SELinux: %s: %d entries and %d/%d buckets used, "
"longest chain length %d, sum of chain length^2 %llu\n",
tag, h->nel, slots_used, h->nslot, max_chain_len,
chain2_len_sum);
}
#endif /* CONFIG_SECURITY_SELINUX_DEBUG */
/* clang-format off */
static const uint16_t spec_order[] = {
AVTAB_ALLOWED,
AVTAB_AUDITDENY,
AVTAB_AUDITALLOW,
AVTAB_TRANSITION,
AVTAB_CHANGE,
AVTAB_MEMBER,
AVTAB_XPERMS_ALLOWED,
AVTAB_XPERMS_AUDITALLOW,
AVTAB_XPERMS_DONTAUDIT
};
/* clang-format on */
int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *pol,
int (*insertf)(struct avtab *a, const struct avtab_key *k,
const struct avtab_datum *d, void *p),
void *p, bool conditional)
{
__le16 buf16[4];
u16 enabled;
u32 items, items2, val, i;
struct avtab_key key;
struct avtab_datum datum;
struct avtab_extended_perms xperms;
__le32 buf32[ARRAY_SIZE(xperms.perms.p)];
int rc;
unsigned int vers = pol->policyvers;
memset(&key, 0, sizeof(struct avtab_key));
memset(&datum, 0, sizeof(struct avtab_datum));
if (vers < POLICYDB_VERSION_AVTAB) {
rc = next_entry(buf32, fp, sizeof(u32));
if (rc) {
pr_err("SELinux: avtab: truncated entry\n");
return rc;
}
/* Read five or more items: source type, target type,
* target class, AV type, and at least one datum.
*/
items2 = le32_to_cpu(buf32[0]);
if (items2 < 5 || items2 > ARRAY_SIZE(buf32)) {
pr_err("SELinux: avtab: invalid item count\n");
return -EINVAL;
}
rc = next_entry(buf32, fp, sizeof(u32) * items2);
if (rc) {
pr_err("SELinux: avtab: truncated entry\n");
return rc;
}
items = 0;
val = le32_to_cpu(buf32[items++]);
key.source_type = (u16)val;
if (key.source_type != val) {
pr_err("SELinux: avtab: truncated source type\n");
return -EINVAL;
}
val = le32_to_cpu(buf32[items++]);
key.target_type = (u16)val;
if (key.target_type != val) {
pr_err("SELinux: avtab: truncated target type\n");
return -EINVAL;
}
val = le32_to_cpu(buf32[items++]);
key.target_class = (u16)val;
if (key.target_class != val) {
pr_err("SELinux: avtab: truncated target class\n");
return -EINVAL;
}
if (!policydb_type_isvalid(pol, key.source_type) ||
!policydb_type_isvalid(pol, key.target_type) ||
!policydb_class_isvalid(pol, key.target_class)) {
pr_err("SELinux: avtab: invalid type or class\n");
return -EINVAL;
}
val = le32_to_cpu(buf32[items++]);
enabled = (val & AVTAB_ENABLED_OLD) ? AVTAB_ENABLED : 0;
if (!(val & (AVTAB_AV | AVTAB_TYPE))) {
pr_err("SELinux: avtab: null entry\n");
return -EINVAL;
}
if ((val & AVTAB_AV) && (val & AVTAB_TYPE)) {
pr_err("SELinux: avtab: entry has both access vectors and types\n");
return -EINVAL;
}
if (val & AVTAB_XPERMS) {
pr_err("SELinux: avtab: entry has extended permissions\n");
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(spec_order); i++) {
if (val & spec_order[i]) {
if (items >= items2) {
pr_err("SELinux: avtab: entry has too many items (%d/%d)\n",
items + 1, items2);
return -EINVAL;
}
key.specified = spec_order[i] | enabled;
datum.u.data = le32_to_cpu(buf32[items++]);
if ((key.specified & AVTAB_TYPE) &&
!policydb_simpletype_isvalid(pol, datum.u.data)) {
pr_err("SELinux: avtab: invalid type\n");
return -EINVAL;
}
rc = insertf(a, &key, &datum, p);
if (rc)
return rc;
}
}
if (items != items2) {
pr_err("SELinux: avtab: entry only had %d items, expected %d\n",
items2, items);
return -EINVAL;
}
return 0;
}
rc = next_entry(buf16, fp, sizeof(u16) * 4);
if (rc) {
pr_err("SELinux: avtab: truncated entry\n");
return rc;
}
items = 0;
key.source_type = le16_to_cpu(buf16[items++]);
key.target_type = le16_to_cpu(buf16[items++]);
key.target_class = le16_to_cpu(buf16[items++]);
key.specified = le16_to_cpu(buf16[items++]);
if (!policydb_type_isvalid(pol, key.source_type) ||
!policydb_type_isvalid(pol, key.target_type) ||
!policydb_class_isvalid(pol, key.target_class)) {
pr_err("SELinux: avtab: invalid type or class\n");
return -EINVAL;
}
if (hweight16(key.specified & ~AVTAB_ENABLED) != 1) {
pr_err("SELinux: avtab: not exactly one specifier\n");
return -EINVAL;
}
if (key.specified & ~AVTAB_SPECIFIER_MASK) {
pr_err("SELinux: avtab: invalid specifier\n");
return -EINVAL;
}
if ((vers < POLICYDB_VERSION_XPERMS_IOCTL) &&
(key.specified & AVTAB_XPERMS)) {
pr_err("SELinux: avtab: policy version %u does not "
"support extended permissions rules and one "
"was specified\n",
vers);
return -EINVAL;
} else if ((vers < POLICYDB_VERSION_COND_XPERMS) &&
(key.specified & AVTAB_XPERMS) && conditional) {
pr_err("SELinux: avtab: policy version %u does not "
"support extended permissions rules in conditional "
"policies and one was specified\n",
vers);
return -EINVAL;
} else if (key.specified & AVTAB_XPERMS) {
memset(&xperms, 0, sizeof(struct avtab_extended_perms));
rc = next_entry(&xperms.specified, fp, sizeof(u8));
if (rc) {
pr_err("SELinux: avtab: truncated entry\n");
return rc;
}
if (!avtab_is_valid_xperm_specified(xperms.specified))
pr_warn_once_policyload(pol,
"SELinux: avtab: unsupported xperm specifier %#x\n",
xperms.specified);
rc = next_entry(&xperms.driver, fp, sizeof(u8));
if (rc) {
pr_err("SELinux: avtab: truncated entry\n");
return rc;
}
rc = next_entry(buf32, fp,
sizeof(u32) * ARRAY_SIZE(xperms.perms.p));
if (rc) {
pr_err("SELinux: avtab: truncated entry\n");
return rc;
}
for (i = 0; i < ARRAY_SIZE(xperms.perms.p); i++)
xperms.perms.p[i] = le32_to_cpu(buf32[i]);
datum.u.xperms = &xperms;
} else {
rc = next_entry(buf32, fp, sizeof(u32));
if (rc) {
pr_err("SELinux: avtab: truncated entry\n");
return rc;
}
datum.u.data = le32_to_cpu(*buf32);
}
if ((key.specified & AVTAB_TYPE) &&
!policydb_simpletype_isvalid(pol, datum.u.data)) {
pr_err("SELinux: avtab: invalid type\n");
return -EINVAL;
}
return insertf(a, &key, &datum, p);
}
static int avtab_insertf(struct avtab *a, const struct avtab_key *k,
const struct avtab_datum *d, void *p)
{
return avtab_insert(a, k, d);
}
int avtab_read(struct avtab *a, struct policy_file *fp, struct policydb *pol)
{
int rc;
__le32 buf[1];
u32 nel, i;
rc = next_entry(buf, fp, sizeof(u32));
if (rc < 0) {
pr_err("SELinux: avtab: truncated table\n");
goto bad;
}
nel = le32_to_cpu(buf[0]);
if (!nel) {
pr_err("SELinux: avtab: table is empty\n");
rc = -EINVAL;
goto bad;
}
/* avtab_read_item() reads at least 96 bytes for any valid entry */
rc = size_check(3 * sizeof(u32), nel, fp);
if (rc)
goto bad;
rc = avtab_alloc(a, nel);
if (rc)
goto bad;
for (i = 0; i < nel; i++) {
rc = avtab_read_item(a, fp, pol, avtab_insertf, NULL, false);
if (rc) {
if (rc == -ENOMEM)
pr_err("SELinux: avtab: out of memory\n");
else if (rc == -EEXIST)
pr_err("SELinux: avtab: duplicate entry\n");
goto bad;
}
}
rc = 0;
out:
return rc;
bad:
avtab_destroy(a);
goto out;
}
int avtab_write_item(struct policydb *p, const struct avtab_node *cur, struct policy_file *fp)
{
__le16 buf16[4];
__le32 buf32[ARRAY_SIZE(cur->datum.u.xperms->perms.p)];
int rc;
unsigned int i;
buf16[0] = cpu_to_le16(cur->key.source_type);
buf16[1] = cpu_to_le16(cur->key.target_type);
buf16[2] = cpu_to_le16(cur->key.target_class);
buf16[3] = cpu_to_le16(cur->key.specified);
rc = put_entry(buf16, sizeof(u16), 4, fp);
if (rc)
return rc;
if (cur->key.specified & AVTAB_XPERMS) {
rc = put_entry(&cur->datum.u.xperms->specified, sizeof(u8), 1,
fp);
if (rc)
return rc;
rc = put_entry(&cur->datum.u.xperms->driver, sizeof(u8), 1, fp);
if (rc)
return rc;
for (i = 0; i < ARRAY_SIZE(cur->datum.u.xperms->perms.p); i++)
buf32[i] = cpu_to_le32(cur->datum.u.xperms->perms.p[i]);
rc = put_entry(buf32, sizeof(u32),
ARRAY_SIZE(cur->datum.u.xperms->perms.p), fp);
} else {
buf32[0] = cpu_to_le32(cur->datum.u.data);
rc = put_entry(buf32, sizeof(u32), 1, fp);
}
if (rc)
return rc;
return 0;
}
int avtab_write(struct policydb *p, struct avtab *a, struct policy_file *fp)
{
u32 i;
int rc = 0;
struct avtab_node *cur;
__le32 buf[1];
buf[0] = cpu_to_le32(a->nel);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;
for (i = 0; i < a->nslot; i++) {
for (cur = a->htable[i]; cur; cur = cur->next) {
rc = avtab_write_item(p, cur, fp);
if (rc)
return rc;
}
}
return rc;
}
void __init avtab_cache_init(void)
{
avtab_node_cachep = KMEM_CACHE(avtab_node, SLAB_PANIC);
avtab_xperms_cachep = KMEM_CACHE(avtab_extended_perms, SLAB_PANIC);
}
+141
View File
@@ -0,0 +1,141 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* An access vector table (avtab) is a hash table
* of access vectors and transition types indexed
* by a type pair and a class. An access vector
* table is used to represent the type enforcement
* tables.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
/* Updated: Frank Mayer <mayerf@tresys.com> and
* Karl MacMillan <kmacmillan@tresys.com>
* Added conditional policy language extensions
* Copyright (C) 2003 Tresys Technology, LLC
*
* Updated: Yuichi Nakamura <ynakam@hitachisoft.jp>
* Tuned number of hash slots for avtab to reduce memory usage
*/
#ifndef _SS_AVTAB_H_
#define _SS_AVTAB_H_
#include "security.h"
struct avtab_key {
u16 source_type; /* source type */
u16 target_type; /* target type */
u16 target_class; /* target object class */
#define AVTAB_ALLOWED 0x0001
#define AVTAB_AUDITALLOW 0x0002
#define AVTAB_AUDITDENY 0x0004
#define AVTAB_AV (AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY)
#define AVTAB_TRANSITION 0x0010
#define AVTAB_MEMBER 0x0020
#define AVTAB_CHANGE 0x0040
#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE)
/* extended permissions */
#define AVTAB_XPERMS_ALLOWED 0x0100
#define AVTAB_XPERMS_AUDITALLOW 0x0200
#define AVTAB_XPERMS_DONTAUDIT 0x0400
#define AVTAB_XPERMS \
(AVTAB_XPERMS_ALLOWED | AVTAB_XPERMS_AUDITALLOW | \
AVTAB_XPERMS_DONTAUDIT)
#define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */
#define AVTAB_ENABLED 0x8000 /* reserved for used in cond_avtab */
#define AVTAB_SPECIFIER_MASK (AVTAB_AV | AVTAB_TYPE | AVTAB_XPERMS | AVTAB_ENABLED)
u16 specified; /* what field is specified */
};
/*
* For operations that require more than the 32 permissions provided by the avc
* extended permissions may be used to provide 256 bits of permissions.
*/
struct avtab_extended_perms {
/* These are not flags. All 256 values may be used */
#define AVTAB_XPERMS_IOCTLFUNCTION 0x01
#define AVTAB_XPERMS_IOCTLDRIVER 0x02
#define AVTAB_XPERMS_NLMSG 0x03
/* extension of the avtab_key specified */
u8 specified; /* ioctl, netfilter, ... */
/*
* if 256 bits is not adequate as is often the case with ioctls, then
* multiple extended perms may be used and the driver field
* specifies which permissions are included.
*/
u8 driver;
/* 256 bits of permissions */
struct extended_perms_data perms;
};
static inline bool avtab_is_valid_xperm_specified(u8 specified)
{
switch (specified) {
case AVTAB_XPERMS_IOCTLFUNCTION:
case AVTAB_XPERMS_IOCTLDRIVER:
case AVTAB_XPERMS_NLMSG:
return true;
default:
return false;
}
}
struct avtab_datum {
union {
u32 data; /* access vector or type value */
struct avtab_extended_perms *xperms;
} u;
};
struct avtab_node {
struct avtab_key key;
struct avtab_datum datum;
struct avtab_node *next;
};
struct avtab {
struct avtab_node **htable;
u32 nel; /* number of elements */
u32 nslot; /* number of hash slots */
u32 mask; /* mask to compute hash func */
};
void avtab_init(struct avtab *h);
int avtab_alloc(struct avtab *h, u32 nrules);
int avtab_alloc_dup(struct avtab *new, const struct avtab *orig);
void avtab_destroy(struct avtab *h);
#define MAX_AVTAB_HASH_BITS 16
#define MAX_AVTAB_HASH_BUCKETS (1 << MAX_AVTAB_HASH_BITS)
#ifdef CONFIG_SECURITY_SELINUX_DEBUG
void avtab_hash_eval(struct avtab *h, const char *tag);
#else
static inline void avtab_hash_eval(struct avtab *h, const char *tag)
{
}
#endif
struct policydb;
struct policy_file;
int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *pol,
int (*insert)(struct avtab *a, const struct avtab_key *k,
const struct avtab_datum *d, void *p),
void *p, bool conditional);
int avtab_read(struct avtab *a, struct policy_file *fp, struct policydb *pol);
int avtab_write_item(struct policydb *p, const struct avtab_node *cur,
struct policy_file *fp);
int avtab_write(struct policydb *p, struct avtab *a, struct policy_file *fp);
struct avtab_node *avtab_insert_nonunique(struct avtab *h,
const struct avtab_key *key,
const struct avtab_datum *datum);
struct avtab_node *avtab_search_node(struct avtab *h,
const struct avtab_key *key);
struct avtab_node *avtab_search_node_next(struct avtab_node *node,
u16 specified);
#endif /* _SS_AVTAB_H_ */
@@ -0,0 +1,765 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Authors: Karl MacMillan <kmacmillan@tresys.com>
* Frank Mayer <mayerf@tresys.com>
* Copyright (C) 2003 - 2004 Tresys Technology, LLC
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include "security.h"
#include "conditional.h"
#include "policydb.h"
#include "services.h"
/*
* cond_evaluate_expr evaluates a conditional expr
* in reverse polish notation. It returns true (1), false (0),
* or undefined (-1). Undefined occurs when the expression
* exceeds the stack depth of COND_EXPR_MAXDEPTH.
*/
static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr)
{
u32 i;
int s[COND_EXPR_MAXDEPTH];
int sp = -1;
if (expr->len == 0)
return -1;
for (i = 0; i < expr->len; i++) {
struct cond_expr_node *node = &expr->nodes[i];
switch (node->expr_type) {
case COND_BOOL:
if (sp == (COND_EXPR_MAXDEPTH - 1))
return -1;
sp++;
s[sp] = p->bool_val_to_struct[node->boolean - 1]->state;
break;
case COND_NOT:
if (sp < 0)
return -1;
s[sp] = !s[sp];
break;
case COND_OR:
if (sp < 1)
return -1;
sp--;
s[sp] |= s[sp + 1];
break;
case COND_AND:
if (sp < 1)
return -1;
sp--;
s[sp] &= s[sp + 1];
break;
case COND_XOR:
if (sp < 1)
return -1;
sp--;
s[sp] ^= s[sp + 1];
break;
case COND_EQ:
if (sp < 1)
return -1;
sp--;
s[sp] = (s[sp] == s[sp + 1]);
break;
case COND_NEQ:
if (sp < 1)
return -1;
sp--;
s[sp] = (s[sp] != s[sp + 1]);
break;
default:
return -1;
}
}
return s[0];
}
/*
* evaluate_cond_node evaluates the conditional stored in
* a struct cond_node and if the result is different than the
* current state of the node it sets the rules in the true/false
* list appropriately. If the result of the expression is undefined
* all of the rules are disabled for safety.
*/
static void evaluate_cond_node(struct policydb *p, struct cond_node *node)
{
struct avtab_node *avnode;
int new_state;
u32 i;
new_state = cond_evaluate_expr(p, &node->expr);
if (new_state != node->cur_state) {
node->cur_state = new_state;
if (new_state == -1)
pr_err("SELinux: expression result was undefined - disabling all rules.\n");
/* turn the rules on or off */
for (i = 0; i < node->true_list.len; i++) {
avnode = node->true_list.nodes[i];
if (new_state <= 0)
avnode->key.specified &= ~AVTAB_ENABLED;
else
avnode->key.specified |= AVTAB_ENABLED;
}
for (i = 0; i < node->false_list.len; i++) {
avnode = node->false_list.nodes[i];
/* -1 or 1 */
if (new_state)
avnode->key.specified &= ~AVTAB_ENABLED;
else
avnode->key.specified |= AVTAB_ENABLED;
}
}
}
void evaluate_cond_nodes(struct policydb *p)
{
u32 i;
for (i = 0; i < p->cond_list_len; i++)
evaluate_cond_node(p, &p->cond_list[i]);
}
void cond_policydb_init(struct policydb *p)
{
p->bool_val_to_struct = NULL;
p->cond_list = NULL;
p->cond_list_len = 0;
avtab_init(&p->te_cond_avtab);
}
static void cond_node_destroy(struct cond_node *node)
{
kfree(node->expr.nodes);
/* the avtab_ptr_t nodes are destroyed by the avtab */
kfree(node->true_list.nodes);
kfree(node->false_list.nodes);
}
static void cond_list_destroy(struct policydb *p)
{
u32 i;
for (i = 0; i < p->cond_list_len; i++)
cond_node_destroy(&p->cond_list[i]);
kfree(p->cond_list);
p->cond_list = NULL;
p->cond_list_len = 0;
}
void cond_policydb_destroy(struct policydb *p)
{
kfree(p->bool_val_to_struct);
avtab_destroy(&p->te_cond_avtab);
cond_list_destroy(p);
}
int cond_init_bool_indexes(struct policydb *p)
{
kfree(p->bool_val_to_struct);
p->bool_val_to_struct = kzalloc_objs(*p->bool_val_to_struct,
p->p_bools.nprim);
if (!p->bool_val_to_struct)
return -ENOMEM;
avtab_hash_eval(&p->te_cond_avtab, "conditional_rules");
return 0;
}
int cond_destroy_bool(void *key, void *datum, void *p)
{
kfree(key);
kfree(datum);
return 0;
}
int cond_index_bool(void *key, void *datum, void *datap)
{
struct policydb *p;
struct cond_bool_datum *booldatum;
booldatum = datum;
p = datap;
if (!booldatum->value || booldatum->value > p->p_bools.nprim)
return -EINVAL;
p->sym_val_to_name[SYM_BOOLS][booldatum->value - 1] = key;
p->bool_val_to_struct[booldatum->value - 1] = booldatum;
return 0;
}
int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
{
char *key = NULL;
struct cond_bool_datum *booldatum;
__le32 buf[3];
u32 len, val;
int rc;
booldatum = kzalloc_obj(*booldatum);
if (!booldatum)
return -ENOMEM;
rc = next_entry(buf, fp, sizeof(buf));
if (rc)
goto err;
booldatum->value = le32_to_cpu(buf[0]);
val = le32_to_cpu(buf[1]);
rc = -EINVAL;
if (!val_is_boolean(val))
goto err;
booldatum->state = (int)val;
len = le32_to_cpu(buf[2]);
rc = str_read(&key, GFP_KERNEL, fp, len);
if (rc)
goto err;
rc = symtab_insert(s, key, booldatum);
if (rc)
goto err;
return 0;
err:
pr_err("SELinux: conditional: failed to read boolean\n");
cond_destroy_bool(key, booldatum, NULL);
return rc;
}
struct cond_insertf_data {
struct policydb *p;
struct avtab_node **dst;
struct cond_av_list *other;
};
static int cond_insertf(struct avtab *a, const struct avtab_key *k,
const struct avtab_datum *d, void *ptr)
{
struct cond_insertf_data *data = ptr;
struct policydb *p = data->p;
struct cond_av_list *other = data->other;
struct avtab_node *node_ptr;
u32 i;
bool found;
/*
* For type rules we have to make certain there aren't any
* conflicting rules by searching the te_avtab and the
* cond_te_avtab.
*/
if (k->specified & AVTAB_TYPE) {
if (avtab_search_node(&p->te_avtab, k)) {
pr_err("SELinux: type rule already exists outside of a conditional.\n");
return -EINVAL;
}
/*
* If we are reading the false list other will be a pointer to
* the true list. We can have duplicate entries if there is only
* 1 other entry and it is in our true list.
*
* If we are reading the true list (other == NULL) there shouldn't
* be any other entries.
*/
if (other) {
node_ptr = avtab_search_node(&p->te_cond_avtab, k);
if (node_ptr) {
if (avtab_search_node_next(node_ptr,
k->specified)) {
pr_err("SELinux: too many conflicting type rules.\n");
return -EINVAL;
}
found = false;
for (i = 0; i < other->len; i++) {
if (other->nodes[i] == node_ptr) {
found = true;
break;
}
}
if (!found) {
pr_err("SELinux: conflicting type rules.\n");
return -EINVAL;
}
}
} else {
if (avtab_search_node(&p->te_cond_avtab, k)) {
pr_err("SELinux: conflicting type rules when adding type rule for true.\n");
return -EINVAL;
}
}
}
node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d);
if (!node_ptr) {
pr_err("SELinux: could not insert rule.\n");
return -ENOMEM;
}
*data->dst = node_ptr;
return 0;
}
static int cond_read_av_list(struct policydb *p, struct policy_file *fp,
struct cond_av_list *list,
struct cond_av_list *other)
{
int rc;
__le32 buf[1];
u32 i, len;
struct cond_insertf_data data;
rc = next_entry(buf, fp, sizeof(u32));
if (rc)
return rc;
len = le32_to_cpu(buf[0]);
if (len == 0)
return 0;
/* avtab_read_item() reads at least 96 bytes for any valid entry */
rc = size_check(3 * sizeof(u32), len, fp);
if (rc)
return rc;
list->nodes = kzalloc_objs(*list->nodes, len);
if (!list->nodes)
return -ENOMEM;
data.p = p;
data.other = other;
for (i = 0; i < len; i++) {
data.dst = &list->nodes[i];
rc = avtab_read_item(&p->te_cond_avtab, fp, p, cond_insertf,
&data, true);
if (rc) {
kfree(list->nodes);
list->nodes = NULL;
return rc;
}
}
list->len = len;
return 0;
}
static int expr_node_isvalid(struct policydb *p, struct cond_expr_node *expr)
{
if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) {
pr_err("SELinux: conditional expressions uses unknown operator.\n");
return 0;
}
if (expr->expr_type == COND_BOOL &&
(expr->boolean == 0 || expr->boolean > p->p_bools.nprim)) {
pr_err("SELinux: conditional expressions uses unknown bool.\n");
return 0;
}
return 1;
}
static int cond_read_node(struct policydb *p, struct cond_node *node, struct policy_file *fp)
{
__le32 buf[2];
u32 i, len;
int rc;
rc = next_entry(buf, fp, sizeof(u32) * 2);
if (rc)
return rc;
node->cur_state = le32_to_cpu(buf[0]);
/* expr */
len = le32_to_cpu(buf[1]);
/* we will read 64 bytes per node */
rc = size_check(2 * sizeof(u32), len, fp);
if (rc)
return rc;
node->expr.nodes = kzalloc_objs(*node->expr.nodes, len);
if (!node->expr.nodes)
return -ENOMEM;
node->expr.len = len;
for (i = 0; i < len; i++) {
struct cond_expr_node *expr = &node->expr.nodes[i];
rc = next_entry(buf, fp, sizeof(u32) * 2);
if (rc)
return rc;
expr->expr_type = le32_to_cpu(buf[0]);
expr->boolean = le32_to_cpu(buf[1]);
if (!expr_node_isvalid(p, expr))
return -EINVAL;
}
rc = cond_read_av_list(p, fp, &node->true_list, NULL);
if (rc)
return rc;
return cond_read_av_list(p, fp, &node->false_list, &node->true_list);
}
int cond_read_list(struct policydb *p, struct policy_file *fp)
{
__le32 buf[1];
u32 i, len;
int rc;
rc = next_entry(buf, fp, sizeof(buf));
if (rc)
return rc;
len = le32_to_cpu(buf[0]);
/* cond_read_node() reads at least 128 bytes for any valid node */
rc = size_check(4 * sizeof(u32), len, fp);
if (rc)
return rc;
p->cond_list = kzalloc_objs(*p->cond_list, len);
if (!p->cond_list)
return -ENOMEM;
rc = avtab_alloc(&(p->te_cond_avtab), p->te_avtab.nel);
if (rc)
goto err;
p->cond_list_len = len;
for (i = 0; i < len; i++) {
rc = cond_read_node(p, &p->cond_list[i], fp);
if (rc)
goto err;
}
return 0;
err:
cond_list_destroy(p);
return rc;
}
int cond_write_bool(void *vkey, void *datum, void *ptr)
{
char *key = vkey;
struct cond_bool_datum *booldatum = datum;
struct policy_data *pd = ptr;
struct policy_file *fp = pd->fp;
__le32 buf[3];
u32 len;
int rc;
len = strlen(key);
buf[0] = cpu_to_le32(booldatum->value);
buf[1] = cpu_to_le32(booldatum->state);
buf[2] = cpu_to_le32(len);
rc = put_entry(buf, sizeof(u32), 3, fp);
if (rc)
return rc;
rc = put_entry(key, 1, len, fp);
if (rc)
return rc;
return 0;
}
/*
* cond_write_cond_av_list doesn't write out the av_list nodes.
* Instead it writes out the key/value pairs from the avtab. This
* is necessary because there is no way to uniquely identifying rules
* in the avtab so it is not possible to associate individual rules
* in the avtab with a conditional without saving them as part of
* the conditional. This means that the avtab with the conditional
* rules will not be saved but will be rebuilt on policy load.
*/
static int cond_write_av_list(struct policydb *p, struct cond_av_list *list,
struct policy_file *fp)
{
__le32 buf[1];
u32 i;
int rc;
buf[0] = cpu_to_le32(list->len);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;
for (i = 0; i < list->len; i++) {
rc = avtab_write_item(p, list->nodes[i], fp);
if (rc)
return rc;
}
return 0;
}
static int cond_write_node(struct policydb *p, struct cond_node *node,
struct policy_file *fp)
{
__le32 buf[2];
int rc;
u32 i;
buf[0] = cpu_to_le32(node->cur_state);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;
buf[0] = cpu_to_le32(node->expr.len);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;
for (i = 0; i < node->expr.len; i++) {
buf[0] = cpu_to_le32(node->expr.nodes[i].expr_type);
buf[1] = cpu_to_le32(node->expr.nodes[i].boolean);
rc = put_entry(buf, sizeof(u32), 2, fp);
if (rc)
return rc;
}
rc = cond_write_av_list(p, &node->true_list, fp);
if (rc)
return rc;
rc = cond_write_av_list(p, &node->false_list, fp);
if (rc)
return rc;
return 0;
}
int cond_write_list(struct policydb *p, struct policy_file *fp)
{
u32 i;
__le32 buf[1];
int rc;
buf[0] = cpu_to_le32(p->cond_list_len);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;
for (i = 0; i < p->cond_list_len; i++) {
rc = cond_write_node(p, &p->cond_list[i], fp);
if (rc)
return rc;
}
return 0;
}
void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key,
struct extended_perms_decision *xpermd)
{
struct avtab_node *node;
if (!ctab || !key || !xpermd)
return;
for (node = avtab_search_node(ctab, key); node;
node = avtab_search_node_next(node, key->specified)) {
if (node->key.specified & AVTAB_ENABLED)
services_compute_xperms_decision(xpermd, node);
}
}
/* Determine whether additional permissions are granted by the conditional
* av table, and if so, add them to the result
*/
void cond_compute_av(struct avtab *ctab, struct avtab_key *key,
struct av_decision *avd, struct extended_perms *xperms)
{
struct avtab_node *node;
if (!ctab || !key || !avd)
return;
for (node = avtab_search_node(ctab, key); node;
node = avtab_search_node_next(node, key->specified)) {
if ((u16)(AVTAB_ALLOWED | AVTAB_ENABLED) ==
(node->key.specified & (AVTAB_ALLOWED | AVTAB_ENABLED)))
avd->allowed |= node->datum.u.data;
if ((u16)(AVTAB_AUDITDENY | AVTAB_ENABLED) ==
(node->key.specified & (AVTAB_AUDITDENY | AVTAB_ENABLED)))
/* Since a '0' in an auditdeny mask represents a
* permission we do NOT want to audit (dontaudit), we use
* the '&' operand to ensure that all '0's in the mask
* are retained (much unlike the allow and auditallow cases).
*/
avd->auditdeny &= node->datum.u.data;
if ((u16)(AVTAB_AUDITALLOW | AVTAB_ENABLED) ==
(node->key.specified & (AVTAB_AUDITALLOW | AVTAB_ENABLED)))
avd->auditallow |= node->datum.u.data;
if (xperms && (node->key.specified & AVTAB_ENABLED) &&
(node->key.specified & AVTAB_XPERMS))
services_compute_xperms_drivers(xperms, node);
}
}
static int cond_dup_av_list(struct cond_av_list *new,
const struct cond_av_list *orig,
struct avtab *avtab)
{
u32 i;
memset(new, 0, sizeof(*new));
new->nodes = kzalloc_objs(*new->nodes, orig->len);
if (!new->nodes)
return -ENOMEM;
for (i = 0; i < orig->len; i++) {
new->nodes[i] = avtab_insert_nonunique(
avtab, &orig->nodes[i]->key, &orig->nodes[i]->datum);
if (!new->nodes[i])
return -ENOMEM;
new->len++;
}
return 0;
}
static int duplicate_policydb_cond_list(struct policydb *newp,
const struct policydb *origp)
{
int rc;
u32 i;
rc = avtab_alloc_dup(&newp->te_cond_avtab, &origp->te_cond_avtab);
if (rc)
return rc;
newp->cond_list_len = 0;
newp->cond_list = kzalloc_objs(*newp->cond_list, origp->cond_list_len);
if (!newp->cond_list)
goto error;
for (i = 0; i < origp->cond_list_len; i++) {
struct cond_node *newn = &newp->cond_list[i];
const struct cond_node *orign = &origp->cond_list[i];
newp->cond_list_len++;
newn->cur_state = orign->cur_state;
newn->expr.nodes =
kmemdup(orign->expr.nodes,
orign->expr.len * sizeof(*orign->expr.nodes),
GFP_KERNEL);
if (!newn->expr.nodes)
goto error;
newn->expr.len = orign->expr.len;
rc = cond_dup_av_list(&newn->true_list, &orign->true_list,
&newp->te_cond_avtab);
if (rc)
goto error;
rc = cond_dup_av_list(&newn->false_list, &orign->false_list,
&newp->te_cond_avtab);
if (rc)
goto error;
}
return 0;
error:
avtab_destroy(&newp->te_cond_avtab);
cond_list_destroy(newp);
return -ENOMEM;
}
static int cond_bools_destroy(void *key, void *datum, void *args)
{
/* key was not copied so no need to free here */
kfree(datum);
return 0;
}
static int cond_bools_copy(struct hashtab_node *new,
const struct hashtab_node *orig, void *args)
{
struct cond_bool_datum *datum;
datum = kmemdup(orig->datum, sizeof(struct cond_bool_datum),
GFP_KERNEL);
if (!datum)
return -ENOMEM;
new->key = orig->key; /* No need to copy, never modified */
new->datum = datum;
return 0;
}
static int cond_bools_index(void *key, void *datum, void *args)
{
struct cond_bool_datum *booldatum, **cond_bool_array;
booldatum = datum;
cond_bool_array = args;
cond_bool_array[booldatum->value - 1] = booldatum;
return 0;
}
static int duplicate_policydb_bools(struct policydb *newdb,
const struct policydb *orig)
{
struct cond_bool_datum **cond_bool_array;
int rc;
cond_bool_array = kzalloc_objs(*orig->bool_val_to_struct,
orig->p_bools.nprim);
if (!cond_bool_array)
return -ENOMEM;
rc = hashtab_duplicate(&newdb->p_bools.table, &orig->p_bools.table,
cond_bools_copy, cond_bools_destroy, NULL);
if (rc) {
kfree(cond_bool_array);
return -ENOMEM;
}
hashtab_map(&newdb->p_bools.table, cond_bools_index, cond_bool_array);
newdb->bool_val_to_struct = cond_bool_array;
newdb->p_bools.nprim = orig->p_bools.nprim;
return 0;
}
void cond_policydb_destroy_dup(struct policydb *p)
{
hashtab_map(&p->p_bools.table, cond_bools_destroy, NULL);
hashtab_destroy(&p->p_bools.table);
cond_policydb_destroy(p);
}
int cond_policydb_dup(struct policydb *new, const struct policydb *orig)
{
cond_policydb_init(new);
if (duplicate_policydb_bools(new, orig))
return -ENOMEM;
if (duplicate_policydb_cond_list(new, orig)) {
cond_policydb_destroy_dup(new);
return -ENOMEM;
}
return 0;
}
@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Authors: Karl MacMillan <kmacmillan@tresys.com>
* Frank Mayer <mayerf@tresys.com>
* Copyright (C) 2003 - 2004 Tresys Technology, LLC
*/
#ifndef _CONDITIONAL_H_
#define _CONDITIONAL_H_
#include "avtab.h"
#include "symtab.h"
#include "policydb.h"
#include "../include/conditional.h"
#define COND_EXPR_MAXDEPTH 10
/*
* A conditional expression is a list of operators and operands
* in reverse polish notation.
*/
struct cond_expr_node {
#define COND_BOOL 1 /* plain bool */
#define COND_NOT 2 /* !bool */
#define COND_OR 3 /* bool || bool */
#define COND_AND 4 /* bool && bool */
#define COND_XOR 5 /* bool ^ bool */
#define COND_EQ 6 /* bool == bool */
#define COND_NEQ 7 /* bool != bool */
#define COND_LAST COND_NEQ
u32 expr_type;
u32 boolean;
};
struct cond_expr {
struct cond_expr_node *nodes;
u32 len;
};
/*
* Each cond_node contains a list of rules to be enabled/disabled
* depending on the current value of the conditional expression. This
* struct is for that list.
*/
struct cond_av_list {
struct avtab_node **nodes;
u32 len;
};
/*
* A cond node represents a conditional block in a policy. It
* contains a conditional expression, the current state of the expression,
* two lists of rules to enable/disable depending on the value of the
* expression (the true list corresponds to if and the false list corresponds
* to else)..
*/
struct cond_node {
int cur_state;
struct cond_expr expr;
struct cond_av_list true_list;
struct cond_av_list false_list;
};
void cond_policydb_init(struct policydb *p);
void cond_policydb_destroy(struct policydb *p);
int cond_init_bool_indexes(struct policydb *p);
int cond_destroy_bool(void *key, void *datum, void *p);
int cond_index_bool(void *key, void *datum, void *datap);
int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp);
int cond_read_list(struct policydb *p, struct policy_file *fp);
int cond_write_bool(void *key, void *datum, void *ptr);
int cond_write_list(struct policydb *p, struct policy_file *fp);
void cond_compute_av(struct avtab *ctab, struct avtab_key *key,
struct av_decision *avd, struct extended_perms *xperms);
void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key,
struct extended_perms_decision *xpermd);
void evaluate_cond_nodes(struct policydb *p);
void cond_policydb_destroy_dup(struct policydb *p);
int cond_policydb_dup(struct policydb *new, const struct policydb *orig);
#endif /* _CONDITIONAL_H_ */
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* A constraint is a condition that must be satisfied in
* order for one or more permissions to be granted.
* Constraints are used to impose additional restrictions
* beyond the type-based rules in `te' or the role-based
* transition rules in `rbac'. Constraints are typically
* used to prevent a process from transitioning to a new user
* identity or role unless it is in a privileged type.
* Constraints are likewise typically used to prevent a
* process from labeling an object with a different user
* identity.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#ifndef _SS_CONSTRAINT_H_
#define _SS_CONSTRAINT_H_
#include "ebitmap.h"
#define CEXPR_MAXDEPTH 5
struct constraint_expr {
#define CEXPR_NOT 1 /* not expr */
#define CEXPR_AND 2 /* expr and expr */
#define CEXPR_OR 3 /* expr or expr */
#define CEXPR_ATTR 4 /* attr op attr */
#define CEXPR_NAMES 5 /* attr op names */
u32 expr_type; /* expression type */
#define CEXPR_USER 1 /* user */
#define CEXPR_ROLE 2 /* role */
#define CEXPR_TYPE 4 /* type */
#define CEXPR_TARGET 8 /* target if set, source otherwise */
#define CEXPR_XTARGET 16 /* special 3rd target for validatetrans rule */
#define CEXPR_L1L2 32 /* low level 1 vs. low level 2 */
#define CEXPR_L1H2 64 /* low level 1 vs. high level 2 */
#define CEXPR_H1L2 128 /* high level 1 vs. low level 2 */
#define CEXPR_H1H2 256 /* high level 1 vs. high level 2 */
#define CEXPR_L1H1 512 /* low level 1 vs. high level 1 */
#define CEXPR_L2H2 1024 /* low level 2 vs. high level 2 */
u32 attr; /* attribute */
#define CEXPR_EQ 1 /* == or eq */
#define CEXPR_NEQ 2 /* != */
#define CEXPR_DOM 3 /* dom */
#define CEXPR_DOMBY 4 /* domby */
#define CEXPR_INCOMP 5 /* incomp */
u32 op; /* operator */
struct ebitmap names; /* names */
/* internally unused, only forwarded via policydb_write() */
struct type_set *type_names;
struct constraint_expr *next; /* next expression */
};
struct constraint_node {
u32 permissions; /* constrained permissions */
struct constraint_expr *expr; /* constraint on permissions */
struct constraint_node *next; /* next constraint */
};
#endif /* _SS_CONSTRAINT_H_ */
+32
View File
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Implementations of the security context functions.
*
* Author: Ondrej Mosnacek <omosnacek@gmail.com>
* Copyright (C) 2020 Red Hat, Inc.
*/
#include <linux/jhash.h>
#include "context.h"
#include "mls.h"
u32 context_compute_hash(const struct context *c)
{
u32 hash = 0;
/*
* If a context is invalid, it will always be represented by a
* context struct with only the len & str set (and vice versa)
* under a given policy. Since context structs from different
* policies should never meet, it is safe to hash valid and
* invalid contexts differently. The context_equal() function
* already operates under the same assumption.
*/
if (c->len)
return full_name_hash(NULL, c->str, c->len);
hash = jhash_3words(c->user, c->role, c->type, hash);
hash = mls_range_hash(&c->range, hash);
return hash;
}
+204
View File
@@ -0,0 +1,204 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* A security context is a set of security attributes
* associated with each subject and object controlled
* by the security policy. Security contexts are
* externally represented as variable-length strings
* that can be interpreted by a user or application
* with an understanding of the security policy.
* Internally, the security server uses a simple
* structure. This structure is private to the
* security server and can be changed without affecting
* clients of the security server.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#ifndef _SS_CONTEXT_H_
#define _SS_CONTEXT_H_
#include "ebitmap.h"
#include "mls_types.h"
#include "security.h"
/*
* A security context consists of an authenticated user
* identity, a role, a type and a MLS range.
*/
struct context {
u32 user;
u32 role;
u32 type;
u32 len; /* length of string in bytes */
struct mls_range range;
char *str; /* string representation if context cannot be mapped. */
};
static inline void mls_context_init(struct context *c)
{
memset(&c->range, 0, sizeof(c->range));
}
static inline int mls_context_cpy(struct context *dst,
const struct context *src)
{
int rc;
dst->range.level[0].sens = src->range.level[0].sens;
rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat);
if (rc)
goto out;
dst->range.level[1].sens = src->range.level[1].sens;
rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat);
if (rc)
ebitmap_destroy(&dst->range.level[0].cat);
out:
return rc;
}
/*
* Sets both levels in the MLS range of 'dst' to the low level of 'src'.
*/
static inline int mls_context_cpy_low(struct context *dst,
const struct context *src)
{
int rc;
dst->range.level[0].sens = src->range.level[0].sens;
rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat);
if (rc)
goto out;
dst->range.level[1].sens = src->range.level[0].sens;
rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[0].cat);
if (rc)
ebitmap_destroy(&dst->range.level[0].cat);
out:
return rc;
}
/*
* Sets both levels in the MLS range of 'dst' to the high level of 'src'.
*/
static inline int mls_context_cpy_high(struct context *dst,
const struct context *src)
{
int rc;
dst->range.level[0].sens = src->range.level[1].sens;
rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[1].cat);
if (rc)
goto out;
dst->range.level[1].sens = src->range.level[1].sens;
rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat);
if (rc)
ebitmap_destroy(&dst->range.level[0].cat);
out:
return rc;
}
static inline int mls_context_glblub(struct context *dst,
const struct context *c1,
const struct context *c2)
{
struct mls_range *dr = &dst->range;
const struct mls_range *r1 = &c1->range, *r2 = &c2->range;
int rc = 0;
if (r1->level[1].sens < r2->level[0].sens ||
r2->level[1].sens < r1->level[0].sens)
/* These ranges have no common sensitivities */
return -EINVAL;
/* Take the greatest of the low */
dr->level[0].sens = max(r1->level[0].sens, r2->level[0].sens);
/* Take the least of the high */
dr->level[1].sens = min(r1->level[1].sens, r2->level[1].sens);
rc = ebitmap_and(&dr->level[0].cat, &r1->level[0].cat,
&r2->level[0].cat);
if (rc)
goto out;
rc = ebitmap_and(&dr->level[1].cat, &r1->level[1].cat,
&r2->level[1].cat);
if (rc)
goto out;
out:
return rc;
}
static inline bool mls_context_equal(const struct context *c1,
const struct context *c2)
{
return ((c1->range.level[0].sens == c2->range.level[0].sens) &&
ebitmap_equal(&c1->range.level[0].cat, &c2->range.level[0].cat) &&
(c1->range.level[1].sens == c2->range.level[1].sens) &&
ebitmap_equal(&c1->range.level[1].cat, &c2->range.level[1].cat));
}
static inline void mls_context_destroy(struct context *c)
{
ebitmap_destroy(&c->range.level[0].cat);
ebitmap_destroy(&c->range.level[1].cat);
mls_context_init(c);
}
static inline void context_init(struct context *c)
{
memset(c, 0, sizeof(*c));
}
static inline int context_cpy(struct context *dst, const struct context *src)
{
int rc;
dst->user = src->user;
dst->role = src->role;
dst->type = src->type;
if (src->str) {
dst->str = kstrdup(src->str, GFP_ATOMIC);
if (!dst->str)
return -ENOMEM;
dst->len = src->len;
} else {
dst->str = NULL;
dst->len = 0;
}
rc = mls_context_cpy(dst, src);
if (rc) {
kfree(dst->str);
dst->str = NULL;
dst->len = 0;
return rc;
}
return 0;
}
static inline void context_destroy(struct context *c)
{
c->user = c->role = c->type = 0;
kfree(c->str);
c->str = NULL;
c->len = 0;
mls_context_destroy(c);
}
static inline bool context_equal(const struct context *c1,
const struct context *c2)
{
if (c1->len && c2->len)
return (c1->len == c2->len && !strcmp(c1->str, c2->str));
if (c1->len || c2->len)
return 0;
return ((c1->user == c2->user) && (c1->role == c2->role) &&
(c1->type == c2->type) && mls_context_equal(c1, c2));
}
u32 context_compute_hash(const struct context *c);
#endif /* _SS_CONTEXT_H_ */
+603
View File
@@ -0,0 +1,603 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Implementation of the extensible bitmap type.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
/*
* Updated: Hewlett-Packard <paul@paul-moore.com>
* Added support to import/export the NetLabel category bitmap
* (c) Copyright Hewlett-Packard Development Company, L.P., 2006
*
* Updated: KaiGai Kohei <kaigai@ak.jp.nec.com>
* Applied standard bit operations to improve bitmap scanning.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/jhash.h>
#include <net/netlabel.h>
#include "ebitmap.h"
#include "policydb.h"
#define BITS_PER_U64 ((u32)(sizeof(u64) * 8))
static struct kmem_cache *ebitmap_node_cachep __ro_after_init;
bool ebitmap_equal(const struct ebitmap *e1, const struct ebitmap *e2)
{
const struct ebitmap_node *n1, *n2;
if (e1->highbit != e2->highbit)
return false;
n1 = e1->node;
n2 = e2->node;
while (n1 && n2 && (n1->startbit == n2->startbit) &&
!memcmp(n1->maps, n2->maps, EBITMAP_SIZE / 8)) {
n1 = n1->next;
n2 = n2->next;
}
if (n1 || n2)
return false;
return true;
}
int ebitmap_cpy(struct ebitmap *dst, const struct ebitmap *src)
{
struct ebitmap_node *new, *prev;
const struct ebitmap_node *n;
ebitmap_init(dst);
n = src->node;
prev = NULL;
while (n) {
new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC);
if (!new) {
ebitmap_destroy(dst);
return -ENOMEM;
}
new->startbit = n->startbit;
memcpy(new->maps, n->maps, EBITMAP_SIZE / 8);
new->next = NULL;
if (prev)
prev->next = new;
else
dst->node = new;
prev = new;
n = n->next;
}
dst->highbit = src->highbit;
return 0;
}
int ebitmap_and(struct ebitmap *dst, const struct ebitmap *e1,
const struct ebitmap *e2)
{
struct ebitmap_node *n;
u32 bit;
int rc;
ebitmap_init(dst);
ebitmap_for_each_positive_bit(e1, n, bit)
{
if (ebitmap_get_bit(e2, bit)) {
rc = ebitmap_set_bit(dst, bit, 1);
if (rc < 0)
return rc;
}
}
return 0;
}
#ifdef CONFIG_NETLABEL
/**
* ebitmap_netlbl_export - Export an ebitmap into a NetLabel category bitmap
* @ebmap: the ebitmap to export
* @catmap: the NetLabel category bitmap
*
* Description:
* Export a SELinux extensibile bitmap into a NetLabel category bitmap.
* Returns zero on success, negative values on error.
*
*/
int ebitmap_netlbl_export(struct ebitmap *ebmap,
struct netlbl_lsm_catmap **catmap)
{
struct ebitmap_node *e_iter = ebmap->node;
unsigned long e_map;
u32 offset;
unsigned int iter;
int rc;
if (e_iter == NULL) {
*catmap = NULL;
return 0;
}
if (*catmap != NULL)
netlbl_catmap_free(*catmap);
*catmap = NULL;
while (e_iter) {
offset = e_iter->startbit;
for (iter = 0; iter < EBITMAP_UNIT_NUMS; iter++) {
e_map = e_iter->maps[iter];
if (e_map != 0) {
rc = netlbl_catmap_setlong(catmap, offset,
e_map, GFP_ATOMIC);
if (rc != 0)
goto netlbl_export_failure;
}
offset += EBITMAP_UNIT_SIZE;
}
e_iter = e_iter->next;
}
return 0;
netlbl_export_failure:
netlbl_catmap_free(*catmap);
return -ENOMEM;
}
/**
* ebitmap_netlbl_import - Import a NetLabel category bitmap into an ebitmap
* @ebmap: the ebitmap to import
* @catmap: the NetLabel category bitmap
*
* Description:
* Import a NetLabel category bitmap into a SELinux extensibile bitmap.
* Returns zero on success, negative values on error.
*
*/
int ebitmap_netlbl_import(struct ebitmap *ebmap,
struct netlbl_lsm_catmap *catmap)
{
int rc;
struct ebitmap_node *e_iter = NULL;
struct ebitmap_node *e_prev = NULL;
u32 offset = 0, idx;
unsigned long bitmap;
for (;;) {
rc = netlbl_catmap_getlong(catmap, &offset, &bitmap);
if (rc < 0)
goto netlbl_import_failure;
if (offset == (u32)-1)
return 0;
/* don't waste ebitmap space if the netlabel bitmap is empty */
if (bitmap == 0) {
offset += EBITMAP_UNIT_SIZE;
continue;
}
if (e_iter == NULL ||
offset >= e_iter->startbit + EBITMAP_SIZE) {
e_prev = e_iter;
e_iter = kmem_cache_zalloc(ebitmap_node_cachep,
GFP_ATOMIC);
if (e_iter == NULL)
goto netlbl_import_failure;
e_iter->startbit = offset - (offset % EBITMAP_SIZE);
if (e_prev == NULL)
ebmap->node = e_iter;
else
e_prev->next = e_iter;
ebmap->highbit = e_iter->startbit + EBITMAP_SIZE;
}
/* offset will always be aligned to an unsigned long */
idx = EBITMAP_NODE_INDEX(e_iter, offset);
e_iter->maps[idx] = bitmap;
/* next */
offset += EBITMAP_UNIT_SIZE;
}
/* NOTE: we should never reach this return */
return 0;
netlbl_import_failure:
ebitmap_destroy(ebmap);
return -ENOMEM;
}
#endif /* CONFIG_NETLABEL */
/*
* Check to see if all the bits set in e2 are also set in e1. Optionally,
* if last_e2bit is non-zero, the highest set bit in e2 cannot exceed
* last_e2bit.
*/
int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2,
u32 last_e2bit)
{
const struct ebitmap_node *n1, *n2;
int i;
if (e1->highbit < e2->highbit)
return 0;
n1 = e1->node;
n2 = e2->node;
while (n1 && n2 && (n1->startbit <= n2->startbit)) {
if (n1->startbit < n2->startbit) {
n1 = n1->next;
continue;
}
for (i = EBITMAP_UNIT_NUMS - 1; (i >= 0) && !n2->maps[i];)
i--; /* Skip trailing NULL map entries */
if (last_e2bit && (i >= 0)) {
u32 lastsetbit = n2->startbit + i * EBITMAP_UNIT_SIZE +
__fls(n2->maps[i]);
if (lastsetbit > last_e2bit)
return 0;
}
while (i >= 0) {
if ((n1->maps[i] & n2->maps[i]) != n2->maps[i])
return 0;
i--;
}
n1 = n1->next;
n2 = n2->next;
}
if (n2)
return 0;
return 1;
}
u32 ebitmap_get_highest_set_bit(const struct ebitmap *e)
{
const struct ebitmap_node *n;
unsigned long unit;
u32 pos = 0;
n = e->node;
if (!n)
return 0;
while (n->next)
n = n->next;
for (unsigned int i = EBITMAP_UNIT_NUMS; i > 0; i--) {
unit = n->maps[i - 1];
if (unit == 0)
continue;
pos = (i - 1) * EBITMAP_UNIT_SIZE;
while (unit >>= 1)
pos++;
break;
}
return n->startbit + pos;
}
int ebitmap_get_bit(const struct ebitmap *e, u32 bit)
{
const struct ebitmap_node *n;
if (e->highbit < bit)
return 0;
n = e->node;
while (n && (n->startbit <= bit)) {
if ((n->startbit + EBITMAP_SIZE) > bit)
return ebitmap_node_get_bit(n, bit);
n = n->next;
}
return 0;
}
int ebitmap_set_bit(struct ebitmap *e, u32 bit, int value)
{
struct ebitmap_node *n, *prev, *new;
prev = NULL;
n = e->node;
while (n && n->startbit <= bit) {
if ((n->startbit + EBITMAP_SIZE) > bit) {
if (value) {
ebitmap_node_set_bit(n, bit);
} else {
u32 s;
ebitmap_node_clr_bit(n, bit);
s = find_first_bit(n->maps, EBITMAP_SIZE);
if (s < EBITMAP_SIZE)
return 0;
/* drop this node from the bitmap */
if (!n->next) {
/*
* this was the highest map
* within the bitmap
*/
if (prev)
e->highbit = prev->startbit +
EBITMAP_SIZE;
else
e->highbit = 0;
}
if (prev)
prev->next = n->next;
else
e->node = n->next;
kmem_cache_free(ebitmap_node_cachep, n);
}
return 0;
}
prev = n;
n = n->next;
}
if (!value)
return 0;
new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC);
if (!new)
return -ENOMEM;
new->startbit = bit - (bit % EBITMAP_SIZE);
ebitmap_node_set_bit(new, bit);
if (!n)
/* this node will be the highest map within the bitmap */
e->highbit = new->startbit + EBITMAP_SIZE;
if (prev) {
new->next = prev->next;
prev->next = new;
} else {
new->next = e->node;
e->node = new;
}
return 0;
}
void ebitmap_destroy(struct ebitmap *e)
{
struct ebitmap_node *n, *temp;
if (!e)
return;
n = e->node;
while (n) {
temp = n;
n = n->next;
kmem_cache_free(ebitmap_node_cachep, temp);
}
e->highbit = 0;
e->node = NULL;
}
int ebitmap_read(struct ebitmap *e, struct policy_file *fp)
{
struct ebitmap_node *n = NULL;
u32 mapunit, count, startbit, index, i;
__le32 ebitmap_start;
u64 map;
__le64 mapbits;
__le32 buf[3];
int rc;
ebitmap_init(e);
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0)
goto out;
mapunit = le32_to_cpu(buf[0]);
e->highbit = le32_to_cpu(buf[1]);
count = le32_to_cpu(buf[2]);
if (mapunit != BITS_PER_U64) {
pr_err("SELinux: ebitmap: map size %u does not "
"match my size %u (high bit was %u)\n",
mapunit, BITS_PER_U64, e->highbit);
goto bad;
}
/* round up e->highbit */
e->highbit += EBITMAP_SIZE - 1;
e->highbit -= (e->highbit % EBITMAP_SIZE);
if (!e->highbit) {
e->node = NULL;
goto ok;
}
if (e->highbit && !count)
goto bad;
for (i = 0; i < count; i++) {
rc = next_entry(&ebitmap_start, fp, sizeof(u32));
if (rc < 0) {
pr_err("SELinux: ebitmap: truncated map\n");
goto bad;
}
startbit = le32_to_cpu(ebitmap_start);
if (startbit & (mapunit - 1)) {
pr_err("SELinux: ebitmap start bit (%u) is "
"not a multiple of the map unit size (%u)\n",
startbit, mapunit);
goto bad;
}
if (startbit > e->highbit - mapunit) {
pr_err("SELinux: ebitmap start bit (%u) is "
"beyond the end of the bitmap (%u)\n",
startbit, (e->highbit - mapunit));
goto bad;
}
if (!n || startbit >= n->startbit + EBITMAP_SIZE) {
struct ebitmap_node *tmp;
tmp = kmem_cache_zalloc(ebitmap_node_cachep,
GFP_KERNEL);
if (!tmp) {
pr_err("SELinux: ebitmap: out of memory\n");
rc = -ENOMEM;
goto bad;
}
/* round down */
tmp->startbit = startbit - (startbit % EBITMAP_SIZE);
if (n)
n->next = tmp;
else
e->node = tmp;
n = tmp;
} else if (startbit <= n->startbit) {
pr_err("SELinux: ebitmap: start bit %u"
" comes after start bit %u\n",
startbit, n->startbit);
goto bad;
}
rc = next_entry(&mapbits, fp, sizeof(u64));
if (rc < 0) {
pr_err("SELinux: ebitmap: truncated map\n");
goto bad;
}
map = le64_to_cpu(mapbits);
if (!map) {
pr_err("SELinux: ebitmap: empty map\n");
goto bad;
}
index = (startbit - n->startbit) / EBITMAP_UNIT_SIZE;
while (map) {
n->maps[index++] = map & (-1UL);
map = EBITMAP_SHIFT_UNIT_SIZE(map);
}
}
if (n && n->startbit + EBITMAP_SIZE != e->highbit) {
pr_err("SELinux: ebitmap: high bit %u is not equal to the expected value %zu\n",
e->highbit, n->startbit + EBITMAP_SIZE);
goto bad;
}
ok:
rc = 0;
out:
return rc;
bad:
if (!rc)
rc = -EINVAL;
ebitmap_destroy(e);
goto out;
}
int ebitmap_write(const struct ebitmap *e, struct policy_file *fp)
{
struct ebitmap_node *n;
u32 bit, count, last_bit, last_startbit;
__le32 buf[3];
u64 map;
int rc;
buf[0] = cpu_to_le32(BITS_PER_U64);
count = 0;
last_bit = 0;
last_startbit = U32_MAX;
ebitmap_for_each_positive_bit(e, n, bit)
{
if (last_startbit == U32_MAX ||
rounddown(bit, BITS_PER_U64) > last_startbit) {
count++;
last_startbit = rounddown(bit, BITS_PER_U64);
}
last_bit = roundup(bit + 1, BITS_PER_U64);
}
buf[1] = cpu_to_le32(last_bit);
buf[2] = cpu_to_le32(count);
rc = put_entry(buf, sizeof(u32), 3, fp);
if (rc)
return rc;
map = 0;
last_startbit = U32_MAX;
ebitmap_for_each_positive_bit(e, n, bit)
{
if (last_startbit == U32_MAX ||
rounddown(bit, BITS_PER_U64) > last_startbit) {
__le64 buf64[1];
/* this is the very first bit */
if (!map) {
last_startbit = rounddown(bit, BITS_PER_U64);
map = (u64)1 << (bit - last_startbit);
continue;
}
/* write the last node */
buf[0] = cpu_to_le32(last_startbit);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;
buf64[0] = cpu_to_le64(map);
rc = put_entry(buf64, sizeof(u64), 1, fp);
if (rc)
return rc;
/* set up for the next node */
map = 0;
last_startbit = rounddown(bit, BITS_PER_U64);
}
map |= (u64)1 << (bit - last_startbit);
}
/* write the last node */
if (map) {
__le64 buf64[1];
/* write the last node */
buf[0] = cpu_to_le32(last_startbit);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;
buf64[0] = cpu_to_le64(map);
rc = put_entry(buf64, sizeof(u64), 1, fp);
if (rc)
return rc;
}
return 0;
}
u32 ebitmap_hash(const struct ebitmap *e, u32 hash)
{
struct ebitmap_node *node;
/* need to change hash even if ebitmap is empty */
hash = jhash_1word(e->highbit, hash);
for (node = e->node; node; node = node->next) {
hash = jhash_1word(node->startbit, hash);
hash = jhash(node->maps, sizeof(node->maps), hash);
}
return hash;
}
void __init ebitmap_cache_init(void)
{
ebitmap_node_cachep = KMEM_CACHE(ebitmap_node, SLAB_PANIC);
}
+156
View File
@@ -0,0 +1,156 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* An extensible bitmap is a bitmap that supports an
* arbitrary number of bits. Extensible bitmaps are
* used to represent sets of values, such as types,
* roles, categories, and classes.
*
* Each extensible bitmap is implemented as a linked
* list of bitmap nodes, where each bitmap node has
* an explicitly specified starting bit position within
* the total bitmap.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#ifndef _SS_EBITMAP_H_
#define _SS_EBITMAP_H_
#include <net/netlabel.h>
#ifdef CONFIG_64BIT
#define EBITMAP_NODE_SIZE 64
#else
#define EBITMAP_NODE_SIZE 32
#endif
#define EBITMAP_UNIT_NUMS \
((EBITMAP_NODE_SIZE - sizeof(void *) - sizeof(u32)) / \
sizeof(unsigned long))
#define EBITMAP_UNIT_SIZE BITS_PER_LONG
#define EBITMAP_SIZE (EBITMAP_UNIT_NUMS * EBITMAP_UNIT_SIZE)
#define EBITMAP_BIT 1UL
#define EBITMAP_SHIFT_UNIT_SIZE(x) \
(((x) >> EBITMAP_UNIT_SIZE / 2) >> EBITMAP_UNIT_SIZE / 2)
struct ebitmap_node {
struct ebitmap_node *next;
unsigned long maps[EBITMAP_UNIT_NUMS];
u32 startbit;
};
struct ebitmap {
struct ebitmap_node *node; /* first node in the bitmap */
u32 highbit; /* highest position in the total bitmap */
};
#define ebitmap_length(e) ((e)->highbit)
static inline u32 ebitmap_start_positive(const struct ebitmap *e,
struct ebitmap_node **n)
{
u32 ofs;
for (*n = e->node; *n; *n = (*n)->next) {
ofs = find_first_bit((*n)->maps, EBITMAP_SIZE);
if (ofs < EBITMAP_SIZE)
return (*n)->startbit + ofs;
}
return ebitmap_length(e);
}
static inline void ebitmap_init(struct ebitmap *e)
{
memset(e, 0, sizeof(*e));
}
static inline u32 ebitmap_next_positive(const struct ebitmap *e,
struct ebitmap_node **n, u32 bit)
{
u32 ofs;
ofs = find_next_bit((*n)->maps, EBITMAP_SIZE, bit - (*n)->startbit + 1);
if (ofs < EBITMAP_SIZE)
return ofs + (*n)->startbit;
for (*n = (*n)->next; *n; *n = (*n)->next) {
ofs = find_first_bit((*n)->maps, EBITMAP_SIZE);
if (ofs < EBITMAP_SIZE)
return ofs + (*n)->startbit;
}
return ebitmap_length(e);
}
#define EBITMAP_NODE_INDEX(node, bit) \
(((bit) - (node)->startbit) / EBITMAP_UNIT_SIZE)
#define EBITMAP_NODE_OFFSET(node, bit) \
(((bit) - (node)->startbit) % EBITMAP_UNIT_SIZE)
static inline int ebitmap_node_get_bit(const struct ebitmap_node *n, u32 bit)
{
u32 index = EBITMAP_NODE_INDEX(n, bit);
u32 ofs = EBITMAP_NODE_OFFSET(n, bit);
BUG_ON(index >= EBITMAP_UNIT_NUMS);
if ((n->maps[index] & (EBITMAP_BIT << ofs)))
return 1;
return 0;
}
static inline void ebitmap_node_set_bit(struct ebitmap_node *n, u32 bit)
{
u32 index = EBITMAP_NODE_INDEX(n, bit);
u32 ofs = EBITMAP_NODE_OFFSET(n, bit);
BUG_ON(index >= EBITMAP_UNIT_NUMS);
n->maps[index] |= (EBITMAP_BIT << ofs);
}
static inline void ebitmap_node_clr_bit(struct ebitmap_node *n, u32 bit)
{
u32 index = EBITMAP_NODE_INDEX(n, bit);
u32 ofs = EBITMAP_NODE_OFFSET(n, bit);
BUG_ON(index >= EBITMAP_UNIT_NUMS);
n->maps[index] &= ~(EBITMAP_BIT << ofs);
}
#define ebitmap_for_each_positive_bit(e, n, bit) \
for ((bit) = ebitmap_start_positive(e, &(n)); \
(bit) < ebitmap_length(e); \
(bit) = ebitmap_next_positive(e, &(n), bit))
bool ebitmap_equal(const struct ebitmap *e1, const struct ebitmap *e2);
int ebitmap_cpy(struct ebitmap *dst, const struct ebitmap *src);
int ebitmap_and(struct ebitmap *dst, const struct ebitmap *e1,
const struct ebitmap *e2);
int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2,
u32 last_e2bit);
u32 ebitmap_get_highest_set_bit(const struct ebitmap *e);
int ebitmap_get_bit(const struct ebitmap *e, u32 bit);
int ebitmap_set_bit(struct ebitmap *e, u32 bit, int value);
void ebitmap_destroy(struct ebitmap *e);
struct policy_file;
int ebitmap_read(struct ebitmap *e, struct policy_file *fp);
int ebitmap_write(const struct ebitmap *e, struct policy_file *fp);
u32 ebitmap_hash(const struct ebitmap *e, u32 hash);
#ifdef CONFIG_NETLABEL
int ebitmap_netlbl_export(struct ebitmap *ebmap,
struct netlbl_lsm_catmap **catmap);
int ebitmap_netlbl_import(struct ebitmap *ebmap,
struct netlbl_lsm_catmap *catmap);
#else
static inline int ebitmap_netlbl_export(struct ebitmap *ebmap,
struct netlbl_lsm_catmap **catmap)
{
return -ENOMEM;
}
static inline int ebitmap_netlbl_import(struct ebitmap *ebmap,
struct netlbl_lsm_catmap *catmap)
{
return -ENOMEM;
}
#endif
#endif /* _SS_EBITMAP_H_ */
+199
View File
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Implementation of the hash table type.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include "hashtab.h"
#include "security.h"
static struct kmem_cache *hashtab_node_cachep __ro_after_init;
/*
* Here we simply round the number of elements up to the nearest power of two.
* I tried also other options like rounding down or rounding to the closest
* power of two (up or down based on which is closer), but I was unable to
* find any significant difference in lookup/insert performance that would
* justify switching to a different (less intuitive) formula. It could be that
* a different formula is actually more optimal, but any future changes here
* should be supported with performance/memory usage data.
*
* The total memory used by the htable arrays (only) with Fedora policy loaded
* is approximately 163 KB at the time of writing.
*/
static u32 hashtab_compute_size(u32 nel)
{
return nel == 0 ? 0 : roundup_pow_of_two(nel);
}
int hashtab_init(struct hashtab *h, u32 nel_hint)
{
u32 size = hashtab_compute_size(nel_hint);
/* should already be zeroed, but better be safe */
h->nel = 0;
h->size = 0;
h->htable = NULL;
if (size) {
h->htable = kzalloc_objs(*h->htable, size,
GFP_KERNEL | __GFP_NOWARN);
if (!h->htable)
return -ENOMEM;
h->size = size;
}
return 0;
}
int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, void *key,
void *datum)
{
struct hashtab_node *newnode;
newnode = kmem_cache_zalloc(hashtab_node_cachep, GFP_KERNEL);
if (!newnode)
return -ENOMEM;
newnode->key = key;
newnode->datum = datum;
newnode->next = *dst;
*dst = newnode;
h->nel++;
return 0;
}
void hashtab_destroy(struct hashtab *h)
{
u32 i;
struct hashtab_node *cur, *temp;
for (i = 0; i < h->size; i++) {
cur = h->htable[i];
while (cur) {
temp = cur;
cur = cur->next;
kmem_cache_free(hashtab_node_cachep, temp);
}
h->htable[i] = NULL;
}
kfree(h->htable);
h->htable = NULL;
}
int hashtab_map(struct hashtab *h, int (*apply)(void *k, void *d, void *args),
void *args)
{
u32 i;
int ret;
struct hashtab_node *cur;
for (i = 0; i < h->size; i++) {
cur = h->htable[i];
while (cur) {
ret = apply(cur->key, cur->datum, args);
if (ret)
return ret;
cur = cur->next;
}
}
return 0;
}
#ifdef CONFIG_SECURITY_SELINUX_DEBUG
void hashtab_stat(struct hashtab *h, struct hashtab_info *info)
{
u32 i, chain_len, slots_used, max_chain_len;
u64 chain2_len_sum;
struct hashtab_node *cur;
slots_used = 0;
max_chain_len = 0;
chain2_len_sum = 0;
for (i = 0; i < h->size; i++) {
cur = h->htable[i];
if (cur) {
slots_used++;
chain_len = 0;
while (cur) {
chain_len++;
cur = cur->next;
}
if (chain_len > max_chain_len)
max_chain_len = chain_len;
chain2_len_sum += (u64)chain_len * chain_len;
}
}
info->slots_used = slots_used;
info->max_chain_len = max_chain_len;
info->chain2_len_sum = chain2_len_sum;
}
#endif /* CONFIG_SECURITY_SELINUX_DEBUG */
int hashtab_duplicate(struct hashtab *new, const struct hashtab *orig,
int (*copy)(struct hashtab_node *new,
const struct hashtab_node *orig, void *args),
int (*destroy)(void *k, void *d, void *args), void *args)
{
const struct hashtab_node *orig_cur;
struct hashtab_node *cur, *tmp, *tail;
u32 i;
int rc;
memset(new, 0, sizeof(*new));
new->htable = kzalloc_objs(*new->htable, orig->size);
if (!new->htable)
return -ENOMEM;
new->size = orig->size;
for (i = 0; i < orig->size; i++) {
tail = NULL;
for (orig_cur = orig->htable[i]; orig_cur;
orig_cur = orig_cur->next) {
tmp = kmem_cache_zalloc(hashtab_node_cachep,
GFP_KERNEL);
if (!tmp)
goto error;
rc = copy(tmp, orig_cur, args);
if (rc) {
kmem_cache_free(hashtab_node_cachep, tmp);
goto error;
}
tmp->next = NULL;
if (!tail)
new->htable[i] = tmp;
else
tail->next = tmp;
tail = tmp;
new->nel++;
}
}
return 0;
error:
for (i = 0; i < new->size; i++) {
for (cur = new->htable[i]; cur; cur = tmp) {
tmp = cur->next;
destroy(cur->key, cur->datum, args);
kmem_cache_free(hashtab_node_cachep, cur);
}
}
kfree(new->htable);
memset(new, 0, sizeof(*new));
return -ENOMEM;
}
void __init hashtab_cache_init(void)
{
hashtab_node_cachep = KMEM_CACHE(hashtab_node, SLAB_PANIC);
}
+154
View File
@@ -0,0 +1,154 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* A hash table (hashtab) maintains associations between
* key values and datum values. The type of the key values
* and the type of the datum values is arbitrary. The
* functions for hash computation and key comparison are
* provided by the creator of the table.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#ifndef _SS_HASHTAB_H_
#define _SS_HASHTAB_H_
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#define HASHTAB_MAX_NODES U32_MAX
struct hashtab_key_params {
u32 (*hash)(const void *key); /* hash func */
int (*cmp)(const void *key1, const void *key2); /* comparison func */
};
struct hashtab_node {
void *key;
void *datum;
struct hashtab_node *next;
};
struct hashtab {
struct hashtab_node **htable; /* hash table */
u32 size; /* number of slots in hash table */
u32 nel; /* number of elements in hash table */
};
struct hashtab_info {
u32 slots_used;
u32 max_chain_len;
u64 chain2_len_sum;
};
/*
* Initializes a new hash table with the specified characteristics.
*
* Returns -ENOMEM if insufficient space is available or 0 otherwise.
*/
int hashtab_init(struct hashtab *h, u32 nel_hint);
int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, void *key,
void *datum);
/*
* Inserts the specified (key, datum) pair into the specified hash table.
*
* Returns -ENOMEM on memory allocation error,
* -EEXIST if there is already an entry with the same key,
* -EINVAL for general errors or
0 otherwise.
*/
static inline int hashtab_insert(struct hashtab *h, void *key, void *datum,
struct hashtab_key_params key_params)
{
u32 hvalue;
struct hashtab_node *prev, *cur;
cond_resched();
if (!h->size || h->nel == HASHTAB_MAX_NODES)
return -EINVAL;
hvalue = key_params.hash(key) & (h->size - 1);
prev = NULL;
cur = h->htable[hvalue];
while (cur) {
int cmp = key_params.cmp(key, cur->key);
if (cmp == 0)
return -EEXIST;
if (cmp < 0)
break;
prev = cur;
cur = cur->next;
}
return __hashtab_insert(h, prev ? &prev->next : &h->htable[hvalue], key,
datum);
}
/*
* Searches for the entry with the specified key in the hash table.
*
* Returns NULL if no entry has the specified key or
* the datum of the entry otherwise.
*/
static inline void *hashtab_search(const struct hashtab *h, const void *key,
struct hashtab_key_params key_params)
{
u32 hvalue;
const struct hashtab_node *cur;
if (!h->size)
return NULL;
hvalue = key_params.hash(key) & (h->size - 1);
cur = h->htable[hvalue];
while (cur) {
int cmp = key_params.cmp(key, cur->key);
if (cmp == 0)
return cur->datum;
if (cmp < 0)
break;
cur = cur->next;
}
return NULL;
}
/*
* Destroys the specified hash table.
*/
void hashtab_destroy(struct hashtab *h);
/*
* Applies the specified apply function to (key,datum,args)
* for each entry in the specified hash table.
*
* The order in which the function is applied to the entries
* is dependent upon the internal structure of the hash table.
*
* If apply returns a non-zero status, then hashtab_map will cease
* iterating through the hash table and will propagate the error
* return to its caller.
*/
int hashtab_map(struct hashtab *h, int (*apply)(void *k, void *d, void *args),
void *args);
int hashtab_duplicate(struct hashtab *new, const struct hashtab *orig,
int (*copy)(struct hashtab_node *new,
const struct hashtab_node *orig, void *args),
int (*destroy)(void *k, void *d, void *args), void *args);
#ifdef CONFIG_SECURITY_SELINUX_DEBUG
/* Fill info with some hash table statistics */
void hashtab_stat(struct hashtab *h, struct hashtab_info *info);
#else
static inline void hashtab_stat(struct hashtab *h, struct hashtab_info *info)
{
return;
}
#endif
#endif /* _SS_HASHTAB_H */
+653
View File
@@ -0,0 +1,653 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Implementation of the multi-level security (MLS) policy.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
/*
* Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com>
* Support for enhanced MLS infrastructure.
* Copyright (C) 2004-2006 Trusted Computer Solutions, Inc.
*
* Updated: Hewlett-Packard <paul@paul-moore.com>
* Added support to import/export the MLS label from NetLabel
* Copyright (C) Hewlett-Packard Development Company, L.P., 2006
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <net/netlabel.h>
#include "sidtab.h"
#include "mls.h"
#include "policydb.h"
#include "services.h"
/*
* Return the length in bytes for the MLS fields of the
* security context string representation of `context'.
*/
int mls_compute_context_len(struct policydb *p, struct context *context)
{
int i, l, len, head, prev;
const char *nm;
struct ebitmap *e;
struct ebitmap_node *node;
if (!p->mls_enabled)
return 0;
len = 1; /* for the beginning ":" */
for (l = 0; l < 2; l++) {
u32 index_sens = context->range.level[l].sens;
len += strlen(sym_name(p, SYM_LEVELS, index_sens - 1));
/* categories */
head = -2;
prev = -2;
e = &context->range.level[l].cat;
ebitmap_for_each_positive_bit(e, node, i)
{
if (i - prev > 1) {
/* one or more negative bits are skipped */
if (head != prev) {
nm = sym_name(p, SYM_CATS, prev);
len += strlen(nm) + 1;
}
nm = sym_name(p, SYM_CATS, i);
len += strlen(nm) + 1;
head = i;
}
prev = i;
}
if (prev != head) {
nm = sym_name(p, SYM_CATS, prev);
len += strlen(nm) + 1;
}
if (l == 0) {
if (mls_level_eq(&context->range.level[0],
&context->range.level[1]))
break;
else
len++;
}
}
return len;
}
/*
* Write the security context string representation of
* the MLS fields of `context' into the string `*scontext'.
* Update `*scontext' to point to the end of the MLS fields.
*/
void mls_sid_to_context(struct policydb *p, struct context *context,
char **scontext)
{
const char *nm;
char *scontextp;
int i, l, head, prev;
struct ebitmap *e;
struct ebitmap_node *node;
if (!p->mls_enabled)
return;
scontextp = *scontext;
*scontextp = ':';
scontextp++;
for (l = 0; l < 2; l++) {
strcpy(scontextp, sym_name(p, SYM_LEVELS,
context->range.level[l].sens - 1));
scontextp += strlen(scontextp);
/* categories */
head = -2;
prev = -2;
e = &context->range.level[l].cat;
ebitmap_for_each_positive_bit(e, node, i)
{
if (i - prev > 1) {
/* one or more negative bits are skipped */
if (prev != head) {
if (prev - head > 1)
*scontextp++ = '.';
else
*scontextp++ = ',';
nm = sym_name(p, SYM_CATS, prev);
strcpy(scontextp, nm);
scontextp += strlen(nm);
}
if (prev < 0)
*scontextp++ = ':';
else
*scontextp++ = ',';
nm = sym_name(p, SYM_CATS, i);
strcpy(scontextp, nm);
scontextp += strlen(nm);
head = i;
}
prev = i;
}
if (prev != head) {
if (prev - head > 1)
*scontextp++ = '.';
else
*scontextp++ = ',';
nm = sym_name(p, SYM_CATS, prev);
strcpy(scontextp, nm);
scontextp += strlen(nm);
}
if (l == 0) {
if (mls_level_eq(&context->range.level[0],
&context->range.level[1]))
break;
else
*scontextp++ = '-';
}
}
*scontext = scontextp;
}
bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l)
{
const char *name;
const struct level_datum *levdatum;
if (!l->sens || l->sens > p->p_levels.nprim)
return false;
name = sym_name(p, SYM_LEVELS, l->sens - 1);
if (!name)
return false;
levdatum = symtab_search(&p->p_levels, name);
if (!levdatum)
return false;
/*
* l is valid iff every bit in l->cat is set in levdatum->level.cat
* and no bit in l->cat is larger than p->p_cats.nprim.
* policydb_index() has already verified that every bit set in
* levdatum->level.cat names a defined category, so containment is
* sufficient here.
*/
return ebitmap_contains(&levdatum->level.cat, &l->cat,
p->p_cats.nprim);
}
bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r)
{
return (mls_level_isvalid(p, &r->level[0]) &&
mls_level_isvalid(p, &r->level[1]) &&
mls_level_dom(&r->level[1], &r->level[0]));
}
/*
* Return true if the MLS fields in the security context
* structure `c' are valid. Return 0 otherwise.
*/
bool mls_context_isvalid(const struct policydb *p, const struct context *c)
{
const struct user_datum *usrdatum;
if (!p->mls_enabled)
return true;
if (!mls_range_isvalid(p, &c->range))
return false;
if (c->role == OBJECT_R_VAL)
return true;
/*
* User must be authorized for the MLS range.
*/
if (!c->user || c->user > p->p_users.nprim)
return false;
usrdatum = p->user_val_to_struct[c->user - 1];
if (!usrdatum || !mls_range_contains(usrdatum->range, c->range))
return false; /* user may not be associated with range */
return true;
}
/*
* Set the MLS fields in the security context structure
* `context' based on the string representation in
* the string `scontext'.
*
* This function modifies the string in place, inserting
* NULL characters to terminate the MLS fields.
*
* If a def_sid is provided and no MLS field is present,
* copy the MLS field of the associated default context.
* Used for upgraded to MLS systems where objects may lack
* MLS fields.
*
* Policy read-lock must be held for sidtab lookup.
*
*/
int mls_context_to_sid(struct policydb *pol, char oldc, char *scontext,
struct context *context, struct sidtab *s, u32 def_sid)
{
char *sensitivity, *cur_cat, *next_cat, *rngptr;
struct level_datum *levdatum;
struct cat_datum *catdatum, *rngdatum;
u32 i;
int l, rc;
char *rangep[2];
if (!pol->mls_enabled) {
/*
* With no MLS, only return -EINVAL if there is a MLS field
* and it did not come from an xattr.
*/
if (oldc && def_sid == SECSID_NULL)
return -EINVAL;
return 0;
}
/*
* No MLS component to the security context, try and map to
* default if provided.
*/
if (!oldc) {
struct context *defcon;
if (def_sid == SECSID_NULL)
return -EINVAL;
defcon = sidtab_search(s, def_sid);
if (!defcon)
return -EINVAL;
return mls_context_cpy(context, defcon);
}
/*
* If we're dealing with a range, figure out where the two parts
* of the range begin.
*/
rangep[0] = scontext;
rangep[1] = strchr(scontext, '-');
if (rangep[1]) {
rangep[1][0] = '\0';
rangep[1]++;
}
/* For each part of the range: */
for (l = 0; l < 2; l++) {
/* Split sensitivity and category set. */
sensitivity = rangep[l];
if (sensitivity == NULL)
break;
next_cat = strchr(sensitivity, ':');
if (next_cat)
*(next_cat++) = '\0';
/* Parse sensitivity. */
levdatum = symtab_search(&pol->p_levels, sensitivity);
if (!levdatum)
return -EINVAL;
context->range.level[l].sens = levdatum->level.sens;
/* Extract category set. */
while (next_cat != NULL) {
cur_cat = next_cat;
next_cat = strchr(next_cat, ',');
if (next_cat != NULL)
*(next_cat++) = '\0';
/* Separate into range if exists */
rngptr = strchr(cur_cat, '.');
if (rngptr != NULL) {
/* Remove '.' */
*rngptr++ = '\0';
}
catdatum = symtab_search(&pol->p_cats, cur_cat);
if (!catdatum)
return -EINVAL;
rc = ebitmap_set_bit(&context->range.level[l].cat,
catdatum->value - 1, 1);
if (rc)
return rc;
/* If range, set all categories in range */
if (rngptr == NULL)
continue;
rngdatum = symtab_search(&pol->p_cats, rngptr);
if (!rngdatum)
return -EINVAL;
if (catdatum->value >= rngdatum->value)
return -EINVAL;
for (i = catdatum->value; i < rngdatum->value; i++) {
rc = ebitmap_set_bit(
&context->range.level[l].cat, i, 1);
if (rc)
return rc;
}
}
}
/* If we didn't see a '-', the range start is also the range end. */
if (rangep[1] == NULL) {
context->range.level[1].sens = context->range.level[0].sens;
rc = ebitmap_cpy(&context->range.level[1].cat,
&context->range.level[0].cat);
if (rc)
return rc;
}
return 0;
}
/*
* Set the MLS fields in the security context structure
* `context' based on the string representation in
* the string `str'. This function will allocate temporary memory with the
* given constraints of gfp_mask.
*/
int mls_from_string(struct policydb *p, char *str, struct context *context,
gfp_t gfp_mask)
{
char *tmpstr;
int rc;
if (!p->mls_enabled)
return -EINVAL;
tmpstr = kstrdup(str, gfp_mask);
if (!tmpstr) {
rc = -ENOMEM;
} else {
rc = mls_context_to_sid(p, ':', tmpstr, context, NULL,
SECSID_NULL);
kfree(tmpstr);
}
return rc;
}
/*
* Copies the MLS range `range' into `context'.
*/
int mls_range_set(struct context *context, struct mls_range *range)
{
int l, rc = 0;
/* Copy the MLS range into the context */
for (l = 0; l < 2; l++) {
context->range.level[l].sens = range->level[l].sens;
rc = ebitmap_cpy(&context->range.level[l].cat,
&range->level[l].cat);
if (rc)
break;
}
return rc;
}
int mls_setup_user_range(struct policydb *p, struct context *fromcon,
struct user_datum *user, struct context *usercon)
{
if (p->mls_enabled) {
struct mls_level *fromcon_sen = &(fromcon->range.level[0]);
struct mls_level *fromcon_clr = &(fromcon->range.level[1]);
struct mls_level *user_low = &(user->range.level[0]);
struct mls_level *user_clr = &(user->range.level[1]);
struct mls_level *user_def = &(user->dfltlevel);
struct mls_level *usercon_sen = &(usercon->range.level[0]);
struct mls_level *usercon_clr = &(usercon->range.level[1]);
/* Honor the user's default level if we can */
if (mls_level_between(user_def, fromcon_sen, fromcon_clr))
*usercon_sen = *user_def;
else if (mls_level_between(fromcon_sen, user_def, user_clr))
*usercon_sen = *fromcon_sen;
else if (mls_level_between(fromcon_clr, user_low, user_def))
*usercon_sen = *user_low;
else
return -EINVAL;
/* Lower the clearance of available contexts
if the clearance of "fromcon" is lower than
that of the user's default clearance (but
only if the "fromcon" clearance dominates
the user's computed sensitivity level) */
if (mls_level_dom(user_clr, fromcon_clr))
*usercon_clr = *fromcon_clr;
else if (mls_level_dom(fromcon_clr, user_clr))
*usercon_clr = *user_clr;
else
return -EINVAL;
}
return 0;
}
/*
* Convert the MLS fields in the security context
* structure `oldc' from the values specified in the
* policy `oldp' to the values specified in the policy `newp',
* storing the resulting context in `newc'.
*/
int mls_convert_context(struct policydb *oldp, struct policydb *newp,
struct context *oldc, struct context *newc)
{
struct level_datum *levdatum;
struct cat_datum *catdatum;
struct ebitmap_node *node;
u32 i;
int l;
if (!oldp->mls_enabled || !newp->mls_enabled)
return 0;
for (l = 0; l < 2; l++) {
const char *name = sym_name(oldp, SYM_LEVELS,
oldc->range.level[l].sens - 1);
levdatum = symtab_search(&newp->p_levels, name);
if (!levdatum)
return -EINVAL;
newc->range.level[l].sens = levdatum->level.sens;
ebitmap_for_each_positive_bit(&oldc->range.level[l].cat, node,
i)
{
int rc;
catdatum = symtab_search(&newp->p_cats,
sym_name(oldp, SYM_CATS, i));
if (!catdatum)
return -EINVAL;
rc = ebitmap_set_bit(&newc->range.level[l].cat,
catdatum->value - 1, 1);
if (rc)
return rc;
}
}
return 0;
}
int mls_compute_sid(struct policydb *p, struct context *scontext,
struct context *tcontext, u16 tclass, u32 specified,
struct context *newcontext, bool sock)
{
struct range_trans rtr;
struct mls_range *r;
struct class_datum *cladatum;
char default_range = 0;
if (!p->mls_enabled)
return 0;
switch (specified) {
case AVTAB_TRANSITION:
/* Look for a range transition rule. */
rtr.source_type = scontext->type;
rtr.target_type = tcontext->type;
rtr.target_class = tclass;
r = policydb_rangetr_search(p, &rtr);
if (r)
return mls_range_set(newcontext, r);
if (tclass && tclass <= p->p_classes.nprim) {
cladatum = p->class_val_to_struct[tclass - 1];
if (cladatum)
default_range = cladatum->default_range;
}
switch (default_range) {
case DEFAULT_SOURCE_LOW:
return mls_context_cpy_low(newcontext, scontext);
case DEFAULT_SOURCE_HIGH:
return mls_context_cpy_high(newcontext, scontext);
case DEFAULT_SOURCE_LOW_HIGH:
return mls_context_cpy(newcontext, scontext);
case DEFAULT_TARGET_LOW:
return mls_context_cpy_low(newcontext, tcontext);
case DEFAULT_TARGET_HIGH:
return mls_context_cpy_high(newcontext, tcontext);
case DEFAULT_TARGET_LOW_HIGH:
return mls_context_cpy(newcontext, tcontext);
case DEFAULT_GLBLUB:
return mls_context_glblub(newcontext, scontext,
tcontext);
}
fallthrough;
case AVTAB_CHANGE:
if ((tclass == p->process_class) || sock)
/* Use the process MLS attributes. */
return mls_context_cpy(newcontext, scontext);
else
/* Use the process effective MLS attributes. */
return mls_context_cpy_low(newcontext, scontext);
case AVTAB_MEMBER:
/* Use the process effective MLS attributes. */
return mls_context_cpy_low(newcontext, scontext);
}
return -EINVAL;
}
#ifdef CONFIG_NETLABEL
/**
* mls_export_netlbl_lvl - Export the MLS sensitivity levels to NetLabel
* @p: the policy
* @context: the security context
* @secattr: the NetLabel security attributes
*
* Description:
* Given the security context copy the low MLS sensitivity level into the
* NetLabel MLS sensitivity level field.
*
*/
void mls_export_netlbl_lvl(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr)
{
if (!p->mls_enabled)
return;
secattr->attr.mls.lvl = context->range.level[0].sens - 1;
secattr->flags |= NETLBL_SECATTR_MLS_LVL;
}
/**
* mls_import_netlbl_lvl - Import the NetLabel MLS sensitivity levels
* @p: the policy
* @context: the security context
* @secattr: the NetLabel security attributes
*
* Description:
* Given the security context and the NetLabel security attributes, copy the
* NetLabel MLS sensitivity level into the context.
*
*/
void mls_import_netlbl_lvl(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr)
{
if (!p->mls_enabled)
return;
context->range.level[0].sens = secattr->attr.mls.lvl + 1;
context->range.level[1].sens = context->range.level[0].sens;
}
/**
* mls_export_netlbl_cat - Export the MLS categories to NetLabel
* @p: the policy
* @context: the security context
* @secattr: the NetLabel security attributes
*
* Description:
* Given the security context copy the low MLS categories into the NetLabel
* MLS category field. Returns zero on success, negative values on failure.
*
*/
int mls_export_netlbl_cat(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr)
{
int rc;
if (!p->mls_enabled)
return 0;
rc = ebitmap_netlbl_export(&context->range.level[0].cat,
&secattr->attr.mls.cat);
if (rc == 0 && secattr->attr.mls.cat != NULL)
secattr->flags |= NETLBL_SECATTR_MLS_CAT;
return rc;
}
/**
* mls_import_netlbl_cat - Import the MLS categories from NetLabel
* @p: the policy
* @context: the security context
* @secattr: the NetLabel security attributes
*
* Description:
* Copy the NetLabel security attributes into the SELinux context; since the
* NetLabel security attribute only contains a single MLS category use it for
* both the low and high categories of the context. Returns zero on success,
* negative values on failure.
*
*/
int mls_import_netlbl_cat(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr)
{
int rc;
if (!p->mls_enabled)
return 0;
rc = ebitmap_netlbl_import(&context->range.level[0].cat,
secattr->attr.mls.cat);
if (rc)
goto import_netlbl_cat_failure;
memcpy(&context->range.level[1].cat, &context->range.level[0].cat,
sizeof(context->range.level[0].cat));
return 0;
import_netlbl_cat_failure:
ebitmap_destroy(&context->range.level[0].cat);
return rc;
}
#endif /* CONFIG_NETLABEL */
+96
View File
@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Multi-level security (MLS) policy operations.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
/*
* Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com>
* Support for enhanced MLS infrastructure.
* Copyright (C) 2004-2006 Trusted Computer Solutions, Inc.
*
* Updated: Hewlett-Packard <paul@paul-moore.com>
* Added support to import/export the MLS label from NetLabel
* Copyright (X) Hewlett-Packard Development Company, L.P., 2006
*/
#ifndef _SS_MLS_H_
#define _SS_MLS_H_
#include <linux/jhash.h>
#include "context.h"
#include "ebitmap.h"
#include "policydb.h"
int mls_compute_context_len(struct policydb *p, struct context *context);
void mls_sid_to_context(struct policydb *p, struct context *context,
char **scontext);
bool mls_context_isvalid(const struct policydb *p, const struct context *c);
bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r);
bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l);
int mls_context_to_sid(struct policydb *p, char oldc, char *scontext,
struct context *context, struct sidtab *s, u32 def_sid);
int mls_from_string(struct policydb *p, char *str, struct context *context,
gfp_t gfp_mask);
int mls_range_set(struct context *context, struct mls_range *range);
int mls_convert_context(struct policydb *oldp, struct policydb *newp,
struct context *oldc, struct context *newc);
int mls_compute_sid(struct policydb *p, struct context *scontext,
struct context *tcontext, u16 tclass, u32 specified,
struct context *newcontext, bool sock);
int mls_setup_user_range(struct policydb *p, struct context *fromcon,
struct user_datum *user, struct context *usercon);
#ifdef CONFIG_NETLABEL
void mls_export_netlbl_lvl(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr);
void mls_import_netlbl_lvl(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr);
int mls_export_netlbl_cat(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr);
int mls_import_netlbl_cat(struct policydb *p, struct context *context,
struct netlbl_lsm_secattr *secattr);
#else
static inline void mls_export_netlbl_lvl(struct policydb *p,
struct context *context,
struct netlbl_lsm_secattr *secattr)
{
return;
}
static inline void mls_import_netlbl_lvl(struct policydb *p,
struct context *context,
struct netlbl_lsm_secattr *secattr)
{
return;
}
static inline int mls_export_netlbl_cat(struct policydb *p,
struct context *context,
struct netlbl_lsm_secattr *secattr)
{
return -ENOMEM;
}
static inline int mls_import_netlbl_cat(struct policydb *p,
struct context *context,
struct netlbl_lsm_secattr *secattr)
{
return -ENOMEM;
}
#endif
static inline u32 mls_range_hash(const struct mls_range *r, u32 hash)
{
hash = jhash_2words(r->level[0].sens, r->level[1].sens, hash);
hash = ebitmap_hash(&r->level[0].cat, hash);
hash = ebitmap_hash(&r->level[1].cat, hash);
return hash;
}
#endif /* _SS_MLS_H */
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Type definitions for the multi-level security (MLS) policy.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
/*
* Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com>
* Support for enhanced MLS infrastructure.
* Copyright (C) 2004-2005 Trusted Computer Solutions, Inc.
*/
#ifndef _SS_MLS_TYPES_H_
#define _SS_MLS_TYPES_H_
#include "security.h"
#include "ebitmap.h"
struct mls_level {
u32 sens; /* sensitivity */
struct ebitmap cat; /* category set */
};
struct mls_range {
struct mls_level level[2]; /* low == level[0], high == level[1] */
};
static inline int mls_level_eq(const struct mls_level *l1,
const struct mls_level *l2)
{
return ((l1->sens == l2->sens) && ebitmap_equal(&l1->cat, &l2->cat));
}
static inline int mls_level_dom(const struct mls_level *l1,
const struct mls_level *l2)
{
return ((l1->sens >= l2->sens) &&
ebitmap_contains(&l1->cat, &l2->cat, 0));
}
#define mls_level_incomp(l1, l2) \
(!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1)))
#define mls_level_between(l1, l2, l3) \
(mls_level_dom((l1), (l2)) && mls_level_dom((l3), (l1)))
#define mls_range_contains(r1, r2) \
(mls_level_dom(&(r2).level[0], &(r1).level[0]) && \
mls_level_dom(&(r1).level[1], &(r2).level[1]))
#endif /* _SS_MLS_TYPES_H_ */
File diff suppressed because it is too large Load Diff
+428
View File
@@ -0,0 +1,428 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* A policy database (policydb) specifies the
* configuration data for the security policy.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
/*
* Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com>
* Support for enhanced MLS infrastructure.
* Copyright (C) 2004-2005 Trusted Computer Solutions, Inc.
*
* Updated: Frank Mayer <mayerf@tresys.com> and
* Karl MacMillan <kmacmillan@tresys.com>
* Added conditional policy language extensions
* Copyright (C) 2003-2004 Tresys Technology, LLC
*/
#ifndef _SS_POLICYDB_H_
#define _SS_POLICYDB_H_
#include "symtab.h"
#include "avtab.h"
#include "sidtab.h"
#include "ebitmap.h"
#include "mls_types.h"
#include "context.h"
#include "constraint.h"
/*
* A datum type is defined for each kind of symbol
* in the configuration data: individual permissions,
* common prefixes for access vectors, classes,
* users, roles, types, sensitivities, categories, etc.
*/
/* Permission attributes */
struct perm_datum {
u32 value; /* permission bit + 1 */
};
/* Attributes of a common prefix for access vectors */
struct common_datum {
u32 value; /* internal common value */
struct symtab permissions; /* common permissions */
};
/* Class attributes */
struct class_datum {
u16 value; /* class value */
char *comkey; /* common name */
struct common_datum *comdatum; /* common datum */
struct symtab permissions; /* class-specific permission symbol table */
struct constraint_node *constraints; /* constraints on class perms */
struct constraint_node *validatetrans; /* special transition rules */
/* Options how a new object user, role, and type should be decided */
#define DEFAULT_SOURCE 1
#define DEFAULT_TARGET 2
char default_user;
char default_role;
char default_type;
/* Options how a new object range should be decided */
#define DEFAULT_SOURCE_LOW 1
#define DEFAULT_SOURCE_HIGH 2
#define DEFAULT_SOURCE_LOW_HIGH 3
#define DEFAULT_TARGET_LOW 4
#define DEFAULT_TARGET_HIGH 5
#define DEFAULT_TARGET_LOW_HIGH 6
#define DEFAULT_GLBLUB 7
char default_range;
};
/* Role attributes */
struct role_datum {
u32 value; /* internal role value */
u32 bounds; /* boundary of role, 0 for none */
struct ebitmap dominates; /* set of roles dominated by this role */
struct ebitmap types; /* set of authorized types for role */
};
struct role_trans_key {
u32 role; /* current role */
u32 type; /* program executable type, or new object type */
u16 tclass; /* process class, or new object class */
};
struct role_trans_datum {
u32 new_role; /* new role */
};
struct filename_trans_key {
u32 ttype; /* parent dir context */
u16 tclass; /* class of new object */
const char *name; /* last path component */
};
struct filename_trans_datum {
struct ebitmap stypes; /* bitmap of source types for this otype */
u32 otype; /* resulting type of new object */
struct filename_trans_datum *next; /* record for next otype*/
};
struct role_allow {
u32 role; /* current role */
u32 new_role; /* new role */
struct role_allow *next;
};
/* Type attributes */
struct type_datum {
u32 value; /* internal type value */
u32 bounds; /* boundary of type, 0 for none */
/* internally unused, only forwarded via policydb_write() */
unsigned char primary; /* primary name? */
unsigned char attribute; /* attribute ?*/
};
/* User attributes */
struct user_datum {
u32 value; /* internal user value */
u32 bounds; /* bounds of user, 0 for none */
struct ebitmap roles; /* set of authorized roles for user */
struct mls_range range; /* MLS range (min - max) for user */
struct mls_level dfltlevel; /* default login MLS level for user */
};
/* Sensitivity attributes */
struct level_datum {
struct mls_level level; /* sensitivity and associated categories */
unsigned char isalias; /* is this sensitivity an alias for another? */
};
/* Category attributes */
struct cat_datum {
u32 value; /* internal category bit + 1 */
unsigned char isalias; /* is this category an alias for another? */
};
struct range_trans {
u32 source_type;
u32 target_type;
u16 target_class;
};
/* Boolean data type */
struct cond_bool_datum {
u32 value; /* internal type value */
int state;
};
struct cond_node;
/*
* type set preserves data needed to determine constraint info from
* policy source. This is not used by the kernel policy but allows
* utilities such as audit2allow to determine constraint denials.
*/
struct type_set {
struct ebitmap types;
struct ebitmap negset;
u32 flags;
};
/*
* The configuration data includes security contexts for
* initial SIDs, unlabeled file systems, TCP and UDP port numbers,
* network interfaces, and nodes. This structure stores the
* relevant data for one such entry. Entries of the same kind
* (e.g. all initial SIDs) are linked together into a list.
*/
struct ocontext {
union {
char *name; /* name of initial SID, fs, netif, fstype, path */
struct {
u8 protocol;
u16 low_port;
u16 high_port;
} port; /* TCP or UDP port information */
struct {
u32 addr;
u32 mask;
} node; /* node information */
struct {
u32 addr[4];
u32 mask[4];
} node6; /* IPv6 node information */
struct {
u64 subnet_prefix;
u16 low_pkey;
u16 high_pkey;
} ibpkey;
struct {
char *dev_name;
u8 port;
} ibendport;
} u;
union {
u16 sclass; /* security class for genfs (can be 0 for wildcard) */
u32 behavior; /* labeling behavior for fs_use */
} v;
struct context context[2]; /* security context(s) */
u32 sid[2]; /* SID(s) */
struct ocontext *next;
};
struct genfs {
char *fstype;
struct ocontext *head;
struct genfs *next;
};
/* symbol table array indices */
#define SYM_COMMONS 0
#define SYM_CLASSES 1
#define SYM_ROLES 2
#define SYM_TYPES 3
#define SYM_USERS 4
#define SYM_BOOLS 5
#define SYM_LEVELS 6
#define SYM_CATS 7
#define SYM_NUM 8
/* object context array indices */
#define OCON_ISID 0 /* initial SIDs */
#define OCON_FS 1 /* unlabeled file systems (deprecated) */
#define OCON_PORT 2 /* TCP and UDP port numbers */
#define OCON_NETIF 3 /* network interfaces */
#define OCON_NODE 4 /* nodes */
#define OCON_FSUSE 5 /* fs_use */
#define OCON_NODE6 6 /* IPv6 nodes */
#define OCON_IBPKEY 7 /* Infiniband PKeys */
#define OCON_IBENDPORT 8 /* Infiniband end ports */
#define OCON_NUM 9
/* The policy database */
struct policydb {
int mls_enabled;
/* symbol tables */
struct symtab symtab[SYM_NUM];
#define p_commons symtab[SYM_COMMONS]
#define p_classes symtab[SYM_CLASSES]
#define p_roles symtab[SYM_ROLES]
#define p_types symtab[SYM_TYPES]
#define p_users symtab[SYM_USERS]
#define p_bools symtab[SYM_BOOLS]
#define p_levels symtab[SYM_LEVELS]
#define p_cats symtab[SYM_CATS]
/* symbol names indexed by (value - 1) */
char **sym_val_to_name[SYM_NUM];
/* class, role, and user attributes indexed by (value - 1) */
struct class_datum **class_val_to_struct;
struct role_datum **role_val_to_struct;
struct user_datum **user_val_to_struct;
struct type_datum **type_val_to_struct;
/* type enforcement access vectors and transitions */
struct avtab te_avtab;
/* role transitions */
struct hashtab role_tr;
/* file transitions with the last path component */
/* quickly exclude lookups when parent ttype has no rules */
struct ebitmap filename_trans_ttypes;
/* actual set of filename_trans rules */
struct hashtab filename_trans;
/* only used if policyvers < POLICYDB_VERSION_COMP_FTRANS */
u32 compat_filename_trans_count;
/* bools indexed by (value - 1) */
struct cond_bool_datum **bool_val_to_struct;
/* type enforcement conditional access vectors and transitions */
struct avtab te_cond_avtab;
/* array indexing te_cond_avtab by conditional */
struct cond_node *cond_list;
u32 cond_list_len;
/* role allows */
struct role_allow *role_allow;
/* security contexts of initial SIDs, unlabeled file systems,
TCP or UDP port numbers, network interfaces and nodes */
struct ocontext *ocontexts[OCON_NUM];
/* security contexts for files in filesystems that cannot support
a persistent label mapping or use another
fixed labeling behavior. */
struct genfs *genfs;
/* range transitions table (range_trans_key -> mls_range) */
struct hashtab range_tr;
/* type -> attribute reverse mapping */
struct ebitmap *type_attr_map_array;
struct ebitmap policycaps;
struct ebitmap permissive_map;
struct ebitmap neveraudit_map;
/* length of this policy when it was loaded */
size_t len;
unsigned int policyvers;
unsigned int reject_unknown : 1;
unsigned int allow_unknown : 1;
u16 process_class;
u32 process_trans_perms;
} __randomize_layout;
struct policy_file {
char *data;
size_t len;
};
extern void policydb_destroy(struct policydb *p);
extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
extern bool policydb_context_isvalid(const struct policydb *p,
const struct context *c);
extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
extern bool policydb_simpletype_isvalid(const struct policydb *p, u32 type);
extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
extern bool policydb_user_isvalid(const struct policydb *p, u32 user);
extern int policydb_read(struct policydb *p, struct policy_file *fp);
extern int policydb_write(struct policydb *p, struct policy_file *fp);
extern struct filename_trans_datum *
policydb_filenametr_search(struct policydb *p, struct filename_trans_key *key);
extern struct mls_range *policydb_rangetr_search(struct policydb *p,
struct range_trans *key);
extern struct role_trans_datum *
policydb_roletr_search(struct policydb *p, struct role_trans_key *key);
#define POLICYDB_CONFIG_MLS 1
/* the config flags related to unknown classes/perms are bits 2 and 3 */
#define REJECT_UNKNOWN 0x00000002
#define ALLOW_UNKNOWN 0x00000004
#define OBJECT_R "object_r"
#define OBJECT_R_VAL 1
#define POLICYDB_MAGIC SELINUX_MAGIC
#define POLICYDB_STRING "SE Linux"
struct policy_data {
struct policydb *p;
struct policy_file *fp;
};
static inline int size_check(size_t bytes, size_t num,
const struct policy_file *fp)
{
size_t len;
if (unlikely(check_mul_overflow(bytes, num, &len)))
return -EINVAL;
if (unlikely(len > fp->len))
return -EINVAL;
return 0;
}
static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes)
{
if (bytes > fp->len)
return -EINVAL;
memcpy(buf, fp->data, bytes);
fp->data += bytes;
fp->len -= bytes;
return 0;
}
static inline int put_entry(const void *buf, size_t bytes, size_t num,
struct policy_file *fp)
{
size_t len;
if (unlikely(check_mul_overflow(bytes, num, &len)))
return -EINVAL;
if (len > fp->len)
return -EINVAL;
memcpy(fp->data, buf, len);
fp->data += len;
fp->len -= len;
return 0;
}
static inline const char *sym_name(const struct policydb *p, unsigned int sym_num,
unsigned int element_nr)
{
return p->sym_val_to_name[sym_num][element_nr];
}
static inline bool val_is_boolean(u32 value)
{
return value == 0 || value == 1;
}
extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
extern u16 string_to_security_class(struct policydb *p, const char *name);
extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name);
#define pr_warn_once_policyload(policy, fmt, ...) \
do { \
static const void *prev_policy__; \
if (prev_policy__ != policy) { \
pr_warn(fmt, ##__VA_ARGS__); \
prev_policy__ = policy; \
} \
} while (0)
#endif /* _SS_POLICYDB_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Implementation of the security services.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#ifndef _SS_SERVICES_H_
#define _SS_SERVICES_H_
#include "policydb.h"
/* Mapping for a single class */
struct selinux_mapping {
u16 value; /* policy value for class */
u16 num_perms; /* number of permissions in class */
u32 perms[sizeof(u32) * 8]; /* policy values for permissions */
};
/* Map for all of the classes, with array size */
struct selinux_map {
struct selinux_mapping *mapping; /* indexed by class */
u16 size; /* array size of mapping */
};
struct selinux_policy {
struct sidtab *sidtab;
struct policydb policydb;
struct selinux_map map;
u32 latest_granting;
} __randomize_layout;
struct convert_context_args {
struct policydb *oldp;
struct policydb *newp;
};
void services_compute_xperms_drivers(struct extended_perms *xperms,
struct avtab_node *node);
void services_compute_xperms_decision(struct extended_perms_decision *xpermd,
struct avtab_node *node);
int services_convert_context(struct convert_context_args *args,
struct context *oldc, struct context *newc,
gfp_t gfp_flags);
#endif /* _SS_SERVICES_H_ */
+638
View File
@@ -0,0 +1,638 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Implementation of the SID table type.
*
* Original author: Stephen Smalley, <stephen.smalley.work@gmail.com>
* Author: Ondrej Mosnacek, <omosnacek@gmail.com>
*
* Copyright (C) 2018 Red Hat, Inc.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <asm/barrier.h>
#include "flask.h"
#include "security.h"
#include "sidtab.h"
#include "services.h"
struct sidtab_str_cache {
struct rcu_head rcu_member;
struct list_head lru_member;
struct sidtab_entry *parent;
u32 len;
char str[] __counted_by(len);
};
#define index_to_sid(index) ((index) + SECINITSID_NUM + 1)
#define sid_to_index(sid) ((sid) - (SECINITSID_NUM + 1))
int sidtab_init(struct sidtab *s)
{
u32 i;
memset(s->roots, 0, sizeof(s->roots));
for (i = 0; i < SECINITSID_NUM; i++)
s->isids[i].set = 0;
s->frozen = false;
s->count = 0;
s->convert = NULL;
hash_init(s->context_to_sid);
spin_lock_init(&s->lock);
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
s->cache_free_slots = CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE;
INIT_LIST_HEAD(&s->cache_lru_list);
spin_lock_init(&s->cache_lock);
#endif
return 0;
}
static u32 context_to_sid(struct sidtab *s, struct context *context, u32 hash)
{
struct sidtab_entry *entry;
u32 sid = 0;
rcu_read_lock();
hash_for_each_possible_rcu(s->context_to_sid, entry, list, hash) {
if (entry->hash != hash)
continue;
if (context_equal(&entry->context, context)) {
sid = entry->sid;
break;
}
}
rcu_read_unlock();
return sid;
}
int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context)
{
struct sidtab_isid_entry *isid;
u32 hash;
int rc;
if (sid == 0 || sid > SECINITSID_NUM)
return -EINVAL;
isid = &s->isids[sid - 1];
rc = context_cpy(&isid->entry.context, context);
if (rc)
return rc;
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
isid->entry.cache = NULL;
#endif
isid->set = 1;
hash = context_compute_hash(context);
/*
* Multiple initial sids may map to the same context. Check that this
* context is not already represented in the context_to_sid hashtable
* to avoid duplicate entries and long linked lists upon hash
* collision.
*/
if (!context_to_sid(s, context, hash)) {
isid->entry.sid = sid;
isid->entry.hash = hash;
hash_add(s->context_to_sid, &isid->entry.list, hash);
}
return 0;
}
int sidtab_hash_stats(struct sidtab *sidtab, char *page)
{
unsigned int i;
int chain_len = 0;
int slots_used = 0;
int entries = 0;
int max_chain_len = 0;
unsigned int cur_bucket = 0;
struct sidtab_entry *entry;
rcu_read_lock();
hash_for_each_rcu(sidtab->context_to_sid, i, entry, list) {
entries++;
if (i == cur_bucket) {
chain_len++;
if (chain_len == 1)
slots_used++;
} else {
cur_bucket = i;
if (chain_len > max_chain_len)
max_chain_len = chain_len;
chain_len = 0;
}
}
rcu_read_unlock();
if (chain_len > max_chain_len)
max_chain_len = chain_len;
return scnprintf(page, PAGE_SIZE,
"entries: %d\nbuckets used: %d/%d\n"
"longest chain: %d\n",
entries, slots_used, SIDTAB_HASH_BUCKETS,
max_chain_len);
}
static u32 sidtab_level_from_count(u32 count)
{
u32 capacity = SIDTAB_LEAF_ENTRIES;
u32 level = 0;
while (count > capacity) {
capacity <<= SIDTAB_INNER_SHIFT;
++level;
}
return level;
}
static int sidtab_alloc_roots(struct sidtab *s, u32 level)
{
u32 l;
if (!s->roots[0].ptr_leaf) {
s->roots[0].ptr_leaf =
kzalloc(SIDTAB_NODE_ALLOC_SIZE, GFP_ATOMIC);
if (!s->roots[0].ptr_leaf)
return -ENOMEM;
}
for (l = 1; l <= level; ++l)
if (!s->roots[l].ptr_inner) {
s->roots[l].ptr_inner =
kzalloc(SIDTAB_NODE_ALLOC_SIZE, GFP_ATOMIC);
if (!s->roots[l].ptr_inner)
return -ENOMEM;
s->roots[l].ptr_inner->entries[0] = s->roots[l - 1];
}
return 0;
}
static struct sidtab_entry *sidtab_do_lookup(struct sidtab *s, u32 index,
int alloc)
{
union sidtab_entry_inner *entry;
u32 level, capacity_shift, leaf_index = index / SIDTAB_LEAF_ENTRIES;
/* find the level of the subtree we need */
level = sidtab_level_from_count(index + 1);
capacity_shift = level * SIDTAB_INNER_SHIFT;
/* allocate roots if needed */
if (alloc && sidtab_alloc_roots(s, level) != 0)
return NULL;
/* lookup inside the subtree */
entry = &s->roots[level];
while (level != 0) {
capacity_shift -= SIDTAB_INNER_SHIFT;
--level;
entry = &entry->ptr_inner->entries[leaf_index >> capacity_shift];
leaf_index &= ((u32)1 << capacity_shift) - 1;
if (!entry->ptr_inner) {
if (alloc)
entry->ptr_inner = kzalloc(
SIDTAB_NODE_ALLOC_SIZE, GFP_ATOMIC);
if (!entry->ptr_inner)
return NULL;
}
}
if (!entry->ptr_leaf) {
if (alloc)
entry->ptr_leaf =
kzalloc(SIDTAB_NODE_ALLOC_SIZE, GFP_ATOMIC);
if (!entry->ptr_leaf)
return NULL;
}
return &entry->ptr_leaf->entries[index % SIDTAB_LEAF_ENTRIES];
}
static struct sidtab_entry *sidtab_lookup(struct sidtab *s, u32 index)
{
/* read entries only after reading count */
u32 count = smp_load_acquire(&s->count);
if (index >= count)
return NULL;
return sidtab_do_lookup(s, index, 0);
}
static struct sidtab_entry *sidtab_lookup_initial(struct sidtab *s, u32 sid)
{
return s->isids[sid - 1].set ? &s->isids[sid - 1].entry : NULL;
}
static struct sidtab_entry *sidtab_search_core(struct sidtab *s, u32 sid,
int force)
{
if (sid != 0) {
struct sidtab_entry *entry;
if (sid > SECINITSID_NUM)
entry = sidtab_lookup(s, sid_to_index(sid));
else
entry = sidtab_lookup_initial(s, sid);
if (entry && (!entry->context.len || force))
return entry;
}
return sidtab_lookup_initial(s, SECINITSID_UNLABELED);
}
struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid)
{
return sidtab_search_core(s, sid, 0);
}
struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid)
{
return sidtab_search_core(s, sid, 1);
}
int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid)
{
unsigned long flags;
u32 count, hash = context_compute_hash(context);
struct sidtab_convert_params *convert;
struct sidtab_entry *dst, *dst_convert;
int rc;
*sid = context_to_sid(s, context, hash);
if (*sid)
return 0;
/* lock-free search failed: lock, re-search, and insert if not found */
spin_lock_irqsave(&s->lock, flags);
rc = 0;
*sid = context_to_sid(s, context, hash);
if (*sid)
goto out_unlock;
if (unlikely(s->frozen)) {
/*
* This sidtab is now frozen - tell the caller to abort and
* get the new one.
*/
rc = -ESTALE;
goto out_unlock;
}
count = s->count;
/* bail out if we already reached max entries */
rc = -EOVERFLOW;
if (count >= SIDTAB_MAX)
goto out_unlock;
/* insert context into new entry */
rc = -ENOMEM;
dst = sidtab_do_lookup(s, count, 1);
if (!dst)
goto out_unlock;
dst->sid = index_to_sid(count);
dst->hash = hash;
rc = context_cpy(&dst->context, context);
if (rc)
goto out_unlock;
/*
* if we are building a new sidtab, we need to convert the context
* and insert it there as well
*/
convert = s->convert;
if (convert) {
struct sidtab *target = convert->target;
rc = -ENOMEM;
dst_convert = sidtab_do_lookup(target, count, 1);
if (!dst_convert) {
context_destroy(&dst->context);
goto out_unlock;
}
rc = services_convert_context(convert->args, context,
&dst_convert->context,
GFP_ATOMIC);
if (rc) {
context_destroy(&dst->context);
goto out_unlock;
}
dst_convert->sid = index_to_sid(count);
dst_convert->hash = context_compute_hash(&dst_convert->context);
target->count = count + 1;
hash_add_rcu(target->context_to_sid, &dst_convert->list,
dst_convert->hash);
}
if (context->len)
pr_info("SELinux: Context %s is not valid (left unmapped).\n",
context->str);
*sid = index_to_sid(count);
/* write entries before updating count */
smp_store_release(&s->count, count + 1);
hash_add_rcu(s->context_to_sid, &dst->list, dst->hash);
rc = 0;
out_unlock:
spin_unlock_irqrestore(&s->lock, flags);
return rc;
}
static void sidtab_convert_hashtable(struct sidtab *s, u32 count)
{
struct sidtab_entry *entry;
u32 i;
for (i = 0; i < count; i++) {
entry = sidtab_do_lookup(s, i, 0);
entry->sid = index_to_sid(i);
entry->hash = context_compute_hash(&entry->context);
hash_add_rcu(s->context_to_sid, &entry->list, entry->hash);
}
}
static int sidtab_convert_tree(union sidtab_entry_inner *edst,
union sidtab_entry_inner *esrc, u32 *pos,
u32 count, u32 level,
struct sidtab_convert_params *convert)
{
int rc;
u32 i;
if (level != 0) {
if (!edst->ptr_inner) {
edst->ptr_inner =
kzalloc(SIDTAB_NODE_ALLOC_SIZE, GFP_KERNEL);
if (!edst->ptr_inner)
return -ENOMEM;
}
i = 0;
while (i < SIDTAB_INNER_ENTRIES && *pos < count) {
rc = sidtab_convert_tree(&edst->ptr_inner->entries[i],
&esrc->ptr_inner->entries[i],
pos, count, level - 1,
convert);
if (rc)
return rc;
i++;
}
} else {
if (!edst->ptr_leaf) {
edst->ptr_leaf =
kzalloc(SIDTAB_NODE_ALLOC_SIZE, GFP_KERNEL);
if (!edst->ptr_leaf)
return -ENOMEM;
}
i = 0;
while (i < SIDTAB_LEAF_ENTRIES && *pos < count) {
rc = services_convert_context(
convert->args,
&esrc->ptr_leaf->entries[i].context,
&edst->ptr_leaf->entries[i].context,
GFP_KERNEL);
if (rc)
return rc;
(*pos)++;
i++;
}
cond_resched();
}
return 0;
}
int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params)
{
unsigned long flags;
u32 count, level, pos;
int rc;
spin_lock_irqsave(&s->lock, flags);
/* concurrent policy loads are not allowed */
if (s->convert) {
spin_unlock_irqrestore(&s->lock, flags);
return -EBUSY;
}
count = s->count;
level = sidtab_level_from_count(count);
/* allocate last leaf in the new sidtab (to avoid race with
* live convert)
*/
rc = sidtab_do_lookup(params->target, count - 1, 1) ? 0 : -ENOMEM;
if (rc) {
spin_unlock_irqrestore(&s->lock, flags);
return rc;
}
/* set count in case no new entries are added during conversion */
params->target->count = count;
/* enable live convert of new entries */
s->convert = params;
/* we can safely convert the tree outside the lock */
spin_unlock_irqrestore(&s->lock, flags);
pr_info("SELinux: Converting %u SID table entries...\n", count);
/* convert all entries not covered by live convert */
pos = 0;
rc = sidtab_convert_tree(&params->target->roots[level],
&s->roots[level], &pos, count, level, params);
if (rc) {
/* we need to keep the old table - disable live convert */
spin_lock_irqsave(&s->lock, flags);
s->convert = NULL;
spin_unlock_irqrestore(&s->lock, flags);
return rc;
}
/*
* The hashtable can also be modified in sidtab_context_to_sid()
* so we must re-acquire the lock here.
*/
spin_lock_irqsave(&s->lock, flags);
sidtab_convert_hashtable(params->target, count);
spin_unlock_irqrestore(&s->lock, flags);
return 0;
}
void sidtab_cancel_convert(struct sidtab *s)
{
unsigned long flags;
/* cancelling policy load - disable live convert of sidtab */
spin_lock_irqsave(&s->lock, flags);
s->convert = NULL;
spin_unlock_irqrestore(&s->lock, flags);
}
void sidtab_freeze_begin(struct sidtab *s, unsigned long *flags)
__acquires(&s->lock)
{
spin_lock_irqsave(&s->lock, *flags);
s->frozen = true;
s->convert = NULL;
}
void sidtab_freeze_end(struct sidtab *s, unsigned long *flags)
__releases(&s->lock)
{
spin_unlock_irqrestore(&s->lock, *flags);
}
static void sidtab_destroy_entry(struct sidtab_entry *entry)
{
context_destroy(&entry->context);
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
kfree(rcu_dereference_raw(entry->cache));
#endif
}
static void sidtab_destroy_tree(union sidtab_entry_inner entry, u32 level)
{
u32 i;
if (level != 0) {
struct sidtab_node_inner *node = entry.ptr_inner;
if (!node)
return;
for (i = 0; i < SIDTAB_INNER_ENTRIES; i++)
sidtab_destroy_tree(node->entries[i], level - 1);
kfree(node);
} else {
struct sidtab_node_leaf *node = entry.ptr_leaf;
if (!node)
return;
for (i = 0; i < SIDTAB_LEAF_ENTRIES; i++)
sidtab_destroy_entry(&node->entries[i]);
kfree(node);
}
}
void sidtab_destroy(struct sidtab *s)
{
u32 i, level;
for (i = 0; i < SECINITSID_NUM; i++)
if (s->isids[i].set)
sidtab_destroy_entry(&s->isids[i].entry);
level = SIDTAB_MAX_LEVEL;
while (level && !s->roots[level].ptr_inner)
--level;
sidtab_destroy_tree(s->roots[level], level);
/*
* The context_to_sid hashtable's objects are all shared
* with the isids array and context tree, and so don't need
* to be cleaned up here.
*/
}
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry,
const char *str, u32 str_len)
{
struct sidtab_str_cache *cache, *victim = NULL;
unsigned long flags;
/* do not cache invalid contexts */
if (entry->context.len)
return;
spin_lock_irqsave(&s->cache_lock, flags);
cache = rcu_dereference_protected(entry->cache,
lockdep_is_held(&s->cache_lock));
if (cache) {
/* entry in cache - just bump to the head of LRU list */
list_move(&cache->lru_member, &s->cache_lru_list);
goto out_unlock;
}
cache = kmalloc_flex(*cache, str, str_len, GFP_ATOMIC);
if (!cache)
goto out_unlock;
if (s->cache_free_slots == 0) {
/* pop a cache entry from the tail and free it */
victim = container_of(s->cache_lru_list.prev,
struct sidtab_str_cache, lru_member);
list_del(&victim->lru_member);
rcu_assign_pointer(victim->parent->cache, NULL);
} else {
s->cache_free_slots--;
}
cache->parent = entry;
cache->len = str_len;
memcpy(cache->str, str, str_len);
list_add(&cache->lru_member, &s->cache_lru_list);
rcu_assign_pointer(entry->cache, cache);
out_unlock:
spin_unlock_irqrestore(&s->cache_lock, flags);
kfree_rcu(victim, rcu_member);
}
int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry, char **out,
u32 *out_len)
{
struct sidtab_str_cache *cache;
int rc = 0;
if (entry->context.len)
return -ENOENT; /* do not cache invalid contexts */
rcu_read_lock();
cache = rcu_dereference(entry->cache);
if (!cache) {
rc = -ENOENT;
} else {
*out_len = cache->len;
if (out) {
*out = kmemdup(cache->str, cache->len, GFP_ATOMIC);
if (!*out)
rc = -ENOMEM;
}
}
rcu_read_unlock();
if (!rc && out)
sidtab_sid2str_put(s, entry, *out, *out_len);
return rc;
}
#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */
+160
View File
@@ -0,0 +1,160 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* A security identifier table (sidtab) is a lookup table
* of security context structures indexed by SID value.
*
* Original author: Stephen Smalley, <stephen.smalley.work@gmail.com>
* Author: Ondrej Mosnacek, <omosnacek@gmail.com>
*
* Copyright (C) 2018 Red Hat, Inc.
*/
#ifndef _SS_SIDTAB_H_
#define _SS_SIDTAB_H_
#include <linux/spinlock_types.h>
#include <linux/log2.h>
#include <linux/hashtable.h>
#include "context.h"
struct sidtab_entry {
u32 sid;
u32 hash;
struct context context;
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
struct sidtab_str_cache __rcu *cache;
#endif
struct hlist_node list;
};
union sidtab_entry_inner {
struct sidtab_node_inner *ptr_inner;
struct sidtab_node_leaf *ptr_leaf;
};
/* align node size to page boundary */
#define SIDTAB_NODE_ALLOC_SHIFT PAGE_SHIFT
#define SIDTAB_NODE_ALLOC_SIZE PAGE_SIZE
#define size_to_shift(size) ((size) == 1 ? 1 : (const_ilog2((size)-1) + 1))
#define SIDTAB_INNER_SHIFT \
(SIDTAB_NODE_ALLOC_SHIFT - \
size_to_shift(sizeof(union sidtab_entry_inner)))
#define SIDTAB_INNER_ENTRIES ((size_t)1 << SIDTAB_INNER_SHIFT)
#define SIDTAB_LEAF_ENTRIES \
(SIDTAB_NODE_ALLOC_SIZE / sizeof(struct sidtab_entry))
#define SIDTAB_MAX_BITS 32
#define SIDTAB_MAX U32_MAX
/* ensure enough tree levels for SIDTAB_MAX entries */
#define SIDTAB_MAX_LEVEL \
DIV_ROUND_UP(SIDTAB_MAX_BITS - size_to_shift(SIDTAB_LEAF_ENTRIES), \
SIDTAB_INNER_SHIFT)
struct sidtab_node_leaf {
struct sidtab_entry entries[SIDTAB_LEAF_ENTRIES];
};
struct sidtab_node_inner {
union sidtab_entry_inner entries[SIDTAB_INNER_ENTRIES];
};
struct sidtab_isid_entry {
int set;
struct sidtab_entry entry;
};
struct sidtab_convert_params {
struct convert_context_args *args;
struct sidtab *target;
};
#define SIDTAB_HASH_BITS CONFIG_SECURITY_SELINUX_SIDTAB_HASH_BITS
#define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS)
struct sidtab {
/*
* lock-free read access only for as many items as a prior read of
* 'count'
*/
union sidtab_entry_inner roots[SIDTAB_MAX_LEVEL + 1];
/*
* access atomically via {READ|WRITE}_ONCE(); only increment under
* spinlock
*/
u32 count;
/* access only under spinlock */
struct sidtab_convert_params *convert;
bool frozen;
spinlock_t lock;
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
/* SID -> context string cache */
u32 cache_free_slots;
struct list_head cache_lru_list;
spinlock_t cache_lock;
#endif
/* index == SID - 1 (no entry for SECSID_NULL) */
struct sidtab_isid_entry isids[SECINITSID_NUM];
/* Hash table for fast reverse context-to-sid lookups. */
DECLARE_HASHTABLE(context_to_sid, SIDTAB_HASH_BITS);
};
int sidtab_init(struct sidtab *s);
int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context);
struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid);
struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid);
static inline struct context *sidtab_search(struct sidtab *s, u32 sid)
{
struct sidtab_entry *entry = sidtab_search_entry(s, sid);
return entry ? &entry->context : NULL;
}
static inline struct context *sidtab_search_force(struct sidtab *s, u32 sid)
{
struct sidtab_entry *entry = sidtab_search_entry_force(s, sid);
return entry ? &entry->context : NULL;
}
int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params);
void sidtab_cancel_convert(struct sidtab *s);
void sidtab_freeze_begin(struct sidtab *s, unsigned long *flags)
__acquires(&s->lock);
void sidtab_freeze_end(struct sidtab *s, unsigned long *flags)
__releases(&s->lock);
int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid);
void sidtab_destroy(struct sidtab *s);
int sidtab_hash_stats(struct sidtab *sidtab, char *page);
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry,
const char *str, u32 str_len);
int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry, char **out,
u32 *out_len);
#else
static inline void sidtab_sid2str_put(struct sidtab *s,
struct sidtab_entry *entry,
const char *str, u32 str_len)
{
}
static inline int sidtab_sid2str_get(struct sidtab *s,
struct sidtab_entry *entry, char **out,
u32 *out_len)
{
return -ENOENT;
}
#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */
#endif /* _SS_SIDTAB_H_ */
+56
View File
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Implementation of the symbol table type.
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include "symtab.h"
static unsigned int symhash(const void *key)
{
/*
* djb2a
* Public domain from cdb v0.75
*/
unsigned int hash = 5381;
unsigned char c;
while ((c = *(const unsigned char *)key++))
hash = ((hash << 5) + hash) ^ c;
return hash;
}
static int symcmp(const void *key1, const void *key2)
{
const char *keyp1, *keyp2;
keyp1 = key1;
keyp2 = key2;
return strcmp(keyp1, keyp2);
}
static const struct hashtab_key_params symtab_key_params = {
.hash = symhash,
.cmp = symcmp,
};
int symtab_init(struct symtab *s, u32 size)
{
s->nprim = 0;
return hashtab_init(&s->table, size);
}
int symtab_insert(struct symtab *s, char *name, void *datum)
{
return hashtab_insert(&s->table, name, datum, symtab_key_params);
}
void *symtab_search(const struct symtab *s, const char *name)
{
return hashtab_search(&s->table, name, symtab_key_params);
}
+26
View File
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* A symbol table (symtab) maintains associations between symbol
* strings and datum values. The type of the datum values
* is arbitrary. The symbol table type is implemented
* using the hash table type (hashtab).
*
* Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
*/
#ifndef _SS_SYMTAB_H_
#define _SS_SYMTAB_H_
#include "hashtab.h"
struct symtab {
struct hashtab table; /* hash table (keyed on a string) */
u32 nprim; /* number of primary names in table */
};
int symtab_init(struct symtab *s, u32 size);
int symtab_insert(struct symtab *s, char *name, void *datum);
void *symtab_search(const struct symtab *s, const char *name);
#endif /* _SS_SYMTAB_H_ */