forked from Minki/linux
12b29f3455
Introduce SELinux support for deferred mapping of security contexts in the SID table upon policy reload, and use this support for inode security contexts when the context is not yet valid under the current policy. Only processes with CAP_MAC_ADMIN + mac_admin permission in policy can set undefined security contexts on inodes. Inodes with such undefined contexts are treated as having the unlabeled context until the context becomes valid upon a policy reload that defines the context. Context invalidation upon policy reload also uses this support to save the context information in the SID table and later recover it upon a subsequent policy reload that defines the context again. This support is to enable package managers and similar programs to set down file contexts unknown to the system policy at the time the file is created in order to better support placing loadable policy modules in packages and to support build systems that need to create images of different distro releases with different policies w/o requiring all of the contexts to be defined or legal in the build host policy. With this patch applied, the following sequence is possible, although in practice it is recommended that this permission only be allowed to specific program domains such as the package manager. # rmdir baz # rm bar # touch bar # chcon -t foo_exec_t bar # foo_exec_t is not yet defined chcon: failed to change context of `bar' to `system_u:object_r:foo_exec_t': Invalid argument # mkdir -Z system_u:object_r:foo_exec_t baz mkdir: failed to set default file creation context to `system_u:object_r:foo_exec_t': Invalid argument # cat setundefined.te policy_module(setundefined, 1.0) require { type unconfined_t; type unlabeled_t; } files_type(unlabeled_t) allow unconfined_t self:capability2 mac_admin; # make -f /usr/share/selinux/devel/Makefile setundefined.pp # semodule -i setundefined.pp # chcon -t foo_exec_t bar # foo_exec_t is not yet defined # mkdir -Z system_u:object_r:foo_exec_t baz # ls -Zd bar baz -rw-r--r-- root root system_u:object_r:unlabeled_t bar drwxr-xr-x root root system_u:object_r:unlabeled_t baz # cat foo.te policy_module(foo, 1.0) type foo_exec_t; files_type(foo_exec_t) # make -f /usr/share/selinux/devel/Makefile foo.pp # semodule -i foo.pp # defines foo_exec_t # ls -Zd bar baz -rw-r--r-- root root user_u:object_r:foo_exec_t bar drwxr-xr-x root root system_u:object_r:foo_exec_t baz # semodule -r foo # ls -Zd bar baz -rw-r--r-- root root system_u:object_r:unlabeled_t bar drwxr-xr-x root root system_u:object_r:unlabeled_t baz # semodule -i foo.pp # ls -Zd bar baz -rw-r--r-- root root user_u:object_r:foo_exec_t bar drwxr-xr-x root root system_u:object_r:foo_exec_t baz # semodule -r setundefined foo # chcon -t foo_exec_t bar # no longer defined and not allowed chcon: failed to change context of `bar' to `system_u:object_r:foo_exec_t': Invalid argument # rmdir baz # mkdir -Z system_u:object_r:foo_exec_t baz mkdir: failed to set default file creation context to `system_u:object_r:foo_exec_t': Invalid argument Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov> Signed-off-by: James Morris <jmorris@namei.org>
283 lines
5.3 KiB
C
283 lines
5.3 KiB
C
/*
|
|
* Implementation of the SID table type.
|
|
*
|
|
* Author : Stephen Smalley, <sds@epoch.ncsc.mil>
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/errno.h>
|
|
#include "flask.h"
|
|
#include "security.h"
|
|
#include "sidtab.h"
|
|
|
|
#define SIDTAB_HASH(sid) \
|
|
(sid & SIDTAB_HASH_MASK)
|
|
|
|
#define INIT_SIDTAB_LOCK(s) spin_lock_init(&s->lock)
|
|
#define SIDTAB_LOCK(s, x) spin_lock_irqsave(&s->lock, x)
|
|
#define SIDTAB_UNLOCK(s, x) spin_unlock_irqrestore(&s->lock, x)
|
|
|
|
int sidtab_init(struct sidtab *s)
|
|
{
|
|
int i;
|
|
|
|
s->htable = kmalloc(sizeof(*(s->htable)) * SIDTAB_SIZE, GFP_ATOMIC);
|
|
if (!s->htable)
|
|
return -ENOMEM;
|
|
for (i = 0; i < SIDTAB_SIZE; i++)
|
|
s->htable[i] = NULL;
|
|
s->nel = 0;
|
|
s->next_sid = 1;
|
|
s->shutdown = 0;
|
|
INIT_SIDTAB_LOCK(s);
|
|
return 0;
|
|
}
|
|
|
|
int sidtab_insert(struct sidtab *s, u32 sid, struct context *context)
|
|
{
|
|
int hvalue, rc = 0;
|
|
struct sidtab_node *prev, *cur, *newnode;
|
|
|
|
if (!s) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
hvalue = SIDTAB_HASH(sid);
|
|
prev = NULL;
|
|
cur = s->htable[hvalue];
|
|
while (cur != NULL && sid > cur->sid) {
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
|
|
if (cur && sid == cur->sid) {
|
|
rc = -EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
newnode = kmalloc(sizeof(*newnode), GFP_ATOMIC);
|
|
if (newnode == NULL) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
newnode->sid = sid;
|
|
if (context_cpy(&newnode->context, context)) {
|
|
kfree(newnode);
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (prev) {
|
|
newnode->next = prev->next;
|
|
wmb();
|
|
prev->next = newnode;
|
|
} else {
|
|
newnode->next = s->htable[hvalue];
|
|
wmb();
|
|
s->htable[hvalue] = newnode;
|
|
}
|
|
|
|
s->nel++;
|
|
if (sid >= s->next_sid)
|
|
s->next_sid = sid + 1;
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force)
|
|
{
|
|
int hvalue;
|
|
struct sidtab_node *cur;
|
|
|
|
if (!s)
|
|
return NULL;
|
|
|
|
hvalue = SIDTAB_HASH(sid);
|
|
cur = s->htable[hvalue];
|
|
while (cur != NULL && sid > cur->sid)
|
|
cur = cur->next;
|
|
|
|
if (force && cur && sid == cur->sid && cur->context.len)
|
|
return &cur->context;
|
|
|
|
if (cur == NULL || sid != cur->sid || cur->context.len) {
|
|
/* Remap invalid SIDs to the unlabeled SID. */
|
|
sid = SECINITSID_UNLABELED;
|
|
hvalue = SIDTAB_HASH(sid);
|
|
cur = s->htable[hvalue];
|
|
while (cur != NULL && sid > cur->sid)
|
|
cur = cur->next;
|
|
if (!cur || sid != cur->sid)
|
|
return NULL;
|
|
}
|
|
|
|
return &cur->context;
|
|
}
|
|
|
|
struct context *sidtab_search(struct sidtab *s, u32 sid)
|
|
{
|
|
return sidtab_search_core(s, sid, 0);
|
|
}
|
|
|
|
struct context *sidtab_search_force(struct sidtab *s, u32 sid)
|
|
{
|
|
return sidtab_search_core(s, sid, 1);
|
|
}
|
|
|
|
int sidtab_map(struct sidtab *s,
|
|
int (*apply) (u32 sid,
|
|
struct context *context,
|
|
void *args),
|
|
void *args)
|
|
{
|
|
int i, rc = 0;
|
|
struct sidtab_node *cur;
|
|
|
|
if (!s)
|
|
goto out;
|
|
|
|
for (i = 0; i < SIDTAB_SIZE; i++) {
|
|
cur = s->htable[i];
|
|
while (cur != NULL) {
|
|
rc = apply(cur->sid, &cur->context, args);
|
|
if (rc)
|
|
goto out;
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static inline u32 sidtab_search_context(struct sidtab *s,
|
|
struct context *context)
|
|
{
|
|
int i;
|
|
struct sidtab_node *cur;
|
|
|
|
for (i = 0; i < SIDTAB_SIZE; i++) {
|
|
cur = s->htable[i];
|
|
while (cur != NULL) {
|
|
if (context_cmp(&cur->context, context))
|
|
return cur->sid;
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int sidtab_context_to_sid(struct sidtab *s,
|
|
struct context *context,
|
|
u32 *out_sid)
|
|
{
|
|
u32 sid;
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
|
|
*out_sid = SECSID_NULL;
|
|
|
|
sid = sidtab_search_context(s, context);
|
|
if (!sid) {
|
|
SIDTAB_LOCK(s, flags);
|
|
/* Rescan now that we hold the lock. */
|
|
sid = sidtab_search_context(s, context);
|
|
if (sid)
|
|
goto unlock_out;
|
|
/* No SID exists for the context. Allocate a new one. */
|
|
if (s->next_sid == UINT_MAX || s->shutdown) {
|
|
ret = -ENOMEM;
|
|
goto unlock_out;
|
|
}
|
|
sid = s->next_sid++;
|
|
if (context->len)
|
|
printk(KERN_INFO
|
|
"SELinux: Context %s is not valid (left unmapped).\n",
|
|
context->str);
|
|
ret = sidtab_insert(s, sid, context);
|
|
if (ret)
|
|
s->next_sid--;
|
|
unlock_out:
|
|
SIDTAB_UNLOCK(s, flags);
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
*out_sid = sid;
|
|
return 0;
|
|
}
|
|
|
|
void sidtab_hash_eval(struct sidtab *h, char *tag)
|
|
{
|
|
int i, chain_len, slots_used, max_chain_len;
|
|
struct sidtab_node *cur;
|
|
|
|
slots_used = 0;
|
|
max_chain_len = 0;
|
|
for (i = 0; i < SIDTAB_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;
|
|
}
|
|
}
|
|
|
|
printk(KERN_DEBUG "%s: %d entries and %d/%d buckets used, longest "
|
|
"chain length %d\n", tag, h->nel, slots_used, SIDTAB_SIZE,
|
|
max_chain_len);
|
|
}
|
|
|
|
void sidtab_destroy(struct sidtab *s)
|
|
{
|
|
int i;
|
|
struct sidtab_node *cur, *temp;
|
|
|
|
if (!s)
|
|
return;
|
|
|
|
for (i = 0; i < SIDTAB_SIZE; i++) {
|
|
cur = s->htable[i];
|
|
while (cur != NULL) {
|
|
temp = cur;
|
|
cur = cur->next;
|
|
context_destroy(&temp->context);
|
|
kfree(temp);
|
|
}
|
|
s->htable[i] = NULL;
|
|
}
|
|
kfree(s->htable);
|
|
s->htable = NULL;
|
|
s->nel = 0;
|
|
s->next_sid = 1;
|
|
}
|
|
|
|
void sidtab_set(struct sidtab *dst, struct sidtab *src)
|
|
{
|
|
unsigned long flags;
|
|
|
|
SIDTAB_LOCK(src, flags);
|
|
dst->htable = src->htable;
|
|
dst->nel = src->nel;
|
|
dst->next_sid = src->next_sid;
|
|
dst->shutdown = 0;
|
|
SIDTAB_UNLOCK(src, flags);
|
|
}
|
|
|
|
void sidtab_shutdown(struct sidtab *s)
|
|
{
|
|
unsigned long flags;
|
|
|
|
SIDTAB_LOCK(s, flags);
|
|
s->shutdown = 1;
|
|
SIDTAB_UNLOCK(s, flags);
|
|
}
|