blob: 5ef5bd5bcc1c45c9eff4ddce1e2d7b07c92b36d0 [file] [log] [blame] [edit]
/*
* Implementation of the multi-level security (MLS) policy.
*
* Author : Stephen Smalley, <sds@epoch.ncsc.mil>
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/errno.h>
#include "mls.h"
#include "policydb.h"
#include "services.h"
/*
* Remove any permissions from `allowed' that are
* denied by the MLS policy.
*/
void mls_compute_av(struct context *scontext,
struct context *tcontext,
struct class_datum *tclass,
u32 *allowed)
{
unsigned int rel[2];
int l;
for (l = 0; l < 2; l++)
rel[l] = mls_level_relation(scontext->range.level[l],
tcontext->range.level[l]);
if (rel[1] != MLS_RELATION_EQ) {
if (rel[1] != MLS_RELATION_DOM &&
!ebitmap_get_bit(&policydb.trustedreaders, scontext->type - 1) &&
!ebitmap_get_bit(&policydb.trustedobjects, tcontext->type - 1)) {
/* read(s,t) = (s.high >= t.high) = False */
*allowed = (*allowed) & ~(tclass->mlsperms.read);
}
if (rel[1] != MLS_RELATION_DOMBY &&
!ebitmap_get_bit(&policydb.trustedreaders, tcontext->type - 1) &&
!ebitmap_get_bit(&policydb.trustedobjects, scontext->type - 1)) {
/* readby(s,t) = read(t,s) = False */
*allowed = (*allowed) & ~(tclass->mlsperms.readby);
}
}
if (((rel[0] != MLS_RELATION_DOMBY && rel[0] != MLS_RELATION_EQ) ||
((!mls_level_eq(tcontext->range.level[0],
tcontext->range.level[1])) &&
(rel[1] != MLS_RELATION_DOM && rel[1] != MLS_RELATION_EQ))) &&
!ebitmap_get_bit(&policydb.trustedwriters, scontext->type - 1) &&
!ebitmap_get_bit(&policydb.trustedobjects, tcontext->type - 1)) {
/*
* write(s,t) = ((s.low <= t.low = t.high) or (s.low
* <= t.low <= t.high <= s.high)) = False
*/
*allowed = (*allowed) & ~(tclass->mlsperms.write);
}
if (((rel[0] != MLS_RELATION_DOM && rel[0] != MLS_RELATION_EQ) ||
((!mls_level_eq(scontext->range.level[0],
scontext->range.level[1])) &&
(rel[1] != MLS_RELATION_DOMBY && rel[1] != MLS_RELATION_EQ))) &&
!ebitmap_get_bit(&policydb.trustedwriters, tcontext->type - 1) &&
!ebitmap_get_bit(&policydb.trustedobjects, scontext->type - 1)) {
/* writeby(s,t) = write(t,s) = False */
*allowed = (*allowed) & ~(tclass->mlsperms.writeby);
}
}
/*
* Return the length in bytes for the MLS fields of the
* security context string representation of `context'.
*/
int mls_compute_context_len(struct context * context)
{
int i, l, len;
len = 0;
for (l = 0; l < 2; l++) {
len += strlen(policydb.p_sens_val_to_name[context->range.level[l].sens - 1]) + 1;
for (i = 1; i <= ebitmap_length(&context->range.level[l].cat); i++)
if (ebitmap_get_bit(&context->range.level[l].cat, i - 1))
len += strlen(policydb.p_cat_val_to_name[i - 1]) + 1;
if (mls_level_relation(context->range.level[0], context->range.level[1])
== MLS_RELATION_EQ)
break;
}
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.
*/
int mls_sid_to_context(struct context *context,
char **scontext)
{
char *scontextp;
int i, l;
scontextp = *scontext;
for (l = 0; l < 2; l++) {
strcpy(scontextp,
policydb.p_sens_val_to_name[context->range.level[l].sens - 1]);
scontextp += strlen(policydb.p_sens_val_to_name[context->range.level[l].sens - 1]);
*scontextp = ':';
scontextp++;
for (i = 1; i <= ebitmap_length(&context->range.level[l].cat); i++)
if (ebitmap_get_bit(&context->range.level[l].cat, i - 1)) {
strcpy(scontextp, policydb.p_cat_val_to_name[i - 1]);
scontextp += strlen(policydb.p_cat_val_to_name[i - 1]);
*scontextp = ',';
scontextp++;
}
if (mls_level_relation(context->range.level[0], context->range.level[1])
!= MLS_RELATION_EQ) {
scontextp--;
sprintf(scontextp, "-");
scontextp++;
} else {
break;
}
}
*scontext = scontextp;
return 0;
}
/*
* Return 1 if the MLS fields in the security context
* structure `c' are valid. Return 0 otherwise.
*/
int mls_context_isvalid(struct policydb *p, struct context *c)
{
unsigned int relation;
struct level_datum *levdatum;
struct user_datum *usrdatum;
struct mls_range_list *rnode;
int i, l;
/*
* MLS range validity checks: high must dominate low, low level must
* be valid (category set <-> sensitivity check), and high level must
* be valid (category set <-> sensitivity check)
*/
relation = mls_level_relation(c->range.level[1],
c->range.level[0]);
if (!(relation & (MLS_RELATION_DOM | MLS_RELATION_EQ)))
/* High does not dominate low. */
return 0;
for (l = 0; l < 2; l++) {
if (!c->range.level[l].sens || c->range.level[l].sens > p->p_levels.nprim)
return 0;
levdatum = hashtab_search(p->p_levels.table,
p->p_sens_val_to_name[c->range.level[l].sens - 1]);
if (!levdatum)
return 0;
for (i = 1; i <= ebitmap_length(&c->range.level[l].cat); i++) {
if (ebitmap_get_bit(&c->range.level[l].cat, i - 1)) {
if (i > p->p_cats.nprim)
return 0;
if (!ebitmap_get_bit(&levdatum->level->cat, i - 1))
/*
* Category may not be associated with
* sensitivity in low level.
*/
return 0;
}
}
}
if (c->role == OBJECT_R_VAL)
return 1;
/*
* User must be authorized for the MLS range.
*/
if (!c->user || c->user > p->p_users.nprim)
return 0;
usrdatum = p->user_val_to_struct[c->user - 1];
for (rnode = usrdatum->ranges; rnode; rnode = rnode->next) {
if (mls_range_contains(rnode->range, c->range))
break;
}
if (!rnode)
/* user may not be associated with range */
return 0;
return 1;
}
/*
* Set the MLS fields in the security context structure
* `context' based on the string representation in
* the string `*scontext'. Update `*scontext' to
* point to the end of the string representation of
* the MLS fields.
*
* This function modifies the string in place, inserting
* NULL characters to terminate the MLS fields.
*/
int mls_context_to_sid(char oldc,
char **scontext,
struct context *context)
{
char delim;
char *scontextp, *p;
struct level_datum *levdatum;
struct cat_datum *catdatum;
int l, rc = -EINVAL;
if (!oldc) {
/* No MLS component to the security context. Try
to use a default 'unclassified' value. */
levdatum = hashtab_search(policydb.p_levels.table,
"unclassified");
if (!levdatum)
goto out;
context->range.level[0].sens = levdatum->level->sens;
context->range.level[1].sens = context->range.level[0].sens;
rc = 0;
goto out;
}
/* Extract low sensitivity. */
scontextp = p = *scontext;
while (*p && *p != ':' && *p != '-')
p++;
delim = *p;
if (delim != 0)
*p++ = 0;
for (l = 0; l < 2; l++) {
levdatum = hashtab_search(policydb.p_levels.table, scontextp);
if (!levdatum)
goto out;
context->range.level[l].sens = levdatum->level->sens;
if (delim == ':') {
/* Extract low category set. */
while (1) {
scontextp = p;
while (*p && *p != ',' && *p != '-')
p++;
delim = *p;
if (delim != 0)
*p++ = 0;
catdatum = hashtab_search(policydb.p_cats.table,
scontextp);
if (!catdatum)
goto out;
rc = ebitmap_set_bit(&context->range.level[l].cat,
catdatum->value - 1, 1);
if (rc)
goto out;
if (delim != ',')
break;
}
}
if (delim == '-') {
/* Extract high sensitivity. */
scontextp = p;
while (*p && *p != ':')
p++;
delim = *p;
if (delim != 0)
*p++ = 0;
} else
break;
}
if (l == 0) {
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)
goto out;
}
*scontext = ++p;
rc = 0;
out:
return rc;
}
/*
* Copies the MLS range from `src' into `dst'.
*/
static inline int mls_copy_context(struct context *dst,
struct context *src)
{
int l, rc = 0;
/* Copy the MLS range from the source context */
for (l = 0; l < 2; l++) {
dst->range.level[l].sens = src->range.level[l].sens;
rc = ebitmap_cpy(&dst->range.level[l].cat,
&src->range.level[l].cat);
if (rc)
break;
}
return rc;
}
/*
* Convert the MLS fields in the security context
* structure `c' from the values specified in the
* policy `oldp' to the values specified in the policy `newp'.
*/
int mls_convert_context(struct policydb *oldp,
struct policydb *newp,
struct context *c)
{
struct level_datum *levdatum;
struct cat_datum *catdatum;
struct ebitmap bitmap;
int l, i;
for (l = 0; l < 2; l++) {
levdatum = hashtab_search(newp->p_levels.table,
oldp->p_sens_val_to_name[c->range.level[l].sens - 1]);
if (!levdatum)
return -EINVAL;
c->range.level[l].sens = levdatum->level->sens;
ebitmap_init(&bitmap);
for (i = 1; i <= ebitmap_length(&c->range.level[l].cat); i++) {
if (ebitmap_get_bit(&c->range.level[l].cat, i - 1)) {
int rc;
catdatum = hashtab_search(newp->p_cats.table,
oldp->p_cat_val_to_name[i - 1]);
if (!catdatum)
return -EINVAL;
rc = ebitmap_set_bit(&bitmap, catdatum->value - 1, 1);
if (rc)
return rc;
}
}
ebitmap_destroy(&c->range.level[l].cat);
c->range.level[l].cat = bitmap;
}
return 0;
}
int mls_compute_sid(struct context *scontext,
struct context *tcontext,
u16 tclass,
u32 specified,
struct context *newcontext)
{
switch (specified) {
case AVTAB_TRANSITION:
case AVTAB_CHANGE:
/* Use the process MLS attributes. */
return mls_copy_context(newcontext, scontext);
case AVTAB_MEMBER:
/* Only polyinstantiate the MLS attributes if
the type is being polyinstantiated */
if (newcontext->type != tcontext->type) {
/* Use the process MLS attributes. */
return mls_copy_context(newcontext, scontext);
} else {
/* Use the related object MLS attributes. */
return mls_copy_context(newcontext, tcontext);
}
default:
return -EINVAL;
}
return -EINVAL;
}
void mls_user_destroy(struct user_datum *usrdatum)
{
struct mls_range_list *rnode, *rtmp;
rnode = usrdatum->ranges;
while (rnode) {
rtmp = rnode;
rnode = rnode->next;
ebitmap_destroy(&rtmp->range.level[0].cat);
ebitmap_destroy(&rtmp->range.level[1].cat);
kfree(rtmp);
}
}
int mls_read_perm(struct perm_datum *perdatum, void *fp)
{
u32 buf[1];
int rc;
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0)
return -EINVAL;
perdatum->base_perms = le32_to_cpu(buf[0]);
return 0;
}
/*
* Read a MLS level structure from a policydb binary
* representation file.
*/
struct mls_level *mls_read_level(void *fp)
{
struct mls_level *l;
u32 buf[1];
int rc;
l = kmalloc(sizeof(*l), GFP_ATOMIC);
if (!l) {
printk(KERN_ERR "security: mls: out of memory\n");
return NULL;
}
memset(l, 0, sizeof(*l));
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0) {
printk(KERN_ERR "security: mls: truncated level\n");
goto bad;
}
l->sens = cpu_to_le32(buf[0]);
if (ebitmap_read(&l->cat, fp)) {
printk(KERN_ERR "security: mls: error reading level "
"categories\n");
goto bad;
}
return l;
bad:
kfree(l);
return NULL;
}
/*
* Read a MLS range structure from a policydb binary
* representation file.
*/
static int mls_read_range_helper(struct mls_range *r, void *fp)
{
u32 buf[2], items;
int rc;
rc = next_entry(buf, fp, sizeof(u32));
if (rc < 0)
goto out;
items = le32_to_cpu(buf[0]);
if (items > ARRAY_SIZE(buf)) {
printk(KERN_ERR "security: mls: range overflow\n");
rc = -EINVAL;
goto out;
}
rc = next_entry(buf, fp, sizeof(u32) * items);
if (rc < 0) {
printk(KERN_ERR "security: mls: truncated range\n");
goto out;
}
r->level[0].sens = le32_to_cpu(buf[0]);
if (items > 1) {
r->level[1].sens = le32_to_cpu(buf[1]);
} else {
r->level[1].sens = r->level[0].sens;
}
rc = ebitmap_read(&r->level[0].cat, fp);
if (rc) {
printk(KERN_ERR "security: mls: error reading low "
"categories\n");
goto out;
}
if (items > 1) {
rc = ebitmap_read(&r->level[1].cat, fp);
if (rc) {
printk(KERN_ERR "security: mls: error reading high "
"categories\n");
goto bad_high;
}
} else {
rc = ebitmap_cpy(&r->level[1].cat, &r->level[0].cat);
if (rc) {
printk(KERN_ERR "security: mls: out of memory\n");
goto bad_high;
}
}
rc = 0;
out:
return rc;
bad_high:
ebitmap_destroy(&r->level[0].cat);
goto out;
}
int mls_read_range(struct context *c, void *fp)
{
return mls_read_range_helper(&c->range, fp);
}
/*
* Read a MLS perms structure from a policydb binary
* representation file.
*/
int mls_read_class(struct class_datum *cladatum, void *fp)
{
struct mls_perms *p = &cladatum->mlsperms;
u32 buf[4];
int rc;
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0) {
printk(KERN_ERR "security: mls: truncated mls permissions\n");
return -EINVAL;
}
p->read = le32_to_cpu(buf[0]);
p->readby = le32_to_cpu(buf[1]);
p->write = le32_to_cpu(buf[2]);
p->writeby = le32_to_cpu(buf[3]);
return 0;
}
int mls_read_user(struct user_datum *usrdatum, void *fp)
{
struct mls_range_list *r, *l;
int rc;
u32 nel, i;
u32 buf[1];
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0)
goto out;
nel = le32_to_cpu(buf[0]);
l = NULL;
for (i = 0; i < nel; i++) {
r = kmalloc(sizeof(*r), GFP_ATOMIC);
if (!r) {
rc = -ENOMEM;
goto out;
}
memset(r, 0, sizeof(*r));
rc = mls_read_range_helper(&r->range, fp);
if (rc) {
kfree(r);
goto out;
}
if (l)
l->next = r;
else
usrdatum->ranges = r;
l = r;
}
out:
return rc;
}
int mls_read_nlevels(struct policydb *p, void *fp)
{
u32 buf[1];
int rc;
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0)
return -EINVAL;
p->nlevels = le32_to_cpu(buf[0]);
return 0;
}
int mls_read_trusted(struct policydb *p, void *fp)
{
int rc = 0;
rc = ebitmap_read(&p->trustedreaders, fp);
if (rc)
goto out;
rc = ebitmap_read(&p->trustedwriters, fp);
if (rc)
goto bad;
rc = ebitmap_read(&p->trustedobjects, fp);
if (rc)
goto bad2;
out:
return rc;
bad2:
ebitmap_destroy(&p->trustedwriters);
bad:
ebitmap_destroy(&p->trustedreaders);
goto out;
}
int sens_index(void *key, void *datum, void *datap)
{
struct policydb *p;
struct level_datum *levdatum;
levdatum = datum;
p = datap;
if (!levdatum->isalias)
p->p_sens_val_to_name[levdatum->level->sens - 1] = key;
return 0;
}
int cat_index(void *key, void *datum, void *datap)
{
struct policydb *p;
struct cat_datum *catdatum;
catdatum = datum;
p = datap;
if (!catdatum->isalias)
p->p_cat_val_to_name[catdatum->value - 1] = key;
return 0;
}
int sens_destroy(void *key, void *datum, void *p)
{
struct level_datum *levdatum;
kfree(key);
levdatum = datum;
if (!levdatum->isalias) {
ebitmap_destroy(&levdatum->level->cat);
kfree(levdatum->level);
}
kfree(datum);
return 0;
}
int cat_destroy(void *key, void *datum, void *p)
{
kfree(key);
kfree(datum);
return 0;
}
int sens_read(struct policydb *p, struct hashtab *h, void *fp)
{
char *key = NULL;
struct level_datum *levdatum;
int rc;
u32 buf[2], len;
levdatum = kmalloc(sizeof(*levdatum), GFP_ATOMIC);
if (!levdatum) {
rc = -ENOMEM;
goto out;
}
memset(levdatum, 0, sizeof(*levdatum));
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0)
goto bad;
len = le32_to_cpu(buf[0]);
levdatum->isalias = le32_to_cpu(buf[1]);
key = kmalloc(len + 1,GFP_ATOMIC);
if (!key) {
rc = -ENOMEM;
goto bad;
}
rc = next_entry(key, fp, len);
if (rc < 0)
goto bad;
key[len] = 0;
levdatum->level = mls_read_level(fp);
if (!levdatum->level) {
rc = -EINVAL;
goto bad;
}
rc = hashtab_insert(h, key, levdatum);
if (rc)
goto bad;
out:
return rc;
bad:
sens_destroy(key, levdatum, NULL);
goto out;
}
int cat_read(struct policydb *p, struct hashtab *h, void *fp)
{
char *key = NULL;
struct cat_datum *catdatum;
int rc;
u32 buf[3], len;
catdatum = kmalloc(sizeof(*catdatum), GFP_ATOMIC);
if (!catdatum) {
rc = -ENOMEM;
goto out;
}
memset(catdatum, 0, sizeof(*catdatum));
rc = next_entry(buf, fp, sizeof buf);
if (rc < 0)
goto bad;
len = le32_to_cpu(buf[0]);
catdatum->value = le32_to_cpu(buf[1]);
catdatum->isalias = le32_to_cpu(buf[2]);
key = kmalloc(len + 1,GFP_ATOMIC);
if (!key) {
rc = -ENOMEM;
goto bad;
}
rc = next_entry(key, fp, len);
if (rc < 0)
goto bad;
key[len] = 0;
rc = hashtab_insert(h, key, catdatum);
if (rc)
goto bad;
out:
return rc;
bad:
cat_destroy(key, catdatum, NULL);
goto out;
}