diff options
Diffstat (limited to 'security')
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_ */ |