diff options
Diffstat (limited to 'security/selinux/hooks.c')
-rw-r--r-- | security/selinux/hooks.c | 496 |
1 files changed, 382 insertions, 114 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index f7e2cd8c3e53..3bac79428c9b 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -83,6 +83,7 @@ #include <linux/export.h> #include <linux/msg.h> #include <linux/shm.h> +#include <linux/bpf.h> #include "avc.h" #include "objsec.h" @@ -242,6 +243,79 @@ 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 (ss_initialized && isec->initialized != LABEL_INITIALIZED) { + 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; +} + +static struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry) +{ + struct inode *inode = d_backing_inode(dentry); + + 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; @@ -333,8 +407,6 @@ static void superblock_free_security(struct super_block *sb) kfree(sbsec); } -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); @@ -572,8 +644,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) @@ -628,8 +700,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; u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; u32 defcontext_sid = 0; char **mount_options = opts->mnt_opts; @@ -672,6 +744,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, && (num_opts == 0)) goto out; + root_isec = backing_inode_security_novalidate(root); + /* * parse the mount options, check if they are valid sids. * also check if someone is trying to mount the same sb more @@ -748,7 +822,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, if (!strcmp(sb->s_type->name, "debugfs") || !strcmp(sb->s_type->name, "tracefs") || !strcmp(sb->s_type->name, "sysfs") || - !strcmp(sb->s_type->name, "pstore")) + !strcmp(sb->s_type->name, "pstore") || + !strcmp(sb->s_type->name, "bpf")) sbsec->flags |= SE_SBGENFS; if (!sbsec->behavior) { @@ -810,7 +885,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) { @@ -860,8 +935,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; } @@ -911,17 +986,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; } @@ -1303,11 +1375,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; @@ -1479,7 +1551,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); @@ -1571,7 +1643,7 @@ static int current_has_perm(const struct task_struct *tsk, /* Check whether a task is allowed to use a capability. */ static int cred_has_capability(const struct cred *cred, - int cap, int audit) + int cap, int audit, bool initns) { struct common_audit_data ad; struct av_decision avd; @@ -1585,10 +1657,10 @@ static int cred_has_capability(const struct cred *cred, switch (CAP_TO_INDEX(cap)) { case 0: - sclass = SECCLASS_CAPABILITY; + sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP_USERNS; break; case 1: - sclass = SECCLASS_CAPABILITY2; + sclass = initns ? SECCLASS_CAPABILITY2 : SECCLASS_CAP2_USERNS; break; default: printk(KERN_ERR @@ -1650,6 +1722,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); } @@ -1665,6 +1738,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); } @@ -1680,6 +1754,10 @@ static inline int file_path_has_perm(const struct cred *cred, return inode_has_perm(cred, file_inode(file), av, &ad); } +#ifdef CONFIG_BPF_SYSCALL +static int bpf_fd_pass(struct file *file, u32 sid); +#endif + /* Check whether a task can use an open file descriptor to access an inode in a given way. Check access to the descriptor itself, and then use dentry_has_perm to @@ -1710,6 +1788,12 @@ static int file_has_perm(const struct cred *cred, goto out; } +#ifdef CONFIG_BPF_SYSCALL + rc = bpf_fd_pass(file, cred_sid(cred)); + if (rc) + return rc; +#endif + /* av is zero if only checking access to the descriptor. */ rc = 0; if (av) @@ -1722,13 +1806,12 @@ 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 task_security_struct *tsec = current_security(); if ((sbsec->flags & SE_SBINITIALIZED) && @@ -1738,6 +1821,7 @@ static int selinux_determine_inode_label(const struct inode *dir, tsec->create_sid) { *_new_isid = tsec->create_sid; } else { + const struct inode_security_struct *dsec = inode_security(dir); return security_transition_sid(tsec->sid, dsec->sid, tclass, name, _new_isid); } @@ -1757,7 +1841,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; @@ -1810,8 +1894,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; @@ -1853,10 +1937,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; @@ -1884,7 +1968,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, @@ -1977,21 +2061,18 @@ static inline u32 open_file_to_av(struct file *file) /* Hook functions begin here. */ -static int selinux_binder_set_context_mgr(struct task_struct *mgr) +static int selinux_binder_set_context_mgr(const struct cred *mgr) { - u32 mysid = current_sid(); - u32 mgrsid = task_sid(mgr); - - return avc_has_perm(mysid, mgrsid, SECCLASS_BINDER, + return avc_has_perm(current_sid(), cred_sid(mgr), SECCLASS_BINDER, BINDER__SET_CONTEXT_MGR, NULL); } -static int selinux_binder_transaction(struct task_struct *from, - struct task_struct *to) +static int selinux_binder_transaction(const struct cred *from, + const struct cred *to) { u32 mysid = current_sid(); - u32 fromsid = task_sid(from); - u32 tosid = task_sid(to); + u32 fromsid = cred_sid(from); + u32 tosid = cred_sid(to); int rc; if (mysid != fromsid) { @@ -2005,24 +2086,22 @@ static int selinux_binder_transaction(struct task_struct *from, NULL); } -static int selinux_binder_transfer_binder(struct task_struct *from, - struct task_struct *to) +static int selinux_binder_transfer_binder(const struct cred *from, + const struct cred *to) { - u32 fromsid = task_sid(from); - u32 tosid = task_sid(to); - - return avc_has_perm(fromsid, tosid, SECCLASS_BINDER, BINDER__TRANSFER, + return avc_has_perm(cred_sid(from), cred_sid(to), + SECCLASS_BINDER, BINDER__TRANSFER, NULL); } -static int selinux_binder_transfer_file(struct task_struct *from, - struct task_struct *to, +static int selinux_binder_transfer_file(const struct cred *from, + const struct cred *to, struct file *file) { - u32 sid = task_sid(to); + u32 sid = cred_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; struct common_audit_data ad; int rc; @@ -2038,9 +2117,16 @@ static int selinux_binder_transfer_file(struct task_struct *from, return rc; } - if (unlikely(IS_PRIVATE(inode))) +#ifdef CONFIG_BPF_SYSCALL + rc = bpf_fd_pass(file, sid); + if (rc) + return rc; +#endif + + if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) return 0; + isec = backing_inode_security(dentry); return avc_has_perm(sid, isec->sid, isec->sclass, file_to_av(file), &ad); } @@ -2089,7 +2175,7 @@ static int selinux_capset(struct cred *new, const struct cred *old, static int selinux_capable(const struct cred *cred, struct user_namespace *ns, int cap, int audit) { - return cred_has_capability(cred, cap, audit); + return cred_has_capability(cred, cap, audit, ns == &init_user_ns); } static int selinux_quotactl(int cmds, int type, int id, struct super_block *sb) @@ -2167,7 +2253,7 @@ static int selinux_vm_enough_memory(struct mm_struct *mm, long pages) int rc, cap_sys_admin = 0; rc = cred_has_capability(current_cred(), CAP_SYS_ADMIN, - SECURITY_CAP_NOAUDIT); + SECURITY_CAP_NOAUDIT, true); if (rc == 0) cap_sys_admin = 1; @@ -2227,7 +2313,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; @@ -2649,7 +2735,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; @@ -2688,7 +2774,7 @@ static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data) return rc; /* Allow all mounts performed by the kernel */ - if (flags & MS_KERNMOUNT) + if (flags & (MS_KERNMOUNT | MS_SUBMOUNT)) return 0; ad.type = LSM_AUDIT_DATA_DENTRY; @@ -2763,13 +2849,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; @@ -2787,7 +2871,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)) @@ -2868,7 +2952,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); @@ -2920,7 +3006,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, @@ -2993,7 +3081,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; struct superblock_security_struct *sbsec; struct common_audit_data ad; u32 newsid, sid = current_sid(); @@ -3012,6 +3100,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; + isec = backing_inode_security(dentry); rc = avc_has_perm(sid, isec->sid, isec->sclass, FILE__RELABELFROM, &ad); if (rc) @@ -3070,7 +3159,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; u32 newsid; int rc; @@ -3087,9 +3176,10 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, return; } + isec = backing_inode_security(dentry); isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; - isec->initialized = 1; + isec->initialized = LABEL_INITIALIZED; return; } @@ -3123,12 +3213,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; if (strcmp(name, XATTR_SELINUX_SUFFIX)) return -EOPNOTSUPP; @@ -3146,7 +3236,8 @@ static int selinux_inode_getsecurity(const struct inode *inode, const char *name SECURITY_CAP_NOAUDIT); if (!error) error = cred_has_capability(current_cred(), CAP_MAC_ADMIN, - SECURITY_CAP_NOAUDIT); + SECURITY_CAP_NOAUDIT, true); + isec = inode_security(inode); if (!error) error = security_sid_to_context_force(isec->sid, &context, &size); @@ -3167,7 +3258,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_novalidate(inode); u32 newsid; int rc; @@ -3183,7 +3274,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; } @@ -3195,9 +3286,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(inode); *secid = isec->sid; } @@ -3220,13 +3311,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. */ @@ -3255,7 +3347,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; struct lsm_ioctlop_audit ioctl; u32 ssid = cred_sid(cred); int rc; @@ -3279,6 +3371,7 @@ static int ioctl_has_perm(const struct cred *cred, struct file *file, if (unlikely(IS_PRIVATE(inode))) return 0; + isec = inode_security(inode); rc = avc_has_extended_perms(ssid, isec->sid, isec->sclass, requested, driver, xperm, &ad); out: @@ -3320,7 +3413,7 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, case KDSKBENT: case KDSKBSENT: error = cred_has_capability(cred, CAP_SYS_TTY_CONFIG, - SECURITY_CAP_AUDIT); + SECURITY_CAP_AUDIT, true); break; /* default case assumes that the command will go @@ -3519,7 +3612,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 @@ -3637,7 +3730,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; @@ -3681,19 +3774,19 @@ static int selinux_kernel_module_from_file(struct file *file) SYSTEM__MODULE_LOAD, NULL); /* finit_module */ + ad.type = LSM_AUDIT_DATA_PATH; ad.u.path = file->f_path; - inode = file_inode(file); - isec = inode->i_security; fsec = file->f_security; - if (sid != fsec->sid) { rc = avc_has_perm(sid, fsec->sid, SECCLASS_FD, FD__USE, &ad); if (rc) return rc; } + inode = file_inode(file); + isec = inode->i_security; return avc_has_perm(sid, isec->sid, SECCLASS_SYSTEM, SYSTEM__MODULE_LOAD, &ad); } @@ -3793,7 +3886,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 */ @@ -4112,7 +4205,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; @@ -4126,7 +4219,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; @@ -4320,12 +4413,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; } @@ -4660,7 +4753,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 || @@ -4741,9 +4835,9 @@ static void selinux_secmark_refcount_dec(void) } static void selinux_req_classify_flow(const struct request_sock *req, - struct flowi *fl) + struct flowi_common *flic) { - fl->flowi_secid = req->secid; + flic->flowic_secid = req->secid; } static int selinux_tun_dev_alloc_security(void **security) @@ -4826,38 +4920,59 @@ static int selinux_tun_dev_open(void *security) static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) { - int err = 0; - u32 perm; + int rc = 0; + unsigned int msg_len; + unsigned int data_len = skb->len; + unsigned char *data = skb->data; struct nlmsghdr *nlh; struct sk_security_struct *sksec = sk->sk_security; + u16 sclass = sksec->sclass; + u32 perm; - if (skb->len < NLMSG_HDRLEN) { - err = -EINVAL; - goto out; - } - nlh = nlmsg_hdr(skb); + while (data_len >= nlmsg_total_size(0)) { + nlh = (struct nlmsghdr *)data; - 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", - sk->sk_protocol, nlh->nlmsg_type, - secclass_map[sksec->sclass - 1].name); - if (!selinux_enforcing || security_get_allow_unknown()) - err = 0; + /* NOTE: the nlmsg_len field isn't reliably set by some netlink + * users which means we can't reject skb's with bogus + * length fields; our solution is to follow what + * netlink_rcv_skb() does and simply skip processing at + * messages with length fields that are clearly junk + */ + if (nlh->nlmsg_len < NLMSG_HDRLEN || nlh->nlmsg_len > data_len) + return 0; + + rc = selinux_nlmsg_lookup(sclass, nlh->nlmsg_type, &perm); + if (rc == 0) { + rc = sock_has_perm(current, sk, perm); + if (rc) + return rc; + } else if (rc == -EINVAL) { + /* -EINVAL is a missing msg/perm mapping */ + pr_warn_ratelimited("SELinux: unrecognized netlink" + " message: protocol=%hu nlmsg_type=%hu sclass=%s" + " pid=%d comm=%s\n", + sk->sk_protocol, nlh->nlmsg_type, + secclass_map[sclass - 1].name, + task_pid_nr(current), current->comm); + if (selinux_enforcing && !security_get_allow_unknown()) + return rc; + rc = 0; + } else if (rc == -ENOENT) { + /* -ENOENT is a missing socket/class mapping, ignore */ + rc = 0; + } else { + return rc; } - /* Ignore */ - if (err == -ENOENT) - err = 0; - goto out; + /* move to the next message after applying netlink padding */ + msg_len = NLMSG_ALIGN(nlh->nlmsg_len); + if (msg_len >= data_len) + return 0; + data_len -= msg_len; + data += msg_len; } - err = sock_has_perm(current, sk, perm); -out: - return err; + return rc; } #ifdef CONFIG_NETFILTER @@ -4993,7 +5108,7 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, struct common_audit_data ad; struct lsm_network_audit net = {0,}; char *addrp; - u8 proto; + u8 proto = 0; if (sk == NULL) return NF_ACCEPT; @@ -5817,6 +5932,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 */ @@ -5911,6 +6035,139 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer) #endif +#ifdef CONFIG_BPF_SYSCALL +static int selinux_bpf(int cmd, union bpf_attr *attr, + unsigned int size) +{ + u32 sid = current_sid(); + int ret; + + switch (cmd) { + case BPF_MAP_CREATE: + ret = avc_has_perm(sid, sid, SECCLASS_BPF, BPF__MAP_CREATE, + NULL); + break; + case BPF_PROG_LOAD: + ret = avc_has_perm(sid, sid, SECCLASS_BPF, BPF__PROG_LOAD, + NULL); + break; + default: + ret = 0; + break; + } + + return ret; +} + +static u32 bpf_map_fmode_to_av(fmode_t fmode) +{ + u32 av = 0; + + if (fmode & FMODE_READ) + av |= BPF__MAP_READ; + if (fmode & FMODE_WRITE) + av |= BPF__MAP_WRITE; + return av; +} + +/* This function will check the file pass through unix socket or binder to see + * if it is a bpf related object. And apply correspinding checks on the bpf + * object based on the type. The bpf maps and programs, not like other files and + * socket, are using a shared anonymous inode inside the kernel as their inode. + * So checking that inode cannot identify if the process have privilege to + * access the bpf object and that's why we have to add this additional check in + * selinux_file_receive and selinux_binder_transfer_files. + */ +static int bpf_fd_pass(struct file *file, u32 sid) +{ + struct bpf_security_struct *bpfsec; + struct bpf_prog *prog; + struct bpf_map *map; + int ret; + + if (file->f_op == &bpf_map_fops) { + map = file->private_data; + bpfsec = map->security; + ret = avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, + bpf_map_fmode_to_av(file->f_mode), NULL); + if (ret) + return ret; + } else if (file->f_op == &bpf_prog_fops) { + prog = file->private_data; + bpfsec = prog->aux->security; + ret = avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, + BPF__PROG_RUN, NULL); + if (ret) + return ret; + } + return 0; +} + +static int selinux_bpf_map(struct bpf_map *map, fmode_t fmode) +{ + u32 sid = current_sid(); + struct bpf_security_struct *bpfsec; + + bpfsec = map->security; + return avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, + bpf_map_fmode_to_av(fmode), NULL); +} + +static int selinux_bpf_prog(struct bpf_prog *prog) +{ + u32 sid = current_sid(); + struct bpf_security_struct *bpfsec; + + bpfsec = prog->aux->security; + return avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, + BPF__PROG_RUN, NULL); +} + +static int selinux_bpf_map_alloc(struct bpf_map *map) +{ + struct bpf_security_struct *bpfsec; + + bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL); + if (!bpfsec) + return -ENOMEM; + + bpfsec->sid = current_sid(); + map->security = bpfsec; + + return 0; +} + +static void selinux_bpf_map_free(struct bpf_map *map) +{ + struct bpf_security_struct *bpfsec = map->security; + + map->security = NULL; + kfree(bpfsec); +} + +static int selinux_bpf_prog_alloc(struct bpf_prog_aux *aux) +{ + struct bpf_security_struct *bpfsec; + + bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL); + if (!bpfsec) + return -ENOMEM; + + bpfsec->sid = current_sid(); + aux->security = bpfsec; + + return 0; +} + +static void selinux_bpf_prog_free(struct bpf_prog_aux *aux) +{ + struct bpf_security_struct *bpfsec = aux->security; + + aux->security = NULL; + kfree(bpfsec); +} +#endif + static struct security_hook_list selinux_hooks[] = { LSM_HOOK_INIT(binder_set_context_mgr, selinux_binder_set_context_mgr), LSM_HOOK_INIT(binder_transaction, selinux_binder_transaction), @@ -6049,6 +6306,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), @@ -6121,6 +6379,16 @@ static struct security_hook_list selinux_hooks[] = { LSM_HOOK_INIT(audit_rule_match, selinux_audit_rule_match), LSM_HOOK_INIT(audit_rule_free, selinux_audit_rule_free), #endif + +#ifdef CONFIG_BPF_SYSCALL + LSM_HOOK_INIT(bpf, selinux_bpf), + LSM_HOOK_INIT(bpf_map, selinux_bpf_map), + LSM_HOOK_INIT(bpf_prog, selinux_bpf_prog), + LSM_HOOK_INIT(bpf_map_alloc_security, selinux_bpf_map_alloc), + LSM_HOOK_INIT(bpf_prog_alloc_security, selinux_bpf_prog_alloc), + LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free), + LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free), +#endif }; static __init int selinux_init(void) |