* Implementation of the kernel access vector cache (AVC).
*
* Authors: Stephen Smalley, <sds@epoch.ncsc.mil>
- * James Morris <jmorris@redhat.com>
+ * James Morris <jmorris@redhat.com>
*
* Update: KaiGai, Kohei <kaigai@ak.jp.nec.com>
- * Replaced the avc_lock spinlock by RCU.
+ * Replaced the avc_lock spinlock by RCU.
*
* Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
+ * as published by the Free Software Foundation.
*/
#include <linux/types.h>
#include <linux/stddef.h>
#include "avc.h"
#include "avc_ss.h"
-static const struct av_perm_to_string
-{
- u16 tclass;
- u32 value;
- const char *name;
-} av_perm_to_string[] = {
+static const struct av_perm_to_string av_perm_to_string[] = {
#define S_(c, v, s) { c, v, s },
#include "av_perm_to_string.h"
#undef S_
};
-#ifdef CONFIG_AUDIT
static const char *class_to_string[] = {
#define S_(s) s,
#include "class_to_string.h"
#undef S_
};
-#endif
-#define TB_(s) static const char * s [] = {
+#define TB_(s) static const char *s[] = {
#define TE_(s) };
#define S_(s) s,
#include "common_perm_to_string.h"
#undef TE_
#undef S_
-static const struct av_inherit
-{
- u16 tclass;
- const char **common_pts;
- u32 common_base;
-} av_inherit[] = {
+static const struct av_inherit av_inherit[] = {
#define S_(c, i, b) { c, common_##i##_perm_to_string, b },
#include "av_inherit.h"
#undef S_
};
+const struct selinux_class_perm selinux_class_perm = {
+ av_perm_to_string,
+ ARRAY_SIZE(av_perm_to_string),
+ class_to_string,
+ ARRAY_SIZE(class_to_string),
+ av_inherit,
+ ARRAY_SIZE(av_inherit)
+};
+
#define AVC_CACHE_SLOTS 512
#define AVC_DEF_CACHE_THRESHOLD 512
#define AVC_CACHE_RECLAIM 16
#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
-#define avc_cache_stats_incr(field) \
+#define avc_cache_stats_incr(field) \
do { \
per_cpu(avc_cache_stats, get_cpu()).field++; \
put_cpu(); \
struct avc_node {
struct avc_entry ae;
struct list_head list;
- struct rcu_head rhead;
+ struct rcu_head rhead;
};
struct avc_cache {
struct avc_callback_node {
int (*callback) (u32 event, u32 ssid, u32 tsid,
- u16 tclass, u32 perms,
- u32 *out_retained);
+ u16 tclass, u32 perms,
+ u32 *out_retained);
u32 events;
u32 ssid;
u32 tsid;
static struct avc_cache avc_cache;
static struct avc_callback_node *avc_callbacks;
-static kmem_cache_t *avc_node_cachep;
+static struct kmem_cache *avc_node_cachep;
static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass)
{
* @tclass: target security class
* @av: access vector
*/
-static void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av)
+void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av)
{
const char **common_pts = NULL;
u32 common_base = 0;
char *scontext;
u32 scontext_len;
- rc = security_sid_to_context(ssid, &scontext, &scontext_len);
+ rc = security_sid_to_context(ssid, &scontext, &scontext_len);
if (rc)
audit_log_format(ab, "ssid=%d", ssid);
else {
audit_log_format(ab, " tcontext=%s", scontext);
kfree(scontext);
}
+
+ BUG_ON(tclass >= ARRAY_SIZE(class_to_string) || !class_to_string[tclass]);
audit_log_format(ab, " tclass=%s", class_to_string[tclass]);
}
atomic_set(&avc_cache.lru_hint, 0);
avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node),
- 0, SLAB_PANIC, NULL, NULL);
+ 0, SLAB_PANIC, NULL);
- audit_log(current->audit_context, AUDIT_KERNEL, "AVC INITIALIZED\n");
+ audit_log(current->audit_context, GFP_KERNEL, AUDIT_KERNEL, "AVC INITIALIZED\n");
}
int avc_get_hash_stats(char *page)
int hvalue, try, ecx;
unsigned long flags;
- for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++ ) {
+ for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++) {
hvalue = atomic_inc_return(&avc_cache.lru_hint) & (AVC_CACHE_SLOTS - 1);
if (!spin_trylock_irqsave(&avc_cache.slots_lock[hvalue], flags))
continue;
+ rcu_read_lock();
list_for_each_entry(node, &avc_cache.slots[hvalue], list) {
if (atomic_dec_and_test(&node->ae.used)) {
/* Recently Unused */
avc_cache_stats_incr(reclaims);
ecx++;
if (ecx >= AVC_CACHE_RECLAIM) {
+ rcu_read_unlock();
spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags);
goto out;
}
}
}
+ rcu_read_unlock();
spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags);
}
out:
{
struct avc_node *node;
- node = kmem_cache_alloc(avc_node_cachep, SLAB_ATOMIC);
+ node = kmem_cache_zalloc(avc_node_cachep, GFP_ATOMIC);
if (!node)
goto out;
- memset(node, 0, sizeof(*node));
INIT_RCU_HEAD(&node->rhead);
INIT_LIST_HEAD(&node->list);
atomic_set(&node->ae.used, 1);
spin_lock_irqsave(¬if_lock, flag);
if (is_insert) {
if (seqno < avc_cache.latest_notif) {
- printk(KERN_WARNING "avc: seqno %d < latest_notif %d\n",
+ printk(KERN_WARNING "SELinux: avc: seqno %d < latest_notif %d\n",
seqno, avc_cache.latest_notif);
ret = -EAGAIN;
}
if (pos->ae.ssid == ssid &&
pos->ae.tsid == tsid &&
pos->ae.tclass == tclass) {
- avc_node_replace(node, pos);
+ avc_node_replace(node, pos);
goto found;
}
}
}
static inline void avc_print_ipv6_addr(struct audit_buffer *ab,
- struct in6_addr *addr, u16 port,
+ struct in6_addr *addr, __be16 port,
char *name1, char *name2)
{
if (!ipv6_addr_any(addr))
- audit_log_format(ab, " %s=%04x:%04x:%04x:%04x:%04x:"
- "%04x:%04x:%04x", name1, NIP6(*addr));
+ audit_log_format(ab, " %s=" NIP6_FMT, name1, NIP6(*addr));
if (port)
audit_log_format(ab, " %s=%d", name2, ntohs(port));
}
-static inline void avc_print_ipv4_addr(struct audit_buffer *ab, u32 addr,
- u16 port, char *name1, char *name2)
+static inline void avc_print_ipv4_addr(struct audit_buffer *ab, __be32 addr,
+ __be16 port, char *name1, char *name2)
{
if (addr)
- audit_log_format(ab, " %s=%d.%d.%d.%d", name1, NIPQUAD(addr));
+ audit_log_format(ab, " %s=" NIPQUAD_FMT, name1, NIPQUAD(addr));
if (port)
audit_log_format(ab, " %s=%d", name2, ntohs(port));
}
* before calling the auditing code.
*/
void avc_audit(u32 ssid, u32 tsid,
- u16 tclass, u32 requested,
- struct av_decision *avd, int result, struct avc_audit_data *a)
+ u16 tclass, u32 requested,
+ struct av_decision *avd, int result, struct avc_audit_data *a)
{
struct task_struct *tsk = current;
struct inode *inode = NULL;
return;
} else if (result) {
audited = denied = requested;
- } else {
+ } else {
audited = requested;
if (!(audited & avd->auditallow))
return;
}
- ab = audit_log_start(current->audit_context, AUDIT_AVC);
+ ab = audit_log_start(current->audit_context, GFP_ATOMIC, AUDIT_AVC);
if (!ab)
return; /* audit_panic has been called */
audit_log_format(ab, "avc: %s ", denied ? "denied" : "granted");
- avc_dump_av(ab, tclass,audited);
+ avc_dump_av(ab, tclass, audited);
audit_log_format(ab, " for ");
if (a && a->tsk)
tsk = a->tsk;
- if (a->tsk && a->tsk->pid) {
+ if (tsk && tsk->pid) {
audit_log_format(ab, " pid=%d comm=", tsk->pid);
audit_log_untrustedstring(ab, tsk->comm);
}
audit_log_format(ab, " capability=%d", a->u.cap);
break;
case AVC_AUDIT_DATA_FS:
- if (a->u.fs.dentry) {
- struct dentry *dentry = a->u.fs.dentry;
- if (a->u.fs.mnt)
- audit_avc_path(dentry, a->u.fs.mnt);
- audit_log_format(ab, " name=%s",
- dentry->d_name.name);
+ if (a->u.fs.path.dentry) {
+ struct dentry *dentry = a->u.fs.path.dentry;
+ if (a->u.fs.path.mnt) {
+ audit_log_d_path(ab, "path=",
+ &a->u.fs.path);
+ } else {
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, dentry->d_name.name);
+ }
inode = dentry->d_inode;
} else if (a->u.fs.inode) {
struct dentry *dentry;
inode = a->u.fs.inode;
dentry = d_find_alias(inode);
if (dentry) {
- audit_log_format(ab, " name=%s",
- dentry->d_name.name);
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, dentry->d_name.name);
dput(dentry);
}
}
if (inode)
- audit_log_format(ab, " dev=%s ino=%ld",
+ audit_log_format(ab, " dev=%s ino=%lu",
inode->i_sb->s_id,
inode->i_ino);
break;
case AF_UNIX:
u = unix_sk(sk);
if (u->dentry) {
- audit_avc_path(u->dentry, u->mnt);
- audit_log_format(ab, " name=%s",
- u->dentry->d_name.name);
-
+ struct path path = {
+ .dentry = u->dentry,
+ .mnt = u->mnt
+ };
+ audit_log_d_path(ab, "path=",
+ &path);
break;
}
if (!u->addr)
break;
len = u->addr->len-sizeof(short);
p = &u->addr->name->sun_path[0];
+ audit_log_format(ab, " path=");
if (*p)
- audit_log_format(ab,
- "path=%*.*s", len,
- len, p);
+ audit_log_untrustedstring(ab, p);
else
- audit_log_format(ab,
- "path=@%*.*s", len-1,
- len-1, p+1);
+ audit_log_n_hex(ab, p, len);
break;
}
}
-
+
switch (a->u.net.family) {
case AF_INET:
avc_print_ipv4_addr(ab, a->u.net.v4info.saddr,
"daddr", "dest");
break;
}
- if (a->u.net.netif)
- audit_log_format(ab, " netif=%s",
- a->u.net.netif);
+ if (a->u.net.netif > 0) {
+ struct net_device *dev;
+
+ /* NOTE: we always use init's namespace */
+ dev = dev_get_by_index(&init_net,
+ a->u.net.netif);
+ if (dev) {
+ audit_log_format(ab, " netif=%s",
+ dev->name);
+ dev_put(dev);
+ }
+ }
break;
}
}
* -%ENOMEM if insufficient memory exists to add the callback.
*/
int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid,
- u16 tclass, u32 perms,
- u32 *out_retained),
- u32 events, u32 ssid, u32 tsid,
- u16 tclass, u32 perms)
+ u16 tclass, u32 perms,
+ u32 *out_retained),
+ u32 events, u32 ssid, u32 tsid,
+ u16 tclass, u32 perms)
{
struct avc_callback_node *c;
int rc = 0;
hvalue = avc_hash(ssid, tsid, tclass);
spin_lock_irqsave(&avc_cache.slots_lock[hvalue], flag);
- list_for_each_entry(pos, &avc_cache.slots[hvalue], list){
- if ( ssid==pos->ae.ssid &&
- tsid==pos->ae.tsid &&
- tclass==pos->ae.tclass ){
+ list_for_each_entry(pos, &avc_cache.slots[hvalue], list) {
+ if (ssid == pos->ae.ssid &&
+ tsid == pos->ae.tsid &&
+ tclass == pos->ae.tclass){
orig = pos;
break;
}
int avc_ss_reset(u32 seqno)
{
struct avc_callback_node *c;
- int i, rc = 0;
+ int i, rc = 0, tmprc;
unsigned long flag;
struct avc_node *node;
for (i = 0; i < AVC_CACHE_SLOTS; i++) {
spin_lock_irqsave(&avc_cache.slots_lock[i], flag);
+ /*
+ * With preemptable RCU, the outer spinlock does not
+ * prevent RCU grace periods from ending.
+ */
+ rcu_read_lock();
list_for_each_entry(node, &avc_cache.slots[i], list)
avc_node_delete(node);
+ rcu_read_unlock();
spin_unlock_irqrestore(&avc_cache.slots_lock[i], flag);
}
for (c = avc_callbacks; c; c = c->next) {
if (c->events & AVC_CALLBACK_RESET) {
- rc = c->callback(AVC_CALLBACK_RESET,
- 0, 0, 0, 0, NULL);
- if (rc)
- goto out;
+ tmprc = c->callback(AVC_CALLBACK_RESET,
+ 0, 0, 0, 0, NULL);
+ /* save the first error encountered for the return
+ value and continue processing the callbacks */
+ if (!rc)
+ rc = tmprc;
}
}
avc_latest_notif_update(seqno, 0);
-out:
return rc;
}
* @tsid: target security identifier
* @tclass: target security class
* @requested: requested permissions, interpreted based on @tclass
+ * @flags: AVC_STRICT or 0
* @avd: access vector decisions
*
* Check the AVC to determine whether the @requested permissions are granted
* should be released for the auditing.
*/
int avc_has_perm_noaudit(u32 ssid, u32 tsid,
- u16 tclass, u32 requested,
- struct av_decision *avd)
+ u16 tclass, u32 requested,
+ unsigned flags,
+ struct av_decision *avd)
{
struct avc_node *node;
struct avc_entry entry, *p_ae;
int rc = 0;
u32 denied;
+ BUG_ON(!requested);
+
rcu_read_lock();
node = avc_lookup(ssid, tsid, tclass, requested);
if (!node) {
rcu_read_unlock();
- rc = security_compute_av(ssid,tsid,tclass,requested,&entry.avd);
+ rc = security_compute_av(ssid, tsid, tclass, requested, &entry.avd);
if (rc)
goto out;
rcu_read_lock();
- node = avc_insert(ssid,tsid,tclass,&entry);
+ node = avc_insert(ssid, tsid, tclass, &entry);
}
p_ae = node ? &node->ae : &entry;
denied = requested & ~(p_ae->avd.allowed);
- if (!requested || denied) {
- if (selinux_enforcing)
+ if (denied) {
+ if (flags & AVC_STRICT)
rc = -EACCES;
+ else if (!selinux_enforcing || security_permissive_sid(ssid))
+ avc_update_node(AVC_CALLBACK_GRANT, requested, ssid,
+ tsid, tclass);
else
- if (node)
- avc_update_node(AVC_CALLBACK_GRANT,requested,
- ssid,tsid,tclass);
+ rc = -EACCES;
}
rcu_read_unlock();
* another -errno upon other errors.
*/
int avc_has_perm(u32 ssid, u32 tsid, u16 tclass,
- u32 requested, struct avc_audit_data *auditdata)
+ u32 requested, struct avc_audit_data *auditdata)
{
struct av_decision avd;
int rc;
- rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, &avd);
+ rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata);
return rc;
}
+
+u32 avc_policy_seqno(void)
+{
+ return avc_cache.latest_notif;
+}