Skip to content

Commit

Permalink
SELinux: allow userspace to read policy back out of the kernel
Browse files Browse the repository at this point in the history
There is interest in being able to see what the actual policy is that was
loaded into the kernel.  The patch creates a new selinuxfs file
/selinux/policy which can be read by userspace.  The actual policy that is
loaded into the kernel will be written back out to userspace.

Signed-off-by: Eric Paris <eparis@redhat.com>
Signed-off-by: James Morris <jmorris@namei.org>
  • Loading branch information
Eric Paris authored and James Morris committed Oct 20, 2010
1 parent 00d85c8 commit cee74f4
Show file tree
Hide file tree
Showing 12 changed files with 1,256 additions and 3 deletions.
2 changes: 1 addition & 1 deletion security/selinux/include/classmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct security_class_mapping secclass_map[] = {
{ "compute_av", "compute_create", "compute_member",
"check_context", "load_policy", "compute_relabel",
"compute_user", "setenforce", "setbool", "setsecparam",
"setcheckreqprot", NULL } },
"setcheckreqprot", "read_policy", NULL } },
{ "process",
{ "fork", "transition", "sigchld", "sigkill",
"sigstop", "signull", "signal", "ptrace", "getsched", "setsched",
Expand Down
2 changes: 2 additions & 0 deletions security/selinux/include/security.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ extern int selinux_policycap_openperm;
int security_mls_enabled(void);

int security_load_policy(void *data, size_t len);
int security_read_policy(void **data, ssize_t *len);
size_t security_policydb_len(void);

int security_policycap_supported(unsigned int req_cap);

Expand Down
95 changes: 95 additions & 0 deletions security/selinux/selinuxfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ static int *bool_pending_values;
static struct dentry *class_dir;
static unsigned long last_class_ino;

static char policy_opened;

/* global data for policy capabilities */
static struct dentry *policycap_dir;

Expand Down Expand Up @@ -111,6 +113,7 @@ enum sel_inos {
SEL_REJECT_UNKNOWN, /* export unknown reject handling to userspace */
SEL_DENY_UNKNOWN, /* export unknown deny handling to userspace */
SEL_STATUS, /* export current status using mmap() */
SEL_POLICY, /* allow userspace to read the in kernel policy */
SEL_INO_NEXT, /* The next inode number to use */
};

Expand Down Expand Up @@ -351,6 +354,97 @@ static const struct file_operations sel_mls_ops = {
.llseek = generic_file_llseek,
};

struct policy_load_memory {
size_t len;
void *data;
};

static int sel_open_policy(struct inode *inode, struct file *filp)
{
struct policy_load_memory *plm = NULL;
int rc;

BUG_ON(filp->private_data);

mutex_lock(&sel_mutex);

rc = task_has_security(current, SECURITY__READ_POLICY);
if (rc)
goto err;

rc = -EBUSY;
if (policy_opened)
goto err;

rc = -ENOMEM;
plm = kzalloc(sizeof(*plm), GFP_KERNEL);
if (!plm)
goto err;

if (i_size_read(inode) != security_policydb_len()) {
mutex_lock(&inode->i_mutex);
i_size_write(inode, security_policydb_len());
mutex_unlock(&inode->i_mutex);
}

rc = security_read_policy(&plm->data, &plm->len);
if (rc)
goto err;

policy_opened = 1;

filp->private_data = plm;

mutex_unlock(&sel_mutex);

return 0;
err:
mutex_unlock(&sel_mutex);

if (plm)
vfree(plm->data);
kfree(plm);
return rc;
}

static int sel_release_policy(struct inode *inode, struct file *filp)
{
struct policy_load_memory *plm = filp->private_data;

BUG_ON(!plm);

policy_opened = 0;

vfree(plm->data);
kfree(plm);

return 0;
}

static ssize_t sel_read_policy(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct policy_load_memory *plm = filp->private_data;
int ret;

mutex_lock(&sel_mutex);

ret = task_has_security(current, SECURITY__READ_POLICY);
if (ret)
goto out;

ret = simple_read_from_buffer(buf, count, ppos, plm->data, plm->len);
out:
mutex_unlock(&sel_mutex);
return ret;
}

static const struct file_operations sel_policy_ops = {
.open = sel_open_policy,
.read = sel_read_policy,
.release = sel_release_policy,
};

static ssize_t sel_write_load(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)

Expand Down Expand Up @@ -1668,6 +1762,7 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent)
[SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
[SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
[SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
[SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUSR},
/* last one */ {""}
};
ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files);
Expand Down
42 changes: 42 additions & 0 deletions security/selinux/ss/avtab.c
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,48 @@ int avtab_read(struct avtab *a, void *fp, struct policydb *pol)
goto out;
}

int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp)
{
__le16 buf16[4];
__le32 buf32[1];
int rc;

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;
buf32[0] = cpu_to_le32(cur->datum.data);
rc = put_entry(buf32, sizeof(u32), 1, fp);
if (rc)
return rc;
return 0;
}

int avtab_write(struct policydb *p, struct avtab *a, void *fp)
{
unsigned int 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 avtab_cache_init(void)
{
avtab_node_cachep = kmem_cache_create("avtab_node",
Expand Down
2 changes: 2 additions & 0 deletions security/selinux/ss/avtab.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
void *p);

int avtab_read(struct avtab *a, void *fp, struct policydb *pol);
int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp);
int avtab_write(struct policydb *p, struct avtab *a, void *fp);

struct avtab_node *avtab_insert_nonunique(struct avtab *h, struct avtab_key *key,
struct avtab_datum *datum);
Expand Down
123 changes: 123 additions & 0 deletions security/selinux/ss/conditional.c
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,129 @@ int cond_read_list(struct policydb *p, void *fp)
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;
void *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];
struct cond_av_list *cur_list;
u32 len;
int rc;

len = 0;
for (cur_list = list; cur_list != NULL; cur_list = cur_list->next)
len++;

buf[0] = cpu_to_le32(len);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;

if (len == 0)
return 0;

for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) {
rc = avtab_write_item(p, cur_list->node, fp);
if (rc)
return rc;
}

return 0;
}

int cond_write_node(struct policydb *p, struct cond_node *node,
struct policy_file *fp)
{
struct cond_expr *cur_expr;
__le32 buf[2];
int rc;
u32 len = 0;

buf[0] = cpu_to_le32(node->cur_state);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;

for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next)
len++;

buf[0] = cpu_to_le32(len);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;

for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) {
buf[0] = cpu_to_le32(cur_expr->expr_type);
buf[1] = cpu_to_le32(cur_expr->bool);
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 cond_node *list, void *fp)
{
struct cond_node *cur;
u32 len;
__le32 buf[1];
int rc;

len = 0;
for (cur = list; cur != NULL; cur = cur->next)
len++;
buf[0] = cpu_to_le32(len);
rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
return rc;

for (cur = list; cur != NULL; cur = cur->next) {
rc = cond_write_node(p, cur, fp);
if (rc)
return rc;
}

return 0;
}
/* Determine whether additional permissions are granted by the conditional
* av table, and if so, add them to the result
*/
Expand Down
2 changes: 2 additions & 0 deletions security/selinux/ss/conditional.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ int cond_index_bool(void *key, void *datum, void *datap);

int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp);
int cond_read_list(struct policydb *p, void *fp);
int cond_write_bool(void *key, void *datum, void *ptr);
int cond_write_list(struct policydb *p, struct cond_node *list, void *fp);

void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd);

Expand Down
Loading

0 comments on commit cee74f4

Please sign in to comment.