aboutsummaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
Diffstat (limited to 'security')
-rw-r--r--security/Kconfig2
-rw-r--r--security/apparmor/domain.c4
-rw-r--r--security/apparmor/include/apparmor.h1
-rw-r--r--security/apparmor/lib.c32
-rw-r--r--security/capability.c24
-rw-r--r--security/commoncap.c11
-rw-r--r--security/lsm_audit.c15
-rw-r--r--security/security.c20
-rw-r--r--security/selinux/avc.c437
-rw-r--r--security/selinux/hooks.c183
-rw-r--r--security/selinux/include/avc.h9
-rw-r--r--security/selinux/include/classmap.h1
-rw-r--r--security/selinux/include/netif.h6
-rw-r--r--security/selinux/include/netnode.h2
-rw-r--r--security/selinux/include/netport.h2
-rw-r--r--security/selinux/include/objsec.h2
-rw-r--r--security/selinux/include/security.h35
-rw-r--r--security/selinux/netif.c58
-rw-r--r--security/selinux/netnode.c15
-rw-r--r--security/selinux/netport.c15
-rw-r--r--security/selinux/nlmsgtab.c9
-rw-r--r--security/selinux/ss/avtab.c94
-rw-r--r--security/selinux/ss/avtab.h25
-rw-r--r--security/selinux/ss/conditional.c32
-rw-r--r--security/selinux/ss/conditional.h6
-rw-r--r--security/selinux/ss/constraint.h1
-rw-r--r--security/selinux/ss/policydb.c101
-rw-r--r--security/selinux/ss/policydb.h11
-rw-r--r--security/selinux/ss/services.c202
-rw-r--r--security/selinux/ss/services.h6
30 files changed, 1155 insertions, 206 deletions
diff --git a/security/Kconfig b/security/Kconfig
index e9c6ac724fef..beb86b500adf 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -103,7 +103,7 @@ config INTEL_TXT
config LSM_MMAP_MIN_ADDR
int "Low address space for LSM to protect from user allocation"
depends on SECURITY && SECURITY_SELINUX
- default 32768 if ARM
+ default 32768 if ARM || (ARM64 && COMPAT)
default 65536
help
This is the portion of low virtual memory which should be protected
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 859abdaac1ea..9aaa4e72cc1f 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -629,7 +629,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
* There is no exception for unconfined as change_hat is not
* available.
*/
- if (current->no_new_privs)
+ if (task_no_new_privs(current))
return -EPERM;
/* released below */
@@ -780,7 +780,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
* no_new_privs is set because this aways results in a reduction
* of permissions.
*/
- if (current->no_new_privs && !unconfined(profile)) {
+ if (task_no_new_privs(current) && !unconfined(profile)) {
put_cred(cred);
return -EPERM;
}
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
index 40aedd9f73ea..4a8cbfeef8b3 100644
--- a/security/apparmor/include/apparmor.h
+++ b/security/apparmor/include/apparmor.h
@@ -65,7 +65,6 @@ extern int apparmor_initialized __initdata;
char *aa_split_fqname(char *args, char **ns_name);
void aa_info_message(const char *str);
void *kvmalloc(size_t size);
-void kvfree(void *buffer);
/**
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index 7430298116d6..ce8d9a84ab2d 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -103,35 +103,3 @@ void *kvmalloc(size_t size)
}
return buffer;
}
-
-/**
- * do_vfree - workqueue routine for freeing vmalloced memory
- * @work: data to be freed
- *
- * The work_struct is overlaid to the data being freed, as at the point
- * the work is scheduled the data is no longer valid, be its freeing
- * needs to be delayed until safe.
- */
-static void do_vfree(struct work_struct *work)
-{
- vfree(work);
-}
-
-/**
- * kvfree - free an allocation do by kvmalloc
- * @buffer: buffer to free (MAYBE_NULL)
- *
- * Free a buffer allocated by kvmalloc
- */
-void kvfree(void *buffer)
-{
- if (is_vmalloc_addr(buffer)) {
- /* Data is no longer valid so just use the allocated space
- * as the work_struct
- */
- struct work_struct *work = (struct work_struct *) buffer;
- INIT_WORK(work, do_vfree);
- schedule_work(work);
- } else
- kfree(buffer);
-}
diff --git a/security/capability.c b/security/capability.c
index 1728d4e375db..6e4fc776badf 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -12,6 +12,26 @@
#include <linux/security.h>
+static int cap_binder_set_context_mgr(struct task_struct *mgr)
+{
+ return 0;
+}
+
+static int cap_binder_transaction(struct task_struct *from, struct task_struct *to)
+{
+ return 0;
+}
+
+static int cap_binder_transfer_binder(struct task_struct *from, struct task_struct *to)
+{
+ return 0;
+}
+
+static int cap_binder_transfer_file(struct task_struct *from, struct task_struct *to, struct file *file)
+{
+ return 0;
+}
+
static int cap_syslog(int type)
{
return 0;
@@ -903,6 +923,10 @@ static void cap_audit_rule_free(void *lsmrule)
void __init security_fixup_ops(struct security_operations *ops)
{
+ set_to_cap_if_null(ops, binder_set_context_mgr);
+ set_to_cap_if_null(ops, binder_transaction);
+ set_to_cap_if_null(ops, binder_transfer_binder);
+ set_to_cap_if_null(ops, binder_transfer_file);
set_to_cap_if_null(ops, ptrace_access_check);
set_to_cap_if_null(ops, ptrace_traceme);
set_to_cap_if_null(ops, capget);
diff --git a/security/commoncap.c b/security/commoncap.c
index c9219a66b7c6..0405522995c5 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -31,6 +31,10 @@
#include <linux/binfmts.h>
#include <linux/personality.h>
+#ifdef CONFIG_ANDROID_PARANOID_NETWORK
+#include <linux/android_aid.h>
+#endif
+
/*
* If a non-root user executes a setuid-root binary in
* !secure(SECURE_NOROOT) mode, then we raise capabilities.
@@ -78,6 +82,13 @@ int cap_capable(const struct cred *cred, struct user_namespace *targ_ns,
{
struct user_namespace *ns = targ_ns;
+#ifdef CONFIG_ANDROID_PARANOID_NETWORK
+ if (cap == CAP_NET_RAW && in_egroup_p(AID_NET_RAW))
+ return 0;
+ if (cap == CAP_NET_ADMIN && in_egroup_p(AID_NET_ADMIN))
+ return 0;
+#endif
+
/* See if cred has the capability in the target user namespace
* by examining the target user namespace and all of the target
* user namespace's parents.
diff --git a/security/lsm_audit.c b/security/lsm_audit.c
index 8d8d97dbb389..8f6b8e8a4cc8 100644
--- a/security/lsm_audit.c
+++ b/security/lsm_audit.c
@@ -245,6 +245,21 @@ static void dump_common_audit_data(struct audit_buffer *ab,
}
break;
}
+ case LSM_AUDIT_DATA_IOCTL_OP: {
+ struct inode *inode;
+
+ audit_log_d_path(ab, " path=", &a->u.op->path);
+
+ inode = a->u.op->path.dentry->d_inode;
+ if (inode) {
+ audit_log_format(ab, " dev=");
+ audit_log_untrustedstring(ab, inode->i_sb->s_id);
+ audit_log_format(ab, " ino=%lu", inode->i_ino);
+ }
+
+ audit_log_format(ab, " ioctlcmd=%hx", a->u.op->cmd);
+ break;
+ }
case LSM_AUDIT_DATA_DENTRY: {
struct inode *inode;
diff --git a/security/security.c b/security/security.c
index a3dce87d1aef..d6ab2d2b363d 100644
--- a/security/security.c
+++ b/security/security.c
@@ -134,6 +134,26 @@ int __init register_security(struct security_operations *ops)
/* Security operations */
+int security_binder_set_context_mgr(struct task_struct *mgr)
+{
+ return security_ops->binder_set_context_mgr(mgr);
+}
+
+int security_binder_transaction(struct task_struct *from, struct task_struct *to)
+{
+ return security_ops->binder_transaction(from, to);
+}
+
+int security_binder_transfer_binder(struct task_struct *from, struct task_struct *to)
+{
+ return security_ops->binder_transfer_binder(from, to);
+}
+
+int security_binder_transfer_file(struct task_struct *from, struct task_struct *to, struct file *file)
+{
+ return security_ops->binder_transfer_file(from, to, file);
+}
+
int security_ptrace_access_check(struct task_struct *child, unsigned int mode)
{
#ifdef CONFIG_SECURITY_YAMA_STACKED
diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index dad36a6ab45f..f3dbbc0f15dd 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -22,6 +22,7 @@
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/percpu.h>
+#include <linux/list.h>
#include <net/sock.h>
#include <linux/un.h>
#include <net/af_unix.h>
@@ -48,6 +49,7 @@ struct avc_entry {
u32 tsid;
u16 tclass;
struct av_decision avd;
+ struct avc_operation_node *ops_node;
};
struct avc_node {
@@ -64,6 +66,16 @@ struct avc_cache {
u32 latest_notif; /* latest revocation notification */
};
+struct avc_operation_decision_node {
+ struct operation_decision od;
+ struct list_head od_list;
+};
+
+struct avc_operation_node {
+ struct operation ops;
+ struct list_head od_head; /* list of operation_decision_node */
+};
+
struct avc_callback_node {
int (*callback) (u32 event);
u32 events;
@@ -80,6 +92,9 @@ DEFINE_PER_CPU(struct avc_cache_stats, avc_cache_stats) = { 0 };
static struct avc_cache avc_cache;
static struct avc_callback_node *avc_callbacks;
static struct kmem_cache *avc_node_cachep;
+static struct kmem_cache *avc_operation_decision_node_cachep;
+static struct kmem_cache *avc_operation_node_cachep;
+static struct kmem_cache *avc_operation_perm_cachep;
static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass)
{
@@ -171,6 +186,16 @@ void __init avc_init(void)
avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node),
0, SLAB_PANIC, NULL);
+ avc_operation_node_cachep = kmem_cache_create("avc_operation_node",
+ sizeof(struct avc_operation_node),
+ 0, SLAB_PANIC, NULL);
+ avc_operation_decision_node_cachep = kmem_cache_create(
+ "avc_operation_decision_node",
+ sizeof(struct avc_operation_decision_node),
+ 0, SLAB_PANIC, NULL);
+ avc_operation_perm_cachep = kmem_cache_create("avc_operation_perm",
+ sizeof(struct operation_perm),
+ 0, SLAB_PANIC, NULL);
audit_log(current->audit_context, GFP_KERNEL, AUDIT_KERNEL, "AVC INITIALIZED\n");
}
@@ -205,9 +230,271 @@ int avc_get_hash_stats(char *page)
slots_used, AVC_CACHE_SLOTS, max_chain_len);
}
+/*
+ * using a linked list for operation_decision lookup because the list is
+ * always small. i.e. less than 5, typically 1
+ */
+static struct operation_decision *avc_operation_lookup(u8 type,
+ struct avc_operation_node *ops_node)
+{
+ struct avc_operation_decision_node *od_node;
+ struct operation_decision *od = NULL;
+
+ list_for_each_entry(od_node, &ops_node->od_head, od_list) {
+ if (od_node->od.type != type)
+ continue;
+ od = &od_node->od;
+ break;
+ }
+ return od;
+}
+
+static inline unsigned int avc_operation_has_perm(struct operation_decision *od,
+ u16 cmd, u8 specified)
+{
+ unsigned int rc = 0;
+ u8 num = cmd & 0xff;
+
+ if ((specified == OPERATION_ALLOWED) &&
+ (od->specified & OPERATION_ALLOWED))
+ rc = security_operation_test(od->allowed->perms, num);
+ else if ((specified == OPERATION_AUDITALLOW) &&
+ (od->specified & OPERATION_AUDITALLOW))
+ rc = security_operation_test(od->auditallow->perms, num);
+ else if ((specified == OPERATION_DONTAUDIT) &&
+ (od->specified & OPERATION_DONTAUDIT))
+ rc = security_operation_test(od->dontaudit->perms, num);
+ return rc;
+}
+
+static void avc_operation_allow_perm(struct avc_operation_node *node, u16 cmd)
+{
+ struct operation_decision *od;
+ u8 type;
+ u8 num;
+
+ type = cmd >> 8;
+ num = cmd & 0xff;
+ security_operation_set(node->ops.type, type);
+ od = avc_operation_lookup(type, node);
+ if (od && od->allowed)
+ security_operation_set(od->allowed->perms, num);
+}
+
+static void avc_operation_decision_free(
+ struct avc_operation_decision_node *od_node)
+{
+ struct operation_decision *od;
+
+ od = &od_node->od;
+ if (od->allowed)
+ kmem_cache_free(avc_operation_perm_cachep, od->allowed);
+ if (od->auditallow)
+ kmem_cache_free(avc_operation_perm_cachep, od->auditallow);
+ if (od->dontaudit)
+ kmem_cache_free(avc_operation_perm_cachep, od->dontaudit);
+ kmem_cache_free(avc_operation_decision_node_cachep, od_node);
+}
+
+static void avc_operation_free(struct avc_operation_node *ops_node)
+{
+ struct avc_operation_decision_node *od_node, *tmp;
+
+ if (!ops_node)
+ return;
+
+ list_for_each_entry_safe(od_node, tmp, &ops_node->od_head, od_list) {
+ list_del(&od_node->od_list);
+ avc_operation_decision_free(od_node);
+ }
+ kmem_cache_free(avc_operation_node_cachep, ops_node);
+}
+
+static void avc_copy_operation_decision(struct operation_decision *dest,
+ struct operation_decision *src)
+{
+ dest->type = src->type;
+ dest->specified = src->specified;
+ if (dest->specified & OPERATION_ALLOWED)
+ memcpy(dest->allowed->perms, src->allowed->perms,
+ sizeof(src->allowed->perms));
+ if (dest->specified & OPERATION_AUDITALLOW)
+ memcpy(dest->auditallow->perms, src->auditallow->perms,
+ sizeof(src->auditallow->perms));
+ if (dest->specified & OPERATION_DONTAUDIT)
+ memcpy(dest->dontaudit->perms, src->dontaudit->perms,
+ sizeof(src->dontaudit->perms));
+}
+
+/*
+ * similar to avc_copy_operation_decision, but only copy decision
+ * information relevant to this command
+ */
+static inline void avc_quick_copy_operation_decision(u16 cmd,
+ struct operation_decision *dest,
+ struct operation_decision *src)
+{
+ /*
+ * compute index of the u32 of the 256 bits (8 u32s) that contain this
+ * command permission
+ */
+ u8 i = (0xff & cmd) >> 5;
+
+ dest->specified = src->specified;
+ if (dest->specified & OPERATION_ALLOWED)
+ dest->allowed->perms[i] = src->allowed->perms[i];
+ if (dest->specified & OPERATION_AUDITALLOW)
+ dest->auditallow->perms[i] = src->auditallow->perms[i];
+ if (dest->specified & OPERATION_DONTAUDIT)
+ dest->dontaudit->perms[i] = src->dontaudit->perms[i];
+}
+
+static struct avc_operation_decision_node
+ *avc_operation_decision_alloc(u8 specified)
+{
+ struct avc_operation_decision_node *node;
+ struct operation_decision *od;
+
+ node = kmem_cache_zalloc(avc_operation_decision_node_cachep,
+ GFP_ATOMIC | __GFP_NOMEMALLOC);
+ if (!node)
+ return NULL;
+
+ od = &node->od;
+ if (specified & OPERATION_ALLOWED) {
+ od->allowed = kmem_cache_zalloc(avc_operation_perm_cachep,
+ GFP_ATOMIC | __GFP_NOMEMALLOC);
+ if (!od->allowed)
+ goto error;
+ }
+ if (specified & OPERATION_AUDITALLOW) {
+ od->auditallow = kmem_cache_zalloc(avc_operation_perm_cachep,
+ GFP_ATOMIC | __GFP_NOMEMALLOC);
+ if (!od->auditallow)
+ goto error;
+ }
+ if (specified & OPERATION_DONTAUDIT) {
+ od->dontaudit = kmem_cache_zalloc(avc_operation_perm_cachep,
+ GFP_ATOMIC | __GFP_NOMEMALLOC);
+ if (!od->dontaudit)
+ goto error;
+ }
+ return node;
+error:
+ avc_operation_decision_free(node);
+ return NULL;
+}
+
+static int avc_add_operation(struct avc_node *node,
+ struct operation_decision *od)
+{
+ struct avc_operation_decision_node *dest_od;
+
+ node->ae.ops_node->ops.len++;
+ dest_od = avc_operation_decision_alloc(od->specified);
+ if (!dest_od)
+ return -ENOMEM;
+ avc_copy_operation_decision(&dest_od->od, od);
+ list_add(&dest_od->od_list, &node->ae.ops_node->od_head);
+ return 0;
+}
+
+static struct avc_operation_node *avc_operation_alloc(void)
+{
+ struct avc_operation_node *ops;
+
+ ops = kmem_cache_zalloc(avc_operation_node_cachep,
+ GFP_ATOMIC|__GFP_NOMEMALLOC);
+ if (!ops)
+ return ops;
+ INIT_LIST_HEAD(&ops->od_head);
+ return ops;
+}
+
+static int avc_operation_populate(struct avc_node *node,
+ struct avc_operation_node *src)
+{
+ struct avc_operation_node *dest;
+ struct avc_operation_decision_node *dest_od;
+ struct avc_operation_decision_node *src_od;
+
+ if (src->ops.len == 0)
+ return 0;
+ dest = avc_operation_alloc();
+ if (!dest)
+ return -ENOMEM;
+
+ memcpy(dest->ops.type, &src->ops.type, sizeof(dest->ops.type));
+ dest->ops.len = src->ops.len;
+
+ /* for each source od allocate a destination od and copy */
+ list_for_each_entry(src_od, &src->od_head, od_list) {
+ dest_od = avc_operation_decision_alloc(src_od->od.specified);
+ if (!dest_od)
+ goto error;
+ avc_copy_operation_decision(&dest_od->od, &src_od->od);
+ list_add(&dest_od->od_list, &dest->od_head);
+ }
+ node->ae.ops_node = dest;
+ return 0;
+error:
+ avc_operation_free(dest);
+ return -ENOMEM;
+
+}
+
+static inline u32 avc_operation_audit_required(u32 requested,
+ struct av_decision *avd,
+ struct operation_decision *od,
+ u16 cmd,
+ int result,
+ u32 *deniedp)
+{
+ u32 denied, audited;
+
+ denied = requested & ~avd->allowed;
+ if (unlikely(denied)) {
+ audited = denied & avd->auditdeny;
+ if (audited && od) {
+ if (avc_operation_has_perm(od, cmd,
+ OPERATION_DONTAUDIT))
+ audited &= ~requested;
+ }
+ } else if (result) {
+ audited = denied = requested;
+ } else {
+ audited = requested & avd->auditallow;
+ if (audited && od) {
+ if (!avc_operation_has_perm(od, cmd,
+ OPERATION_AUDITALLOW))
+ audited &= ~requested;
+ }
+ }
+
+ *deniedp = denied;
+ return audited;
+}
+
+static inline int avc_operation_audit(u32 ssid, u32 tsid, u16 tclass,
+ u32 requested, struct av_decision *avd,
+ struct operation_decision *od,
+ u16 cmd, int result,
+ struct common_audit_data *ad)
+{
+ u32 audited, denied;
+
+ audited = avc_operation_audit_required(
+ requested, avd, od, cmd, result, &denied);
+ if (likely(!audited))
+ return 0;
+ return slow_avc_audit(ssid, tsid, tclass, requested,
+ audited, denied, result, ad, 0);
+}
+
static void avc_node_free(struct rcu_head *rhead)
{
struct avc_node *node = container_of(rhead, struct avc_node, rhead);
+ avc_operation_free(node->ae.ops_node);
kmem_cache_free(avc_node_cachep, node);
avc_cache_stats_incr(frees);
}
@@ -221,6 +508,7 @@ static void avc_node_delete(struct avc_node *node)
static void avc_node_kill(struct avc_node *node)
{
+ avc_operation_free(node->ae.ops_node);
kmem_cache_free(avc_node_cachep, node);
avc_cache_stats_incr(frees);
atomic_dec(&avc_cache.active_nodes);
@@ -367,6 +655,7 @@ static int avc_latest_notif_update(int seqno, int is_insert)
* @tsid: target security identifier
* @tclass: target security class
* @avd: resulting av decision
+ * @ops: resulting operation decisions
*
* Insert an AVC entry for the SID pair
* (@ssid, @tsid) and class @tclass.
@@ -378,7 +667,9 @@ static int avc_latest_notif_update(int seqno, int is_insert)
* the access vectors into a cache entry, returns
* avc_node inserted. Otherwise, this function returns NULL.
*/
-static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd)
+static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass,
+ struct av_decision *avd,
+ struct avc_operation_node *ops_node)
{
struct avc_node *pos, *node = NULL;
int hvalue;
@@ -391,10 +682,15 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec
if (node) {
struct hlist_head *head;
spinlock_t *lock;
+ int rc = 0;
hvalue = avc_hash(ssid, tsid, tclass);
avc_node_populate(node, ssid, tsid, tclass, avd);
-
+ rc = avc_operation_populate(node, ops_node);
+ if (rc) {
+ kmem_cache_free(avc_node_cachep, node);
+ return NULL;
+ }
head = &avc_cache.slots[hvalue];
lock = &avc_cache.slots_lock[hvalue];
@@ -444,11 +740,15 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
avc_dump_query(ab, ad->selinux_audit_data->ssid,
ad->selinux_audit_data->tsid,
ad->selinux_audit_data->tclass);
+ if (ad->selinux_audit_data->denied) {
+ audit_log_format(ab, " permissive=%u",
+ ad->selinux_audit_data->result ? 0 : 1);
+ }
}
/* This is the slow part of avc audit with big stack footprint */
noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
- u32 requested, u32 audited, u32 denied,
+ u32 requested, u32 audited, u32 denied, int result,
struct common_audit_data *a,
unsigned flags)
{
@@ -477,6 +777,7 @@ noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
sad.tsid = tsid;
sad.audited = audited;
sad.denied = denied;
+ sad.result = result;
a->selinux_audit_data = &sad;
@@ -523,14 +824,17 @@ static inline int avc_sidcmp(u32 x, u32 y)
* @perms : Permission mask bits
* @ssid,@tsid,@tclass : identifier of an AVC entry
* @seqno : sequence number when decision was made
+ * @od: operation_decision to be added to the node
*
* if a valid AVC entry doesn't exist,this function returns -ENOENT.
* if kmalloc() called internal returns NULL, this function returns -ENOMEM.
* otherwise, this function updates the AVC entry. The original AVC-entry object
* will release later by RCU.
*/
-static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass,
- u32 seqno)
+static int avc_update_node(u32 event, u32 perms, u16 cmd, u32 ssid, u32 tsid,
+ u16 tclass, u32 seqno,
+ struct operation_decision *od,
+ u32 flags)
{
int hvalue, rc = 0;
unsigned long flag;
@@ -574,9 +878,19 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass,
avc_node_populate(node, ssid, tsid, tclass, &orig->ae.avd);
+ if (orig->ae.ops_node) {
+ rc = avc_operation_populate(node, orig->ae.ops_node);
+ if (rc) {
+ kmem_cache_free(avc_node_cachep, node);
+ goto out_unlock;
+ }
+ }
+
switch (event) {
case AVC_CALLBACK_GRANT:
node->ae.avd.allowed |= perms;
+ if (node->ae.ops_node && (flags & AVC_OPERATION_CMD))
+ avc_operation_allow_perm(node->ae.ops_node, cmd);
break;
case AVC_CALLBACK_TRY_REVOKE:
case AVC_CALLBACK_REVOKE:
@@ -594,6 +908,9 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass,
case AVC_CALLBACK_AUDITDENY_DISABLE:
node->ae.avd.auditdeny &= ~perms;
break;
+ case AVC_CALLBACK_ADD_OPERATION:
+ avc_add_operation(node, od);
+ break;
}
avc_node_replace(node, orig);
out_unlock:
@@ -665,18 +982,20 @@ int avc_ss_reset(u32 seqno)
* results in a bigger stack frame.
*/
static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid,
- u16 tclass, struct av_decision *avd)
+ u16 tclass, struct av_decision *avd,
+ struct avc_operation_node *ops_node)
{
rcu_read_unlock();
- security_compute_av(ssid, tsid, tclass, avd);
+ INIT_LIST_HEAD(&ops_node->od_head);
+ security_compute_av(ssid, tsid, tclass, avd, &ops_node->ops);
rcu_read_lock();
- return avc_insert(ssid, tsid, tclass, avd);
+ return avc_insert(ssid, tsid, tclass, avd, ops_node);
}
static noinline int avc_denied(u32 ssid, u32 tsid,
- u16 tclass, u32 requested,
- unsigned flags,
- struct av_decision *avd)
+ u16 tclass, u32 requested,
+ u16 cmd, unsigned flags,
+ struct av_decision *avd)
{
if (flags & AVC_STRICT)
return -EACCES;
@@ -684,11 +1003,92 @@ static noinline int avc_denied(u32 ssid, u32 tsid,
if (selinux_enforcing && !(avd->flags & AVD_FLAGS_PERMISSIVE))
return -EACCES;
- avc_update_node(AVC_CALLBACK_GRANT, requested, ssid,
- tsid, tclass, avd->seqno);
+ avc_update_node(AVC_CALLBACK_GRANT, requested, cmd, ssid,
+ tsid, tclass, avd->seqno, NULL, flags);
return 0;
}
+/*
+ * ioctl commands are comprised of four fields, direction, size, type, and
+ * number. The avc operation logic filters based on two of them:
+ *
+ * type: or code, typically unique to each driver
+ * number: or function
+ *
+ * For example, 0x89 is a socket type, and number 0x27 is the get hardware
+ * address function.
+ */
+int avc_has_operation(u32 ssid, u32 tsid, u16 tclass, u32 requested,
+ u16 cmd, struct common_audit_data *ad)
+{
+ struct avc_node *node;
+ struct av_decision avd;
+ u32 denied;
+ struct operation_decision *od = NULL;
+ struct operation_decision od_local;
+ struct operation_perm allowed;
+ struct operation_perm auditallow;
+ struct operation_perm dontaudit;
+ struct avc_operation_node local_ops_node;
+ struct avc_operation_node *ops_node;
+ u8 type = cmd >> 8;
+ int rc = 0, rc2;
+
+ ops_node = &local_ops_node;
+ BUG_ON(!requested);
+
+ rcu_read_lock();
+
+ node = avc_lookup(ssid, tsid, tclass);
+ if (unlikely(!node)) {
+ node = avc_compute_av(ssid, tsid, tclass, &avd, ops_node);
+ } else {
+ memcpy(&avd, &node->ae.avd, sizeof(avd));
+ ops_node = node->ae.ops_node;
+ }
+ /* if operations are not defined, only consider av_decision */
+ if (!ops_node || !ops_node->ops.len)
+ goto decision;
+
+ od_local.allowed = &allowed;
+ od_local.auditallow = &auditallow;
+ od_local.dontaudit = &dontaudit;
+
+ /* lookup operation decision */
+ od = avc_operation_lookup(type, ops_node);
+ if (unlikely(!od)) {
+ /* Compute operation decision if type is flagged */
+ if (!security_operation_test(ops_node->ops.type, type)) {
+ avd.allowed &= ~requested;
+ goto decision;
+ }
+ rcu_read_unlock();
+ security_compute_operation(ssid, tsid, tclass, type, &od_local);
+ rcu_read_lock();
+ avc_update_node(AVC_CALLBACK_ADD_OPERATION, requested, cmd,
+ ssid, tsid, tclass, avd.seqno, &od_local, 0);
+ } else {
+ avc_quick_copy_operation_decision(cmd, &od_local, od);
+ }
+ od = &od_local;
+
+ if (!avc_operation_has_perm(od, cmd, OPERATION_ALLOWED))
+ avd.allowed &= ~requested;
+
+decision:
+ denied = requested & ~(avd.allowed);
+ if (unlikely(denied))
+ rc = avc_denied(ssid, tsid, tclass, requested, cmd,
+ AVC_OPERATION_CMD, &avd);
+
+ rcu_read_unlock();
+
+ rc2 = avc_operation_audit(ssid, tsid, tclass, requested,
+ &avd, od, cmd, rc, ad);
+ if (rc2)
+ return rc2;
+ return rc;
+}
/**
* avc_has_perm_noaudit - Check permissions but perform no auditing.
@@ -716,6 +1116,7 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,
struct av_decision *avd)
{
struct avc_node *node;
+ struct avc_operation_node ops_node;
int rc = 0;
u32 denied;
@@ -724,16 +1125,14 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,
rcu_read_lock();
node = avc_lookup(ssid, tsid, tclass);
- if (unlikely(!node)) {
- node = avc_compute_av(ssid, tsid, tclass, avd);
- } else {
+ if (unlikely(!node))
+ node = avc_compute_av(ssid, tsid, tclass, avd, &ops_node);
+ else
memcpy(avd, &node->ae.avd, sizeof(*avd));
- avd = &node->ae.avd;
- }
denied = requested & ~(avd->allowed);
if (unlikely(denied))
- rc = avc_denied(ssid, tsid, tclass, requested, flags, avd);
+ rc = avc_denied(ssid, tsid, tclass, requested, 0, flags, avd);
rcu_read_unlock();
return rc;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index fdd6e4f8be39..607c3d58a691 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -147,6 +147,17 @@ static int selinux_secmark_enabled(void)
return (atomic_read(&selinux_secmark_refcount) > 0);
}
+static int selinux_netcache_avc_callback(u32 event)
+{
+ if (event == AVC_CALLBACK_RESET) {
+ sel_netif_flush();
+ sel_netnode_flush();
+ sel_netport_flush();
+ synchronize_net();
+ }
+ return 0;
+}
+
/*
* initialise the security for the init task
*/
@@ -419,8 +430,11 @@ static int sb_finish_set_opts(struct super_block *sb)
sbsec->behavior > ARRAY_SIZE(labeling_behaviors))
sbsec->flags &= ~SE_SBLABELSUPP;
- /* Special handling for sysfs. Is genfs but also has setxattr handler*/
- if (strncmp(sb->s_type->name, "sysfs", sizeof("sysfs")) == 0)
+ /* Special handling. Is genfs but also has in-core setxattr handler*/
+ if (!strcmp(sb->s_type->name, "sysfs") ||
+ !strcmp(sb->s_type->name, "pstore") ||
+ !strcmp(sb->s_type->name, "debugfs") ||
+ !strcmp(sb->s_type->name, "rootfs"))
sbsec->flags |= SE_SBLABELSUPP;
/* Initialize the root inode. */
@@ -1856,6 +1870,65 @@ static inline u32 open_file_to_av(struct file *file)
/* Hook functions begin here. */
+static int selinux_binder_set_context_mgr(struct task_struct *mgr)
+{
+ u32 mysid = current_sid();
+ u32 mgrsid = task_sid(mgr);
+
+ return avc_has_perm(mysid, mgrsid, SECCLASS_BINDER, BINDER__SET_CONTEXT_MGR, NULL);
+}
+
+static int selinux_binder_transaction(struct task_struct *from, struct task_struct *to)
+{
+ u32 mysid = current_sid();
+ u32 fromsid = task_sid(from);
+ u32 tosid = task_sid(to);
+ int rc;
+
+ if (mysid != fromsid) {
+ rc = avc_has_perm(mysid, fromsid, SECCLASS_BINDER, BINDER__IMPERSONATE, NULL);
+ if (rc)
+ return rc;
+ }
+
+ return avc_has_perm(fromsid, tosid, SECCLASS_BINDER, BINDER__CALL, NULL);
+}
+
+static int selinux_binder_transfer_binder(struct task_struct *from, struct task_struct *to)
+{
+ u32 fromsid = task_sid(from);
+ u32 tosid = task_sid(to);
+ return avc_has_perm(fromsid, tosid, SECCLASS_BINDER, BINDER__TRANSFER, NULL);
+}
+
+static int selinux_binder_transfer_file(struct task_struct *from, struct task_struct *to, struct file *file)
+{
+ u32 sid = task_sid(to);
+ struct file_security_struct *fsec = file->f_security;
+ struct inode *inode = file->f_path.dentry->d_inode;
+ struct inode_security_struct *isec = inode->i_security;
+ struct common_audit_data ad;
+ int rc;
+
+ ad.type = LSM_AUDIT_DATA_PATH;
+ ad.u.path = file->f_path;
+
+ if (sid != fsec->sid) {
+ rc = avc_has_perm(sid, fsec->sid,
+ SECCLASS_FD,
+ FD__USE,
+ &ad);
+ if (rc)
+ return rc;
+ }
+
+ if (unlikely(IS_PRIVATE(inode)))
+ return 0;
+
+ return avc_has_perm(sid, isec->sid, isec->sclass, file_to_av(file),
+ &ad);
+}
+
static int selinux_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
@@ -2672,6 +2745,7 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *na
static noinline int audit_inode_permission(struct inode *inode,
u32 perms, u32 audited, u32 denied,
+ int result,
unsigned flags)
{
struct common_audit_data ad;
@@ -2682,7 +2756,7 @@ static noinline int audit_inode_permission(struct inode *inode,
ad.u.inode = inode;
rc = slow_avc_audit(current_sid(), isec->sid, isec->sclass, perms,
- audited, denied, &ad, flags);
+ audited, denied, result, &ad, flags);
if (rc)
return rc;
return 0;
@@ -2724,7 +2798,7 @@ static int selinux_inode_permission(struct inode *inode, int mask)
if (likely(!audited))
return rc;
- rc2 = audit_inode_permission(inode, perms, audited, denied, flags);
+ rc2 = audit_inode_permission(inode, perms, audited, denied, rc, flags);
if (rc2)
return rc2;
return rc;
@@ -3035,6 +3109,44 @@ static void selinux_file_free_security(struct file *file)
file_free_security(file);
}
+/*
+ * Check whether a task has the ioctl permission and cmd
+ * operation to an inode.
+ */
+int ioctl_has_perm(const struct cred *cred, struct file *file,
+ u32 requested, u16 cmd)
+{
+ struct common_audit_data ad;
+ struct file_security_struct *fsec = file->f_security;
+ struct inode *inode = file_inode(file);
+ struct inode_security_struct *isec = inode->i_security;
+ struct lsm_ioctlop_audit ioctl;
+ u32 ssid = cred_sid(cred);
+ int rc;
+
+ ad.type = LSM_AUDIT_DATA_IOCTL_OP;
+ ad.u.op = &ioctl;
+ ad.u.op->cmd = cmd;
+ ad.u.op->path = file->f_path;
+
+ if (ssid != fsec->sid) {
+ rc = avc_has_perm(ssid, fsec->sid,
+ SECCLASS_FD,
+ FD__USE,
+ &ad);
+ if (rc)
+ goto out;
+ }
+
+ if (unlikely(IS_PRIVATE(inode)))
+ return 0;
+
+ rc = avc_has_operation(ssid, isec->sid, isec->sclass,
+ requested, cmd, &ad);
+out:
+ return rc;
+}
+
static int selinux_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
@@ -3077,7 +3189,7 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd,
* to the file's ioctl() function.
*/
default:
- error = file_has_perm(cred, file, FILE__IOCTL);
+ error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd);
}
return error;
}
@@ -3121,24 +3233,20 @@ error:
static int selinux_mmap_addr(unsigned long addr)
{
- int rc = 0;
- u32 sid = current_sid();
+ int rc;
+
+ /* do DAC check on address space usage */
+ rc = cap_mmap_addr(addr);
+ if (rc)
+ return rc;
- /*
- * notice that we are intentionally putting the SELinux check before
- * the secondary cap_file_mmap check. This is such a likely attempt
- * at bad behaviour/exploit that we always want to get the AVC, even
- * if DAC would have also denied the operation.
- */
if (addr < CONFIG_LSM_MMAP_MIN_ADDR) {
+ u32 sid = current_sid();
rc = avc_has_perm(sid, sid, SECCLASS_MEMPROTECT,
MEMPROTECT__MMAP_ZERO, NULL);
- if (rc)
- return rc;
}
- /* do DAC check on address space usage */
- return cap_mmap_addr(addr);
+ return rc;
}
static int selinux_mmap_file(struct file *file, unsigned long reqprot,
@@ -4153,15 +4261,15 @@ static int selinux_socket_unix_may_send(struct socket *sock,
&ad);
}
-static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family,
- u32 peer_sid,
+static int selinux_inet_sys_rcv_skb(struct net *ns, int ifindex,
+ char *addrp, u16 family, u32 peer_sid,
struct common_audit_data *ad)
{
int err;
u32 if_sid;
u32 node_sid;
- err = sel_netif_sid(ifindex, &if_sid);
+ err = sel_netif_sid(ns, ifindex, &if_sid);
if (err)
return err;
err = avc_has_perm(peer_sid, if_sid,
@@ -4254,8 +4362,8 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
err = selinux_skb_peerlbl_sid(skb, family, &peer_sid);
if (err)
return err;
- err = selinux_inet_sys_rcv_skb(skb->skb_iif, addrp, family,
- peer_sid, &ad);
+ err = selinux_inet_sys_rcv_skb(sock_net(sk), skb->skb_iif,
+ addrp, family, peer_sid, &ad);
if (err) {
selinux_netlbl_err(skb, err, 0);
return err;
@@ -4598,7 +4706,8 @@ out:
#ifdef CONFIG_NETFILTER
-static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex,
+static unsigned int selinux_ip_forward(struct sk_buff *skb,
+ const struct net_device *indev,
u16 family)
{
int err;
@@ -4624,14 +4733,14 @@ static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex,
ad.type = LSM_AUDIT_DATA_NET;
ad.u.net = &net;
- ad.u.net->netif = ifindex;
+ ad.u.net->netif = indev->ifindex;
ad.u.net->family = family;
if (selinux_parse_skb(skb, &ad, &addrp, 1, NULL) != 0)
return NF_DROP;
if (peerlbl_active) {
- err = selinux_inet_sys_rcv_skb(ifindex, addrp, family,
- peer_sid, &ad);
+ err = selinux_inet_sys_rcv_skb(dev_net(indev), indev->ifindex,
+ addrp, family, peer_sid, &ad);
if (err) {
selinux_netlbl_err(skb, err, 1);
return NF_DROP;
@@ -4660,7 +4769,7 @@ static unsigned int selinux_ipv4_forward(unsigned int hooknum,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
- return selinux_ip_forward(skb, in->ifindex, PF_INET);
+ return selinux_ip_forward(skb, in, PF_INET);
}
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
@@ -4670,7 +4779,7 @@ static unsigned int selinux_ipv6_forward(unsigned int hooknum,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
- return selinux_ip_forward(skb, in->ifindex, PF_INET6);
+ return selinux_ip_forward(skb, in, PF_INET6);
}
#endif /* IPV6 */
@@ -4758,11 +4867,13 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb,
return NF_ACCEPT;
}
-static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex,
+static unsigned int selinux_ip_postroute(struct sk_buff *skb,
+ const struct net_device *outdev,
u16 family)
{
u32 secmark_perm;
u32 peer_sid;
+ int ifindex = outdev->ifindex;
struct sock *sk;
struct common_audit_data ad;
struct lsm_network_audit net = {0,};
@@ -4875,7 +4986,7 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex,
u32 if_sid;
u32 node_sid;
- if (sel_netif_sid(ifindex, &if_sid))
+ if (sel_netif_sid(dev_net(outdev), ifindex, &if_sid))
return NF_DROP;
if (avc_has_perm(peer_sid, if_sid,
SECCLASS_NETIF, NETIF__EGRESS, &ad))
@@ -4897,7 +5008,7 @@ static unsigned int selinux_ipv4_postroute(unsigned int hooknum,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
- return selinux_ip_postroute(skb, out->ifindex, PF_INET);
+ return selinux_ip_postroute(skb, out, PF_INET);
}
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
@@ -4907,7 +5018,7 @@ static unsigned int selinux_ipv6_postroute(unsigned int hooknum,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
- return selinux_ip_postroute(skb, out->ifindex, PF_INET6);
+ return selinux_ip_postroute(skb, out, PF_INET6);
}
#endif /* IPV6 */
@@ -5656,6 +5767,11 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer)
static struct security_operations selinux_ops = {
.name = "selinux",
+ .binder_set_context_mgr = selinux_binder_set_context_mgr,
+ .binder_transaction = selinux_binder_transaction,
+ .binder_transfer_binder = selinux_binder_transfer_binder,
+ .binder_transfer_file = selinux_binder_transfer_file,
+
.ptrace_access_check = selinux_ptrace_access_check,
.ptrace_traceme = selinux_ptrace_traceme,
.capget = selinux_capget,
@@ -5882,6 +5998,9 @@ static __init int selinux_init(void)
if (register_security(&selinux_ops))
panic("SELinux: Unable to register with kernel.\n");
+ if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
+ panic("SELinux: Unable to register AVC netcache callback\n");
+
if (selinux_enforcing)
printk(KERN_DEBUG "SELinux: Starting in enforcing mode\n");
else
diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h
index 92d0ab561db8..8109ad846e99 100644
--- a/security/selinux/include/avc.h
+++ b/security/selinux/include/avc.h
@@ -102,7 +102,7 @@ static inline u32 avc_audit_required(u32 requested,
}
int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
- u32 requested, u32 audited, u32 denied,
+ u32 requested, u32 audited, u32 denied, int result,
struct common_audit_data *a,
unsigned flags);
@@ -137,16 +137,20 @@ static inline int avc_audit(u32 ssid, u32 tsid,
if (likely(!audited))
return 0;
return slow_avc_audit(ssid, tsid, tclass,
- requested, audited, denied,
+ requested, audited, denied, result,
a, flags);
}
#define AVC_STRICT 1 /* Ignore permissive mode. */
+#define AVC_OPERATION_CMD 2 /* ignore command when updating operations */
int avc_has_perm_noaudit(u32 ssid, u32 tsid,
u16 tclass, u32 requested,
unsigned flags,
struct av_decision *avd);
+int avc_has_operation(u32 ssid, u32 tsid, u16 tclass, u32 requested,
+ u16 cmd, struct common_audit_data *ad);
+
int avc_has_perm_flags(u32 ssid, u32 tsid,
u16 tclass, u32 requested,
struct common_audit_data *auditdata,
@@ -169,6 +173,7 @@ u32 avc_policy_seqno(void);
#define AVC_CALLBACK_AUDITALLOW_DISABLE 32
#define AVC_CALLBACK_AUDITDENY_ENABLE 64
#define AVC_CALLBACK_AUDITDENY_DISABLE 128
+#define AVC_CALLBACK_ADD_OPERATION 256
int avc_add_callback(int (*callback)(u32 event), u32 events);
diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
index 14d04e63b1f0..c32ff7bde81a 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -151,5 +151,6 @@ struct security_class_mapping secclass_map[] = {
{ "kernel_service", { "use_as_override", "create_files_as", NULL } },
{ "tun_socket",
{ COMMON_SOCK_PERMS, "attach_queue", NULL } },
+ { "binder", { "impersonate", "call", "set_context_mgr", "transfer", NULL } },
{ NULL }
};
diff --git a/security/selinux/include/netif.h b/security/selinux/include/netif.h
index 43d507242b42..c72145444090 100644
--- a/security/selinux/include/netif.h
+++ b/security/selinux/include/netif.h
@@ -17,7 +17,11 @@
#ifndef _SELINUX_NETIF_H_
#define _SELINUX_NETIF_H_
-int sel_netif_sid(int ifindex, u32 *sid);
+#include <net/net_namespace.h>
+
+void sel_netif_flush(void);
+
+int sel_netif_sid(struct net *ns, int ifindex, u32 *sid);
#endif /* _SELINUX_NETIF_H_ */
diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h
index df7a5ed6c694..937668dd3024 100644
--- a/security/selinux/include/netnode.h
+++ b/security/selinux/include/netnode.h
@@ -27,6 +27,8 @@
#ifndef _SELINUX_NETNODE_H
#define _SELINUX_NETNODE_H
+void sel_netnode_flush(void);
+
int sel_netnode_sid(void *addr, u16 family, u32 *sid);
#endif
diff --git a/security/selinux/include/netport.h b/security/selinux/include/netport.h
index 4d965b83d735..d1ce896b2cb0 100644
--- a/security/selinux/include/netport.h
+++ b/security/selinux/include/netport.h
@@ -26,6 +26,8 @@
#ifndef _SELINUX_NETPORT_H
#define _SELINUX_NETPORT_H
+void sel_netport_flush(void);
+
int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid);
#endif
diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
index 6fd9dd256a62..2a3be7adc071 100644
--- a/security/selinux/include/objsec.h
+++ b/security/selinux/include/objsec.h
@@ -24,6 +24,7 @@
#include <linux/binfmts.h>
#include <linux/in.h>
#include <linux/spinlock.h>
+#include <net/net_namespace.h>
#include "flask.h"
#include "avc.h"
@@ -78,6 +79,7 @@ struct ipc_security_struct {
};
struct netif_security_struct {
+ struct net *ns; /* network namespace */
int ifindex; /* device index */
u32 sid; /* SID for this interface */
};
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index 6d3885165d14..e72d8de93725 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -33,13 +33,15 @@
#define POLICYDB_VERSION_ROLETRANS 26
#define POLICYDB_VERSION_NEW_OBJECT_DEFAULTS 27
#define POLICYDB_VERSION_DEFAULT_TYPE 28
+#define POLICYDB_VERSION_CONSTRAINT_NAMES 29
+#define POLICYDB_VERSION_IOCTL_OPERATIONS 30
/* Range of policy versions we understand*/
#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE
#ifdef CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX
#define POLICYDB_VERSION_MAX CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE
#else
-#define POLICYDB_VERSION_MAX POLICYDB_VERSION_DEFAULT_TYPE
+#define POLICYDB_VERSION_MAX POLICYDB_VERSION_IOCTL_OPERATIONS
#endif
/* Mask for just the mount related flags */
@@ -102,11 +104,40 @@ struct av_decision {
u32 flags;
};
+#define security_operation_set(perms, x) (perms[x >> 5] |= 1 << (x & 0x1f))
+#define security_operation_test(perms, x) (1 & (perms[x >> 5] >> (x & 0x1f)))
+
+struct operation_perm {
+ u32 perms[8];
+};
+
+struct operation_decision {
+ u8 type;
+ u8 specified;
+ struct operation_perm *allowed;
+ struct operation_perm *auditallow;
+ struct operation_perm *dontaudit;
+};
+
+#define OPERATION_ALLOWED 1
+#define OPERATION_AUDITALLOW 2
+#define OPERATION_DONTAUDIT 4
+#define OPERATION_ALL (OPERATION_ALLOWED | OPERATION_AUDITALLOW |\
+ OPERATION_DONTAUDIT)
+struct operation {
+ u16 len; /* length of operation decision chain */
+ u32 type[8]; /* 256 types */
+};
+
/* definitions of av_decision.flags */
#define AVD_FLAGS_PERMISSIVE 0x0001
void security_compute_av(u32 ssid, u32 tsid,
- u16 tclass, struct av_decision *avd);
+ u16 tclass, struct av_decision *avd,
+ struct operation *ops);
+
+void security_compute_operation(u32 ssid, u32 tsid, u16 tclass,
+ u8 type, struct operation_decision *od);
void security_compute_av_user(u32 ssid, u32 tsid,
u16 tclass, struct av_decision *avd);
diff --git a/security/selinux/netif.c b/security/selinux/netif.c
index 47a49d1a6f6a..548651a73876 100644
--- a/security/selinux/netif.c
+++ b/security/selinux/netif.c
@@ -45,6 +45,7 @@ static struct list_head sel_netif_hash[SEL_NETIF_HASH_SIZE];
/**
* sel_netif_hashfn - Hashing function for the interface table
+ * @ns: the network namespace
* @ifindex: the network interface
*
* Description:
@@ -52,13 +53,14 @@ static struct list_head sel_netif_hash[SEL_NETIF_HASH_SIZE];
* bucket number for the given interface.
*
*/
-static inline u32 sel_netif_hashfn(int ifindex)
+static inline u32 sel_netif_hashfn(const struct net *ns, int ifindex)
{
- return (ifindex & (SEL_NETIF_HASH_SIZE - 1));
+ return (((uintptr_t)ns + ifindex) & (SEL_NETIF_HASH_SIZE - 1));
}
/**
* sel_netif_find - Search for an interface record
+ * @ns: the network namespace
* @ifindex: the network interface
*
* Description:
@@ -66,15 +68,15 @@ static inline u32 sel_netif_hashfn(int ifindex)
* If an entry can not be found in the table return NULL.
*
*/
-static inline struct sel_netif *sel_netif_find(int ifindex)
+static inline struct sel_netif *sel_netif_find(const struct net *ns,
+ int ifindex)
{
- int idx = sel_netif_hashfn(ifindex);
+ int idx = sel_netif_hashfn(ns, ifindex);
struct sel_netif *netif;
list_for_each_entry_rcu(netif, &sel_netif_hash[idx], list)
- /* all of the devices should normally fit in the hash, so we
- * optimize for that case */
- if (likely(netif->nsec.ifindex == ifindex))
+ if (net_eq(netif->nsec.ns, ns) &&
+ netif->nsec.ifindex == ifindex)
return netif;
return NULL;
@@ -96,7 +98,7 @@ static int sel_netif_insert(struct sel_netif *netif)
if (sel_netif_total >= SEL_NETIF_HASH_MAX)
return -ENOSPC;
- idx = sel_netif_hashfn(netif->nsec.ifindex);
+ idx = sel_netif_hashfn(netif->nsec.ns, netif->nsec.ifindex);
list_add_rcu(&netif->list, &sel_netif_hash[idx]);
sel_netif_total++;
@@ -120,6 +122,7 @@ static void sel_netif_destroy(struct sel_netif *netif)
/**
* sel_netif_sid_slow - Lookup the SID of a network interface using the policy
+ * @ns: the network namespace
* @ifindex: the network interface
* @sid: interface SID
*
@@ -130,7 +133,7 @@ static void sel_netif_destroy(struct sel_netif *netif)
* failure.
*
*/
-static int sel_netif_sid_slow(int ifindex, u32 *sid)
+static int sel_netif_sid_slow(struct net *ns, int ifindex, u32 *sid)
{
int ret;
struct sel_netif *netif;
@@ -140,7 +143,7 @@ static int sel_netif_sid_slow(int ifindex, u32 *sid)
/* NOTE: we always use init's network namespace since we don't
* currently support containers */
- dev = dev_get_by_index(&init_net, ifindex);
+ dev = dev_get_by_index(ns, ifindex);
if (unlikely(dev == NULL)) {
printk(KERN_WARNING
"SELinux: failure in sel_netif_sid_slow(),"
@@ -149,7 +152,7 @@ static int sel_netif_sid_slow(int ifindex, u32 *sid)
}
spin_lock_bh(&sel_netif_lock);
- netif = sel_netif_find(ifindex);
+ netif = sel_netif_find(ns, ifindex);
if (netif != NULL) {
*sid = netif->nsec.sid;
ret = 0;
@@ -163,6 +166,7 @@ static int sel_netif_sid_slow(int ifindex, u32 *sid)
ret = security_netif_sid(dev->name, &new->nsec.sid);
if (ret != 0)
goto out;
+ new->nsec.ns = ns;
new->nsec.ifindex = ifindex;
ret = sel_netif_insert(new);
if (ret != 0)
@@ -184,6 +188,7 @@ out:
/**
* sel_netif_sid - Lookup the SID of a network interface
+ * @ns: the network namespace
* @ifindex: the network interface
* @sid: interface SID
*
@@ -195,12 +200,12 @@ out:
* on failure.
*
*/
-int sel_netif_sid(int ifindex, u32 *sid)
+int sel_netif_sid(struct net *ns, int ifindex, u32 *sid)
{
struct sel_netif *netif;
rcu_read_lock();
- netif = sel_netif_find(ifindex);
+ netif = sel_netif_find(ns, ifindex);
if (likely(netif != NULL)) {
*sid = netif->nsec.sid;
rcu_read_unlock();
@@ -208,11 +213,12 @@ int sel_netif_sid(int ifindex, u32 *sid)
}
rcu_read_unlock();
- return sel_netif_sid_slow(ifindex, sid);
+ return sel_netif_sid_slow(ns, ifindex, sid);
}
/**
* sel_netif_kill - Remove an entry from the network interface table
+ * @ns: the network namespace
* @ifindex: the network interface
*
* Description:
@@ -220,13 +226,13 @@ int sel_netif_sid(int ifindex, u32 *sid)
* table if it exists.
*
*/
-static void sel_netif_kill(int ifindex)
+static void sel_netif_kill(const struct net *ns, int ifindex)
{
struct sel_netif *netif;
rcu_read_lock();
spin_lock_bh(&sel_netif_lock);
- netif = sel_netif_find(ifindex);
+ netif = sel_netif_find(ns, ifindex);
if (netif)
sel_netif_destroy(netif);
spin_unlock_bh(&sel_netif_lock);
@@ -240,7 +246,7 @@ static void sel_netif_kill(int ifindex)
* Remove all entries from the network interface table.
*
*/
-static void sel_netif_flush(void)
+void sel_netif_flush(void)
{
int idx;
struct sel_netif *netif;
@@ -252,25 +258,13 @@ static void sel_netif_flush(void)
spin_unlock_bh(&sel_netif_lock);
}
-static int sel_netif_avc_callback(u32 event)
-{
- if (event == AVC_CALLBACK_RESET) {
- sel_netif_flush();
- synchronize_net();
- }
- return 0;
-}
-
static int sel_netif_netdev_notifier_handler(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct net_device *dev = ptr;
- if (dev_net(dev) != &init_net)
- return NOTIFY_DONE;
-
if (event == NETDEV_DOWN)
- sel_netif_kill(dev->ifindex);
+ sel_netif_kill(dev_net(dev), dev->ifindex);
return NOTIFY_DONE;
}
@@ -291,10 +285,6 @@ static __init int sel_netif_init(void)
register_netdevice_notifier(&sel_netif_netdev_notifier);
- err = avc_add_callback(sel_netif_avc_callback, AVC_CALLBACK_RESET);
- if (err)
- panic("avc_add_callback() failed, error %d\n", err);
-
return err;
}
diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c
index c5454c0477c3..bb8de9d9b429 100644
--- a/security/selinux/netnode.c
+++ b/security/selinux/netnode.c
@@ -281,7 +281,7 @@ int sel_netnode_sid(void *addr, u16 family, u32 *sid)
* Remove all entries from the network address table.
*
*/
-static void sel_netnode_flush(void)
+void sel_netnode_flush(void)
{
unsigned int idx;
struct sel_netnode *node, *node_tmp;
@@ -298,15 +298,6 @@ static void sel_netnode_flush(void)
spin_unlock_bh(&sel_netnode_lock);
}
-static int sel_netnode_avc_callback(u32 event)
-{
- if (event == AVC_CALLBACK_RESET) {
- sel_netnode_flush();
- synchronize_net();
- }
- return 0;
-}
-
static __init int sel_netnode_init(void)
{
int iter;
@@ -320,10 +311,6 @@ static __init int sel_netnode_init(void)
sel_netnode_hash[iter].size = 0;
}
- ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET);
- if (ret != 0)
- panic("avc_add_callback() failed, error %d\n", ret);
-
return ret;
}
diff --git a/security/selinux/netport.c b/security/selinux/netport.c
index d35379781c2c..73ac6784d091 100644
--- a/security/selinux/netport.c
+++ b/security/selinux/netport.c
@@ -217,7 +217,7 @@ int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid)
* Remove all entries from the network address table.
*
*/
-static void sel_netport_flush(void)
+void sel_netport_flush(void)
{
unsigned int idx;
struct sel_netport *port, *port_tmp;
@@ -234,15 +234,6 @@ static void sel_netport_flush(void)
spin_unlock_bh(&sel_netport_lock);
}
-static int sel_netport_avc_callback(u32 event)
-{
- if (event == AVC_CALLBACK_RESET) {
- sel_netport_flush();
- synchronize_net();
- }
- return 0;
-}
-
static __init int sel_netport_init(void)
{
int iter;
@@ -256,10 +247,6 @@ static __init int sel_netport_init(void)
sel_netport_hash[iter].size = 0;
}
- ret = avc_add_callback(sel_netport_avc_callback, AVC_CALLBACK_RESET);
- if (ret != 0)
- panic("avc_add_callback() failed, error %d\n", ret);
-
return ret;
}
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index 855e464e92ef..4c29bcc7fad2 100644
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -17,6 +17,7 @@
#include <linux/inet_diag.h>
#include <linux/xfrm.h>
#include <linux/audit.h>
+#include <linux/sock_diag.h>
#include "flask.h"
#include "av_permissions.h"
@@ -78,6 +79,7 @@ static struct nlmsg_perm nlmsg_tcpdiag_perms[] =
{
{ TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },
{ DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },
+ { SOCK_DIAG_BY_FAMILY, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },
};
static struct nlmsg_perm nlmsg_xfrm_perms[] =
@@ -98,6 +100,13 @@ static struct nlmsg_perm nlmsg_xfrm_perms[] =
{ XFRM_MSG_FLUSHPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
{ XFRM_MSG_NEWAE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
{ XFRM_MSG_GETAE, NETLINK_XFRM_SOCKET__NLMSG_READ },
+ { XFRM_MSG_REPORT, NETLINK_XFRM_SOCKET__NLMSG_READ },
+ { XFRM_MSG_MIGRATE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
+ { XFRM_MSG_NEWSADINFO, NETLINK_XFRM_SOCKET__NLMSG_READ },
+ { XFRM_MSG_GETSADINFO, NETLINK_XFRM_SOCKET__NLMSG_READ },
+ { XFRM_MSG_NEWSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
+ { XFRM_MSG_GETSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_READ },
+ { XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ },
};
static struct nlmsg_perm nlmsg_audit_perms[] =
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index a3dd9faa19c0..dd7466cb2021 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -24,6 +24,7 @@
#include "policydb.h"
static struct kmem_cache *avtab_node_cachep;
+static struct kmem_cache *avtab_operation_cachep;
static inline int avtab_hash(struct avtab_key *keyp, u16 mask)
{
@@ -37,11 +38,24 @@ avtab_insert_node(struct avtab *h, int hvalue,
struct avtab_key *key, struct avtab_datum *datum)
{
struct avtab_node *newnode;
+ struct avtab_operation *ops;
newnode = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL);
if (newnode == NULL)
return NULL;
newnode->key = *key;
- newnode->datum = *datum;
+
+ if (key->specified & AVTAB_OP) {
+ ops = kmem_cache_zalloc(avtab_operation_cachep, GFP_KERNEL);
+ if (ops == NULL) {
+ kmem_cache_free(avtab_node_cachep, newnode);
+ return NULL;
+ }
+ *ops = *(datum->u.ops);
+ newnode->datum.u.ops = ops;
+ } else {
+ newnode->datum.u.data = datum->u.data;
+ }
+
if (prev) {
newnode->next = prev->next;
prev->next = newnode;
@@ -70,8 +84,11 @@ static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_dat
if (key->source_type == cur->key.source_type &&
key->target_type == cur->key.target_type &&
key->target_class == cur->key.target_class &&
- (specified & cur->key.specified))
+ (specified & cur->key.specified)) {
+ if (specified & AVTAB_OPNUM)
+ break;
return -EEXIST;
+ }
if (key->source_type < cur->key.source_type)
break;
if (key->source_type == cur->key.source_type &&
@@ -232,6 +249,9 @@ void avtab_destroy(struct avtab *h)
while (cur) {
temp = cur;
cur = cur->next;
+ if (temp->key.specified & AVTAB_OP)
+ kmem_cache_free(avtab_operation_cachep,
+ temp->datum.u.ops);
kmem_cache_free(avtab_node_cachep, temp);
}
h->htable[i] = NULL;
@@ -320,7 +340,13 @@ static uint16_t spec_order[] = {
AVTAB_AUDITALLOW,
AVTAB_TRANSITION,
AVTAB_CHANGE,
- AVTAB_MEMBER
+ AVTAB_MEMBER,
+ AVTAB_OPNUM_ALLOWED,
+ AVTAB_OPNUM_AUDITALLOW,
+ AVTAB_OPNUM_DONTAUDIT,
+ AVTAB_OPTYPE_ALLOWED,
+ AVTAB_OPTYPE_AUDITALLOW,
+ AVTAB_OPTYPE_DONTAUDIT
};
int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
@@ -330,10 +356,11 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
{
__le16 buf16[4];
u16 enabled;
- __le32 buf32[7];
u32 items, items2, val, vers = pol->policyvers;
struct avtab_key key;
struct avtab_datum datum;
+ struct avtab_operation ops;
+ __le32 buf32[ARRAY_SIZE(ops.op.perms)];
int i, rc;
unsigned set;
@@ -390,11 +417,15 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
printk(KERN_ERR "SELinux: avtab: entry has both access vectors and types\n");
return -EINVAL;
}
+ if (val & AVTAB_OP) {
+ printk(KERN_ERR "SELinux: avtab: entry has operations\n");
+ return -EINVAL;
+ }
for (i = 0; i < ARRAY_SIZE(spec_order); i++) {
if (val & spec_order[i]) {
key.specified = spec_order[i] | enabled;
- datum.data = le32_to_cpu(buf32[items++]);
+ datum.u.data = le32_to_cpu(buf32[items++]);
rc = insertf(a, &key, &datum, p);
if (rc)
return rc;
@@ -413,7 +444,6 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
printk(KERN_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++]);
@@ -437,14 +467,32 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
return -EINVAL;
}
- rc = next_entry(buf32, fp, sizeof(u32));
- if (rc) {
- printk(KERN_ERR "SELinux: avtab: truncated entry\n");
- return rc;
+ if ((vers < POLICYDB_VERSION_IOCTL_OPERATIONS)
+ || !(key.specified & AVTAB_OP)) {
+ rc = next_entry(buf32, fp, sizeof(u32));
+ if (rc) {
+ printk(KERN_ERR "SELinux: avtab: truncated entry\n");
+ return rc;
+ }
+ datum.u.data = le32_to_cpu(*buf32);
+ } else {
+ memset(&ops, 0, sizeof(struct avtab_operation));
+ rc = next_entry(&ops.type, fp, sizeof(u8));
+ if (rc) {
+ printk(KERN_ERR "SELinux: avtab: truncated entry\n");
+ return rc;
+ }
+ rc = next_entry(buf32, fp, sizeof(u32)*ARRAY_SIZE(ops.op.perms));
+ if (rc) {
+ printk(KERN_ERR "SELinux: avtab: truncated entry\n");
+ return rc;
+ }
+ for (i = 0; i < ARRAY_SIZE(ops.op.perms); i++)
+ ops.op.perms[i] = le32_to_cpu(buf32[i]);
+ datum.u.ops = &ops;
}
- datum.data = le32_to_cpu(*buf32);
if ((key.specified & AVTAB_TYPE) &&
- !policydb_type_isvalid(pol, datum.data)) {
+ !policydb_type_isvalid(pol, datum.u.data)) {
printk(KERN_ERR "SELinux: avtab: invalid type\n");
return -EINVAL;
}
@@ -504,8 +552,9 @@ bad:
int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp)
{
__le16 buf16[4];
- __le32 buf32[1];
+ __le32 buf32[ARRAY_SIZE(cur->datum.u.ops->op.perms)];
int rc;
+ unsigned int i;
buf16[0] = cpu_to_le16(cur->key.source_type);
buf16[1] = cpu_to_le16(cur->key.target_type);
@@ -514,8 +563,19 @@ int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp)
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 (cur->key.specified & AVTAB_OP) {
+ rc = put_entry(&cur->datum.u.ops->type, sizeof(u8), 1, fp);
+ if (rc)
+ return rc;
+ for (i = 0; i < ARRAY_SIZE(cur->datum.u.ops->op.perms); i++)
+ buf32[i] = cpu_to_le32(cur->datum.u.ops->op.perms[i]);
+ rc = put_entry(buf32, sizeof(u32),
+ ARRAY_SIZE(cur->datum.u.ops->op.perms), 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;
@@ -548,9 +608,13 @@ void avtab_cache_init(void)
avtab_node_cachep = kmem_cache_create("avtab_node",
sizeof(struct avtab_node),
0, SLAB_PANIC, NULL);
+ avtab_operation_cachep = kmem_cache_create("avtab_operation",
+ sizeof(struct avtab_operation),
+ 0, SLAB_PANIC, NULL);
}
void avtab_cache_destroy(void)
{
kmem_cache_destroy(avtab_node_cachep);
+ kmem_cache_destroy(avtab_operation_cachep);
}
diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h
index 63ce2f9e441d..97acd6fa705e 100644
--- a/security/selinux/ss/avtab.h
+++ b/security/selinux/ss/avtab.h
@@ -23,6 +23,8 @@
#ifndef _SS_AVTAB_H_
#define _SS_AVTAB_H_
+#include "security.h"
+
struct avtab_key {
u16 source_type; /* source type */
u16 target_type; /* target type */
@@ -35,13 +37,34 @@ struct avtab_key {
#define AVTAB_MEMBER 0x0020
#define AVTAB_CHANGE 0x0040
#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE)
+#define AVTAB_OPNUM_ALLOWED 0x0100
+#define AVTAB_OPNUM_AUDITALLOW 0x0200
+#define AVTAB_OPNUM_DONTAUDIT 0x0400
+#define AVTAB_OPNUM (AVTAB_OPNUM_ALLOWED | \
+ AVTAB_OPNUM_AUDITALLOW | \
+ AVTAB_OPNUM_DONTAUDIT)
+#define AVTAB_OPTYPE_ALLOWED 0x1000
+#define AVTAB_OPTYPE_AUDITALLOW 0x2000
+#define AVTAB_OPTYPE_DONTAUDIT 0x4000
+#define AVTAB_OPTYPE (AVTAB_OPTYPE_ALLOWED | \
+ AVTAB_OPTYPE_AUDITALLOW | \
+ AVTAB_OPTYPE_DONTAUDIT)
+#define AVTAB_OP (AVTAB_OPNUM | AVTAB_OPTYPE)
#define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */
#define AVTAB_ENABLED 0x8000 /* reserved for used in cond_avtab */
u16 specified; /* what field is specified */
};
+struct avtab_operation {
+ u8 type;
+ struct operation_perm op;
+};
+
struct avtab_datum {
- u32 data; /* access vector or type value */
+ union {
+ u32 data; /* access vector or type value */
+ struct avtab_operation *ops; /* ioctl operations */
+ } u;
};
struct avtab_node {
diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
index 377d148e7157..16651c7a1541 100644
--- a/security/selinux/ss/conditional.c
+++ b/security/selinux/ss/conditional.c
@@ -15,6 +15,7 @@
#include "security.h"
#include "conditional.h"
+#include "services.h"
/*
* cond_evaluate_expr evaluates a conditional expr
@@ -617,21 +618,39 @@ int cond_write_list(struct policydb *p, struct cond_node *list, void *fp)
return 0;
}
+
+void cond_compute_operation(struct avtab *ctab, struct avtab_key *key,
+ struct operation_decision *od)
+{
+ struct avtab_node *node;
+
+ if (!ctab || !key || !od)
+ 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_operation_num(od, node);
+ }
+ return;
+
+}
/* 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)
+void cond_compute_av(struct avtab *ctab, struct avtab_key *key,
+ struct av_decision *avd, struct operation *ops)
{
struct avtab_node *node;
- if (!ctab || !key || !avd)
+ if (!ctab || !key || !avd || !ops)
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.data;
+ 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
@@ -639,10 +658,13 @@ void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decisi
* the '&' operand to ensure that all '0's in the mask
* are retained (much unlike the allow and auditallow cases).
*/
- avd->auditdeny &= node->datum.data;
+ avd->auditdeny &= node->datum.u.data;
if ((u16)(AVTAB_AUDITALLOW|AVTAB_ENABLED) ==
(node->key.specified & (AVTAB_AUDITALLOW|AVTAB_ENABLED)))
- avd->auditallow |= node->datum.data;
+ avd->auditallow |= node->datum.u.data;
+ if ((node->key.specified & AVTAB_ENABLED) &&
+ (node->key.specified & AVTAB_OP))
+ services_compute_operation_type(ops, node);
}
return;
}
diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h
index 4d1f87466508..80ee2bb20eee 100644
--- a/security/selinux/ss/conditional.h
+++ b/security/selinux/ss/conditional.h
@@ -73,8 +73,10 @@ 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);
-
+void cond_compute_av(struct avtab *ctab, struct avtab_key *key,
+ struct av_decision *avd, struct operation *ops);
+void cond_compute_operation(struct avtab *ctab, struct avtab_key *key,
+ struct operation_decision *od);
int evaluate_cond_node(struct policydb *p, struct cond_node *node);
#endif /* _CONDITIONAL_H_ */
diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h
index 149dda731fd3..96fd947c494b 100644
--- a/security/selinux/ss/constraint.h
+++ b/security/selinux/ss/constraint.h
@@ -48,6 +48,7 @@ struct constraint_expr {
u32 op; /* operator */
struct ebitmap names; /* names */
+ struct type_set *type_names;
struct constraint_expr *next; /* next expression */
};
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index bcdca73033f3..a683475e1635 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -143,6 +143,16 @@ static struct policydb_compat_info policydb_compat[] = {
.sym_num = SYM_NUM,
.ocon_num = OCON_NUM,
},
+ {
+ .version = POLICYDB_VERSION_CONSTRAINT_NAMES,
+ .sym_num = SYM_NUM,
+ .ocon_num = OCON_NUM,
+ },
+ {
+ .version = POLICYDB_VERSION_IOCTL_OPERATIONS,
+ .sym_num = SYM_NUM,
+ .ocon_num = OCON_NUM,
+ },
};
static struct policydb_compat_info *policydb_lookup_compat(int version)
@@ -613,6 +623,19 @@ static int common_destroy(void *key, void *datum, void *p)
return 0;
}
+static void constraint_expr_destroy(struct constraint_expr *expr)
+{
+ if (expr) {
+ ebitmap_destroy(&expr->names);
+ if (expr->type_names) {
+ ebitmap_destroy(&expr->type_names->types);
+ ebitmap_destroy(&expr->type_names->negset);
+ kfree(expr->type_names);
+ }
+ kfree(expr);
+ }
+}
+
static int cls_destroy(void *key, void *datum, void *p)
{
struct class_datum *cladatum;
@@ -628,10 +651,9 @@ static int cls_destroy(void *key, void *datum, void *p)
while (constraint) {
e = constraint->expr;
while (e) {
- ebitmap_destroy(&e->names);
etmp = e;
e = e->next;
- kfree(etmp);
+ constraint_expr_destroy(etmp);
}
ctemp = constraint;
constraint = constraint->next;
@@ -642,16 +664,14 @@ static int cls_destroy(void *key, void *datum, void *p)
while (constraint) {
e = constraint->expr;
while (e) {
- ebitmap_destroy(&e->names);
etmp = e;
e = e->next;
- kfree(etmp);
+ constraint_expr_destroy(etmp);
}
ctemp = constraint;
constraint = constraint->next;
kfree(ctemp);
}
-
kfree(cladatum->comkey);
}
kfree(datum);
@@ -1156,8 +1176,34 @@ bad:
return rc;
}
-static int read_cons_helper(struct constraint_node **nodep, int ncons,
- int allowxtarget, void *fp)
+static void type_set_init(struct type_set *t)
+{
+ ebitmap_init(&t->types);
+ ebitmap_init(&t->negset);
+}
+
+static int type_set_read(struct type_set *t, void *fp)
+{
+ __le32 buf[1];
+ int rc;
+
+ if (ebitmap_read(&t->types, fp))
+ return -EINVAL;
+ if (ebitmap_read(&t->negset, fp))
+ return -EINVAL;
+
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc < 0)
+ return -EINVAL;
+ t->flags = le32_to_cpu(buf[0]);
+
+ return 0;
+}
+
+
+static int read_cons_helper(struct policydb *p,
+ struct constraint_node **nodep,
+ int ncons, int allowxtarget, void *fp)
{
struct constraint_node *c, *lc;
struct constraint_expr *e, *le;
@@ -1225,6 +1271,18 @@ static int read_cons_helper(struct constraint_node **nodep, int ncons,
rc = ebitmap_read(&e->names, fp);
if (rc)
return rc;
+ if (p->policyvers >=
+ POLICYDB_VERSION_CONSTRAINT_NAMES) {
+ e->type_names = kzalloc(sizeof
+ (*e->type_names),
+ GFP_KERNEL);
+ if (!e->type_names)
+ return -ENOMEM;
+ type_set_init(e->type_names);
+ rc = type_set_read(e->type_names, fp);
+ if (rc)
+ return rc;
+ }
break;
default:
return -EINVAL;
@@ -1301,7 +1359,7 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp)
goto bad;
}
- rc = read_cons_helper(&cladatum->constraints, ncons, 0, fp);
+ rc = read_cons_helper(p, &cladatum->constraints, ncons, 0, fp);
if (rc)
goto bad;
@@ -1311,7 +1369,8 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp)
if (rc)
goto bad;
ncons = le32_to_cpu(buf[0]);
- rc = read_cons_helper(&cladatum->validatetrans, ncons, 1, fp);
+ rc = read_cons_helper(p, &cladatum->validatetrans,
+ ncons, 1, fp);
if (rc)
goto bad;
}
@@ -2762,6 +2821,24 @@ static int common_write(void *vkey, void *datum, void *ptr)
return 0;
}
+static int type_set_write(struct type_set *t, void *fp)
+{
+ int rc;
+ __le32 buf[1];
+
+ if (ebitmap_write(&t->types, fp))
+ return -EINVAL;
+ if (ebitmap_write(&t->negset, fp))
+ return -EINVAL;
+
+ buf[0] = cpu_to_le32(t->flags);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return -EINVAL;
+
+ return 0;
+}
+
static int write_cons_helper(struct policydb *p, struct constraint_node *node,
void *fp)
{
@@ -2793,6 +2870,12 @@ static int write_cons_helper(struct policydb *p, struct constraint_node *node,
rc = ebitmap_write(&e->names, fp);
if (rc)
return rc;
+ if (p->policyvers >=
+ POLICYDB_VERSION_CONSTRAINT_NAMES) {
+ rc = type_set_write(e->type_names, fp);
+ if (rc)
+ return rc;
+ }
break;
default:
break;
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index da637471d4ce..725d5945a97e 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -154,6 +154,17 @@ struct cond_bool_datum {
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
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 18caa16de27b..88178da2b834 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -92,9 +92,10 @@ static int context_struct_to_string(struct context *context, char **scontext,
u32 *scontext_len);
static void context_struct_compute_av(struct context *scontext,
- struct context *tcontext,
- u16 tclass,
- struct av_decision *avd);
+ struct context *tcontext,
+ u16 tclass,
+ struct av_decision *avd,
+ struct operation *ops);
struct selinux_mapping {
u16 value; /* policy value */
@@ -564,7 +565,8 @@ static void type_attribute_bounds_av(struct context *scontext,
context_struct_compute_av(&lo_scontext,
tcontext,
tclass,
- &lo_avd);
+ &lo_avd,
+ NULL);
if ((lo_avd.allowed & avd->allowed) == avd->allowed)
return; /* no masked permission */
masked = ~lo_avd.allowed & avd->allowed;
@@ -579,7 +581,8 @@ static void type_attribute_bounds_av(struct context *scontext,
context_struct_compute_av(scontext,
&lo_tcontext,
tclass,
- &lo_avd);
+ &lo_avd,
+ NULL);
if ((lo_avd.allowed & avd->allowed) == avd->allowed)
return; /* no masked permission */
masked = ~lo_avd.allowed & avd->allowed;
@@ -595,7 +598,8 @@ static void type_attribute_bounds_av(struct context *scontext,
context_struct_compute_av(&lo_scontext,
&lo_tcontext,
tclass,
- &lo_avd);
+ &lo_avd,
+ NULL);
if ((lo_avd.allowed & avd->allowed) == avd->allowed)
return; /* no masked permission */
masked = ~lo_avd.allowed & avd->allowed;
@@ -611,14 +615,39 @@ static void type_attribute_bounds_av(struct context *scontext,
}
}
+/* flag ioctl types that have operation permissions */
+void services_compute_operation_type(
+ struct operation *ops,
+ struct avtab_node *node)
+{
+ u8 type;
+ unsigned int i;
+
+ if (node->key.specified & AVTAB_OPTYPE) {
+ /* if allowing one or more complete types */
+ for (i = 0; i < ARRAY_SIZE(ops->type); i++)
+ ops->type[i] |= node->datum.u.ops->op.perms[i];
+ } else {
+ /* if allowing operations within a type */
+ type = node->datum.u.ops->type;
+ security_operation_set(ops->type, type);
+ }
+
+ /* If no ioctl commands are allowed, ignore auditallow and auditdeny */
+ if (node->key.specified & AVTAB_OPTYPE_ALLOWED ||
+ node->key.specified & AVTAB_OPNUM_ALLOWED)
+ ops->len = 1;
+}
+
/*
- * Compute access vectors based on a context structure pair for
- * the permissions in a particular class.
+ * Compute access vectors and operations ranges based on a context
+ * structure pair for the permissions in a particular class.
*/
static void context_struct_compute_av(struct context *scontext,
- struct context *tcontext,
- u16 tclass,
- struct av_decision *avd)
+ struct context *tcontext,
+ u16 tclass,
+ struct av_decision *avd,
+ struct operation *ops)
{
struct constraint_node *constraint;
struct role_allow *ra;
@@ -632,6 +661,10 @@ static void context_struct_compute_av(struct context *scontext,
avd->allowed = 0;
avd->auditallow = 0;
avd->auditdeny = 0xffffffff;
+ if (ops) {
+ memset(&ops->type, 0, sizeof(ops->type));
+ ops->len = 0;
+ }
if (unlikely(!tclass || tclass > policydb.p_classes.nprim)) {
if (printk_ratelimit())
@@ -646,7 +679,7 @@ static void context_struct_compute_av(struct context *scontext,
* this permission check, then use it.
*/
avkey.target_class = tclass;
- avkey.specified = AVTAB_AV;
+ avkey.specified = AVTAB_AV | AVTAB_OP;
sattr = flex_array_get(policydb.type_attr_map_array, scontext->type - 1);
BUG_ON(!sattr);
tattr = flex_array_get(policydb.type_attr_map_array, tcontext->type - 1);
@@ -659,15 +692,17 @@ static void context_struct_compute_av(struct context *scontext,
node;
node = avtab_search_node_next(node, avkey.specified)) {
if (node->key.specified == AVTAB_ALLOWED)
- avd->allowed |= node->datum.data;
+ avd->allowed |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITALLOW)
- avd->auditallow |= node->datum.data;
+ avd->auditallow |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITDENY)
- avd->auditdeny &= node->datum.data;
+ avd->auditdeny &= node->datum.u.data;
+ else if (ops && (node->key.specified & AVTAB_OP))
+ services_compute_operation_type(ops, node);
}
/* Check conditional av table for additional permissions */
- cond_compute_av(&policydb.te_cond_avtab, &avkey, avd);
+ cond_compute_av(&policydb.te_cond_avtab, &avkey, avd, ops);
}
}
@@ -898,13 +933,138 @@ static void avd_init(struct av_decision *avd)
avd->flags = 0;
}
+void services_compute_operation_num(struct operation_decision *od,
+ struct avtab_node *node)
+{
+ unsigned int i;
+ if (node->key.specified & AVTAB_OPNUM) {
+ if (od->type != node->datum.u.ops->type)
+ return;
+ } else {
+ if (!security_operation_test(node->datum.u.ops->op.perms,
+ od->type))
+ return;
+ }
+
+ if (node->key.specified == AVTAB_OPTYPE_ALLOWED) {
+ od->specified |= OPERATION_ALLOWED;
+ memset(od->allowed->perms, 0xff,
+ sizeof(od->allowed->perms));
+ } else if (node->key.specified == AVTAB_OPTYPE_AUDITALLOW) {
+ od->specified |= OPERATION_AUDITALLOW;
+ memset(od->auditallow->perms, 0xff,
+ sizeof(od->auditallow->perms));
+ } else if (node->key.specified == AVTAB_OPTYPE_DONTAUDIT) {
+ od->specified |= OPERATION_DONTAUDIT;
+ memset(od->dontaudit->perms, 0xff,
+ sizeof(od->dontaudit->perms));
+ } else if (node->key.specified == AVTAB_OPNUM_ALLOWED) {
+ od->specified |= OPERATION_ALLOWED;
+ for (i = 0; i < ARRAY_SIZE(od->allowed->perms); i++)
+ od->allowed->perms[i] |=
+ node->datum.u.ops->op.perms[i];
+ } else if (node->key.specified == AVTAB_OPNUM_AUDITALLOW) {
+ od->specified |= OPERATION_AUDITALLOW;
+ for (i = 0; i < ARRAY_SIZE(od->auditallow->perms); i++)
+ od->auditallow->perms[i] |=
+ node->datum.u.ops->op.perms[i];
+ } else if (node->key.specified == AVTAB_OPNUM_DONTAUDIT) {
+ od->specified |= OPERATION_DONTAUDIT;
+ for (i = 0; i < ARRAY_SIZE(od->dontaudit->perms); i++)
+ od->dontaudit->perms[i] |=
+ node->datum.u.ops->op.perms[i];
+ } else {
+ BUG();
+ }
+}
+
+void security_compute_operation(u32 ssid,
+ u32 tsid,
+ u16 orig_tclass,
+ u8 type,
+ struct operation_decision *od)
+{
+ u16 tclass;
+ struct context *scontext, *tcontext;
+ struct avtab_key avkey;
+ struct avtab_node *node;
+ struct ebitmap *sattr, *tattr;
+ struct ebitmap_node *snode, *tnode;
+ unsigned int i, j;
+
+ od->type = type;
+ od->specified = 0;
+ memset(od->allowed->perms, 0, sizeof(od->allowed->perms));
+ memset(od->auditallow->perms, 0, sizeof(od->auditallow->perms));
+ memset(od->dontaudit->perms, 0, sizeof(od->dontaudit->perms));
+
+ read_lock(&policy_rwlock);
+ if (!ss_initialized)
+ goto allow;
+
+ scontext = sidtab_search(&sidtab, ssid);
+ if (!scontext) {
+ printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n",
+ __func__, ssid);
+ goto out;
+ }
+
+ tcontext = sidtab_search(&sidtab, tsid);
+ if (!tcontext) {
+ printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n",
+ __func__, tsid);
+ goto out;
+ }
+
+ tclass = unmap_class(orig_tclass);
+ if (unlikely(orig_tclass && !tclass)) {
+ if (policydb.allow_unknown)
+ goto allow;
+ goto out;
+ }
+
+
+ if (unlikely(!tclass || tclass > policydb.p_classes.nprim)) {
+ pr_warn_ratelimited("SELinux: Invalid class %hu\n", tclass);
+ goto out;
+ }
+
+ avkey.target_class = tclass;
+ avkey.specified = AVTAB_OP;
+ sattr = flex_array_get(policydb.type_attr_map_array,
+ scontext->type - 1);
+ BUG_ON(!sattr);
+ tattr = flex_array_get(policydb.type_attr_map_array,
+ tcontext->type - 1);
+ BUG_ON(!tattr);
+ ebitmap_for_each_positive_bit(sattr, snode, i) {
+ ebitmap_for_each_positive_bit(tattr, tnode, j) {
+ avkey.source_type = i + 1;
+ avkey.target_type = j + 1;
+ for (node = avtab_search_node(&policydb.te_avtab, &avkey);
+ node;
+ node = avtab_search_node_next(node, avkey.specified))
+ services_compute_operation_num(od, node);
+
+ cond_compute_operation(&policydb.te_cond_avtab,
+ &avkey, od);
+ }
+ }
+out:
+ read_unlock(&policy_rwlock);
+ return;
+allow:
+ memset(od->allowed->perms, 0xff, sizeof(od->allowed->perms));
+ goto out;
+}
/**
* security_compute_av - Compute access vector decisions.
* @ssid: source security identifier
* @tsid: target security identifier
* @tclass: target security class
* @avd: access vector decisions
+ * @od: operation decisions
*
* Compute a set of access vector decisions based on the
* SID pair (@ssid, @tsid) for the permissions in @tclass.
@@ -912,13 +1072,15 @@ static void avd_init(struct av_decision *avd)
void security_compute_av(u32 ssid,
u32 tsid,
u16 orig_tclass,
- struct av_decision *avd)
+ struct av_decision *avd,
+ struct operation *ops)
{
u16 tclass;
struct context *scontext = NULL, *tcontext = NULL;
read_lock(&policy_rwlock);
avd_init(avd);
+ ops->len = 0;
if (!ss_initialized)
goto allow;
@@ -946,7 +1108,7 @@ void security_compute_av(u32 ssid,
goto allow;
goto out;
}
- context_struct_compute_av(scontext, tcontext, tclass, avd);
+ context_struct_compute_av(scontext, tcontext, tclass, avd, ops);
map_decision(orig_tclass, avd, policydb.allow_unknown);
out:
read_unlock(&policy_rwlock);
@@ -992,7 +1154,7 @@ void security_compute_av_user(u32 ssid,
goto out;
}
- context_struct_compute_av(scontext, tcontext, tclass, avd);
+ context_struct_compute_av(scontext, tcontext, tclass, avd, NULL);
out:
read_unlock(&policy_rwlock);
return;
@@ -1512,7 +1674,7 @@ static int security_compute_sid(u32 ssid,
if (avdatum) {
/* Use the type from the type transition/member/change rule. */
- newcontext.type = avdatum->data;
+ newcontext.type = avdatum->u.data;
}
/* if we have a objname this is a file trans check so check those rules */
diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h
index e8d907e903cd..569757484d05 100644
--- a/security/selinux/ss/services.h
+++ b/security/selinux/ss/services.h
@@ -11,5 +11,11 @@
extern struct policydb policydb;
+void services_compute_operation_type(struct operation *ops,
+ struct avtab_node *node);
+
+void services_compute_operation_num(struct operation_decision *od,
+ struct avtab_node *node);
+
#endif /* _SS_SERVICES_H_ */