From 03dd4cb26d967f9588437b0fc9cc0e8353322bb7 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Fri, 25 Mar 2016 03:53:42 -0300 Subject: Linux-libre 4.5-gnu --- security/selinux/hooks.c | 200 +++++++++++++++++++++++++----------- security/selinux/include/classmap.h | 2 +- security/selinux/include/objsec.h | 6 ++ security/selinux/include/security.h | 3 + security/selinux/nlmsgtab.c | 1 + security/selinux/selinuxfs.c | 198 +++++++++++++++++++++-------------- security/selinux/ss/services.c | 34 ++++-- 7 files changed, 300 insertions(+), 144 deletions(-) (limited to 'security/selinux') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d0cfaa9f1..f1ab71504 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -242,6 +242,72 @@ static int inode_alloc_security(struct inode *inode) return 0; } +static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry); + +/* + * Try reloading inode security labels that have been marked as invalid. The + * @may_sleep parameter indicates when sleeping and thus reloading labels is + * allowed; when set to false, returns ERR_PTR(-ECHILD) when the label is + * invalid. The @opt_dentry parameter should be set to a dentry of the inode; + * when no dentry is available, set it to NULL instead. + */ +static int __inode_security_revalidate(struct inode *inode, + struct dentry *opt_dentry, + bool may_sleep) +{ + struct inode_security_struct *isec = inode->i_security; + + might_sleep_if(may_sleep); + + if (isec->initialized == LABEL_INVALID) { + if (!may_sleep) + return -ECHILD; + + /* + * Try reloading the inode security label. This will fail if + * @opt_dentry is NULL and no dentry for this inode can be + * found; in that case, continue using the old label. + */ + inode_doinit_with_dentry(inode, opt_dentry); + } + return 0; +} + +static struct inode_security_struct *inode_security_novalidate(struct inode *inode) +{ + return inode->i_security; +} + +static struct inode_security_struct *inode_security_rcu(struct inode *inode, bool rcu) +{ + int error; + + error = __inode_security_revalidate(inode, NULL, !rcu); + if (error) + return ERR_PTR(error); + return inode->i_security; +} + +/* + * Get the security label of an inode. + */ +static struct inode_security_struct *inode_security(struct inode *inode) +{ + __inode_security_revalidate(inode, NULL, true); + return inode->i_security; +} + +/* + * Get the security label of a dentry's backing inode. + */ +static struct inode_security_struct *backing_inode_security(struct dentry *dentry) +{ + struct inode *inode = d_backing_inode(dentry); + + __inode_security_revalidate(inode, dentry, true); + return inode->i_security; +} + static void inode_free_rcu(struct rcu_head *head) { struct inode_security_struct *isec; @@ -345,8 +411,6 @@ static const char *labeling_behaviors[7] = { "uses native labeling", }; -static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry); - static inline int inode_doinit(struct inode *inode) { return inode_doinit_with_dentry(inode, NULL); @@ -565,8 +629,8 @@ static int selinux_get_mnt_opts(const struct super_block *sb, opts->mnt_opts_flags[i++] = DEFCONTEXT_MNT; } if (sbsec->flags & ROOTCONTEXT_MNT) { - struct inode *root = d_backing_inode(sbsec->sb->s_root); - struct inode_security_struct *isec = root->i_security; + struct dentry *root = sbsec->sb->s_root; + struct inode_security_struct *isec = backing_inode_security(root); rc = security_sid_to_context(isec->sid, &context, &len); if (rc) @@ -621,8 +685,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, int rc = 0, i; struct superblock_security_struct *sbsec = sb->s_security; const char *name = sb->s_type->name; - struct inode *inode = d_backing_inode(sbsec->sb->s_root); - struct inode_security_struct *root_isec = inode->i_security; + struct dentry *root = sbsec->sb->s_root; + struct inode_security_struct *root_isec = backing_inode_security(root); u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; u32 defcontext_sid = 0; char **mount_options = opts->mnt_opts; @@ -802,7 +866,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, goto out; root_isec->sid = rootcontext_sid; - root_isec->initialized = 1; + root_isec->initialized = LABEL_INITIALIZED; } if (defcontext_sid) { @@ -852,8 +916,8 @@ static int selinux_cmp_sb_context(const struct super_block *oldsb, if ((oldflags & DEFCONTEXT_MNT) && old->def_sid != new->def_sid) goto mismatch; if (oldflags & ROOTCONTEXT_MNT) { - struct inode_security_struct *oldroot = d_backing_inode(oldsb->s_root)->i_security; - struct inode_security_struct *newroot = d_backing_inode(newsb->s_root)->i_security; + struct inode_security_struct *oldroot = backing_inode_security(oldsb->s_root); + struct inode_security_struct *newroot = backing_inode_security(newsb->s_root); if (oldroot->sid != newroot->sid) goto mismatch; } @@ -903,17 +967,14 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, if (!set_fscontext) newsbsec->sid = sid; if (!set_rootcontext) { - struct inode *newinode = d_backing_inode(newsb->s_root); - struct inode_security_struct *newisec = newinode->i_security; + struct inode_security_struct *newisec = backing_inode_security(newsb->s_root); newisec->sid = sid; } newsbsec->mntpoint_sid = sid; } if (set_rootcontext) { - const struct inode *oldinode = d_backing_inode(oldsb->s_root); - const struct inode_security_struct *oldisec = oldinode->i_security; - struct inode *newinode = d_backing_inode(newsb->s_root); - struct inode_security_struct *newisec = newinode->i_security; + const struct inode_security_struct *oldisec = backing_inode_security(oldsb->s_root); + struct inode_security_struct *newisec = backing_inode_security(newsb->s_root); newisec->sid = oldisec->sid; } @@ -1293,11 +1354,11 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent unsigned len = 0; int rc = 0; - if (isec->initialized) + if (isec->initialized == LABEL_INITIALIZED) goto out; mutex_lock(&isec->lock); - if (isec->initialized) + if (isec->initialized == LABEL_INITIALIZED) goto out_unlock; sbsec = inode->i_sb->s_security; @@ -1469,7 +1530,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent break; } - isec->initialized = 1; + isec->initialized = LABEL_INITIALIZED; out_unlock: mutex_unlock(&isec->lock); @@ -1640,6 +1701,7 @@ static inline int dentry_has_perm(const struct cred *cred, ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; + __inode_security_revalidate(inode, dentry, true); return inode_has_perm(cred, inode, av, &ad); } @@ -1655,6 +1717,7 @@ static inline int path_has_perm(const struct cred *cred, ad.type = LSM_AUDIT_DATA_PATH; ad.u.path = *path; + __inode_security_revalidate(inode, path->dentry, true); return inode_has_perm(cred, inode, av, &ad); } @@ -1712,13 +1775,13 @@ out: /* * Determine the label for an inode that might be unioned. */ -static int selinux_determine_inode_label(const struct inode *dir, +static int selinux_determine_inode_label(struct inode *dir, const struct qstr *name, u16 tclass, u32 *_new_isid) { const struct superblock_security_struct *sbsec = dir->i_sb->s_security; - const struct inode_security_struct *dsec = dir->i_security; + const struct inode_security_struct *dsec = inode_security(dir); const struct task_security_struct *tsec = current_security(); if ((sbsec->flags & SE_SBINITIALIZED) && @@ -1747,7 +1810,7 @@ static int may_create(struct inode *dir, struct common_audit_data ad; int rc; - dsec = dir->i_security; + dsec = inode_security(dir); sbsec = dir->i_sb->s_security; sid = tsec->sid; @@ -1800,8 +1863,8 @@ static int may_link(struct inode *dir, u32 av; int rc; - dsec = dir->i_security; - isec = d_backing_inode(dentry)->i_security; + dsec = inode_security(dir); + isec = backing_inode_security(dentry); ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; @@ -1844,10 +1907,10 @@ static inline int may_rename(struct inode *old_dir, int old_is_dir, new_is_dir; int rc; - old_dsec = old_dir->i_security; - old_isec = d_backing_inode(old_dentry)->i_security; + old_dsec = inode_security(old_dir); + old_isec = backing_inode_security(old_dentry); old_is_dir = d_is_dir(old_dentry); - new_dsec = new_dir->i_security; + new_dsec = inode_security(new_dir); ad.type = LSM_AUDIT_DATA_DENTRY; @@ -1875,7 +1938,7 @@ static inline int may_rename(struct inode *old_dir, if (rc) return rc; if (d_is_positive(new_dentry)) { - new_isec = d_backing_inode(new_dentry)->i_security; + new_isec = backing_inode_security(new_dentry); new_is_dir = d_is_dir(new_dentry); rc = avc_has_perm(sid, new_isec->sid, new_isec->sclass, @@ -2011,8 +2074,8 @@ static int selinux_binder_transfer_file(struct task_struct *from, { u32 sid = task_sid(to); struct file_security_struct *fsec = file->f_security; - struct inode *inode = d_backing_inode(file->f_path.dentry); - struct inode_security_struct *isec = inode->i_security; + struct dentry *dentry = file->f_path.dentry; + struct inode_security_struct *isec = backing_inode_security(dentry); struct common_audit_data ad; int rc; @@ -2028,7 +2091,7 @@ static int selinux_binder_transfer_file(struct task_struct *from, return rc; } - if (unlikely(IS_PRIVATE(inode))) + if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) return 0; return avc_has_perm(sid, isec->sid, isec->sclass, file_to_av(file), @@ -2217,7 +2280,7 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) old_tsec = current_security(); new_tsec = bprm->cred->security; - isec = inode->i_security; + isec = inode_security(inode); /* Default to the current task SID. */ new_tsec->sid = old_tsec->sid; @@ -2639,7 +2702,7 @@ static int selinux_sb_remount(struct super_block *sb, void *data) break; case ROOTCONTEXT_MNT: { struct inode_security_struct *root_isec; - root_isec = d_backing_inode(sb->s_root)->i_security; + root_isec = backing_inode_security(sb->s_root); if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid)) goto out_bad_option; @@ -2753,13 +2816,11 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, void **value, size_t *len) { const struct task_security_struct *tsec = current_security(); - struct inode_security_struct *dsec; struct superblock_security_struct *sbsec; u32 sid, newsid, clen; int rc; char *context; - dsec = dir->i_security; sbsec = dir->i_sb->s_security; sid = tsec->sid; @@ -2777,7 +2838,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, struct inode_security_struct *isec = inode->i_security; isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; - isec->initialized = 1; + isec->initialized = LABEL_INITIALIZED; } if (!ss_initialized || !(sbsec->flags & SBLABEL_MNT)) @@ -2858,7 +2919,9 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct inode *inode, ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; sid = cred_sid(cred); - isec = inode->i_security; + isec = inode_security_rcu(inode, rcu); + if (IS_ERR(isec)) + return PTR_ERR(isec); return avc_has_perm_flags(sid, isec->sid, isec->sclass, FILE__READ, &ad, rcu ? MAY_NOT_BLOCK : 0); @@ -2910,7 +2973,9 @@ static int selinux_inode_permission(struct inode *inode, int mask) perms = file_mask_to_av(inode->i_mode, mask); sid = cred_sid(cred); - isec = inode->i_security; + isec = inode_security_rcu(inode, flags & MAY_NOT_BLOCK); + if (IS_ERR(isec)) + return PTR_ERR(isec); rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0, &avd); audited = avc_audit_required(perms, &avd, rc, @@ -2980,7 +3045,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct inode *inode = d_backing_inode(dentry); - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec = backing_inode_security(dentry); struct superblock_security_struct *sbsec; struct common_audit_data ad; u32 newsid, sid = current_sid(); @@ -3057,7 +3122,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, int flags) { struct inode *inode = d_backing_inode(dentry); - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec = backing_inode_security(dentry); u32 newsid; int rc; @@ -3076,7 +3141,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; - isec->initialized = 1; + isec->initialized = LABEL_INITIALIZED; return; } @@ -3110,12 +3175,12 @@ static int selinux_inode_removexattr(struct dentry *dentry, const char *name) * * Permission check is handled by selinux_inode_getxattr hook. */ -static int selinux_inode_getsecurity(const struct inode *inode, const char *name, void **buffer, bool alloc) +static int selinux_inode_getsecurity(struct inode *inode, const char *name, void **buffer, bool alloc) { u32 size; int error; char *context = NULL; - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec = inode_security(inode); if (strcmp(name, XATTR_SELINUX_SUFFIX)) return -EOPNOTSUPP; @@ -3154,7 +3219,7 @@ out_nofree: static int selinux_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags) { - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec = inode_security(inode); u32 newsid; int rc; @@ -3170,7 +3235,7 @@ static int selinux_inode_setsecurity(struct inode *inode, const char *name, isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; - isec->initialized = 1; + isec->initialized = LABEL_INITIALIZED; return 0; } @@ -3182,9 +3247,9 @@ static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t return len; } -static void selinux_inode_getsecid(const struct inode *inode, u32 *secid) +static void selinux_inode_getsecid(struct inode *inode, u32 *secid) { - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec = inode_security_novalidate(inode); *secid = isec->sid; } @@ -3207,13 +3272,14 @@ static int selinux_file_permission(struct file *file, int mask) { struct inode *inode = file_inode(file); struct file_security_struct *fsec = file->f_security; - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec; u32 sid = current_sid(); if (!mask) /* No permission to check. Existence test. */ return 0; + isec = inode_security(inode); if (sid == fsec->sid && fsec->isid == isec->sid && fsec->pseqno == avc_policy_seqno()) /* No change since file_open check. */ @@ -3242,7 +3308,7 @@ static int ioctl_has_perm(const struct cred *cred, struct file *file, struct common_audit_data ad; struct file_security_struct *fsec = file->f_security; struct inode *inode = file_inode(file); - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec = inode_security(inode); struct lsm_ioctlop_audit ioctl; u32 ssid = cred_sid(cred); int rc; @@ -3506,7 +3572,7 @@ static int selinux_file_open(struct file *file, const struct cred *cred) struct inode_security_struct *isec; fsec = file->f_security; - isec = file_inode(file)->i_security; + isec = inode_security(file_inode(file)); /* * Save inode label and policy sequence number * at open-time so that selinux_file_permission @@ -3624,7 +3690,7 @@ static int selinux_kernel_act_as(struct cred *new, u32 secid) */ static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) { - struct inode_security_struct *isec = inode->i_security; + struct inode_security_struct *isec = inode_security(inode); struct task_security_struct *tsec = new->security; u32 sid = current_sid(); int ret; @@ -3748,7 +3814,7 @@ static void selinux_task_to_inode(struct task_struct *p, u32 sid = task_sid(p); isec->sid = sid; - isec->initialized = 1; + isec->initialized = LABEL_INITIALIZED; } /* Returns error only if unable to parse addresses */ @@ -4065,7 +4131,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, int type, int protocol, int kern) { const struct task_security_struct *tsec = current_security(); - struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; + struct inode_security_struct *isec = inode_security_novalidate(SOCK_INODE(sock)); struct sk_security_struct *sksec; int err = 0; @@ -4079,7 +4145,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, return err; } - isec->initialized = 1; + isec->initialized = LABEL_INITIALIZED; if (sock->sk) { sksec = sock->sk->sk_security; @@ -4265,12 +4331,12 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock) if (err) return err; - newisec = SOCK_INODE(newsock)->i_security; + newisec = inode_security_novalidate(SOCK_INODE(newsock)); - isec = SOCK_INODE(sock)->i_security; + isec = inode_security_novalidate(SOCK_INODE(sock)); newisec->sclass = isec->sclass; newisec->sid = isec->sid; - newisec->initialized = 1; + newisec->initialized = LABEL_INITIALIZED; return 0; } @@ -4605,7 +4671,8 @@ static void selinux_sk_getsecid(struct sock *sk, u32 *secid) static void selinux_sock_graft(struct sock *sk, struct socket *parent) { - struct inode_security_struct *isec = SOCK_INODE(parent)->i_security; + struct inode_security_struct *isec = + inode_security_novalidate(SOCK_INODE(parent)); struct sk_security_struct *sksec = sk->sk_security; if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 || @@ -4785,11 +4852,12 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm); if (err) { if (err == -EINVAL) { - printk(KERN_WARNING - "SELinux: unrecognized netlink message:" - " protocol=%hu nlmsg_type=%hu sclass=%s\n", + pr_warn_ratelimited("SELinux: unrecognized netlink" + " message: protocol=%hu nlmsg_type=%hu sclass=%s" + " pig=%d comm=%s\n", sk->sk_protocol, nlh->nlmsg_type, - secclass_map[sksec->sclass - 1].name); + secclass_map[sksec->sclass - 1].name, + task_pid_nr(current), current->comm); if (!selinux_enforcing || security_get_allow_unknown()) err = 0; } @@ -5762,6 +5830,15 @@ static void selinux_release_secctx(char *secdata, u32 seclen) kfree(secdata); } +static void selinux_inode_invalidate_secctx(struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + + mutex_lock(&isec->lock); + isec->initialized = LABEL_INVALID; + mutex_unlock(&isec->lock); +} + /* * called with inode->i_mutex locked */ @@ -5993,6 +6070,7 @@ static struct security_hook_list selinux_hooks[] = { LSM_HOOK_INIT(secid_to_secctx, selinux_secid_to_secctx), LSM_HOOK_INIT(secctx_to_secid, selinux_secctx_to_secid), LSM_HOOK_INIT(release_secctx, selinux_release_secctx), + LSM_HOOK_INIT(inode_invalidate_secctx, selinux_inode_invalidate_secctx), LSM_HOOK_INIT(inode_notifysecctx, selinux_inode_notifysecctx), LSM_HOOK_INIT(inode_setsecctx, selinux_inode_setsecctx), LSM_HOOK_INIT(inode_getsecctx, selinux_inode_getsecctx), diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 5a4eef59a..ef83c4b85 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -21,7 +21,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", "read_policy", NULL } }, + "setcheckreqprot", "read_policy", "validate_trans", NULL } }, { "process", { "fork", "transition", "sigchld", "sigkill", "sigstop", "signull", "signal", "ptrace", "getsched", "setsched", diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 81fa718d5..a2ae05414 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -37,6 +37,12 @@ struct task_security_struct { u32 sockcreate_sid; /* fscreate SID */ }; +enum label_initialized { + LABEL_MISSING, /* not initialized */ + LABEL_INITIALIZED, /* inizialized */ + LABEL_INVALID /* invalid */ +}; + struct inode_security_struct { struct inode *inode; /* back pointer to inode object */ union { diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 223e9fd15..38feb55d5 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -187,6 +187,9 @@ int security_node_sid(u16 domain, void *addr, u32 addrlen, int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, u16 tclass); +int security_validate_transition_user(u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass); + int security_bounded_transition(u32 oldsid, u32 newsid); int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid); diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 2bbb41822..8495b9368 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -83,6 +83,7 @@ static struct nlmsg_perm nlmsg_tcpdiag_perms[] = { TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, { DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, { SOCK_DIAG_BY_FAMILY, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, + { SOCK_DESTROY, NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE }, }; static struct nlmsg_perm nlmsg_xfrm_perms[] = diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index c02da25d7..1b1fd27de 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -116,6 +116,7 @@ enum sel_inos { 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_VALIDATE_TRANS, /* compute validatetrans decision */ SEL_INO_NEXT, /* The next inode number to use */ }; @@ -147,23 +148,16 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, ssize_t length; int new_value; - length = -ENOMEM; if (count >= PAGE_SIZE) - goto out; + return -ENOMEM; /* No partial writes. */ - length = -EINVAL; if (*ppos != 0) - goto out; - - length = -ENOMEM; - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - goto out; + return -EINVAL; - length = -EFAULT; - if (copy_from_user(page, buf, count)) - goto out; + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); length = -EINVAL; if (sscanf(page, "%d", &new_value) != 1) @@ -186,7 +180,7 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, } length = count; out: - free_page((unsigned long) page); + kfree(page); return length; } #else @@ -275,27 +269,20 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char *page = NULL; + char *page; ssize_t length; int new_value; - length = -ENOMEM; if (count >= PAGE_SIZE) - goto out; + return -ENOMEM; /* No partial writes. */ - length = -EINVAL; if (*ppos != 0) - goto out; - - length = -ENOMEM; - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - goto out; + return -EINVAL; - length = -EFAULT; - if (copy_from_user(page, buf, count)) - goto out; + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); length = -EINVAL; if (sscanf(page, "%d", &new_value) != 1) @@ -313,7 +300,7 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, length = count; out: - free_page((unsigned long) page); + kfree(page); return length; } #else @@ -393,9 +380,9 @@ static int sel_open_policy(struct inode *inode, struct file *filp) goto err; if (i_size_read(inode) != security_policydb_len()) { - mutex_lock(&inode->i_mutex); + inode_lock(inode); i_size_write(inode, security_policydb_len()); - mutex_unlock(&inode->i_mutex); + inode_unlock(inode); } rc = security_read_policy(&plm->data, &plm->len); @@ -611,31 +598,24 @@ static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char *page = NULL; + char *page; ssize_t length; unsigned int new_value; length = task_has_security(current, SECURITY__SETCHECKREQPROT); if (length) - goto out; + return length; - length = -ENOMEM; if (count >= PAGE_SIZE) - goto out; + return -ENOMEM; /* No partial writes. */ - length = -EINVAL; if (*ppos != 0) - goto out; - - length = -ENOMEM; - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - goto out; + return -EINVAL; - length = -EFAULT; - if (copy_from_user(page, buf, count)) - goto out; + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); length = -EINVAL; if (sscanf(page, "%u", &new_value) != 1) @@ -644,7 +624,7 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, selinux_checkreqprot = new_value ? 1 : 0; length = count; out: - free_page((unsigned long) page); + kfree(page); return length; } static const struct file_operations sel_checkreqprot_ops = { @@ -653,6 +633,83 @@ static const struct file_operations sel_checkreqprot_ops = { .llseek = generic_file_llseek, }; +static ssize_t sel_write_validatetrans(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + char *oldcon = NULL, *newcon = NULL, *taskcon = NULL; + char *req = NULL; + u32 osid, nsid, tsid; + u16 tclass; + int rc; + + rc = task_has_security(current, SECURITY__VALIDATE_TRANS); + if (rc) + goto out; + + rc = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + rc = -EINVAL; + if (*ppos != 0) + goto out; + + rc = -ENOMEM; + req = kzalloc(count + 1, GFP_KERNEL); + if (!req) + goto out; + + rc = -EFAULT; + if (copy_from_user(req, buf, count)) + goto out; + + rc = -ENOMEM; + oldcon = kzalloc(count + 1, GFP_KERNEL); + if (!oldcon) + goto out; + + newcon = kzalloc(count + 1, GFP_KERNEL); + if (!newcon) + goto out; + + taskcon = kzalloc(count + 1, GFP_KERNEL); + if (!taskcon) + goto out; + + rc = -EINVAL; + if (sscanf(req, "%s %s %hu %s", oldcon, newcon, &tclass, taskcon) != 4) + goto out; + + rc = security_context_str_to_sid(oldcon, &osid, GFP_KERNEL); + if (rc) + goto out; + + rc = security_context_str_to_sid(newcon, &nsid, GFP_KERNEL); + if (rc) + goto out; + + rc = security_context_str_to_sid(taskcon, &tsid, GFP_KERNEL); + if (rc) + goto out; + + rc = security_validate_transition_user(osid, nsid, tsid, tclass); + if (!rc) + rc = count; +out: + kfree(req); + kfree(oldcon); + kfree(newcon); + kfree(taskcon); + return rc; +} + +static const struct file_operations sel_transition_ops = { + .write = sel_write_validatetrans, + .llseek = generic_file_llseek, +}; + /* * Remaining nodes use transaction based IO methods like nfsd/nfsctl.c */ @@ -1100,14 +1157,12 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf, if (*ppos != 0) goto out; - length = -ENOMEM; - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - goto out; - - length = -EFAULT; - if (copy_from_user(page, buf, count)) + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) { + length = PTR_ERR(page); + page = NULL; goto out; + } length = -EINVAL; if (sscanf(page, "%d", &new_value) != 1) @@ -1121,7 +1176,7 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf, out: mutex_unlock(&sel_mutex); - free_page((unsigned long) page); + kfree(page); return length; } @@ -1154,14 +1209,12 @@ static ssize_t sel_commit_bools_write(struct file *filep, if (*ppos != 0) goto out; - length = -ENOMEM; - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - goto out; - - length = -EFAULT; - if (copy_from_user(page, buf, count)) + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) { + length = PTR_ERR(page); + page = NULL; goto out; + } length = -EINVAL; if (sscanf(page, "%d", &new_value) != 1) @@ -1176,7 +1229,7 @@ static ssize_t sel_commit_bools_write(struct file *filep, out: mutex_unlock(&sel_mutex); - free_page((unsigned long) page); + kfree(page); return length; } @@ -1292,31 +1345,24 @@ static ssize_t sel_write_avc_cache_threshold(struct file *file, size_t count, loff_t *ppos) { - char *page = NULL; + char *page; ssize_t ret; int new_value; ret = task_has_security(current, SECURITY__SETSECPARAM); if (ret) - goto out; + return ret; - ret = -ENOMEM; if (count >= PAGE_SIZE) - goto out; + return -ENOMEM; /* No partial writes. */ - ret = -EINVAL; if (*ppos != 0) - goto out; - - ret = -ENOMEM; - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - goto out; + return -EINVAL; - ret = -EFAULT; - if (copy_from_user(page, buf, count)) - goto out; + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); ret = -EINVAL; if (sscanf(page, "%u", &new_value) != 1) @@ -1326,7 +1372,7 @@ static ssize_t sel_write_avc_cache_threshold(struct file *file, ret = count; out: - free_page((unsigned long)page); + kfree(page); return ret; } @@ -1759,6 +1805,8 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent) [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_IRUGO}, + [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops, + S_IWUGO}, /* last one */ {""} }; ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index ebb5eb3c3..ebda97333 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -778,8 +778,8 @@ out: return -EPERM; } -int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, - u16 orig_tclass) +static int security_compute_validatetrans(u32 oldsid, u32 newsid, u32 tasksid, + u16 orig_tclass, bool user) { struct context *ocontext; struct context *ncontext; @@ -794,11 +794,12 @@ int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, read_lock(&policy_rwlock); - tclass = unmap_class(orig_tclass); + if (!user) + tclass = unmap_class(orig_tclass); + else + tclass = orig_tclass; if (!tclass || tclass > policydb.p_classes.nprim) { - printk(KERN_ERR "SELinux: %s: unrecognized class %d\n", - __func__, tclass); rc = -EINVAL; goto out; } @@ -832,8 +833,13 @@ int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, while (constraint) { if (!constraint_expr_eval(ocontext, ncontext, tcontext, constraint->expr)) { - rc = security_validtrans_handle_fail(ocontext, ncontext, - tcontext, tclass); + if (user) + rc = -EPERM; + else + rc = security_validtrans_handle_fail(ocontext, + ncontext, + tcontext, + tclass); goto out; } constraint = constraint->next; @@ -844,6 +850,20 @@ out: return rc; } +int security_validate_transition_user(u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass) +{ + return security_compute_validatetrans(oldsid, newsid, tasksid, + tclass, true); +} + +int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, + u16 orig_tclass) +{ + return security_compute_validatetrans(oldsid, newsid, tasksid, + orig_tclass, false); +} + /* * security_bounded_transition - check whether the given * transition is directed to bounded, or not. -- cgit v1.2.3-54-g00ecf