diff options
-rw-r--r-- | Documentation/sysctl/vm.txt | 15 | ||||
-rw-r--r-- | include/linux/security.h | 17 | ||||
-rw-r--r-- | kernel/sysctl.c | 10 | ||||
-rw-r--r-- | mm/mmap.c | 4 | ||||
-rw-r--r-- | mm/mremap.c | 13 | ||||
-rw-r--r-- | mm/nommu.c | 2 | ||||
-rw-r--r-- | security/dummy.c | 6 | ||||
-rw-r--r-- | security/security.c | 1 | ||||
-rw-r--r-- | security/selinux/avc.c | 12 | ||||
-rw-r--r-- | security/selinux/hooks.c | 42 | ||||
-rw-r--r-- | security/selinux/include/av_perm_to_string.h | 1 | ||||
-rw-r--r-- | security/selinux/include/av_permissions.h | 1 | ||||
-rw-r--r-- | security/selinux/include/avc.h | 6 | ||||
-rw-r--r-- | security/selinux/include/class_to_string.h | 1 | ||||
-rw-r--r-- | security/selinux/include/flask.h | 1 | ||||
-rw-r--r-- | security/selinux/include/security.h | 4 | ||||
-rw-r--r-- | security/selinux/netlabel.c | 34 | ||||
-rw-r--r-- | security/selinux/selinuxfs.c | 269 | ||||
-rw-r--r-- | security/selinux/ss/policydb.c | 7 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 144 |
20 files changed, 504 insertions, 86 deletions
diff --git a/Documentation/sysctl/vm.txt b/Documentation/sysctl/vm.txt index 1d192565e18..8cfca173d4b 100644 --- a/Documentation/sysctl/vm.txt +++ b/Documentation/sysctl/vm.txt @@ -31,6 +31,7 @@ Currently, these files are in /proc/sys/vm: - min_unmapped_ratio - min_slab_ratio - panic_on_oom +- mmap_min_address ============================================================== @@ -216,3 +217,17 @@ above-mentioned. The default value is 0. 1 and 2 are for failover of clustering. Please select either according to your policy of failover. + +============================================================== + +mmap_min_addr + +This file indicates the amount of address space which a user process will +be restricted from mmaping. Since kernel null dereference bugs could +accidentally operate based on the information in the first couple of pages +of memory userspace processes should not be allowed to write to them. By +default this value is set to 0 and no protections will be enforced by the +security module. Setting this value to something like 64k will allow the +vast majority of applications to work correctly and provide defense in depth +against future potential kernel bugs. + diff --git a/include/linux/security.h b/include/linux/security.h index 9eb9e0fe033..c11dc8aa035 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -71,6 +71,7 @@ struct xfrm_user_sec_ctx; extern int cap_netlink_send(struct sock *sk, struct sk_buff *skb); extern int cap_netlink_recv(struct sk_buff *skb, int cap); +extern unsigned long mmap_min_addr; /* * Values used in the task_security_ops calls */ @@ -1241,8 +1242,9 @@ struct security_operations { int (*file_ioctl) (struct file * file, unsigned int cmd, unsigned long arg); int (*file_mmap) (struct file * file, - unsigned long reqprot, - unsigned long prot, unsigned long flags); + unsigned long reqprot, unsigned long prot, + unsigned long flags, unsigned long addr, + unsigned long addr_only); int (*file_mprotect) (struct vm_area_struct * vma, unsigned long reqprot, unsigned long prot); @@ -1814,9 +1816,12 @@ static inline int security_file_ioctl (struct file *file, unsigned int cmd, static inline int security_file_mmap (struct file *file, unsigned long reqprot, unsigned long prot, - unsigned long flags) + unsigned long flags, + unsigned long addr, + unsigned long addr_only) { - return security_ops->file_mmap (file, reqprot, prot, flags); + return security_ops->file_mmap (file, reqprot, prot, flags, addr, + addr_only); } static inline int security_file_mprotect (struct vm_area_struct *vma, @@ -2489,7 +2494,9 @@ static inline int security_file_ioctl (struct file *file, unsigned int cmd, static inline int security_file_mmap (struct file *file, unsigned long reqprot, unsigned long prot, - unsigned long flags) + unsigned long flags, + unsigned long addr, + unsigned long addr_only) { return 0; } diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 51f5dac42a0..d93e13d93f2 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -949,6 +949,16 @@ static ctl_table vm_table[] = { .strategy = &sysctl_jiffies, }, #endif +#ifdef CONFIG_SECURITY + { + .ctl_name = CTL_UNNUMBERED, + .procname = "mmap_min_addr", + .data = &mmap_min_addr, + .maxlen = sizeof(unsigned long), + .mode = 0644, + .proc_handler = &proc_doulongvec_minmax, + }, +#endif #if defined(CONFIG_X86_32) || \ (defined(CONFIG_SUPERH) && defined(CONFIG_VSYSCALL)) { diff --git a/mm/mmap.c b/mm/mmap.c index 906ed402f7c..9f70c8e8c87 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1023,10 +1023,10 @@ unsigned long do_mmap_pgoff(struct file * file, unsigned long addr, } } - error = security_file_mmap(file, reqprot, prot, flags); + error = security_file_mmap(file, reqprot, prot, flags, addr, 0); if (error) return error; - + /* Clear old maps */ error = -ENOMEM; munmap_back: diff --git a/mm/mremap.c b/mm/mremap.c index 5d4bd4f95b8..bc7c52efc71 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -291,6 +291,10 @@ unsigned long do_mremap(unsigned long addr, if ((addr <= new_addr) && (addr+old_len) > new_addr) goto out; + ret = security_file_mmap(0, 0, 0, 0, new_addr, 1); + if (ret) + goto out; + ret = do_munmap(mm, new_addr, new_len); if (ret) goto out; @@ -390,8 +394,13 @@ unsigned long do_mremap(unsigned long addr, new_addr = get_unmapped_area(vma->vm_file, 0, new_len, vma->vm_pgoff, map_flags); - ret = new_addr; - if (new_addr & ~PAGE_MASK) + if (new_addr & ~PAGE_MASK) { + ret = new_addr; + goto out; + } + + ret = security_file_mmap(0, 0, 0, 0, new_addr, 1); + if (ret) goto out; } ret = move_vma(vma, addr, old_len, new_len, new_addr); diff --git a/mm/nommu.c b/mm/nommu.c index 2b16b00a5b1..989e2e9af5c 100644 --- a/mm/nommu.c +++ b/mm/nommu.c @@ -639,7 +639,7 @@ static int validate_mmap_request(struct file *file, } /* allow the security API to have its say */ - ret = security_file_mmap(file, reqprot, prot, flags); + ret = security_file_mmap(file, reqprot, prot, flags, addr, 0); if (ret < 0) return ret; diff --git a/security/dummy.c b/security/dummy.c index 8ffd76405b5..d6a112ce297 100644 --- a/security/dummy.c +++ b/security/dummy.c @@ -420,8 +420,12 @@ static int dummy_file_ioctl (struct file *file, unsigned int command, static int dummy_file_mmap (struct file *file, unsigned long reqprot, unsigned long prot, - unsigned long flags) + unsigned long flags, + unsigned long addr, + unsigned long addr_only) { + if (addr < mmap_min_addr) + return -EACCES; return 0; } diff --git a/security/security.c b/security/security.c index fc8601b2b7a..27e5863d30f 100644 --- a/security/security.c +++ b/security/security.c @@ -24,6 +24,7 @@ extern struct security_operations dummy_security_ops; extern void security_fixup_ops(struct security_operations *ops); struct security_operations *security_ops; /* Initialized to NULL */ +unsigned long mmap_min_addr; /* 0 means no protection */ static inline int verify(struct security_operations *ops) { diff --git a/security/selinux/avc.c b/security/selinux/avc.c index e4396a89edc..78c408fd2b0 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -586,7 +586,7 @@ void avc_audit(u32 ssid, u32 tsid, } } if (inode) - audit_log_format(ab, " dev=%s ino=%ld", + audit_log_format(ab, " dev=%s ino=%lu", inode->i_sb->s_id, inode->i_ino); break; @@ -832,6 +832,7 @@ int avc_ss_reset(u32 seqno) * @tsid: target security identifier * @tclass: target security class * @requested: requested permissions, interpreted based on @tclass + * @flags: AVC_STRICT or 0 * @avd: access vector decisions * * Check the AVC to determine whether the @requested permissions are granted @@ -846,8 +847,9 @@ int avc_ss_reset(u32 seqno) * should be released for the auditing. */ int avc_has_perm_noaudit(u32 ssid, u32 tsid, - u16 tclass, u32 requested, - struct av_decision *avd) + u16 tclass, u32 requested, + unsigned flags, + struct av_decision *avd) { struct avc_node *node; struct avc_entry entry, *p_ae; @@ -874,7 +876,7 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, denied = requested & ~(p_ae->avd.allowed); if (!requested || denied) { - if (selinux_enforcing) + if (selinux_enforcing || (flags & AVC_STRICT)) rc = -EACCES; else if (node) @@ -909,7 +911,7 @@ int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, struct av_decision avd; int rc; - rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, &avd); + rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd); avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata); return rc; } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index ad8dd4e8657..aff8f46c2aa 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1592,9 +1592,10 @@ static int selinux_vm_enough_memory(long pages) rc = secondary_ops->capable(current, CAP_SYS_ADMIN); if (rc == 0) rc = avc_has_perm_noaudit(tsec->sid, tsec->sid, - SECCLASS_CAPABILITY, - CAP_TO_MASK(CAP_SYS_ADMIN), - NULL); + SECCLASS_CAPABILITY, + CAP_TO_MASK(CAP_SYS_ADMIN), + 0, + NULL); if (rc == 0) cap_sys_admin = 1; @@ -2568,12 +2569,16 @@ static int file_map_prot_check(struct file *file, unsigned long prot, int shared } static int selinux_file_mmap(struct file *file, unsigned long reqprot, - unsigned long prot, unsigned long flags) + unsigned long prot, unsigned long flags, + unsigned long addr, unsigned long addr_only) { - int rc; + int rc = 0; + u32 sid = ((struct task_security_struct*)(current->security))->sid; - rc = secondary_ops->file_mmap(file, reqprot, prot, flags); - if (rc) + if (addr < mmap_min_addr) + rc = avc_has_perm(sid, sid, SECCLASS_MEMPROTECT, + MEMPROTECT__MMAP_ZERO, NULL); + if (rc || addr_only) return rc; if (selinux_checkreqprot) @@ -3124,17 +3129,19 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, /** * selinux_skb_extlbl_sid - Determine the external label of a packet * @skb: the packet - * @base_sid: the SELinux SID to use as a context for MLS only external labels * @sid: the packet's SID * * Description: * Check the various different forms of external packet labeling and determine - * the external SID for the packet. + * the external SID for the packet. If only one form of external labeling is + * present then it is used, if both labeled IPsec and NetLabel labels are + * present then the SELinux type information is taken from the labeled IPsec + * SA and the MLS sensitivity label information is taken from the NetLabel + * security attributes. This bit of "magic" is done in the call to + * selinux_netlbl_skbuff_getsid(). * */ -static void selinux_skb_extlbl_sid(struct sk_buff *skb, - u32 base_sid, - u32 *sid) +static void selinux_skb_extlbl_sid(struct sk_buff *skb, u32 *sid) { u32 xfrm_sid; u32 nlbl_sid; @@ -3142,10 +3149,9 @@ static void selinux_skb_extlbl_sid(struct sk_buff *skb, selinux_skb_xfrm_sid(skb, &xfrm_sid); if (selinux_netlbl_skbuff_getsid(skb, (xfrm_sid == SECSID_NULL ? - base_sid : xfrm_sid), + SECINITSID_NETMSG : xfrm_sid), &nlbl_sid) != 0) nlbl_sid = SECSID_NULL; - *sid = (nlbl_sid == SECSID_NULL ? xfrm_sid : nlbl_sid); } @@ -3690,7 +3696,7 @@ static int selinux_socket_getpeersec_dgram(struct socket *sock, struct sk_buff * if (sock && sock->sk->sk_family == PF_UNIX) selinux_get_inode_sid(SOCK_INODE(sock), &peer_secid); else if (skb) - selinux_skb_extlbl_sid(skb, SECINITSID_UNLABELED, &peer_secid); + selinux_skb_extlbl_sid(skb, &peer_secid); if (peer_secid == SECSID_NULL) err = -EINVAL; @@ -3751,7 +3757,7 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, u32 newsid; u32 peersid; - selinux_skb_extlbl_sid(skb, SECINITSID_UNLABELED, &peersid); + selinux_skb_extlbl_sid(skb, &peersid); if (peersid == SECSID_NULL) { req->secid = sksec->sid; req->peer_secid = SECSID_NULL; @@ -3789,7 +3795,7 @@ static void selinux_inet_conn_established(struct sock *sk, { struct sk_security_struct *sksec = sk->sk_security; - selinux_skb_extlbl_sid(skb, SECINITSID_UNLABELED, &sksec->peer_sid); + selinux_skb_extlbl_sid(skb, &sksec->peer_sid); } static void selinux_req_classify_flow(const struct request_sock *req, @@ -4626,7 +4632,7 @@ static int selinux_setprocattr(struct task_struct *p, if (p->ptrace & PT_PTRACED) { error = avc_has_perm_noaudit(tsec->ptrace_sid, sid, SECCLASS_PROCESS, - PROCESS__PTRACE, &avd); + PROCESS__PTRACE, 0, &avd); if (!error) tsec->sid = sid; task_unlock(p); diff --git a/security/selinux/include/av_perm_to_string.h b/security/selinux/include/av_perm_to_string.h index b83e74012a9..049bf69429b 100644 --- a/security/selinux/include/av_perm_to_string.h +++ b/security/selinux/include/av_perm_to_string.h @@ -158,3 +158,4 @@ S_(SECCLASS_KEY, KEY__CREATE, "create") S_(SECCLASS_DCCP_SOCKET, DCCP_SOCKET__NODE_BIND, "node_bind") S_(SECCLASS_DCCP_SOCKET, DCCP_SOCKET__NAME_CONNECT, "name_connect") + S_(SECCLASS_MEMPROTECT, MEMPROTECT__MMAP_ZERO, "mmap_zero") diff --git a/security/selinux/include/av_permissions.h b/security/selinux/include/av_permissions.h index 5fee1735bff..eda89a2ec63 100644 --- a/security/selinux/include/av_permissions.h +++ b/security/selinux/include/av_permissions.h @@ -823,3 +823,4 @@ #define DCCP_SOCKET__NAME_BIND 0x00200000UL #define DCCP_SOCKET__NODE_BIND 0x00400000UL #define DCCP_SOCKET__NAME_CONNECT 0x00800000UL +#define MEMPROTECT__MMAP_ZERO 0x00000001UL diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 6ed10c3d333..e145f6e13b0 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -102,9 +102,11 @@ void avc_audit(u32 ssid, u32 tsid, u16 tclass, u32 requested, struct av_decision *avd, int result, struct avc_audit_data *auditdata); +#define AVC_STRICT 1 /* Ignore permissive mode. */ int avc_has_perm_noaudit(u32 ssid, u32 tsid, - u16 tclass, u32 requested, - struct av_decision *avd); + u16 tclass, u32 requested, + unsigned flags, + struct av_decision *avd); int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, u32 requested, diff --git a/security/selinux/include/class_to_string.h b/security/selinux/include/class_to_string.h index 37879906844..e77de0e62ea 100644 --- a/security/selinux/include/class_to_string.h +++ b/security/selinux/include/class_to_string.h @@ -63,3 +63,4 @@ S_("key") S_(NULL) S_("dccp_socket") + S_("memprotect") diff --git a/security/selinux/include/flask.h b/security/selinux/include/flask.h index 35f309f4787..a9c2b20f14b 100644 --- a/security/selinux/include/flask.h +++ b/security/selinux/include/flask.h @@ -49,6 +49,7 @@ #define SECCLASS_PACKET 57 #define SECCLASS_KEY 58 #define SECCLASS_DCCP_SOCKET 60 +#define SECCLASS_MEMPROTECT 61 /* * Security identifier indices for initial entities diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index b94378afea2..83bdd4d2a29 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -41,6 +41,7 @@ extern int selinux_mls_enabled; int security_load_policy(void * data, size_t len); +#define SEL_VEC_MAX 32 struct av_decision { u32 allowed; u32 decided; @@ -87,6 +88,9 @@ int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid); +int security_get_classes(char ***classes, int *nclasses); +int security_get_permissions(char *class, char ***perms, int *nperms); + #define SECURITY_FS_USE_XATTR 1 /* use xattr */ #define SECURITY_FS_USE_TRANS 2 /* use transition SIDs, e.g. devpts/tmpfs */ #define SECURITY_FS_USE_TASK 3 /* use task SIDs, e.g. pipefs/sockfs */ diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c index e64eca246f1..8192e8bc9f5 100644 --- a/security/selinux/netlabel.c +++ b/security/selinux/netlabel.c @@ -158,9 +158,7 @@ int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u32 base_sid, u32 *sid) netlbl_secattr_init(&secattr); rc = netlbl_skbuff_getattr(skb, &secattr); if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) - rc = security_netlbl_secattr_to_sid(&secattr, - base_sid, - sid); + rc = security_netlbl_secattr_to_sid(&secattr, base_sid, sid); else *sid = SECSID_NULL; netlbl_secattr_destroy(&secattr); @@ -198,7 +196,7 @@ void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock) if (netlbl_sock_getattr(sk, &secattr) == 0 && secattr.flags != NETLBL_SECATTR_NONE && security_netlbl_secattr_to_sid(&secattr, - SECINITSID_UNLABELED, + SECINITSID_NETMSG, &nlbl_peer_sid) == 0) sksec->peer_sid = nlbl_peer_sid; netlbl_secattr_destroy(&secattr); @@ -295,38 +293,32 @@ int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, struct avc_audit_data *ad) { int rc; - u32 netlbl_sid; - u32 recv_perm; + u32 nlbl_sid; + u32 perm; - rc = selinux_netlbl_skbuff_getsid(skb, - SECINITSID_UNLABELED, - &netlbl_sid); + rc = selinux_netlbl_skbuff_getsid(skb, SECINITSID_NETMSG, &nlbl_sid); if (rc != 0) return rc; - - if (netlbl_sid == SECSID_NULL) - return 0; + if (nlbl_sid == SECSID_NULL) + nlbl_sid = SECINITSID_UNLABELED; switch (sksec->sclass) { case SECCLASS_UDP_SOCKET: - recv_perm = UDP_SOCKET__RECVFROM; + perm = UDP_SOCKET__RECVFROM; break; case SECCLASS_TCP_SOCKET: - recv_perm = TCP_SOCKET__RECVFROM; + perm = TCP_SOCKET__RECVFROM; break; default: - recv_perm = RAWIP_SOCKET__RECVFROM; + perm = RAWIP_SOCKET__RECVFROM; } - rc = avc_has_perm(sksec->sid, - netlbl_sid, - sksec->sclass, - recv_perm, - ad); + rc = avc_has_perm(sksec->sid, nlbl_sid, sksec->sclass, perm, ad); if (rc == 0) return 0; - netlbl_skbuff_err(skb, rc); + if (nlbl_sid != SECINITSID_UNLABELED) + netlbl_skbuff_err(skb, rc); return rc; } diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index aca099aa2ed..c9e92daedee 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -67,6 +67,10 @@ static struct dentry *bool_dir = NULL; static int bool_num = 0; static int *bool_pending_values = NULL; +/* global data for classes */ +static struct dentry *class_dir = NULL; +static unsigned long last_class_ino; + extern void selnl_notify_setenforce(int val); /* Check whether a task is allowed to use a security operation. */ @@ -106,6 +110,7 @@ static unsigned long sel_last_ino = SEL_INO_NEXT - 1; #define SEL_INITCON_INO_OFFSET 0x01000000 #define SEL_BOOL_INO_OFFSET 0x02000000 +#define SEL_CLASS_INO_OFFSET 0x04000000 #define SEL_INO_MASK 0x00ffffff #define TMPBUFLEN 12 @@ -237,6 +242,11 @@ static const struct file_operations sel_policyvers_ops = { /* declaration for sel_write_load */ static int sel_make_bools(void); +static int sel_make_classes(void); + +/* declaration for sel_make_class_dirs */ +static int sel_make_dir(struct inode *dir, struct dentry *dentry, + unsigned long *ino); static ssize_t sel_read_mls(struct file *filp, char __user *buf, size_t count, loff_t *ppos) @@ -287,10 +297,18 @@ static ssize_t sel_write_load(struct file * file, const char __user * buf, goto out; ret = sel_make_bools(); + if (ret) { + length = ret; + goto out1; + } + + ret = sel_make_classes(); if (ret) length = ret; else length = count; + +out1: audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_POLICY_LOAD, "policy loaded auid=%u", audit_get_loginuid(current->audit_context)); @@ -940,9 +958,8 @@ static const struct file_operations sel_commit_bools_ops = { .write = sel_commit_bools_write, }; -/* delete booleans - partial revoke() from - * fs/proc/generic.c proc_kill_inodes */ -static void sel_remove_bools(struct dentry *de) +/* partial revoke() from fs/proc/generic.c proc_kill_inodes */ +static void sel_remove_entries(struct dentry *de) { struct list_head *p, *node; struct super_block *sb = de->d_sb; @@ -998,7 +1015,7 @@ static int sel_make_bools(void) kfree(bool_pending_values); bool_pending_values = NULL; - sel_remove_bools(dir); + sel_remove_entries(dir); if (!(page = (char*)get_zeroed_page(GFP_KERNEL))) return -ENOMEM; @@ -1048,7 +1065,7 @@ out: return ret; err: kfree(values); - sel_remove_bools(dir); + sel_remove_entries(dir); ret = -ENOMEM; goto out; } @@ -1294,7 +1311,227 @@ out: return ret; } -static int sel_make_dir(struct inode *dir, struct dentry *dentry) +static inline unsigned int sel_div(unsigned long a, unsigned long b) +{ + return a / b - (a % b < 0); +} + +static inline unsigned long sel_class_to_ino(u16 class) +{ + return (class * (SEL_VEC_MAX + 1)) | SEL_CLASS_INO_OFFSET; +} + +static inline u16 sel_ino_to_class(unsigned long ino) +{ + return sel_div(ino & SEL_INO_MASK, SEL_VEC_MAX + 1); +} + +static inline unsigned long sel_perm_to_ino(u16 class, u32 perm) +{ + return (class * (SEL_VEC_MAX + 1) + perm) | SEL_CLASS_INO_OFFSET; +} + +static inline u32 sel_ino_to_perm(unsigned long ino) +{ + return (ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1); +} + +static ssize_t sel_read_class(struct file * file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rc, len; + char *page; + unsigned long ino = file->f_path.dentry->d_inode->i_ino; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + rc = -ENOMEM; + goto out; + } + + len = snprintf(page, PAGE_SIZE, "%d", sel_ino_to_class(ino)); + rc = simple_read_from_buffer(buf, count, ppos, page, len); + free_page((unsigned long)page); +out: + return rc; +} + +static const struct file_operations sel_class_ops = { + .read = sel_read_class, +}; + +static ssize_t sel_read_perm(struct file * file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rc, len; + char *page; + unsigned long ino = file->f_path.dentry->d_inode->i_ino; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + rc = -ENOMEM; + goto out; + } + + len = snprintf(page, PAGE_SIZE,"%d", sel_ino_to_perm(ino)); + rc = simple_read_from_buffer(buf, count, ppos, page, len); + free_page((unsigned long)page); +out: + return rc; +} + +static const struct file_operations sel_perm_ops = { + .read = sel_read_perm, +}; + +static int sel_make_perm_files(char *objclass, int classvalue, + struct dentry *dir) +{ + int i, rc = 0, nperms; + char **perms; + + rc = security_get_permissions(objclass, &perms, &nperms); + if (rc) + goto out; + + for (i = 0; i < nperms; i++) { + struct inode *inode; + struct dentry *dentry; + + dentry = d_alloc_name(dir, perms[i]); + if (!dentry) { + rc = -ENOMEM; + goto out1; + } + + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) { + rc = -ENOMEM; + goto out1; + } + inode->i_fop = &sel_perm_ops; + /* i+1 since perm values are 1-indexed */ + inode->i_ino = sel_perm_to_ino(classvalue, i+1); + d_add(dentry, inode); + } + +out1: + for (i = 0; i < nperms; i++) + kfree(perms[i]); + kfree(perms); +out: + return rc; +} + +static int sel_make_class_dir_entries(char *classname, int index, + struct dentry *dir) +{ + struct dentry *dentry = NULL; + struct inode *inode = NULL; + int rc; + + dentry = d_alloc_name(dir, "index"); + if (!dentry) { + rc = -ENOMEM; + goto out; + } + + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) { + rc = -ENOMEM; + goto out; + } + + inode->i_fop = &sel_class_ops; + inode->i_ino = sel_class_to_ino(index); + d_add(dentry, inode); + + dentry = d_alloc_name(dir, "perms"); + if (!dentry) { + rc = -ENOMEM; + goto out; + } + + rc = sel_make_dir(dir->d_inode, dentry, &last_class_ino); + if (rc) + goto out; + + rc = sel_make_perm_files(classname, index, dentry); + +out: + return rc; +} + +static void sel_remove_classes(void) +{ + struct list_head *class_node; + + list_for_each(class_node, &class_dir->d_subdirs) { + struct dentry *class_subdir = list_entry(class_node, + struct dentry, d_u.d_child); + struct list_head *class_subdir_node; + + list_for_each(class_subdir_node, &class_subdir->d_subdirs) { + struct dentry *d = list_entry(class_subdir_node, + struct dentry, d_u.d_child); + + if (d->d_inode) + if (d->d_inode->i_mode & S_IFDIR) + sel_remove_entries(d); + } + + sel_remove_entries(class_subdir); + } + + sel_remove_entries(class_dir); +} + +static int sel_make_classes(void) +{ + int rc = 0, nclasses, i; + char **classes; + + /* delete any existing entries */ + sel_remove_classes(); + + rc = security_get_classes(&classes, &nclasses); + if (rc < 0) + goto out; + + /* +2 since classes are 1-indexed */ + last_class_ino = sel_class_to_ino(nclasses+2); + + for (i = 0; i < nclasses; i++) { + struct dentry *class_name_dir; + + class_name_dir = d_alloc_name(class_dir, classes[i]); + if (!class_name_dir) { + rc = -ENOMEM; + goto out1; + } + + rc = sel_make_dir(class_dir->d_inode, class_name_dir, + &last_class_ino); + if (rc) + goto out1; + + /* i+1 since class values are 1-indexed */ + rc = sel_make_class_dir_entries(classes[i], i+1, + class_name_dir); + if (rc) + goto out1; + } + +out1: + for (i = 0; i < nclasses; i++) + kfree(classes[i]); + kfree(classes); +out: + return rc; +} + +static int sel_make_dir(struct inode *dir, struct dentry *dentry, + unsigned long *ino) { int ret = 0; struct inode *inode; @@ -1306,7 +1543,7 @@ static int sel_make_dir(struct inode *dir, struct dentry *dentry) } inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; - inode->i_ino = ++sel_last_ino; + inode->i_ino = ++(*ino); /* directory inodes start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); d_add(dentry, inode); @@ -1352,7 +1589,7 @@ static int sel_fill_super(struct super_block * sb, void * data, int silent) goto err; } - ret = sel_make_dir(root_inode, dentry); + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); if (ret) goto err; @@ -1385,7 +1622,7 @@ static int sel_fill_super(struct super_block * sb, void * data, int silent) goto err; } - ret = sel_make_dir(root_inode, dentry); + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); if (ret) goto err; @@ -1399,7 +1636,7 @@ static int sel_fill_super(struct super_block * sb, void * data, int silent) goto err; } - ret = sel_make_dir(root_inode, dentry); + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); if (ret) goto err; @@ -1407,6 +1644,18 @@ static int sel_fill_super(struct super_block * sb, void * data, int silent) if (ret) goto err; + dentry = d_alloc_name(sb->s_root, "class"); + if (!dentry) { + ret = -ENOMEM; + goto err; + } + + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); + if (ret) + goto err; + + class_dir = dentry; + out: return ret; err: diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 0ac1021734c..f05f97a2bc3 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -21,6 +21,7 @@ */ #include <linux/kernel.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/errno.h> @@ -598,6 +599,7 @@ void policydb_destroy(struct policydb *p) struct range_trans *rt, *lrt = NULL; for (i = 0; i < SYM_NUM; i++) { + cond_resched(); hashtab_map(p->symtab[i].table, destroy_f[i], NULL); hashtab_destroy(p->symtab[i].table); } @@ -612,6 +614,7 @@ void policydb_destroy(struct policydb *p) avtab_destroy(&p->te_avtab); for (i = 0; i < OCON_NUM; i++) { + cond_resched(); c = p->ocontexts[i]; while (c) { ctmp = c; @@ -623,6 +626,7 @@ void policydb_destroy(struct policydb *p) g = p->genfs; while (g) { + cond_resched(); kfree(g->fstype); c = g->head; while (c) { @@ -639,18 +643,21 @@ void policydb_destroy(struct policydb *p) cond_policydb_destroy(p); for (tr = p->role_tr; tr; tr = tr->next) { + cond_resched(); kfree(ltr); ltr = tr; } kfree(ltr); for (ra = p->role_allow; ra; ra = ra -> next) { + cond_resched(); kfree(lra); lra = ra; } kfree(lra); for (rt = p->range_tr; rt; rt = rt -> next) { + cond_resched(); if (lrt) { ebitmap_destroy(&lrt->target_range.level[0].cat); ebitmap_destroy(&lrt->target_range.level[1].cat); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 40660ffd49b..b5f017f07a7 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -1587,19 +1587,18 @@ int security_get_user_sids(u32 fromsid, u32 *nel) { struct context *fromcon, usercon; - u32 *mysids, *mysids2, sid; + u32 *mysids = NULL, *mysids2, sid; u32 mynel = 0, maxnel = SIDS_NEL; struct user_datum *user; struct role_datum *role; - struct av_decision avd; struct ebitmap_node *rnode, *tnode; int rc = 0, i, j; - if (!ss_initialized) { - *sids = NULL; - *nel = 0; + *sids = NULL; + *nel = 0; + + if (!ss_initialized) goto out; - } POLICY_RDLOCK; @@ -1635,17 +1634,9 @@ int security_get_user_sids(u32 fromsid, if (mls_setup_user_range(fromcon, user, &usercon)) continue; - rc = context_struct_compute_av(fromcon, &usercon, - SECCLASS_PROCESS, - PROCESS__TRANSITION, - &avd); - if (rc || !(avd.allowed & PROCESS__TRANSITION)) - continue; rc = sidtab_context_to_sid(&sidtab, &usercon, &sid); - if (rc) { - kfree(mysids); + if (rc) goto out_unlock; - } if (mynel < maxnel) { mysids[mynel++] = sid; } else { @@ -1653,7 +1644,6 @@ int security_get_user_sids(u32 fromsid, mysids2 = kcalloc(maxnel, sizeof(*mysids2), GFP_ATOMIC); if (!mysids2) { rc = -ENOMEM; - kfree(mysids); goto out_unlock; } memcpy(mysids2, mysids, mynel * sizeof(*mysids2)); @@ -1664,11 +1654,32 @@ int security_get_user_sids(u32 fromsid, } } - *sids = mysids; - *nel = mynel; - out_unlock: POLICY_RDUNLOCK; + if (rc || !mynel) { + kfree(mysids); + goto out; + } + + mysids2 = kcalloc(mynel, sizeof(*mysids2), GFP_KERNEL); + if (!mysids2) { + rc = -ENOMEM; + kfree(mysids); + goto out; + } + for (i = 0, j = 0; i < mynel; i++) { + rc = avc_has_perm_noaudit(fromsid, mysids[i], + SECCLASS_PROCESS, + PROCESS__TRANSITION, AVC_STRICT, + NULL); + if (!rc) + mysids2[j++] = mysids[i]; + cond_resched(); + } + rc = 0; + kfree(mysids); + *sids = mysids2; + *nel = j; out: return rc; } @@ -1996,6 +2007,101 @@ out: return rc; } +static int get_classes_callback(void *k, void *d, void *args) +{ + struct class_datum *datum = d; + char *name = k, **classes = args; + int value = datum->value - 1; + + classes[value] = kstrdup(name, GFP_ATOMIC); + if (!classes[value]) + return -ENOMEM; + + return 0; +} + +int security_get_classes(char ***classes, int *nclasses) +{ + int rc = -ENOMEM; + + POLICY_RDLOCK; + + *nclasses = policydb.p_classes.nprim; + *classes = kcalloc(*nclasses, sizeof(*classes), GFP_ATOMIC); + if (!*classes) + goto out; + + rc = hashtab_map(policydb.p_classes.table, get_classes_callback, + *classes); + if (rc < 0) { + int i; + for (i = 0; i < *nclasses; i++) + kfree((*classes)[i]); + kfree(*classes); + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +static int get_permissions_callback(void *k, void *d, void *args) +{ + struct perm_datum *datum = d; + char *name = k, **perms = args; + int value = datum->value - 1; + + perms[value] = kstrdup(name, GFP_ATOMIC); + if (!perms[value]) + return -ENOMEM; + + return 0; +} + +int security_get_permissions(char *class, char ***perms, int *nperms) +{ + int rc = -ENOMEM, i; + struct class_datum *match; + + POLICY_RDLOCK; + + match = hashtab_search(policydb.p_classes.table, class); + if (!match) { + printk(KERN_ERR "%s: unrecognized class %s\n", + __FUNCTION__, class); + rc = -EINVAL; + goto out; + } + + *nperms = match->permissions.nprim; + *perms = kcalloc(*nperms, sizeof(*perms), GFP_ATOMIC); + if (!*perms) + goto out; + + if (match->comdatum) { + rc = hashtab_map(match->comdatum->permissions.table, + get_permissions_callback, *perms); + if (rc < 0) + goto err; + } + + rc = hashtab_map(match->permissions.table, get_permissions_callback, + *perms); + if (rc < 0) + goto err; + +out: + POLICY_RDUNLOCK; + return rc; + +err: + POLICY_RDUNLOCK; + for (i = 0; i < *nperms; i++) + kfree((*perms)[i]); + kfree(*perms); + return rc; +} + struct selinux_audit_rule { u32 au_seqno; struct context au_ctxt; |