diff options
Diffstat (limited to 'security/selinux')
-rw-r--r-- | security/selinux/Makefile | 23 | ||||
-rw-r--r-- | security/selinux/avc.c | 25 | ||||
-rw-r--r-- | security/selinux/exports.c | 49 | ||||
-rw-r--r-- | security/selinux/hooks.c | 341 | ||||
-rw-r--r-- | security/selinux/include/classmap.h | 18 | ||||
-rw-r--r-- | security/selinux/include/security.h | 23 | ||||
-rw-r--r-- | security/selinux/netnode.c | 2 | ||||
-rw-r--r-- | security/selinux/selinuxfs.c | 221 | ||||
-rw-r--r-- | security/selinux/ss/Makefile | 9 | ||||
-rw-r--r-- | security/selinux/ss/avtab.c | 85 | ||||
-rw-r--r-- | security/selinux/ss/avtab.h | 3 | ||||
-rw-r--r-- | security/selinux/ss/conditional.c | 188 | ||||
-rw-r--r-- | security/selinux/ss/conditional.h | 2 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.c | 81 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.h | 1 | ||||
-rw-r--r-- | security/selinux/ss/policydb.c | 1493 | ||||
-rw-r--r-- | security/selinux/ss/policydb.h | 24 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 70 | ||||
-rw-r--r-- | security/selinux/ss/status.c | 126 | ||||
-rw-r--r-- | security/selinux/ss/symtab.c | 2 |
20 files changed, 2172 insertions, 614 deletions
diff --git a/security/selinux/Makefile b/security/selinux/Makefile index f013982df417..ad5cd76ec231 100644 --- a/security/selinux/Makefile +++ b/security/selinux/Makefile @@ -2,29 +2,24 @@ # Makefile for building the SELinux module as part of the kernel tree. # -obj-$(CONFIG_SECURITY_SELINUX) := selinux.o ss/ - -selinux-y := avc.o \ - hooks.o \ - selinuxfs.o \ - netlink.o \ - nlmsgtab.o \ - netif.o \ - netnode.o \ - netport.o \ - exports.o +obj-$(CONFIG_SECURITY_SELINUX) := selinux.o + +selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \ + netnode.o netport.o exports.o \ + ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \ + ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/status.o selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o selinux-$(CONFIG_NETLABEL) += netlabel.o -EXTRA_CFLAGS += -Isecurity/selinux -Isecurity/selinux/include +ccflags-y := -Isecurity/selinux -Isecurity/selinux/include -$(obj)/avc.o: $(obj)/flask.h +$(addprefix $(obj)/,$(selinux-y)): $(obj)/flask.h quiet_cmd_flask = GEN $(obj)/flask.h $(obj)/av_permissions.h cmd_flask = scripts/selinux/genheaders/genheaders $(obj)/flask.h $(obj)/av_permissions.h -targets += flask.h +targets += flask.h av_permissions.h $(obj)/flask.h: $(src)/include/classmap.h FORCE $(call if_changed,flask) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 7f1a304712a9..9da6420e2056 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -288,7 +288,6 @@ static struct avc_node *avc_alloc_node(void) if (!node) goto out; - INIT_RCU_HEAD(&node->rhead); INIT_HLIST_NODE(&node->list); avc_cache_stats_incr(allocations); @@ -489,9 +488,29 @@ void avc_audit(u32 ssid, u32 tsid, struct common_audit_data stack_data; u32 denied, audited; denied = requested & ~avd->allowed; - if (denied) + if (denied) { audited = denied & avd->auditdeny; - else if (result) + /* + * a->selinux_audit_data.auditdeny is TRICKY! Setting a bit in + * this field means that ANY denials should NOT be audited if + * the policy contains an explicit dontaudit rule for that + * permission. Take notice that this is unrelated to the + * actual permissions that were denied. As an example lets + * assume: + * + * denied == READ + * avd.auditdeny & ACCESS == 0 (not set means explicit rule) + * selinux_audit_data.auditdeny & ACCESS == 1 + * + * We will NOT audit the denial even though the denied + * permission was READ and the auditdeny checks were for + * ACCESS + */ + if (a && + a->selinux_audit_data.auditdeny && + !(a->selinux_audit_data.auditdeny & avd->auditdeny)) + audited = 0; + } else if (result) audited = denied = requested; else audited = requested & avd->auditallow; diff --git a/security/selinux/exports.c b/security/selinux/exports.c index c0a454aee1e0..90664385dead 100644 --- a/security/selinux/exports.c +++ b/security/selinux/exports.c @@ -11,58 +11,9 @@ * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. */ -#include <linux/types.h> -#include <linux/kernel.h> #include <linux/module.h> -#include <linux/selinux.h> -#include <linux/fs.h> -#include <linux/ipc.h> -#include <asm/atomic.h> #include "security.h" -#include "objsec.h" - -/* SECMARK reference count */ -extern atomic_t selinux_secmark_refcount; - -int selinux_string_to_sid(char *str, u32 *sid) -{ - if (selinux_enabled) - return security_context_to_sid(str, strlen(str), sid); - else { - *sid = 0; - return 0; - } -} -EXPORT_SYMBOL_GPL(selinux_string_to_sid); - -int selinux_secmark_relabel_packet_permission(u32 sid) -{ - if (selinux_enabled) { - const struct task_security_struct *__tsec; - u32 tsid; - - __tsec = current_security(); - tsid = __tsec->sid; - - return avc_has_perm(tsid, sid, SECCLASS_PACKET, - PACKET__RELABELTO, NULL); - } - return 0; -} -EXPORT_SYMBOL_GPL(selinux_secmark_relabel_packet_permission); - -void selinux_secmark_refcount_inc(void) -{ - atomic_inc(&selinux_secmark_refcount); -} -EXPORT_SYMBOL_GPL(selinux_secmark_refcount_inc); - -void selinux_secmark_refcount_dec(void) -{ - atomic_dec(&selinux_secmark_refcount); -} -EXPORT_SYMBOL_GPL(selinux_secmark_refcount_dec); bool selinux_is_enabled(void) { diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 5c9f25ba1c95..d9154cf90ae1 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -87,9 +87,6 @@ #include "netlabel.h" #include "audit.h" -#define XATTR_SELINUX_SUFFIX "selinux" -#define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX - #define NUM_SEL_MNT_OPTS 5 extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); @@ -188,7 +185,7 @@ static inline u32 task_sid(const struct task_struct *task) */ static inline u32 current_sid(void) { - const struct task_security_struct *tsec = current_cred()->security; + const struct task_security_struct *tsec = current_security(); return tsec->sid; } @@ -279,32 +276,6 @@ static void superblock_free_security(struct super_block *sb) kfree(sbsec); } -static int sk_alloc_security(struct sock *sk, int family, gfp_t priority) -{ - struct sk_security_struct *sksec; - - sksec = kzalloc(sizeof(*sksec), priority); - if (!sksec) - return -ENOMEM; - - sksec->peer_sid = SECINITSID_UNLABELED; - sksec->sid = SECINITSID_UNLABELED; - sk->sk_security = sksec; - - selinux_netlbl_sk_security_reset(sksec); - - return 0; -} - -static void sk_free_security(struct sock *sk) -{ - struct sk_security_struct *sksec = sk->sk_security; - - sk->sk_security = NULL; - selinux_netlbl_sk_security_free(sksec); - kfree(sksec); -} - /* The security server must be initialized before any labeling or access decisions can be provided. */ extern int ss_initialized; @@ -1584,8 +1555,7 @@ static int may_create(struct inode *dir, struct dentry *dentry, u16 tclass) { - const struct cred *cred = current_cred(); - const struct task_security_struct *tsec = cred->security; + const struct task_security_struct *tsec = current_security(); struct inode_security_struct *dsec; struct superblock_security_struct *sbsec; u32 sid, newsid; @@ -1806,27 +1776,9 @@ static inline u32 open_file_to_av(struct file *file) { u32 av = file_to_av(file); - if (selinux_policycap_openperm) { - mode_t mode = file->f_path.dentry->d_inode->i_mode; - /* - * lnk files and socks do not really have an 'open' - */ - if (S_ISREG(mode)) - av |= FILE__OPEN; - else if (S_ISCHR(mode)) - av |= CHR_FILE__OPEN; - else if (S_ISBLK(mode)) - av |= BLK_FILE__OPEN; - else if (S_ISFIFO(mode)) - av |= FIFO_FILE__OPEN; - else if (S_ISDIR(mode)) - av |= DIR__OPEN; - else if (S_ISSOCK(mode)) - av |= SOCK_FILE__OPEN; - else - printk(KERN_ERR "SELinux: WARNING: inside %s with " - "unknown mode:%o\n", __func__, mode); - } + if (selinux_policycap_openperm) + av |= FILE__OPEN; + return av; } @@ -2183,8 +2135,7 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) static int selinux_bprm_secureexec(struct linux_binprm *bprm) { - const struct cred *cred = current_cred(); - const struct task_security_struct *tsec = cred->security; + const struct task_security_struct *tsec = current_security(); u32 sid, osid; int atsecure = 0; @@ -2219,8 +2170,9 @@ static inline void flush_unauthorized_files(const struct cred *cred, tty = get_current_tty(); if (tty) { - file_list_lock(); + spin_lock(&tty_files_lock); if (!list_empty(&tty->tty_files)) { + struct tty_file_private *file_priv; struct inode *inode; /* Revalidate access to controlling tty. @@ -2228,14 +2180,16 @@ static inline void flush_unauthorized_files(const struct cred *cred, than using file_has_perm, as this particular open file may belong to another process and we are only interested in the inode-based check here. */ - file = list_first_entry(&tty->tty_files, struct file, f_u.fu_list); + file_priv = list_first_entry(&tty->tty_files, + struct tty_file_private, list); + file = file_priv->file; inode = file->f_path.dentry->d_inode; if (inode_has_perm(cred, inode, FILE__READ | FILE__WRITE, NULL)) { drop_tty = 1; } } - file_list_unlock(); + spin_unlock(&tty_files_lock); tty_kref_put(tty); } /* Reset controlling tty. */ @@ -2333,12 +2287,15 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm) rc = avc_has_perm(new_tsec->osid, new_tsec->sid, SECCLASS_PROCESS, PROCESS__RLIMITINH, NULL); if (rc) { + /* protect against do_prlimit() */ + task_lock(current); for (i = 0; i < RLIM_NLIMITS; i++) { rlim = current->signal->rlim + i; initrlim = init_task.signal->rlim + i; rlim->rlim_cur = min(rlim->rlim_max, initrlim->rlim_cur); } - update_rlimit_cpu(current->signal->rlim[RLIMIT_CPU].rlim_cur); + task_unlock(current); + update_rlimit_cpu(current, rlimit(RLIMIT_CPU)); } } @@ -2559,8 +2516,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, char **name, void **value, size_t *len) { - const struct cred *cred = current_cred(); - const struct task_security_struct *tsec = cred->security; + const struct task_security_struct *tsec = current_security(); struct inode_security_struct *dsec; struct superblock_security_struct *sbsec; u32 sid, newsid, clen; @@ -2676,14 +2632,26 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *na static int selinux_inode_permission(struct inode *inode, int mask) { const struct cred *cred = current_cred(); + struct common_audit_data ad; + u32 perms; + bool from_access; - if (!mask) { - /* No permission to check. Existence test. */ + from_access = mask & MAY_ACCESS; + mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); + + /* No permission to check. Existence test. */ + if (!mask) return 0; - } - return inode_has_perm(cred, inode, - file_mask_to_av(inode->i_mode, mask), NULL); + COMMON_AUDIT_DATA_INIT(&ad, FS); + ad.u.fs.inode = inode; + + if (from_access) + ad.selinux_audit_data.auditdeny |= FILE__AUDIT_ACCESS; + + perms = file_mask_to_av(inode->i_mode, mask); + + return inode_has_perm(cred, inode, perms, &ad); } static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) @@ -3371,25 +3339,26 @@ static int selinux_task_getioprio(struct task_struct *p) return current_has_perm(p, PROCESS__GETSCHED); } -static int selinux_task_setrlimit(unsigned int resource, struct rlimit *new_rlim) +static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource, + struct rlimit *new_rlim) { - struct rlimit *old_rlim = current->signal->rlim + resource; + struct rlimit *old_rlim = p->signal->rlim + resource; /* Control the ability to change the hard limit (whether lowering or raising it), so that the hard limit can later be used as a safe reset point for the soft limit upon context transitions. See selinux_bprm_committing_creds. */ if (old_rlim->rlim_max != new_rlim->rlim_max) - return current_has_perm(current, PROCESS__SETRLIMIT); + return current_has_perm(p, PROCESS__SETRLIMIT); return 0; } -static int selinux_task_setscheduler(struct task_struct *p, int policy, struct sched_param *lp) +static int selinux_task_setscheduler(struct task_struct *p) { int rc; - rc = cap_task_setscheduler(p, policy, lp); + rc = cap_task_setscheduler(p); if (rc) return rc; @@ -3671,71 +3640,54 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) } /* socket security operations */ -static int socket_has_perm(struct task_struct *task, struct socket *sock, - u32 perms) + +static u32 socket_sockcreate_sid(const struct task_security_struct *tsec) { - struct inode_security_struct *isec; - struct common_audit_data ad; - u32 sid; - int err = 0; + return tsec->sockcreate_sid ? : tsec->sid; +} - isec = SOCK_INODE(sock)->i_security; +static int sock_has_perm(struct task_struct *task, struct sock *sk, u32 perms) +{ + struct sk_security_struct *sksec = sk->sk_security; + struct common_audit_data ad; + u32 tsid = task_sid(task); - if (isec->sid == SECINITSID_KERNEL) - goto out; - sid = task_sid(task); + if (sksec->sid == SECINITSID_KERNEL) + return 0; COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.sk = sock->sk; - err = avc_has_perm(sid, isec->sid, isec->sclass, perms, &ad); + ad.u.net.sk = sk; -out: - return err; + return avc_has_perm(tsid, sksec->sid, sksec->sclass, perms, &ad); } static int selinux_socket_create(int family, int type, int protocol, int kern) { - const struct cred *cred = current_cred(); - const struct task_security_struct *tsec = cred->security; - u32 sid, newsid; + const struct task_security_struct *tsec = current_security(); + u32 newsid; u16 secclass; - int err = 0; if (kern) - goto out; - - sid = tsec->sid; - newsid = tsec->sockcreate_sid ?: sid; + return 0; + newsid = socket_sockcreate_sid(tsec); secclass = socket_type_to_security_class(family, type, protocol); - err = avc_has_perm(sid, newsid, secclass, SOCKET__CREATE, NULL); - -out: - return err; + return avc_has_perm(tsec->sid, newsid, secclass, SOCKET__CREATE, NULL); } static int selinux_socket_post_create(struct socket *sock, int family, int type, int protocol, int kern) { - const struct cred *cred = current_cred(); - const struct task_security_struct *tsec = cred->security; - struct inode_security_struct *isec; + const struct task_security_struct *tsec = current_security(); + struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; struct sk_security_struct *sksec; - u32 sid, newsid; int err = 0; - sid = tsec->sid; - newsid = tsec->sockcreate_sid; - - isec = SOCK_INODE(sock)->i_security; - if (kern) isec->sid = SECINITSID_KERNEL; - else if (newsid) - isec->sid = newsid; else - isec->sid = sid; + isec->sid = socket_sockcreate_sid(tsec); isec->sclass = socket_type_to_security_class(family, type, protocol); isec->initialized = 1; @@ -3756,10 +3708,11 @@ static int selinux_socket_post_create(struct socket *sock, int family, static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) { + struct sock *sk = sock->sk; u16 family; int err; - err = socket_has_perm(current, sock, SOCKET__BIND); + err = sock_has_perm(current, sk, SOCKET__BIND); if (err) goto out; @@ -3768,19 +3721,16 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in * Multiple address binding for SCTP is not supported yet: we just * check the first address now. */ - family = sock->sk->sk_family; + family = sk->sk_family; if (family == PF_INET || family == PF_INET6) { char *addrp; - struct inode_security_struct *isec; + struct sk_security_struct *sksec = sk->sk_security; struct common_audit_data ad; struct sockaddr_in *addr4 = NULL; struct sockaddr_in6 *addr6 = NULL; unsigned short snum; - struct sock *sk = sock->sk; u32 sid, node_perm; - isec = SOCK_INODE(sock)->i_security; - if (family == PF_INET) { addr4 = (struct sockaddr_in *)address; snum = ntohs(addr4->sin_port); @@ -3804,15 +3754,15 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in COMMON_AUDIT_DATA_INIT(&ad, NET); ad.u.net.sport = htons(snum); ad.u.net.family = family; - err = avc_has_perm(isec->sid, sid, - isec->sclass, + err = avc_has_perm(sksec->sid, sid, + sksec->sclass, SOCKET__NAME_BIND, &ad); if (err) goto out; } } - switch (isec->sclass) { + switch (sksec->sclass) { case SECCLASS_TCP_SOCKET: node_perm = TCP_SOCKET__NODE_BIND; break; @@ -3843,8 +3793,8 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in else ipv6_addr_copy(&ad.u.net.v6info.saddr, &addr6->sin6_addr); - err = avc_has_perm(isec->sid, sid, - isec->sclass, node_perm, &ad); + err = avc_has_perm(sksec->sid, sid, + sksec->sclass, node_perm, &ad); if (err) goto out; } @@ -3855,19 +3805,18 @@ out: static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen) { struct sock *sk = sock->sk; - struct inode_security_struct *isec; + struct sk_security_struct *sksec = sk->sk_security; int err; - err = socket_has_perm(current, sock, SOCKET__CONNECT); + err = sock_has_perm(current, sk, SOCKET__CONNECT); if (err) return err; /* * If a TCP or DCCP socket, check name_connect permission for the port. */ - isec = SOCK_INODE(sock)->i_security; - if (isec->sclass == SECCLASS_TCP_SOCKET || - isec->sclass == SECCLASS_DCCP_SOCKET) { + if (sksec->sclass == SECCLASS_TCP_SOCKET || + sksec->sclass == SECCLASS_DCCP_SOCKET) { struct common_audit_data ad; struct sockaddr_in *addr4 = NULL; struct sockaddr_in6 *addr6 = NULL; @@ -3890,13 +3839,13 @@ static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, if (err) goto out; - perm = (isec->sclass == SECCLASS_TCP_SOCKET) ? + perm = (sksec->sclass == SECCLASS_TCP_SOCKET) ? TCP_SOCKET__NAME_CONNECT : DCCP_SOCKET__NAME_CONNECT; COMMON_AUDIT_DATA_INIT(&ad, NET); ad.u.net.dport = htons(snum); ad.u.net.family = sk->sk_family; - err = avc_has_perm(isec->sid, sid, isec->sclass, perm, &ad); + err = avc_has_perm(sksec->sid, sid, sksec->sclass, perm, &ad); if (err) goto out; } @@ -3909,7 +3858,7 @@ out: static int selinux_socket_listen(struct socket *sock, int backlog) { - return socket_has_perm(current, sock, SOCKET__LISTEN); + return sock_has_perm(current, sock->sk, SOCKET__LISTEN); } static int selinux_socket_accept(struct socket *sock, struct socket *newsock) @@ -3918,7 +3867,7 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock) struct inode_security_struct *isec; struct inode_security_struct *newisec; - err = socket_has_perm(current, sock, SOCKET__ACCEPT); + err = sock_has_perm(current, sock->sk, SOCKET__ACCEPT); if (err) return err; @@ -3935,30 +3884,30 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock) static int selinux_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { - return socket_has_perm(current, sock, SOCKET__WRITE); + return sock_has_perm(current, sock->sk, SOCKET__WRITE); } static int selinux_socket_recvmsg(struct socket *sock, struct msghdr *msg, int size, int flags) { - return socket_has_perm(current, sock, SOCKET__READ); + return sock_has_perm(current, sock->sk, SOCKET__READ); } static int selinux_socket_getsockname(struct socket *sock) { - return socket_has_perm(current, sock, SOCKET__GETATTR); + return sock_has_perm(current, sock->sk, SOCKET__GETATTR); } static int selinux_socket_getpeername(struct socket *sock) { - return socket_has_perm(current, sock, SOCKET__GETATTR); + return sock_has_perm(current, sock->sk, SOCKET__GETATTR); } static int selinux_socket_setsockopt(struct socket *sock, int level, int optname) { int err; - err = socket_has_perm(current, sock, SOCKET__SETOPT); + err = sock_has_perm(current, sock->sk, SOCKET__SETOPT); if (err) return err; @@ -3968,68 +3917,58 @@ static int selinux_socket_setsockopt(struct socket *sock, int level, int optname static int selinux_socket_getsockopt(struct socket *sock, int level, int optname) { - return socket_has_perm(current, sock, SOCKET__GETOPT); + return sock_has_perm(current, sock->sk, SOCKET__GETOPT); } static int selinux_socket_shutdown(struct socket *sock, int how) { - return socket_has_perm(current, sock, SOCKET__SHUTDOWN); + return sock_has_perm(current, sock->sk, SOCKET__SHUTDOWN); } static int selinux_socket_unix_stream_connect(struct socket *sock, struct socket *other, struct sock *newsk) { - struct sk_security_struct *sksec; - struct inode_security_struct *isec; - struct inode_security_struct *other_isec; + struct sk_security_struct *sksec_sock = sock->sk->sk_security; + struct sk_security_struct *sksec_other = other->sk->sk_security; + struct sk_security_struct *sksec_new = newsk->sk_security; struct common_audit_data ad; int err; - isec = SOCK_INODE(sock)->i_security; - other_isec = SOCK_INODE(other)->i_security; - COMMON_AUDIT_DATA_INIT(&ad, NET); ad.u.net.sk = other->sk; - err = avc_has_perm(isec->sid, other_isec->sid, - isec->sclass, + err = avc_has_perm(sksec_sock->sid, sksec_other->sid, + sksec_other->sclass, UNIX_STREAM_SOCKET__CONNECTTO, &ad); if (err) return err; - /* connecting socket */ - sksec = sock->sk->sk_security; - sksec->peer_sid = other_isec->sid; - /* server child socket */ - sksec = newsk->sk_security; - sksec->peer_sid = isec->sid; - err = security_sid_mls_copy(other_isec->sid, sksec->peer_sid, &sksec->sid); + sksec_new->peer_sid = sksec_sock->sid; + err = security_sid_mls_copy(sksec_other->sid, sksec_sock->sid, + &sksec_new->sid); + if (err) + return err; - return err; + /* connecting socket */ + sksec_sock->peer_sid = sksec_new->sid; + + return 0; } static int selinux_socket_unix_may_send(struct socket *sock, struct socket *other) { - struct inode_security_struct *isec; - struct inode_security_struct *other_isec; + struct sk_security_struct *ssec = sock->sk->sk_security; + struct sk_security_struct *osec = other->sk->sk_security; struct common_audit_data ad; - int err; - - isec = SOCK_INODE(sock)->i_security; - other_isec = SOCK_INODE(other)->i_security; COMMON_AUDIT_DATA_INIT(&ad, NET); ad.u.net.sk = other->sk; - err = avc_has_perm(isec->sid, other_isec->sid, - isec->sclass, SOCKET__SENDTO, &ad); - if (err) - return err; - - return 0; + return avc_has_perm(ssec->sid, osec->sid, osec->sclass, SOCKET__SENDTO, + &ad); } static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family, @@ -4168,26 +4107,18 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op int err = 0; char *scontext; u32 scontext_len; - struct sk_security_struct *sksec; - struct inode_security_struct *isec; + struct sk_security_struct *sksec = sock->sk->sk_security; u32 peer_sid = SECSID_NULL; - isec = SOCK_INODE(sock)->i_security; - - if (isec->sclass == SECCLASS_UNIX_STREAM_SOCKET || - isec->sclass == SECCLASS_TCP_SOCKET) { - sksec = sock->sk->sk_security; + if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET || + sksec->sclass == SECCLASS_TCP_SOCKET) peer_sid = sksec->peer_sid; - } - if (peer_sid == SECSID_NULL) { - err = -ENOPROTOOPT; - goto out; - } + if (peer_sid == SECSID_NULL) + return -ENOPROTOOPT; err = security_sid_to_context(peer_sid, &scontext, &scontext_len); - if (err) - goto out; + return err; if (scontext_len > len) { err = -ERANGE; @@ -4200,9 +4131,7 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op out_len: if (put_user(scontext_len, optlen)) err = -EFAULT; - kfree(scontext); -out: return err; } @@ -4234,12 +4163,27 @@ out: static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority) { - return sk_alloc_security(sk, family, priority); + struct sk_security_struct *sksec; + + sksec = kzalloc(sizeof(*sksec), priority); + if (!sksec) + return -ENOMEM; + + sksec->peer_sid = SECINITSID_UNLABELED; + sksec->sid = SECINITSID_UNLABELED; + selinux_netlbl_sk_security_reset(sksec); + sk->sk_security = sksec; + + return 0; } static void selinux_sk_free_security(struct sock *sk) { - sk_free_security(sk); + struct sk_security_struct *sksec = sk->sk_security; + + sk->sk_security = NULL; + selinux_netlbl_sk_security_free(sksec); + kfree(sksec); } static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) @@ -4335,6 +4279,27 @@ static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb) selinux_skb_peerlbl_sid(skb, family, &sksec->peer_sid); } +static int selinux_secmark_relabel_packet(u32 sid) +{ + const struct task_security_struct *__tsec; + u32 tsid; + + __tsec = current_security(); + tsid = __tsec->sid; + + return avc_has_perm(tsid, sid, SECCLASS_PACKET, PACKET__RELABELTO, NULL); +} + +static void selinux_secmark_refcount_inc(void) +{ + atomic_inc(&selinux_secmark_refcount); +} + +static void selinux_secmark_refcount_dec(void) +{ + atomic_dec(&selinux_secmark_refcount); +} + static void selinux_req_classify_flow(const struct request_sock *req, struct flowi *fl) { @@ -4399,8 +4364,7 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) int err = 0; u32 perm; struct nlmsghdr *nlh; - struct socket *sock = sk->sk_socket; - struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; + struct sk_security_struct *sksec = sk->sk_security; if (skb->len < NLMSG_SPACE(0)) { err = -EINVAL; @@ -4408,13 +4372,13 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) } nlh = nlmsg_hdr(skb); - err = selinux_nlmsg_lookup(isec->sclass, nlh->nlmsg_type, &perm); + err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm); if (err) { if (err == -EINVAL) { audit_log(current->audit_context, GFP_KERNEL, AUDIT_SELINUX_ERR, "SELinux: unrecognized netlink message" " type=%hu for sclass=%hu\n", - nlh->nlmsg_type, isec->sclass); + nlh->nlmsg_type, sksec->sclass); if (!selinux_enforcing || security_get_allow_unknown()) err = 0; } @@ -4425,7 +4389,7 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) goto out; } - err = socket_has_perm(current, sock, perm); + err = sock_has_perm(current, sk, perm); out: return err; } @@ -5590,6 +5554,9 @@ static struct security_operations selinux_ops = { .inet_conn_request = selinux_inet_conn_request, .inet_csk_clone = selinux_inet_csk_clone, .inet_conn_established = selinux_inet_conn_established, + .secmark_relabel_packet = selinux_secmark_relabel_packet, + .secmark_refcount_inc = selinux_secmark_refcount_inc, + .secmark_refcount_dec = selinux_secmark_refcount_dec, .req_classify_flow = selinux_req_classify_flow, .tun_dev_create = selinux_tun_dev_create, .tun_dev_post_create = selinux_tun_dev_post_create, diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 8b32e959bb2e..8858d2b2d4b6 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -2,7 +2,8 @@ "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append" #define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \ - "rename", "execute", "swapon", "quotaon", "mounton" + "rename", "execute", "swapon", "quotaon", "mounton", "audit_access", \ + "open", "execmod" #define COMMON_SOCK_PERMS COMMON_FILE_SOCK_PERMS, "bind", "connect", \ "listen", "accept", "getopt", "setopt", "shutdown", "recvfrom", \ @@ -16,7 +17,7 @@ struct security_class_mapping secclass_map[] = { { "compute_av", "compute_create", "compute_member", "check_context", "load_policy", "compute_relabel", "compute_user", "setenforce", "setbool", "setsecparam", - "setcheckreqprot", NULL } }, + "setcheckreqprot", "read_policy", NULL } }, { "process", { "fork", "transition", "sigchld", "sigkill", "sigstop", "signull", "signal", "ptrace", "getsched", "setsched", @@ -43,22 +44,21 @@ struct security_class_mapping secclass_map[] = { "quotaget", NULL } }, { "file", { COMMON_FILE_PERMS, - "execute_no_trans", "entrypoint", "execmod", "open", NULL } }, + "execute_no_trans", "entrypoint", NULL } }, { "dir", { COMMON_FILE_PERMS, "add_name", "remove_name", - "reparent", "search", "rmdir", "open", NULL } }, + "reparent", "search", "rmdir", NULL } }, { "fd", { "use", NULL } }, { "lnk_file", { COMMON_FILE_PERMS, NULL } }, { "chr_file", - { COMMON_FILE_PERMS, - "execute_no_trans", "entrypoint", "execmod", "open", NULL } }, + { COMMON_FILE_PERMS, NULL } }, { "blk_file", - { COMMON_FILE_PERMS, "open", NULL } }, + { COMMON_FILE_PERMS, NULL } }, { "sock_file", - { COMMON_FILE_PERMS, "open", NULL } }, + { COMMON_FILE_PERMS, NULL } }, { "fifo_file", - { COMMON_FILE_PERMS, "open", NULL } }, + { COMMON_FILE_PERMS, NULL } }, { "socket", { COMMON_SOCK_PERMS, NULL } }, { "tcp_socket", diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 1f7c2491d3dc..671273eb1115 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -9,6 +9,7 @@ #define _SELINUX_SECURITY_H_ #include <linux/magic.h> +#include <linux/types.h> #include "flask.h" #define SECSID_NULL 0x00000000 /* unspecified SID */ @@ -82,6 +83,8 @@ extern int selinux_policycap_openperm; int security_mls_enabled(void); int security_load_policy(void *data, size_t len); +int security_read_policy(void **data, ssize_t *len); +size_t security_policydb_len(void); int security_policycap_supported(unsigned int req_cap); @@ -191,5 +194,25 @@ static inline int security_netlbl_sid_to_secattr(u32 sid, const char *security_get_initial_sid_context(u32 sid); +/* + * status notifier using mmap interface + */ +extern struct page *selinux_kernel_status_page(void); + +#define SELINUX_KERNEL_STATUS_VERSION 1 +struct selinux_kernel_status { + u32 version; /* version number of thie structure */ + u32 sequence; /* sequence number of seqlock logic */ + u32 enforcing; /* current setting of enforcing mode */ + u32 policyload; /* times of policy reloaded */ + u32 deny_unknown; /* current setting of deny_unknown */ + /* + * The version > 0 supports above members. + */ +} __attribute__((packed)); + +extern void selinux_status_update_setenforce(int enforcing); +extern void selinux_status_update_policyload(int seqno); + #endif /* _SELINUX_SECURITY_H_ */ diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index dc92792271f1..65ebfe954f85 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -183,8 +183,6 @@ static void sel_netnode_insert(struct sel_netnode *node) BUG(); } - INIT_RCU_HEAD(&node->rcu); - /* we need to impose a limit on the growth of the hash table so check * this bucket to make sure it is within the specified bounds */ list_add_rcu(&node->list, &sel_netnode_hash[idx].list); diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 0293843f7eda..073fd5b0a53a 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -68,6 +68,8 @@ static int *bool_pending_values; static struct dentry *class_dir; static unsigned long last_class_ino; +static char policy_opened; + /* global data for policy capabilities */ static struct dentry *policycap_dir; @@ -110,6 +112,8 @@ enum sel_inos { SEL_COMPAT_NET, /* whether to use old compat network packet controls */ SEL_REJECT_UNKNOWN, /* export unknown reject handling to userspace */ SEL_DENY_UNKNOWN, /* export unknown deny handling to userspace */ + SEL_STATUS, /* export current status using mmap() */ + SEL_POLICY, /* allow userspace to read the in kernel policy */ SEL_INO_NEXT, /* The next inode number to use */ }; @@ -171,6 +175,7 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, if (selinux_enforcing) avc_ss_reset(0); selnl_notify_setenforce(selinux_enforcing); + selinux_status_update_setenforce(selinux_enforcing); } length = count; out: @@ -184,6 +189,7 @@ out: static const struct file_operations sel_enforce_ops = { .read = sel_read_enforce, .write = sel_write_enforce, + .llseek = generic_file_llseek, }; static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf, @@ -201,6 +207,60 @@ static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf, static const struct file_operations sel_handle_unknown_ops = { .read = sel_read_handle_unknown, + .llseek = generic_file_llseek, +}; + +static int sel_open_handle_status(struct inode *inode, struct file *filp) +{ + struct page *status = selinux_kernel_status_page(); + + if (!status) + return -ENOMEM; + + filp->private_data = status; + + return 0; +} + +static ssize_t sel_read_handle_status(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct page *status = filp->private_data; + + BUG_ON(!status); + + return simple_read_from_buffer(buf, count, ppos, + page_address(status), + sizeof(struct selinux_kernel_status)); +} + +static int sel_mmap_handle_status(struct file *filp, + struct vm_area_struct *vma) +{ + struct page *status = filp->private_data; + unsigned long size = vma->vm_end - vma->vm_start; + + BUG_ON(!status); + + /* only allows one page from the head */ + if (vma->vm_pgoff > 0 || size != PAGE_SIZE) + return -EIO; + /* disallow writable mapping */ + if (vma->vm_flags & VM_WRITE) + return -EPERM; + /* disallow mprotect() turns it into writable */ + vma->vm_flags &= ~VM_MAYWRITE; + + return remap_pfn_range(vma, vma->vm_start, + page_to_pfn(status), + size, vma->vm_page_prot); +} + +static const struct file_operations sel_handle_status_ops = { + .open = sel_open_handle_status, + .read = sel_read_handle_status, + .mmap = sel_mmap_handle_status, + .llseek = generic_file_llseek, }; #ifdef CONFIG_SECURITY_SELINUX_DISABLE @@ -251,6 +311,7 @@ out: static const struct file_operations sel_disable_ops = { .write = sel_write_disable, + .llseek = generic_file_llseek, }; static ssize_t sel_read_policyvers(struct file *filp, char __user *buf, @@ -265,6 +326,7 @@ static ssize_t sel_read_policyvers(struct file *filp, char __user *buf, static const struct file_operations sel_policyvers_ops = { .read = sel_read_policyvers, + .llseek = generic_file_llseek, }; /* declaration for sel_write_load */ @@ -289,6 +351,142 @@ static ssize_t sel_read_mls(struct file *filp, char __user *buf, static const struct file_operations sel_mls_ops = { .read = sel_read_mls, + .llseek = generic_file_llseek, +}; + +struct policy_load_memory { + size_t len; + void *data; +}; + +static int sel_open_policy(struct inode *inode, struct file *filp) +{ + struct policy_load_memory *plm = NULL; + int rc; + + BUG_ON(filp->private_data); + + mutex_lock(&sel_mutex); + + rc = task_has_security(current, SECURITY__READ_POLICY); + if (rc) + goto err; + + rc = -EBUSY; + if (policy_opened) + goto err; + + rc = -ENOMEM; + plm = kzalloc(sizeof(*plm), GFP_KERNEL); + if (!plm) + goto err; + + if (i_size_read(inode) != security_policydb_len()) { + mutex_lock(&inode->i_mutex); + i_size_write(inode, security_policydb_len()); + mutex_unlock(&inode->i_mutex); + } + + rc = security_read_policy(&plm->data, &plm->len); + if (rc) + goto err; + + policy_opened = 1; + + filp->private_data = plm; + + mutex_unlock(&sel_mutex); + + return 0; +err: + mutex_unlock(&sel_mutex); + + if (plm) + vfree(plm->data); + kfree(plm); + return rc; +} + +static int sel_release_policy(struct inode *inode, struct file *filp) +{ + struct policy_load_memory *plm = filp->private_data; + + BUG_ON(!plm); + + policy_opened = 0; + + vfree(plm->data); + kfree(plm); + + return 0; +} + +static ssize_t sel_read_policy(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct policy_load_memory *plm = filp->private_data; + int ret; + + mutex_lock(&sel_mutex); + + ret = task_has_security(current, SECURITY__READ_POLICY); + if (ret) + goto out; + + ret = simple_read_from_buffer(buf, count, ppos, plm->data, plm->len); +out: + mutex_unlock(&sel_mutex); + return ret; +} + +static int sel_mmap_policy_fault(struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + struct policy_load_memory *plm = vma->vm_file->private_data; + unsigned long offset; + struct page *page; + + if (vmf->flags & (FAULT_FLAG_MKWRITE | FAULT_FLAG_WRITE)) + return VM_FAULT_SIGBUS; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= roundup(plm->len, PAGE_SIZE)) + return VM_FAULT_SIGBUS; + + page = vmalloc_to_page(plm->data + offset); + get_page(page); + + vmf->page = page; + + return 0; +} + +static struct vm_operations_struct sel_mmap_policy_ops = { + .fault = sel_mmap_policy_fault, + .page_mkwrite = sel_mmap_policy_fault, +}; + +int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma) +{ + if (vma->vm_flags & VM_SHARED) { + /* do not allow mprotect to make mapping writable */ + vma->vm_flags &= ~VM_MAYWRITE; + + if (vma->vm_flags & VM_WRITE) + return -EACCES; + } + + vma->vm_flags |= VM_RESERVED; + vma->vm_ops = &sel_mmap_policy_ops; + + return 0; +} + +static const struct file_operations sel_policy_ops = { + .open = sel_open_policy, + .read = sel_read_policy, + .mmap = sel_mmap_policy, + .release = sel_release_policy, }; static ssize_t sel_write_load(struct file *file, const char __user *buf, @@ -356,6 +554,7 @@ out: static const struct file_operations sel_load_ops = { .write = sel_write_load, + .llseek = generic_file_llseek, }; static ssize_t sel_write_context(struct file *file, char *buf, size_t size) @@ -437,6 +636,7 @@ out: static const struct file_operations sel_checkreqprot_ops = { .read = sel_read_checkreqprot, .write = sel_write_checkreqprot, + .llseek = generic_file_llseek, }; /* @@ -482,6 +682,7 @@ static const struct file_operations transaction_ops = { .write = selinux_transaction_write, .read = simple_transaction_read, .release = simple_transaction_release, + .llseek = generic_file_llseek, }; /* @@ -777,6 +978,7 @@ static struct inode *sel_make_inode(struct super_block *sb, int mode) struct inode *ret = new_inode(sb); if (ret) { + ret->i_ino = get_next_ino(); ret->i_mode = mode; ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME; } @@ -883,6 +1085,7 @@ out: static const struct file_operations sel_bool_ops = { .read = sel_read_bool, .write = sel_write_bool, + .llseek = generic_file_llseek, }; static ssize_t sel_commit_bools_write(struct file *filep, @@ -935,6 +1138,7 @@ out: static const struct file_operations sel_commit_bools_ops = { .write = sel_commit_bools_write, + .llseek = generic_file_llseek, }; static void sel_remove_entries(struct dentry *de) @@ -1127,10 +1331,12 @@ out: static const struct file_operations sel_avc_cache_threshold_ops = { .read = sel_read_avc_cache_threshold, .write = sel_write_avc_cache_threshold, + .llseek = generic_file_llseek, }; static const struct file_operations sel_avc_hash_stats_ops = { .read = sel_read_avc_hash_stats, + .llseek = generic_file_llseek, }; #ifdef CONFIG_SECURITY_SELINUX_AVC_STATS @@ -1255,6 +1461,7 @@ static ssize_t sel_read_initcon(struct file *file, char __user *buf, static const struct file_operations sel_initcon_ops = { .read = sel_read_initcon, + .llseek = generic_file_llseek, }; static int sel_make_initcon_files(struct dentry *dir) @@ -1330,6 +1537,7 @@ out: static const struct file_operations sel_class_ops = { .read = sel_read_class, + .llseek = generic_file_llseek, }; static ssize_t sel_read_perm(struct file *file, char __user *buf, @@ -1354,6 +1562,7 @@ out: static const struct file_operations sel_perm_ops = { .read = sel_read_perm, + .llseek = generic_file_llseek, }; static ssize_t sel_read_policycap(struct file *file, char __user *buf, @@ -1372,6 +1581,7 @@ static ssize_t sel_read_policycap(struct file *file, char __user *buf, static const struct file_operations sel_policycap_ops = { .read = sel_read_policycap, + .llseek = generic_file_llseek, }; static int sel_make_perm_files(char *objclass, int classvalue, @@ -1596,6 +1806,8 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent) [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO}, [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO}, + [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO}, + [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUSR}, /* last one */ {""} }; ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); @@ -1697,16 +1909,15 @@ err: goto out; } -static int sel_get_sb(struct file_system_type *fs_type, - int flags, const char *dev_name, void *data, - struct vfsmount *mnt) +static struct dentry *sel_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) { - return get_sb_single(fs_type, flags, data, sel_fill_super, mnt); + return mount_single(fs_type, flags, data, sel_fill_super); } static struct file_system_type sel_fs_type = { .name = "selinuxfs", - .get_sb = sel_get_sb, + .mount = sel_mount, .kill_sb = kill_litter_super, }; diff --git a/security/selinux/ss/Makefile b/security/selinux/ss/Makefile deleted file mode 100644 index 15d4e62917de..000000000000 --- a/security/selinux/ss/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -# -# Makefile for building the SELinux security server as part of the kernel tree. -# - -EXTRA_CFLAGS += -Isecurity/selinux -Isecurity/selinux/include -obj-y := ss.o - -ss-y := ebitmap.o hashtab.o symtab.o sidtab.o avtab.o policydb.o services.o conditional.o mls.o - diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c index 1215b8e47dba..a3dd9faa19c0 100644 --- a/security/selinux/ss/avtab.c +++ b/security/selinux/ss/avtab.c @@ -266,8 +266,8 @@ int avtab_alloc(struct avtab *h, u32 nrules) if (shift > 2) shift = shift - 2; nslot = 1 << shift; - if (nslot > MAX_AVTAB_SIZE) - nslot = MAX_AVTAB_SIZE; + if (nslot > MAX_AVTAB_HASH_BUCKETS) + nslot = MAX_AVTAB_HASH_BUCKETS; mask = nslot - 1; h->htable = kcalloc(nslot, sizeof(*(h->htable)), GFP_KERNEL); @@ -342,20 +342,20 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, if (vers < POLICYDB_VERSION_AVTAB) { rc = next_entry(buf32, fp, sizeof(u32)); - if (rc < 0) { + if (rc) { printk(KERN_ERR "SELinux: avtab: truncated entry\n"); - return -1; + return rc; } items2 = le32_to_cpu(buf32[0]); if (items2 > ARRAY_SIZE(buf32)) { printk(KERN_ERR "SELinux: avtab: entry overflow\n"); - return -1; + return -EINVAL; } rc = next_entry(buf32, fp, sizeof(u32)*items2); - if (rc < 0) { + if (rc) { printk(KERN_ERR "SELinux: avtab: truncated entry\n"); - return -1; + return rc; } items = 0; @@ -363,19 +363,19 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, key.source_type = (u16)val; if (key.source_type != val) { printk(KERN_ERR "SELinux: avtab: truncated source type\n"); - return -1; + return -EINVAL; } val = le32_to_cpu(buf32[items++]); key.target_type = (u16)val; if (key.target_type != val) { printk(KERN_ERR "SELinux: avtab: truncated target type\n"); - return -1; + return -EINVAL; } val = le32_to_cpu(buf32[items++]); key.target_class = (u16)val; if (key.target_class != val) { printk(KERN_ERR "SELinux: avtab: truncated target class\n"); - return -1; + return -EINVAL; } val = le32_to_cpu(buf32[items++]); @@ -383,12 +383,12 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, if (!(val & (AVTAB_AV | AVTAB_TYPE))) { printk(KERN_ERR "SELinux: avtab: null entry\n"); - return -1; + return -EINVAL; } if ((val & AVTAB_AV) && (val & AVTAB_TYPE)) { printk(KERN_ERR "SELinux: avtab: entry has both access vectors and types\n"); - return -1; + return -EINVAL; } for (i = 0; i < ARRAY_SIZE(spec_order); i++) { @@ -403,15 +403,15 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, if (items != items2) { printk(KERN_ERR "SELinux: avtab: entry only had %d items, expected %d\n", items2, items); - return -1; + return -EINVAL; } return 0; } rc = next_entry(buf16, fp, sizeof(u16)*4); - if (rc < 0) { + if (rc) { printk(KERN_ERR "SELinux: avtab: truncated entry\n"); - return -1; + return rc; } items = 0; @@ -424,7 +424,7 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, !policydb_type_isvalid(pol, key.target_type) || !policydb_class_isvalid(pol, key.target_class)) { printk(KERN_ERR "SELinux: avtab: invalid type or class\n"); - return -1; + return -EINVAL; } set = 0; @@ -434,19 +434,19 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, } if (!set || set > 1) { printk(KERN_ERR "SELinux: avtab: more than one specifier\n"); - return -1; + return -EINVAL; } rc = next_entry(buf32, fp, sizeof(u32)); - if (rc < 0) { + if (rc) { printk(KERN_ERR "SELinux: avtab: truncated entry\n"); - return -1; + return rc; } datum.data = le32_to_cpu(*buf32); if ((key.specified & AVTAB_TYPE) && !policydb_type_isvalid(pol, datum.data)) { printk(KERN_ERR "SELinux: avtab: invalid type\n"); - return -1; + return -EINVAL; } return insertf(a, &key, &datum, p); } @@ -487,8 +487,7 @@ int avtab_read(struct avtab *a, void *fp, struct policydb *pol) printk(KERN_ERR "SELinux: avtab: out of memory\n"); else if (rc == -EEXIST) printk(KERN_ERR "SELinux: avtab: duplicate entry\n"); - else - rc = -EINVAL; + goto bad; } } @@ -502,6 +501,48 @@ bad: goto out; } +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp) +{ + __le16 buf16[4]; + __le32 buf32[1]; + int rc; + + buf16[0] = cpu_to_le16(cur->key.source_type); + buf16[1] = cpu_to_le16(cur->key.target_type); + buf16[2] = cpu_to_le16(cur->key.target_class); + buf16[3] = cpu_to_le16(cur->key.specified); + rc = put_entry(buf16, sizeof(u16), 4, fp); + if (rc) + return rc; + buf32[0] = cpu_to_le32(cur->datum.data); + rc = put_entry(buf32, sizeof(u32), 1, fp); + if (rc) + return rc; + return 0; +} + +int avtab_write(struct policydb *p, struct avtab *a, void *fp) +{ + unsigned int i; + int rc = 0; + struct avtab_node *cur; + __le32 buf[1]; + + buf[0] = cpu_to_le32(a->nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (i = 0; i < a->nslot; i++) { + for (cur = a->htable[i]; cur; cur = cur->next) { + rc = avtab_write_item(p, cur, fp); + if (rc) + return rc; + } + } + + return rc; +} void avtab_cache_init(void) { avtab_node_cachep = kmem_cache_create("avtab_node", diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h index cd4f734e2749..dff0c75345c1 100644 --- a/security/selinux/ss/avtab.h +++ b/security/selinux/ss/avtab.h @@ -71,6 +71,8 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, void *p); int avtab_read(struct avtab *a, void *fp, struct policydb *pol); +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp); +int avtab_write(struct policydb *p, struct avtab *a, void *fp); struct avtab_node *avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum); @@ -85,7 +87,6 @@ void avtab_cache_destroy(void); #define MAX_AVTAB_HASH_BITS 11 #define MAX_AVTAB_HASH_BUCKETS (1 << MAX_AVTAB_HASH_BITS) #define MAX_AVTAB_HASH_MASK (MAX_AVTAB_HASH_BUCKETS-1) -#define MAX_AVTAB_SIZE MAX_AVTAB_HASH_BUCKETS #endif /* _SS_AVTAB_H_ */ diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index 4a4e35cac22b..655fe1c6cc69 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -117,10 +117,14 @@ int evaluate_cond_node(struct policydb *p, struct cond_node *node) int cond_policydb_init(struct policydb *p) { + int rc; + p->bool_val_to_struct = NULL; p->cond_list = NULL; - if (avtab_init(&p->te_cond_avtab)) - return -1; + + rc = avtab_init(&p->te_cond_avtab); + if (rc) + return rc; return 0; } @@ -219,34 +223,37 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) booldatum = kzalloc(sizeof(struct cond_bool_datum), GFP_KERNEL); if (!booldatum) - return -1; + return -ENOMEM; rc = next_entry(buf, fp, sizeof buf); - if (rc < 0) + if (rc) goto err; booldatum->value = le32_to_cpu(buf[0]); booldatum->state = le32_to_cpu(buf[1]); + rc = -EINVAL; if (!bool_isvalid(booldatum)) goto err; len = le32_to_cpu(buf[2]); + rc = -ENOMEM; key = kmalloc(len + 1, GFP_KERNEL); if (!key) goto err; rc = next_entry(key, fp, len); - if (rc < 0) + if (rc) goto err; key[len] = '\0'; - if (hashtab_insert(h, key, booldatum)) + rc = hashtab_insert(h, key, booldatum); + if (rc) goto err; return 0; err: cond_destroy_bool(key, booldatum, NULL); - return -1; + return rc; } struct cond_insertf_data { @@ -263,7 +270,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum struct cond_av_list *other = data->other, *list, *cur; struct avtab_node *node_ptr; u8 found; - + int rc = -EINVAL; /* * For type rules we have to make certain there aren't any @@ -313,12 +320,15 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d); if (!node_ptr) { printk(KERN_ERR "SELinux: could not insert rule.\n"); + rc = -ENOMEM; goto err; } list = kzalloc(sizeof(struct cond_av_list), GFP_KERNEL); - if (!list) + if (!list) { + rc = -ENOMEM; goto err; + } list->node = node_ptr; if (!data->head) @@ -331,7 +341,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum err: cond_av_list_destroy(data->head); data->head = NULL; - return -1; + return rc; } static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, struct cond_av_list *other) @@ -345,8 +355,8 @@ static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list * len = 0; rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - return -1; + if (rc) + return rc; len = le32_to_cpu(buf[0]); if (len == 0) @@ -361,7 +371,6 @@ static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list * &data); if (rc) return rc; - } *ret_list = data.head; @@ -390,24 +399,25 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) struct cond_expr *expr = NULL, *last = NULL; rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - return -1; + if (rc) + return rc; node->cur_state = le32_to_cpu(buf[0]); len = 0; rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - return -1; + if (rc) + return rc; /* expr */ len = le32_to_cpu(buf[0]); for (i = 0; i < len; i++) { rc = next_entry(buf, fp, sizeof(u32) * 2); - if (rc < 0) + if (rc) goto err; + rc = -ENOMEM; expr = kzalloc(sizeof(struct cond_expr), GFP_KERNEL); if (!expr) goto err; @@ -416,6 +426,7 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) expr->bool = le32_to_cpu(buf[1]); if (!expr_isvalid(p, expr)) { + rc = -EINVAL; kfree(expr); goto err; } @@ -427,14 +438,16 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) last = expr; } - if (cond_read_av_list(p, fp, &node->true_list, NULL) != 0) + rc = cond_read_av_list(p, fp, &node->true_list, NULL); + if (rc) goto err; - if (cond_read_av_list(p, fp, &node->false_list, node->true_list) != 0) + rc = cond_read_av_list(p, fp, &node->false_list, node->true_list); + if (rc) goto err; return 0; err: cond_node_destroy(node); - return -1; + return rc; } int cond_read_list(struct policydb *p, void *fp) @@ -445,8 +458,8 @@ int cond_read_list(struct policydb *p, void *fp) int rc; rc = next_entry(buf, fp, sizeof buf); - if (rc < 0) - return -1; + if (rc) + return rc; len = le32_to_cpu(buf[0]); @@ -455,11 +468,13 @@ int cond_read_list(struct policydb *p, void *fp) goto err; for (i = 0; i < len; i++) { + rc = -ENOMEM; node = kzalloc(sizeof(struct cond_node), GFP_KERNEL); if (!node) goto err; - if (cond_read_node(p, node, fp) != 0) + rc = cond_read_node(p, node, fp); + if (rc) goto err; if (i == 0) @@ -472,9 +487,132 @@ int cond_read_list(struct policydb *p, void *fp) err: cond_list_destroy(p->cond_list); p->cond_list = NULL; - return -1; + return rc; +} + +int cond_write_bool(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct cond_bool_datum *booldatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[3]; + u32 len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(booldatum->value); + buf[1] = cpu_to_le32(booldatum->state); + buf[2] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + return 0; +} + +/* + * cond_write_cond_av_list doesn't write out the av_list nodes. + * Instead it writes out the key/value pairs from the avtab. This + * is necessary because there is no way to uniquely identifying rules + * in the avtab so it is not possible to associate individual rules + * in the avtab with a conditional without saving them as part of + * the conditional. This means that the avtab with the conditional + * rules will not be saved but will be rebuilt on policy load. + */ +static int cond_write_av_list(struct policydb *p, + struct cond_av_list *list, struct policy_file *fp) +{ + __le32 buf[1]; + struct cond_av_list *cur_list; + u32 len; + int rc; + + len = 0; + for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) + len++; + + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + if (len == 0) + return 0; + + for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { + rc = avtab_write_item(p, cur_list->node, fp); + if (rc) + return rc; + } + + return 0; +} + +int cond_write_node(struct policydb *p, struct cond_node *node, + struct policy_file *fp) +{ + struct cond_expr *cur_expr; + __le32 buf[2]; + int rc; + u32 len = 0; + + buf[0] = cpu_to_le32(node->cur_state); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) + len++; + + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) { + buf[0] = cpu_to_le32(cur_expr->expr_type); + buf[1] = cpu_to_le32(cur_expr->bool); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + } + + rc = cond_write_av_list(p, node->true_list, fp); + if (rc) + return rc; + rc = cond_write_av_list(p, node->false_list, fp); + if (rc) + return rc; + + return 0; } +int cond_write_list(struct policydb *p, struct cond_node *list, void *fp) +{ + struct cond_node *cur; + u32 len; + __le32 buf[1]; + int rc; + + len = 0; + for (cur = list; cur != NULL; cur = cur->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur = list; cur != NULL; cur = cur->next) { + rc = cond_write_node(p, cur, fp); + if (rc) + return rc; + } + + return 0; +} /* Determine whether additional permissions are granted by the conditional * av table, and if so, add them to the result */ diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h index 53ddb013ae57..3f209c635295 100644 --- a/security/selinux/ss/conditional.h +++ b/security/selinux/ss/conditional.h @@ -69,6 +69,8 @@ int cond_index_bool(void *key, void *datum, void *datap); int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp); int cond_read_list(struct policydb *p, void *fp); +int cond_write_bool(void *key, void *datum, void *ptr); +int cond_write_list(struct policydb *p, struct cond_node *list, void *fp); void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd); diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c index 04b6145d767f..d42951fcbe87 100644 --- a/security/selinux/ss/ebitmap.c +++ b/security/selinux/ss/ebitmap.c @@ -22,6 +22,8 @@ #include "ebitmap.h" #include "policydb.h" +#define BITS_PER_U64 (sizeof(u64) * 8) + int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) { struct ebitmap_node *n1, *n2; @@ -363,10 +365,10 @@ int ebitmap_read(struct ebitmap *e, void *fp) e->highbit = le32_to_cpu(buf[1]); count = le32_to_cpu(buf[2]); - if (mapunit != sizeof(u64) * 8) { + if (mapunit != BITS_PER_U64) { printk(KERN_ERR "SELinux: ebitmap: map size %u does not " "match my size %Zd (high bit was %d)\n", - mapunit, sizeof(u64) * 8, e->highbit); + mapunit, BITS_PER_U64, e->highbit); goto bad; } @@ -446,3 +448,78 @@ bad: ebitmap_destroy(e); goto out; } + +int ebitmap_write(struct ebitmap *e, void *fp) +{ + struct ebitmap_node *n; + u32 count; + __le32 buf[3]; + u64 map; + int bit, last_bit, last_startbit, rc; + + buf[0] = cpu_to_le32(BITS_PER_U64); + + count = 0; + last_bit = 0; + last_startbit = -1; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + count++; + last_startbit = rounddown(bit, BITS_PER_U64); + } + last_bit = roundup(bit + 1, BITS_PER_U64); + } + buf[1] = cpu_to_le32(last_bit); + buf[2] = cpu_to_le32(count); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + map = 0; + last_startbit = INT_MIN; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + __le64 buf64[1]; + + /* this is the very first bit */ + if (!map) { + last_startbit = rounddown(bit, BITS_PER_U64); + map = (u64)1 << (bit - last_startbit); + continue; + } + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + + /* set up for the next node */ + map = 0; + last_startbit = rounddown(bit, BITS_PER_U64); + } + map |= (u64)1 << (bit - last_startbit); + } + /* write the last node */ + if (map) { + __le64 buf64[1]; + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + } + return 0; +} diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h index f283b4367f54..1f4e93c2ae86 100644 --- a/security/selinux/ss/ebitmap.h +++ b/security/selinux/ss/ebitmap.h @@ -123,6 +123,7 @@ int ebitmap_get_bit(struct ebitmap *e, unsigned long bit); int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value); void ebitmap_destroy(struct ebitmap *e); int ebitmap_read(struct ebitmap *e, void *fp); +int ebitmap_write(struct ebitmap *e, void *fp); #ifdef CONFIG_NETLABEL int ebitmap_netlbl_export(struct ebitmap *ebmap, diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index c57802a164d5..94f630d93a5c 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -31,11 +31,13 @@ #include <linux/string.h> #include <linux/errno.h> #include <linux/audit.h> +#include <linux/flex_array.h> #include "security.h" #include "policydb.h" #include "conditional.h" #include "mls.h" +#include "services.h" #define _DEBUG_HASHES @@ -184,9 +186,19 @@ static u32 rangetr_hash(struct hashtab *h, const void *k) static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) { const struct range_trans *key1 = k1, *key2 = k2; - return (key1->source_type != key2->source_type || - key1->target_type != key2->target_type || - key1->target_class != key2->target_class); + int v; + + v = key1->source_type - key2->source_type; + if (v) + return v; + + v = key1->target_type - key2->target_type; + if (v) + return v; + + v = key1->target_class - key2->target_class; + + return v; } /* @@ -655,6 +667,9 @@ static int range_tr_destroy(void *key, void *datum, void *p) static void ocontext_destroy(struct ocontext *c, int i) { + if (!c) + return; + context_destroy(&c->context[0]); context_destroy(&c->context[1]); if (i == OCON_ISID || i == OCON_FS || @@ -736,11 +751,17 @@ void policydb_destroy(struct policydb *p) hashtab_map(p->range_tr, range_tr_destroy, NULL); hashtab_destroy(p->range_tr); - if (p->type_attr_map) { - for (i = 0; i < p->p_types.nprim; i++) - ebitmap_destroy(&p->type_attr_map[i]); + if (p->type_attr_map_array) { + for (i = 0; i < p->p_types.nprim; i++) { + struct ebitmap *e; + + e = flex_array_get(p->type_attr_map_array, i); + if (!e) + continue; + ebitmap_destroy(e); + } + flex_array_free(p->type_attr_map_array); } - kfree(p->type_attr_map); ebitmap_destroy(&p->policycaps); ebitmap_destroy(&p->permissive_map); @@ -1614,11 +1635,11 @@ static int role_bounds_sanity_check(void *key, void *datum, void *datap) static int type_bounds_sanity_check(void *key, void *datum, void *datap) { - struct type_datum *upper, *type; + struct type_datum *upper; struct policydb *p = datap; int depth = 0; - upper = type = datum; + upper = datum; while (upper->bounds) { if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { printk(KERN_ERR "SELinux: type %s: " @@ -1701,6 +1722,333 @@ u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name) return 1U << (perdatum->value-1); } +static int range_read(struct policydb *p, void *fp) +{ + struct range_trans *rt = NULL; + struct mls_range *r = NULL; + int i, rc; + __le32 buf[2]; + u32 nel; + + if (p->policyvers < POLICYDB_VERSION_MLS) + return 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + nel = le32_to_cpu(buf[0]); + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + rt = kzalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) + goto out; + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc) + goto out; + + rt->source_type = le32_to_cpu(buf[0]); + rt->target_type = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + rt->target_class = le32_to_cpu(buf[0]); + } else + rt->target_class = p->process_class; + + rc = -EINVAL; + if (!policydb_type_isvalid(p, rt->source_type) || + !policydb_type_isvalid(p, rt->target_type) || + !policydb_class_isvalid(p, rt->target_class)) + goto out; + + rc = -ENOMEM; + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) + goto out; + + rc = mls_read_range_helper(r, fp); + if (rc) + goto out; + + rc = -EINVAL; + if (!mls_range_isvalid(p, r)) { + printk(KERN_WARNING "SELinux: rangetrans: invalid range\n"); + goto out; + } + + rc = hashtab_insert(p->range_tr, rt, r); + if (rc) + goto out; + + rt = NULL; + r = NULL; + } + rangetr_hash_eval(p->range_tr); + rc = 0; +out: + kfree(rt); + kfree(r); + return rc; +} + +static int genfs_read(struct policydb *p, void *fp) +{ + int i, j, rc; + u32 nel, nel2, len, len2; + __le32 buf[1]; + struct ocontext *l, *c; + struct ocontext *newc = NULL; + struct genfs *genfs_p, *genfs; + struct genfs *newgenfs = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + nel = le32_to_cpu(buf[0]); + + for (i = 0; i < nel; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL); + if (!newgenfs) + goto out; + + rc = -ENOMEM; + newgenfs->fstype = kmalloc(len + 1, GFP_KERNEL); + if (!newgenfs->fstype) + goto out; + + rc = next_entry(newgenfs->fstype, fp, len); + if (rc) + goto out; + + newgenfs->fstype[len] = 0; + + for (genfs_p = NULL, genfs = p->genfs; genfs; + genfs_p = genfs, genfs = genfs->next) { + rc = -EINVAL; + if (strcmp(newgenfs->fstype, genfs->fstype) == 0) { + printk(KERN_ERR "SELinux: dup genfs fstype %s\n", + newgenfs->fstype); + goto out; + } + if (strcmp(newgenfs->fstype, genfs->fstype) < 0) + break; + } + newgenfs->next = genfs; + if (genfs_p) + genfs_p->next = newgenfs; + else + p->genfs = newgenfs; + genfs = newgenfs; + newgenfs = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + nel2 = le32_to_cpu(buf[0]); + for (j = 0; j < nel2; j++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + newc = kzalloc(sizeof(*newc), GFP_KERNEL); + if (!newc) + goto out; + + rc = -ENOMEM; + newc->u.name = kmalloc(len + 1, GFP_KERNEL); + if (!newc->u.name) + goto out; + + rc = next_entry(newc->u.name, fp, len); + if (rc) + goto out; + newc->u.name[len] = 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + newc->v.sclass = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&newc->context[0], p, fp); + if (rc) + goto out; + + for (l = NULL, c = genfs->head; c; + l = c, c = c->next) { + rc = -EINVAL; + if (!strcmp(newc->u.name, c->u.name) && + (!c->v.sclass || !newc->v.sclass || + newc->v.sclass == c->v.sclass)) { + printk(KERN_ERR "SELinux: dup genfs entry (%s,%s)\n", + genfs->fstype, c->u.name); + goto out; + } + len = strlen(newc->u.name); + len2 = strlen(c->u.name); + if (len > len2) + break; + } + + newc->next = c; + if (l) + l->next = newc; + else + genfs->head = newc; + newc = NULL; + } + } + rc = 0; +out: + if (newgenfs) + kfree(newgenfs->fstype); + kfree(newgenfs); + ocontext_destroy(newc, OCON_FSUSE); + + return rc; +} + +static int ocontext_read(struct policydb *p, struct policydb_compat_info *info, + void *fp) +{ + int i, j, rc; + u32 nel, len; + __le32 buf[3]; + struct ocontext *l, *c; + u32 nodebuf[8]; + + for (i = 0; i < info->ocon_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + nel = le32_to_cpu(buf[0]); + + l = NULL; + for (j = 0; j < nel; j++) { + rc = -ENOMEM; + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + goto out; + if (l) + l->next = c; + else + p->ocontexts[i] = c; + l = c; + + switch (i) { + case OCON_ISID: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + c->sid[0] = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_FS: + case OCON_NETIF: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + c->u.name = kmalloc(len + 1, GFP_KERNEL); + if (!c->u.name) + goto out; + + rc = next_entry(c->u.name, fp, len); + if (rc) + goto out; + + c->u.name[len] = 0; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + rc = context_read_and_validate(&c->context[1], p, fp); + if (rc) + goto out; + break; + case OCON_PORT: + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc) + goto out; + c->u.port.protocol = le32_to_cpu(buf[0]); + c->u.port.low_port = le32_to_cpu(buf[1]); + c->u.port.high_port = le32_to_cpu(buf[2]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_NODE: + rc = next_entry(nodebuf, fp, sizeof(u32) * 2); + if (rc) + goto out; + c->u.node.addr = nodebuf[0]; /* network order */ + c->u.node.mask = nodebuf[1]; /* network order */ + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_FSUSE: + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto out; + + rc = -EINVAL; + c->v.behavior = le32_to_cpu(buf[0]); + if (c->v.behavior > SECURITY_FS_USE_NONE) + goto out; + + rc = -ENOMEM; + len = le32_to_cpu(buf[1]); + c->u.name = kmalloc(len + 1, GFP_KERNEL); + if (!c->u.name) + goto out; + + rc = next_entry(c->u.name, fp, len); + if (rc) + goto out; + c->u.name[len] = 0; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_NODE6: { + int k; + + rc = next_entry(nodebuf, fp, sizeof(u32) * 8); + if (rc) + goto out; + for (k = 0; k < 4; k++) + c->u.node6.addr[k] = nodebuf[k]; + for (k = 0; k < 4; k++) + c->u.node6.mask[k] = nodebuf[k+4]; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + } + } + } + } + rc = 0; +out: + return rc; +} + /* * Read the configuration data from a policy database binary * representation file into a policy database structure. @@ -1709,16 +2057,12 @@ int policydb_read(struct policydb *p, void *fp) { struct role_allow *ra, *lra; struct role_trans *tr, *ltr; - struct ocontext *l, *c, *newc; - struct genfs *genfs_p, *genfs, *newgenfs; int i, j, rc; __le32 buf[4]; - u32 nodebuf[8]; - u32 len, len2, nprim, nel, nel2; + u32 len, nprim, nel; + char *policydb_str; struct policydb_compat_info *info; - struct range_trans *rt; - struct mls_range *r; rc = policydb_init(p); if (rc) @@ -1919,308 +2263,897 @@ int policydb_read(struct policydb *p, void *fp) if (!p->process_trans_perms) goto bad; - for (i = 0; i < info->ocon_num; i++) { - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - nel = le32_to_cpu(buf[0]); - l = NULL; - for (j = 0; j < nel; j++) { - c = kzalloc(sizeof(*c), GFP_KERNEL); - if (!c) { - rc = -ENOMEM; + rc = ocontext_read(p, info, fp); + if (rc) + goto bad; + + rc = genfs_read(p, fp); + if (rc) + goto bad; + + rc = range_read(p, fp); + if (rc) + goto bad; + + rc = -ENOMEM; + p->type_attr_map_array = flex_array_alloc(sizeof(struct ebitmap), + p->p_types.nprim, + GFP_KERNEL | __GFP_ZERO); + if (!p->type_attr_map_array) + goto bad; + + /* preallocate so we don't have to worry about the put ever failing */ + rc = flex_array_prealloc(p->type_attr_map_array, 0, p->p_types.nprim - 1, + GFP_KERNEL | __GFP_ZERO); + if (rc) + goto bad; + + for (i = 0; i < p->p_types.nprim; i++) { + struct ebitmap *e = flex_array_get(p->type_attr_map_array, i); + + BUG_ON(!e); + ebitmap_init(e); + if (p->policyvers >= POLICYDB_VERSION_AVTAB) { + rc = ebitmap_read(e, fp); + if (rc) goto bad; + } + /* add the type itself as the degenerate case */ + rc = ebitmap_set_bit(e, i, 1); + if (rc) + goto bad; + } + + rc = policydb_bounds_sanity_check(p); + if (rc) + goto bad; + + rc = 0; +out: + return rc; +bad: + if (!rc) + rc = -EINVAL; + policydb_destroy(p); + goto out; +} + +/* + * Write a MLS level structure to a policydb binary + * representation file. + */ +static int mls_write_level(struct mls_level *l, void *fp) +{ + __le32 buf[1]; + int rc; + + buf[0] = cpu_to_le32(l->sens); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = ebitmap_write(&l->cat, fp); + if (rc) + return rc; + + return 0; +} + +/* + * Write a MLS range structure to a policydb binary + * representation file. + */ +static int mls_write_range_helper(struct mls_range *r, void *fp) +{ + __le32 buf[3]; + size_t items; + int rc, eq; + + eq = mls_level_eq(&r->level[1], &r->level[0]); + + if (eq) + items = 2; + else + items = 3; + buf[0] = cpu_to_le32(items-1); + buf[1] = cpu_to_le32(r->level[0].sens); + if (!eq) + buf[2] = cpu_to_le32(r->level[1].sens); + + BUG_ON(items > (sizeof(buf)/sizeof(buf[0]))); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = ebitmap_write(&r->level[0].cat, fp); + if (rc) + return rc; + if (!eq) { + rc = ebitmap_write(&r->level[1].cat, fp); + if (rc) + return rc; + } + + return 0; +} + +static int sens_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct level_datum *levdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(levdatum->isalias); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = mls_write_level(levdatum->level, fp); + if (rc) + return rc; + + return 0; +} + +static int cat_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct cat_datum *catdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[3]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(catdatum->value); + buf[2] = cpu_to_le32(catdatum->isalias); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int role_trans_write(struct role_trans *r, void *fp) +{ + struct role_trans *tr; + u32 buf[3]; + size_t nel; + int rc; + + nel = 0; + for (tr = r; tr; tr = tr->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (tr = r; tr; tr = tr->next) { + buf[0] = cpu_to_le32(tr->role); + buf[1] = cpu_to_le32(tr->type); + buf[2] = cpu_to_le32(tr->new_role); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + } + + return 0; +} + +static int role_allow_write(struct role_allow *r, void *fp) +{ + struct role_allow *ra; + u32 buf[2]; + size_t nel; + int rc; + + nel = 0; + for (ra = r; ra; ra = ra->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (ra = r; ra; ra = ra->next) { + buf[0] = cpu_to_le32(ra->role); + buf[1] = cpu_to_le32(ra->new_role); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + } + return 0; +} + +/* + * Write a security context structure + * to a policydb binary representation file. + */ +static int context_write(struct policydb *p, struct context *c, + void *fp) +{ + int rc; + __le32 buf[3]; + + buf[0] = cpu_to_le32(c->user); + buf[1] = cpu_to_le32(c->role); + buf[2] = cpu_to_le32(c->type); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&c->range, fp); + if (rc) + return rc; + + return 0; +} + +/* + * The following *_write functions are used to + * write the symbol data to a policy database + * binary representation file. + */ + +static int perm_write(void *vkey, void *datum, void *fp) +{ + char *key = vkey; + struct perm_datum *perdatum = datum; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(perdatum->value); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int common_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct common_datum *comdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[4]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(comdatum->value); + buf[2] = cpu_to_le32(comdatum->permissions.nprim); + buf[3] = cpu_to_le32(comdatum->permissions.table->nel); + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = hashtab_map(comdatum->permissions.table, perm_write, fp); + if (rc) + return rc; + + return 0; +} + +static int write_cons_helper(struct policydb *p, struct constraint_node *node, + void *fp) +{ + struct constraint_node *c; + struct constraint_expr *e; + __le32 buf[3]; + u32 nel; + int rc; + + for (c = node; c; c = c->next) { + nel = 0; + for (e = c->expr; e; e = e->next) + nel++; + buf[0] = cpu_to_le32(c->permissions); + buf[1] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + for (e = c->expr; e; e = e->next) { + buf[0] = cpu_to_le32(e->expr_type); + buf[1] = cpu_to_le32(e->attr); + buf[2] = cpu_to_le32(e->op); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + switch (e->expr_type) { + case CEXPR_NAMES: + rc = ebitmap_write(&e->names, fp); + if (rc) + return rc; + break; + default: + break; } - if (l) - l->next = c; - else - p->ocontexts[i] = c; - l = c; - rc = -EINVAL; + } + } + + return 0; +} + +static int class_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct class_datum *cladatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + struct constraint_node *c; + __le32 buf[6]; + u32 ncons; + size_t len, len2; + int rc; + + len = strlen(key); + if (cladatum->comkey) + len2 = strlen(cladatum->comkey); + else + len2 = 0; + + ncons = 0; + for (c = cladatum->constraints; c; c = c->next) + ncons++; + + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(len2); + buf[2] = cpu_to_le32(cladatum->value); + buf[3] = cpu_to_le32(cladatum->permissions.nprim); + if (cladatum->permissions.table) + buf[4] = cpu_to_le32(cladatum->permissions.table->nel); + else + buf[4] = 0; + buf[5] = cpu_to_le32(ncons); + rc = put_entry(buf, sizeof(u32), 6, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + if (cladatum->comkey) { + rc = put_entry(cladatum->comkey, 1, len2, fp); + if (rc) + return rc; + } + + rc = hashtab_map(cladatum->permissions.table, perm_write, fp); + if (rc) + return rc; + + rc = write_cons_helper(p, cladatum->constraints, fp); + if (rc) + return rc; + + /* write out the validatetrans rule */ + ncons = 0; + for (c = cladatum->validatetrans; c; c = c->next) + ncons++; + + buf[0] = cpu_to_le32(ncons); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = write_cons_helper(p, cladatum->validatetrans, fp); + if (rc) + return rc; + + return 0; +} + +static int role_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct role_datum *role = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(role->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(role->bounds); + + BUG_ON(items > (sizeof(buf)/sizeof(buf[0]))); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->dominates, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->types, fp); + if (rc) + return rc; + + return 0; +} + +static int type_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct type_datum *typdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[4]; + int rc; + size_t items, len; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(typdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) { + u32 properties = 0; + + if (typdatum->primary) + properties |= TYPEDATUM_PROPERTY_PRIMARY; + + if (typdatum->attribute) + properties |= TYPEDATUM_PROPERTY_ATTRIBUTE; + + buf[items++] = cpu_to_le32(properties); + buf[items++] = cpu_to_le32(typdatum->bounds); + } else { + buf[items++] = cpu_to_le32(typdatum->primary); + } + BUG_ON(items > (sizeof(buf) / sizeof(buf[0]))); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int user_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct user_datum *usrdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(usrdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(usrdatum->bounds); + BUG_ON(items > (sizeof(buf) / sizeof(buf[0]))); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&usrdatum->roles, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&usrdatum->range, fp); + if (rc) + return rc; + + rc = mls_write_level(&usrdatum->dfltlevel, fp); + if (rc) + return rc; + + return 0; +} + +static int (*write_f[SYM_NUM]) (void *key, void *datum, + void *datap) = +{ + common_write, + class_write, + role_write, + type_write, + user_write, + cond_write_bool, + sens_write, + cat_write, +}; + +static int ocontext_write(struct policydb *p, struct policydb_compat_info *info, + void *fp) +{ + unsigned int i, j, rc; + size_t nel, len; + __le32 buf[3]; + u32 nodebuf[8]; + struct ocontext *c; + for (i = 0; i < info->ocon_num; i++) { + nel = 0; + for (c = p->ocontexts[i]; c; c = c->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = p->ocontexts[i]; c; c = c->next) { switch (i) { case OCON_ISID: - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - c->sid[0] = le32_to_cpu(buf[0]); - rc = context_read_and_validate(&c->context[0], p, fp); + buf[0] = cpu_to_le32(c->sid[0]); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); if (rc) - goto bad; + return rc; break; case OCON_FS: case OCON_NETIF: - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - len = le32_to_cpu(buf[0]); - c->u.name = kmalloc(len + 1, GFP_KERNEL); - if (!c->u.name) { - rc = -ENOMEM; - goto bad; - } - rc = next_entry(c->u.name, fp, len); - if (rc < 0) - goto bad; - c->u.name[len] = 0; - rc = context_read_and_validate(&c->context[0], p, fp); + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) - goto bad; - rc = context_read_and_validate(&c->context[1], p, fp); + return rc; + rc = put_entry(c->u.name, 1, len, fp); if (rc) - goto bad; + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + rc = context_write(p, &c->context[1], fp); + if (rc) + return rc; break; case OCON_PORT: - rc = next_entry(buf, fp, sizeof(u32)*3); - if (rc < 0) - goto bad; - c->u.port.protocol = le32_to_cpu(buf[0]); - c->u.port.low_port = le32_to_cpu(buf[1]); - c->u.port.high_port = le32_to_cpu(buf[2]); - rc = context_read_and_validate(&c->context[0], p, fp); + buf[0] = cpu_to_le32(c->u.port.protocol); + buf[1] = cpu_to_le32(c->u.port.low_port); + buf[2] = cpu_to_le32(c->u.port.high_port); + rc = put_entry(buf, sizeof(u32), 3, fp); if (rc) - goto bad; + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; break; case OCON_NODE: - rc = next_entry(nodebuf, fp, sizeof(u32) * 2); - if (rc < 0) - goto bad; - c->u.node.addr = nodebuf[0]; /* network order */ - c->u.node.mask = nodebuf[1]; /* network order */ - rc = context_read_and_validate(&c->context[0], p, fp); + nodebuf[0] = c->u.node.addr; /* network order */ + nodebuf[1] = c->u.node.mask; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 2, fp); if (rc) - goto bad; + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; break; case OCON_FSUSE: - rc = next_entry(buf, fp, sizeof(u32)*2); - if (rc < 0) - goto bad; - c->v.behavior = le32_to_cpu(buf[0]); - if (c->v.behavior > SECURITY_FS_USE_NONE) - goto bad; - len = le32_to_cpu(buf[1]); - c->u.name = kmalloc(len + 1, GFP_KERNEL); - if (!c->u.name) { - rc = -ENOMEM; - goto bad; - } - rc = next_entry(c->u.name, fp, len); - if (rc < 0) - goto bad; - c->u.name[len] = 0; - rc = context_read_and_validate(&c->context[0], p, fp); + buf[0] = cpu_to_le32(c->v.behavior); + len = strlen(c->u.name); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); if (rc) - goto bad; + return rc; break; - case OCON_NODE6: { - int k; - - rc = next_entry(nodebuf, fp, sizeof(u32) * 8); - if (rc < 0) - goto bad; - for (k = 0; k < 4; k++) - c->u.node6.addr[k] = nodebuf[k]; - for (k = 0; k < 4; k++) - c->u.node6.mask[k] = nodebuf[k+4]; - if (context_read_and_validate(&c->context[0], p, fp)) - goto bad; + case OCON_NODE6: + for (j = 0; j < 4; j++) + nodebuf[j] = c->u.node6.addr[j]; /* network order */ + for (j = 0; j < 4; j++) + nodebuf[j + 4] = c->u.node6.mask[j]; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 8, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; break; } - } } } + return 0; +} - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - nel = le32_to_cpu(buf[0]); - genfs_p = NULL; - rc = -EINVAL; - for (i = 0; i < nel; i++) { - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - len = le32_to_cpu(buf[0]); - newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL); - if (!newgenfs) { - rc = -ENOMEM; - goto bad; - } +static int genfs_write(struct policydb *p, void *fp) +{ + struct genfs *genfs; + struct ocontext *c; + size_t len; + __le32 buf[1]; + int rc; - newgenfs->fstype = kmalloc(len + 1, GFP_KERNEL); - if (!newgenfs->fstype) { - rc = -ENOMEM; - kfree(newgenfs); - goto bad; - } - rc = next_entry(newgenfs->fstype, fp, len); - if (rc < 0) { - kfree(newgenfs->fstype); - kfree(newgenfs); - goto bad; - } - newgenfs->fstype[len] = 0; - for (genfs_p = NULL, genfs = p->genfs; genfs; - genfs_p = genfs, genfs = genfs->next) { - if (strcmp(newgenfs->fstype, genfs->fstype) == 0) { - printk(KERN_ERR "SELinux: dup genfs " - "fstype %s\n", newgenfs->fstype); - kfree(newgenfs->fstype); - kfree(newgenfs); - goto bad; - } - if (strcmp(newgenfs->fstype, genfs->fstype) < 0) - break; + len = 0; + for (genfs = p->genfs; genfs; genfs = genfs->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (genfs = p->genfs; genfs; genfs = genfs->next) { + len = strlen(genfs->fstype); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(genfs->fstype, 1, len, fp); + if (rc) + return rc; + len = 0; + for (c = genfs->head; c; c = c->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + buf[0] = cpu_to_le32(c->v.sclass); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; } - newgenfs->next = genfs; - if (genfs_p) - genfs_p->next = newgenfs; - else - p->genfs = newgenfs; - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - nel2 = le32_to_cpu(buf[0]); - for (j = 0; j < nel2; j++) { - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - len = le32_to_cpu(buf[0]); + } + return 0; +} - newc = kzalloc(sizeof(*newc), GFP_KERNEL); - if (!newc) { - rc = -ENOMEM; - goto bad; - } +static int range_count(void *key, void *data, void *ptr) +{ + int *cnt = ptr; + *cnt = *cnt + 1; - newc->u.name = kmalloc(len + 1, GFP_KERNEL); - if (!newc->u.name) { - rc = -ENOMEM; - goto bad_newc; - } - rc = next_entry(newc->u.name, fp, len); - if (rc < 0) - goto bad_newc; - newc->u.name[len] = 0; - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad_newc; - newc->v.sclass = le32_to_cpu(buf[0]); - if (context_read_and_validate(&newc->context[0], p, fp)) - goto bad_newc; - for (l = NULL, c = newgenfs->head; c; - l = c, c = c->next) { - if (!strcmp(newc->u.name, c->u.name) && - (!c->v.sclass || !newc->v.sclass || - newc->v.sclass == c->v.sclass)) { - printk(KERN_ERR "SELinux: dup genfs " - "entry (%s,%s)\n", - newgenfs->fstype, c->u.name); - goto bad_newc; - } - len = strlen(newc->u.name); - len2 = strlen(c->u.name); - if (len > len2) - break; - } + return 0; +} - newc->next = c; - if (l) - l->next = newc; - else - newgenfs->head = newc; - } +static int range_write_helper(void *key, void *data, void *ptr) +{ + __le32 buf[2]; + struct range_trans *rt = key; + struct mls_range *r = data; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + int rc; + + buf[0] = cpu_to_le32(rt->source_type); + buf[1] = cpu_to_le32(rt->target_type); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) { + buf[0] = cpu_to_le32(rt->target_class); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; } + rc = mls_write_range_helper(r, fp); + if (rc) + return rc; - if (p->policyvers >= POLICYDB_VERSION_MLS) { - int new_rangetr = p->policyvers >= POLICYDB_VERSION_RANGETRANS; - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) - goto bad; - nel = le32_to_cpu(buf[0]); - for (i = 0; i < nel; i++) { - rt = kzalloc(sizeof(*rt), GFP_KERNEL); - if (!rt) { - rc = -ENOMEM; - goto bad; - } - rc = next_entry(buf, fp, (sizeof(u32) * 2)); - if (rc < 0) { - kfree(rt); - goto bad; - } - rt->source_type = le32_to_cpu(buf[0]); - rt->target_type = le32_to_cpu(buf[1]); - if (new_rangetr) { - rc = next_entry(buf, fp, sizeof(u32)); - if (rc < 0) { - kfree(rt); - goto bad; - } - rt->target_class = le32_to_cpu(buf[0]); - } else - rt->target_class = p->process_class; - if (!policydb_type_isvalid(p, rt->source_type) || - !policydb_type_isvalid(p, rt->target_type) || - !policydb_class_isvalid(p, rt->target_class)) { - kfree(rt); - rc = -EINVAL; - goto bad; - } - r = kzalloc(sizeof(*r), GFP_KERNEL); - if (!r) { - kfree(rt); - rc = -ENOMEM; - goto bad; - } - rc = mls_read_range_helper(r, fp); - if (rc) { - kfree(rt); - kfree(r); - goto bad; - } - if (!mls_range_isvalid(p, r)) { - printk(KERN_WARNING "SELinux: rangetrans: invalid range\n"); - kfree(rt); - kfree(r); - goto bad; - } - rc = hashtab_insert(p->range_tr, rt, r); - if (rc) { - kfree(rt); - kfree(r); - goto bad; - } - } - rangetr_hash_eval(p->range_tr); + return 0; +} + +static int range_write(struct policydb *p, void *fp) +{ + size_t nel; + __le32 buf[1]; + int rc; + struct policy_data pd; + + pd.p = p; + pd.fp = fp; + + /* count the number of entries in the hashtab */ + nel = 0; + rc = hashtab_map(p->range_tr, range_count, &nel); + if (rc) + return rc; + + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + /* actually write all of the entries */ + rc = hashtab_map(p->range_tr, range_write_helper, &pd); + if (rc) + return rc; + + return 0; +} + +/* + * Write the configuration data in a policy database + * structure to a policy database binary representation + * file. + */ +int policydb_write(struct policydb *p, void *fp) +{ + unsigned int i, num_syms; + int rc; + __le32 buf[4]; + u32 config; + size_t len; + struct policydb_compat_info *info; + + /* + * refuse to write policy older than compressed avtab + * to simplify the writer. There are other tests dropped + * since we assume this throughout the writer code. Be + * careful if you ever try to remove this restriction + */ + if (p->policyvers < POLICYDB_VERSION_AVTAB) { + printk(KERN_ERR "SELinux: refusing to write policy version %d." + " Because it is less than version %d\n", p->policyvers, + POLICYDB_VERSION_AVTAB); + return -EINVAL; } - p->type_attr_map = kmalloc(p->p_types.nprim * sizeof(struct ebitmap), GFP_KERNEL); - if (!p->type_attr_map) - goto bad; + config = 0; + if (p->mls_enabled) + config |= POLICYDB_CONFIG_MLS; + + if (p->reject_unknown) + config |= REJECT_UNKNOWN; + if (p->allow_unknown) + config |= ALLOW_UNKNOWN; + + /* Write the magic number and string identifiers. */ + buf[0] = cpu_to_le32(POLICYDB_MAGIC); + len = strlen(POLICYDB_STRING); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(POLICYDB_STRING, 1, len, fp); + if (rc) + return rc; - for (i = 0; i < p->p_types.nprim; i++) { - ebitmap_init(&p->type_attr_map[i]); - if (p->policyvers >= POLICYDB_VERSION_AVTAB) { - if (ebitmap_read(&p->type_attr_map[i], fp)) - goto bad; - } - /* add the type itself as the degenerate case */ - if (ebitmap_set_bit(&p->type_attr_map[i], i, 1)) - goto bad; + /* Write the version, config, and table sizes. */ + info = policydb_lookup_compat(p->policyvers); + if (!info) { + printk(KERN_ERR "SELinux: compatibility lookup failed for policy " + "version %d", p->policyvers); + return rc; } - rc = policydb_bounds_sanity_check(p); + buf[0] = cpu_to_le32(p->policyvers); + buf[1] = cpu_to_le32(config); + buf[2] = cpu_to_le32(info->sym_num); + buf[3] = cpu_to_le32(info->ocon_num); + + rc = put_entry(buf, sizeof(u32), 4, fp); if (rc) - goto bad; + return rc; - rc = 0; -out: - return rc; -bad_newc: - ocontext_destroy(newc, OCON_FSUSE); -bad: - if (!rc) - rc = -EINVAL; - policydb_destroy(p); - goto out; + if (p->policyvers >= POLICYDB_VERSION_POLCAP) { + rc = ebitmap_write(&p->policycaps, fp); + if (rc) + return rc; + } + + if (p->policyvers >= POLICYDB_VERSION_PERMISSIVE) { + rc = ebitmap_write(&p->permissive_map, fp); + if (rc) + return rc; + } + + num_syms = info->sym_num; + for (i = 0; i < num_syms; i++) { + struct policy_data pd; + + pd.fp = fp; + pd.p = p; + + buf[0] = cpu_to_le32(p->symtab[i].nprim); + buf[1] = cpu_to_le32(p->symtab[i].table->nel); + + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = hashtab_map(p->symtab[i].table, write_f[i], &pd); + if (rc) + return rc; + } + + rc = avtab_write(p, &p->te_avtab, fp); + if (rc) + return rc; + + rc = cond_write_list(p, p->cond_list, fp); + if (rc) + return rc; + + rc = role_trans_write(p->role_tr, fp); + if (rc) + return rc; + + rc = role_allow_write(p->role_allow, fp); + if (rc) + return rc; + + rc = ocontext_write(p, info, fp); + if (rc) + return rc; + + rc = genfs_write(p, fp); + if (rc) + return rc; + + rc = range_write(p, fp); + if (rc) + return rc; + + for (i = 0; i < p->p_types.nprim; i++) { + struct ebitmap *e = flex_array_get(p->type_attr_map_array, i); + + BUG_ON(!e); + rc = ebitmap_write(e, fp); + if (rc) + return rc; + } + + return 0; } diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index 26d9adf8542b..95d3d7de361e 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -24,6 +24,8 @@ #ifndef _SS_POLICYDB_H_ #define _SS_POLICYDB_H_ +#include <linux/flex_array.h> + #include "symtab.h" #include "avtab.h" #include "sidtab.h" @@ -246,12 +248,15 @@ struct policydb { struct hashtab *range_tr; /* type -> attribute reverse mapping */ - struct ebitmap *type_attr_map; + struct flex_array *type_attr_map_array; struct ebitmap policycaps; struct ebitmap permissive_map; + /* length of this policy when it was loaded */ + size_t len; + unsigned int policyvers; unsigned int reject_unknown : 1; @@ -268,6 +273,7 @@ extern int policydb_class_isvalid(struct policydb *p, unsigned int class); extern int policydb_type_isvalid(struct policydb *p, unsigned int type); extern int policydb_role_isvalid(struct policydb *p, unsigned int role); extern int policydb_read(struct policydb *p, void *fp); +extern int policydb_write(struct policydb *p, void *fp); #define PERM_SYMTAB_SIZE 32 @@ -288,6 +294,11 @@ struct policy_file { size_t len; }; +struct policy_data { + struct policydb *p; + void *fp; +}; + static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes) { if (bytes > fp->len) @@ -299,6 +310,17 @@ static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes) return 0; } +static inline int put_entry(void *buf, size_t bytes, int num, struct policy_file *fp) +{ + size_t len = bytes * num; + + memcpy(fp->data, buf, len); + fp->data += len; + fp->len -= len; + + return 0; +} + extern u16 string_to_security_class(struct policydb *p, const char *name); extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 1de60ce90d9a..223c1ff6ef23 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -50,6 +50,8 @@ #include <linux/audit.h> #include <linux/mutex.h> #include <linux/selinux.h> +#include <linux/flex_array.h> +#include <linux/vmalloc.h> #include <net/netlabel.h> #include "flask.h" @@ -626,8 +628,10 @@ static void context_struct_compute_av(struct context *scontext, */ avkey.target_class = tclass; avkey.specified = AVTAB_AV; - sattr = &policydb.type_attr_map[scontext->type - 1]; - tattr = &policydb.type_attr_map[tcontext->type - 1]; + 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; @@ -988,7 +992,8 @@ static int context_struct_to_string(struct context *context, char **scontext, u3 { char *scontextp; - *scontext = NULL; + if (scontext) + *scontext = NULL; *scontext_len = 0; if (context->len) { @@ -1005,6 +1010,9 @@ static int context_struct_to_string(struct context *context, char **scontext, u3 *scontext_len += strlen(policydb.p_type_val_to_name[context->type - 1]) + 1; *scontext_len += mls_compute_context_len(context); + if (!scontext) + return 0; + /* Allocate space for the context; caller must free this space. */ scontextp = kmalloc(*scontext_len, GFP_ATOMIC); if (!scontextp) @@ -1044,7 +1052,8 @@ static int security_sid_to_context_core(u32 sid, char **scontext, struct context *context; int rc = 0; - *scontext = NULL; + if (scontext) + *scontext = NULL; *scontext_len = 0; if (!ss_initialized) { @@ -1052,6 +1061,8 @@ static int security_sid_to_context_core(u32 sid, char **scontext, char *scontextp; *scontext_len = strlen(initial_sid_to_string[sid]) + 1; + if (!scontext) + goto out; scontextp = kmalloc(*scontext_len, GFP_ATOMIC); if (!scontextp) { rc = -ENOMEM; @@ -1766,6 +1777,7 @@ int security_load_policy(void *data, size_t len) return rc; } + policydb.len = len; rc = selinux_set_mapping(&policydb, secclass_map, ¤t_mapping, ¤t_mapping_size); @@ -1788,6 +1800,7 @@ int security_load_policy(void *data, size_t len) selinux_complete_init(); avc_ss_reset(seqno); selnl_notify_policyload(seqno); + selinux_status_update_policyload(seqno); selinux_netlbl_cache_invalidate(); selinux_xfrm_notify_policyload(); return 0; @@ -1801,6 +1814,7 @@ int security_load_policy(void *data, size_t len) if (rc) return rc; + newpolicydb.len = len; /* If switching between different policy types, log MLS status */ if (policydb.mls_enabled && !newpolicydb.mls_enabled) printk(KERN_INFO "SELinux: Disabling MLS support...\n"); @@ -1867,6 +1881,7 @@ int security_load_policy(void *data, size_t len) avc_ss_reset(seqno); selnl_notify_policyload(seqno); + selinux_status_update_policyload(seqno); selinux_netlbl_cache_invalidate(); selinux_xfrm_notify_policyload(); @@ -1880,6 +1895,17 @@ err: } +size_t security_policydb_len(void) +{ + size_t len; + + read_lock(&policy_rwlock); + len = policydb.len; + read_unlock(&policy_rwlock); + + return len; +} + /** * security_port_sid - Obtain the SID for a port. * @protocol: protocol number @@ -2371,6 +2397,7 @@ out: if (!rc) { avc_ss_reset(seqno); selnl_notify_policyload(seqno); + selinux_status_update_policyload(seqno); selinux_xfrm_notify_policyload(); } return rc; @@ -3126,3 +3153,38 @@ netlbl_sid_to_secattr_failure: return rc; } #endif /* CONFIG_NETLABEL */ + +/** + * security_read_policy - read the policy. + * @data: binary policy data + * @len: length of data in bytes + * + */ +int security_read_policy(void **data, ssize_t *len) +{ + int rc; + struct policy_file fp; + + if (!ss_initialized) + return -EINVAL; + + *len = security_policydb_len(); + + *data = vmalloc_user(*len); + if (!*data) + return -ENOMEM; + + fp.data = *data; + fp.len = *len; + + read_lock(&policy_rwlock); + rc = policydb_write(&policydb, &fp); + read_unlock(&policy_rwlock); + + if (rc) + return rc; + + *len = (unsigned long)fp.data - (unsigned long)*data; + return 0; + +} diff --git a/security/selinux/ss/status.c b/security/selinux/ss/status.c new file mode 100644 index 000000000000..d982365f9d1a --- /dev/null +++ b/security/selinux/ss/status.c @@ -0,0 +1,126 @@ +/* + * mmap based event notifications for SELinux + * + * Author: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + * Copyright (C) 2010 NEC corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/gfp.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include "avc.h" +#include "services.h" + +/* + * The selinux_status_page shall be exposed to userspace applications + * using mmap interface on /selinux/status. + * It enables to notify applications a few events that will cause reset + * of userspace access vector without context switching. + * + * The selinux_kernel_status structure on the head of status page is + * protected from concurrent accesses using seqlock logic, so userspace + * application should reference the status page according to the seqlock + * logic. + * + * Typically, application checks status->sequence at the head of access + * control routine. If it is odd-number, kernel is updating the status, + * so please wait for a moment. If it is changed from the last sequence + * number, it means something happen, so application will reset userspace + * avc, if needed. + * In most cases, application shall confirm the kernel status is not + * changed without any system call invocations. + */ +static struct page *selinux_status_page; +static DEFINE_MUTEX(selinux_status_lock); + +/* + * selinux_kernel_status_page + * + * It returns a reference to selinux_status_page. If the status page is + * not allocated yet, it also tries to allocate it at the first time. + */ +struct page *selinux_kernel_status_page(void) +{ + struct selinux_kernel_status *status; + struct page *result = NULL; + + mutex_lock(&selinux_status_lock); + if (!selinux_status_page) { + selinux_status_page = alloc_page(GFP_KERNEL|__GFP_ZERO); + + if (selinux_status_page) { + status = page_address(selinux_status_page); + + status->version = SELINUX_KERNEL_STATUS_VERSION; + status->sequence = 0; + status->enforcing = selinux_enforcing; + /* + * NOTE: the next policyload event shall set + * a positive value on the status->policyload, + * although it may not be 1, but never zero. + * So, application can know it was updated. + */ + status->policyload = 0; + status->deny_unknown = !security_get_allow_unknown(); + } + } + result = selinux_status_page; + mutex_unlock(&selinux_status_lock); + + return result; +} + +/* + * selinux_status_update_setenforce + * + * It updates status of the current enforcing/permissive mode. + */ +void selinux_status_update_setenforce(int enforcing) +{ + struct selinux_kernel_status *status; + + mutex_lock(&selinux_status_lock); + if (selinux_status_page) { + status = page_address(selinux_status_page); + + status->sequence++; + smp_wmb(); + + status->enforcing = enforcing; + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&selinux_status_lock); +} + +/* + * selinux_status_update_policyload + * + * It updates status of the times of policy reloaded, and current + * setting of deny_unknown. + */ +void selinux_status_update_policyload(int seqno) +{ + struct selinux_kernel_status *status; + + mutex_lock(&selinux_status_lock); + if (selinux_status_page) { + status = page_address(selinux_status_page); + + status->sequence++; + smp_wmb(); + + status->policyload = seqno; + status->deny_unknown = !security_get_allow_unknown(); + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&selinux_status_lock); +} diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c index bcf9f620426e..160326ee99e5 100644 --- a/security/selinux/ss/symtab.c +++ b/security/selinux/ss/symtab.c @@ -36,7 +36,7 @@ int symtab_init(struct symtab *s, unsigned int size) { s->table = hashtab_create(symhash, symcmp, size); if (!s->table) - return -1; + return -ENOMEM; s->nprim = 0; return 0; } |