From patchwork Mon Apr 18 09:45:46 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alexander Kozhevnikov X-Patchwork-Id: 12816438 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C93BAC433F5 for ; Mon, 18 Apr 2022 09:46:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236198AbiDRJtD (ORCPT ); Mon, 18 Apr 2022 05:49:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60260 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233208AbiDRJtB (ORCPT ); Mon, 18 Apr 2022 05:49:01 -0400 Received: from szxga02-in.huawei.com (szxga02-in.huawei.com [45.249.212.188]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 36A2C165A2; Mon, 18 Apr 2022 02:46:20 -0700 (PDT) Received: from dggpemm500023.china.huawei.com (unknown [172.30.72.56]) by szxga02-in.huawei.com (SkyGuard) with ESMTP id 4KhhpF1c25zFq5C; Mon, 18 Apr 2022 17:43:49 +0800 (CST) Received: from dggpemm500011.china.huawei.com (7.185.36.110) by dggpemm500023.china.huawei.com (7.185.36.83) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:18 +0800 Received: from mscphmkozh00002.huawei.com (10.219.174.70) by dggpemm500011.china.huawei.com (7.185.36.110) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:16 +0800 From: Alexander Kozhevnikov To: CC: , , , , , , , , , , Subject: [RFC PATCH 1/7] LSM: Infrastructure management of the superblock Date: Mon, 18 Apr 2022 17:45:46 +0800 Message-ID: <20220418094552.128898-2-alexander.kozhevnikov@huawei.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> References: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.219.174.70] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To dggpemm500011.china.huawei.com (7.185.36.110) X-CFilter-Loop: Reflected Precedence: bulk List-ID: From: Igor Baranov From 6618c9c9b6aba516a32cd38798055c061c7e4455 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Thu, 22 Apr 2021 17:41:15 +0200 Subject: [PATCH 1/4] LSM: Infrastructure management of the superblock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move management of the superblock->sb_security blob out of the individual security modules and into the security infrastructure. Instead of allocating the blobs from within the modules, the modules tell the infrastructure how much space is required, and the space is allocated there. Cc: John Johansen Signed-off-by: Casey Schaufler Signed-off-by: Mickaël Salaün Reviewed-by: Stephen Smalley Acked-by: Serge Hallyn Reviewed-by: Kees Cook Link: https://lore.kernel.org/r/20210422154123.13086-6-mic@digikod.net Signed-off-by: James Morris --- include/linux/lsm_hooks.h | 1 + security/security.c | 46 +++++++++++++++++++---- security/selinux/hooks.c | 58 +++++++++++------------------ security/selinux/include/objsec.h | 16 ++++++++ security/selinux/include/security.h | 10 ----- security/selinux/ss/services.c | 3 +- security/smack/smack.h | 6 +++ security/smack/smack_lsm.c | 35 +++++------------ 8 files changed, 95 insertions(+), 80 deletions(-) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 9e2e3e63719d..29df5075b35d 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1550,6 +1550,7 @@ struct lsm_blob_sizes { int lbs_cred; int lbs_file; int lbs_inode; + int lbs_superblock; int lbs_ipc; int lbs_msg_msg; int lbs_task; diff --git a/security/security.c b/security/security.c index 70a7ad357bc6..d60aa835b670 100644 --- a/security/security.c +++ b/security/security.c @@ -201,6 +201,7 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode); lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc); lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg); + lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock); lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task); } @@ -331,12 +332,13 @@ static void __init ordered_lsm_init(void) for (lsm = ordered_lsms; *lsm; lsm++) prepare_lsm(*lsm); - init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); - init_debug("file blob size = %d\n", blob_sizes.lbs_file); - init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); - init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); - init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); - init_debug("task blob size = %d\n", blob_sizes.lbs_task); + init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); + init_debug("file blob size = %d\n", blob_sizes.lbs_file); + init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); + init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); + init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); + init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock); + init_debug("task blob size = %d\n", blob_sizes.lbs_task); /* * Create any kmem_caches needed for blobs @@ -668,6 +670,27 @@ static void __init lsm_early_task(struct task_struct *task) panic("%s: Early task alloc failed.\n", __func__); } +/** + * lsm_superblock_alloc - allocate a composite superblock blob + * @sb: the superblock that needs a blob + * + * Allocate the superblock blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. + */ +static int lsm_superblock_alloc(struct super_block *sb) +{ + if (blob_sizes.lbs_superblock == 0) { + sb->s_security = NULL; + return 0; + } + + sb->s_security = kzalloc(blob_sizes.lbs_superblock, GFP_KERNEL); + if (sb->s_security == NULL) + return -ENOMEM; + return 0; +} + /* * The default value of the LSM hook is defined in linux/lsm_hook_defs.h and * can be accessed with: @@ -865,12 +888,21 @@ int security_fs_context_parse_param(struct fs_context *fc, struct fs_parameter * int security_sb_alloc(struct super_block *sb) { - return call_int_hook(sb_alloc_security, 0, sb); + int rc = lsm_superblock_alloc(sb); + + if (unlikely(rc)) + return rc; + rc = call_int_hook(sb_alloc_security, 0, sb); + if (unlikely(rc)) + security_sb_free(sb); + return rc; } void security_sb_free(struct super_block *sb) { call_void_hook(sb_free_security, sb); + kfree(sb->s_security); + sb->s_security = NULL; } void security_free_mnt_opts(void **mnt_opts) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index f31d1ddca601..8d88b6bec24b 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -342,7 +342,7 @@ static void inode_free_security(struct inode *inode) if (!isec) return; - sbsec = inode->i_sb->s_security; + sbsec = selinux_superblock(inode->i_sb); /* * As not all inode security structures are in a list, we check for * empty list outside of the lock to make sure that we won't waste @@ -360,13 +360,6 @@ static void inode_free_security(struct inode *inode) } } -static void superblock_free_security(struct super_block *sb) -{ - struct superblock_security_struct *sbsec = sb->s_security; - sb->s_security = NULL; - kfree(sbsec); -} - struct selinux_mnt_opts { const char *fscontext, *context, *rootcontext, *defcontext; }; @@ -478,7 +471,7 @@ static int selinux_is_genfs_special_handling(struct super_block *sb) static int selinux_is_sblabel_mnt(struct super_block *sb) { - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); /* * IMPORTANT: Double-check logic in this function when adding a new @@ -506,7 +499,7 @@ static int selinux_is_sblabel_mnt(struct super_block *sb) static int sb_finish_set_opts(struct super_block *sb) { - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); struct dentry *root = sb->s_root; struct inode *root_inode = d_backing_inode(root); int rc = 0; @@ -619,7 +612,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, unsigned long *set_kern_flags) { const struct cred *cred = current_cred(); - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); struct dentry *root = sbsec->sb->s_root; struct selinux_mnt_opts *opts = mnt_opts; struct inode_security_struct *root_isec; @@ -856,8 +849,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, static int selinux_cmp_sb_context(const struct super_block *oldsb, const struct super_block *newsb) { - struct superblock_security_struct *old = oldsb->s_security; - struct superblock_security_struct *new = newsb->s_security; + struct superblock_security_struct *old = selinux_superblock(oldsb); + struct superblock_security_struct *new = selinux_superblock(newsb); char oldflags = old->flags & SE_MNTMASK; char newflags = new->flags & SE_MNTMASK; @@ -889,8 +882,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, unsigned long *set_kern_flags) { int rc = 0; - const struct superblock_security_struct *oldsbsec = oldsb->s_security; - struct superblock_security_struct *newsbsec = newsb->s_security; + const struct superblock_security_struct *oldsbsec = + selinux_superblock(oldsb); + struct superblock_security_struct *newsbsec = selinux_superblock(newsb); int set_fscontext = (oldsbsec->flags & FSCONTEXT_MNT); int set_context = (oldsbsec->flags & CONTEXT_MNT); @@ -1069,7 +1063,7 @@ static int show_sid(struct seq_file *m, u32 sid) static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) { - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); int rc; if (!(sbsec->flags & SE_SBINITIALIZED)) @@ -1419,7 +1413,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent if (isec->sclass == SECCLASS_FILE) isec->sclass = inode_mode_to_security_class(inode->i_mode); - sbsec = inode->i_sb->s_security; + sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SE_SBINITIALIZED)) { /* Defer initialization until selinux_complete_init, after the initial policy is loaded and the security @@ -1762,7 +1756,8 @@ selinux_determine_inode_label(const struct task_security_struct *tsec, const struct qstr *name, u16 tclass, u32 *_new_isid) { - const struct superblock_security_struct *sbsec = dir->i_sb->s_security; + const struct superblock_security_struct *sbsec = + selinux_superblock(dir->i_sb); if ((sbsec->flags & SE_SBINITIALIZED) && (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { @@ -1793,7 +1788,7 @@ static int may_create(struct inode *dir, int rc; dsec = inode_security(dir); - sbsec = dir->i_sb->s_security; + sbsec = selinux_superblock(dir->i_sb); sid = tsec->sid; @@ -1942,7 +1937,7 @@ static int superblock_has_perm(const struct cred *cred, struct superblock_security_struct *sbsec; u32 sid = cred_sid(cred); - sbsec = sb->s_security; + sbsec = selinux_superblock(sb); return avc_has_perm(cred_selinux_state(cred), sid, sbsec->sid, SECCLASS_FILESYSTEM, perms, ad); } @@ -2571,11 +2566,7 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) static int selinux_sb_alloc_security(struct super_block *sb) { - struct superblock_security_struct *sbsec; - - sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); - if (!sbsec) - return -ENOMEM; + struct superblock_security_struct *sbsec = selinux_superblock(sb); mutex_init(&sbsec->lock); INIT_LIST_HEAD(&sbsec->isec_head); @@ -2584,16 +2575,10 @@ static int selinux_sb_alloc_security(struct super_block *sb) sbsec->sid = SECINITSID_UNLABELED; sbsec->def_sid = SECINITSID_FILE; sbsec->mntpoint_sid = SECINITSID_UNLABELED; - sb->s_security = sbsec; return 0; } -static void selinux_sb_free_security(struct super_block *sb) -{ - superblock_free_security(sb); -} - static inline int opt_len(const char *s) { bool open_quote = false; @@ -2672,7 +2657,7 @@ static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts) static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) { struct selinux_mnt_opts *opts = mnt_opts; - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); u32 sid; int rc; @@ -2910,7 +2895,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, int rc; char *context; - sbsec = dir->i_sb->s_security; + sbsec = selinux_superblock(dir->i_sb); newsid = tsec->create_sid; @@ -3155,7 +3140,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, if (!selinux_initialized(current_selinux_state)) return (inode_owner_or_capable(inode) ? 0 : -EPERM); - sbsec = inode->i_sb->s_security; + sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; @@ -3397,13 +3382,14 @@ 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_security_novalidate(inode); - struct superblock_security_struct *sbsec = inode->i_sb->s_security; + struct superblock_security_struct *sbsec; u32 newsid; int rc; if (strcmp(name, XATTR_SELINUX_SUFFIX)) return -EOPNOTSUPP; + sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; @@ -6917,6 +6903,7 @@ struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = { .lbs_inode = sizeof(struct inode_security_struct), .lbs_ipc = sizeof(struct ipc_security_struct), .lbs_msg_msg = sizeof(struct msg_security_struct), + .lbs_superblock = sizeof(struct superblock_security_struct), }; #ifdef CONFIG_PERF_EVENTS @@ -7017,7 +7004,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds), LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds), - LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts), LSM_HOOK_INIT(sb_remount, selinux_sb_remount), LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index a24b97800111..f456bb5782a3 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -166,4 +166,20 @@ static inline struct ipc_security_struct *selinux_ipc( return ipc->security + selinux_blob_sizes.lbs_ipc; } +/* + * get the subjective security ID of the current task + */ +static inline u32 current_sid(void) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + + return tsec->sid; +} + +static inline struct superblock_security_struct *selinux_superblock( + const struct super_block *superblock) +{ + return superblock->s_security + selinux_blob_sizes.lbs_superblock; +} + #endif /* _SELINUX_OBJSEC_H_ */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 8eb058e1182c..1a7d76f0faf3 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -151,16 +151,6 @@ static inline struct task_security_struct *selinux_cred(const struct cred *cred) return cred->security + selinux_blob_sizes.lbs_cred; } -/* - * get the subjective security ID of the current task - */ -static inline u32 current_sid(void) -{ - const struct task_security_struct *tsec = selinux_cred(current_cred()); - - return tsec->sid; -} - #define current_selinux_state (selinux_cred(current_cred())->state) #define cred_selinux_state(cred) (selinux_cred(cred)->state) diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index a89a5107c5d7..2c9e77ca4754 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include "flask.h" @@ -2874,7 +2875,7 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) struct sidtab *sidtab; int rc = 0; struct ocontext *c; - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); const char *fstype = sb->s_type->name; if (!selinux_initialized(state)) { diff --git a/security/smack/smack.h b/security/smack/smack.h index e9e817d09785..d33d2a7b73a3 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -364,6 +364,12 @@ static inline struct smack_known **smack_ipc(const struct kern_ipc_perm *ipc) return ipc->security + smack_blob_sizes.lbs_ipc; } +static inline struct superblock_smack *smack_superblock( + const struct super_block *superblock) +{ + return superblock->s_security + smack_blob_sizes.lbs_superblock; +} + /* * Is the directory transmuting? */ diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8ffbf951b7ed..8d8f70a99374 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -535,12 +535,7 @@ static int smack_syslog(int typefrom_file) */ static int smack_sb_alloc_security(struct super_block *sb) { - struct superblock_smack *sbsp; - - sbsp = kzalloc(sizeof(struct superblock_smack), GFP_KERNEL); - - if (sbsp == NULL) - return -ENOMEM; + struct superblock_smack *sbsp = smack_superblock(sb); sbsp->smk_root = &smack_known_floor; sbsp->smk_default = &smack_known_floor; @@ -549,22 +544,10 @@ static int smack_sb_alloc_security(struct super_block *sb) /* * SMK_SB_INITIALIZED will be zero from kzalloc. */ - sb->s_security = sbsp; return 0; } -/** - * smack_sb_free_security - free a superblock blob - * @sb: the superblock getting the blob - * - */ -static void smack_sb_free_security(struct super_block *sb) -{ - kfree(sb->s_security); - sb->s_security = NULL; -} - struct smack_mnt_opts { const char *fsdefault, *fsfloor, *fshat, *fsroot, *fstransmute; }; @@ -772,7 +755,7 @@ static int smack_set_mnt_opts(struct super_block *sb, { struct dentry *root = sb->s_root; struct inode *inode = d_backing_inode(root); - struct superblock_smack *sp = sb->s_security; + struct superblock_smack *sp = smack_superblock(sb); struct inode_smack *isp; struct smack_known *skp; struct smack_mnt_opts *opts = mnt_opts; @@ -871,7 +854,7 @@ static int smack_set_mnt_opts(struct super_block *sb, */ static int smack_sb_statfs(struct dentry *dentry) { - struct superblock_smack *sbp = dentry->d_sb->s_security; + struct superblock_smack *sbp = smack_superblock(dentry->d_sb); int rc; struct smk_audit_info ad; @@ -905,7 +888,7 @@ static int smack_bprm_creds_for_exec(struct linux_binprm *bprm) if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task) return 0; - sbsp = inode->i_sb->s_security; + sbsp = smack_superblock(inode->i_sb); if ((sbsp->smk_flags & SMK_SB_UNTRUSTED) && isp->smk_task != sbsp->smk_root) return 0; @@ -1157,7 +1140,7 @@ static int smack_inode_rename(struct inode *old_inode, */ static int smack_inode_permission(struct inode *inode, int mask) { - struct superblock_smack *sbsp = inode->i_sb->s_security; + struct superblock_smack *sbsp = smack_superblock(inode->i_sb); struct smk_audit_info ad; int no_block = mask & MAY_NOT_BLOCK; int rc; @@ -1398,7 +1381,7 @@ static int smack_inode_removexattr(struct dentry *dentry, const char *name) */ if (strcmp(name, XATTR_NAME_SMACK) == 0) { struct super_block *sbp = dentry->d_sb; - struct superblock_smack *sbsp = sbp->s_security; + struct superblock_smack *sbsp = smack_superblock(sbp); isp->smk_inode = sbsp->smk_default; } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) @@ -1668,7 +1651,7 @@ static int smack_mmap_file(struct file *file, isp = smack_inode(file_inode(file)); if (isp->smk_mmap == NULL) return 0; - sbsp = file_inode(file)->i_sb->s_security; + sbsp = smack_superblock(file_inode(file)->i_sb); if (sbsp->smk_flags & SMK_SB_UNTRUSTED && isp->smk_mmap != sbsp->smk_root) return -EACCES; @@ -3268,7 +3251,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) return; sbp = inode->i_sb; - sbsp = sbp->s_security; + sbsp = smack_superblock(sbp); /* * We're going to use the superblock default label * if there's no label on the file. @@ -4653,6 +4636,7 @@ struct lsm_blob_sizes smack_blob_sizes __lsm_ro_after_init = { .lbs_inode = sizeof(struct inode_smack), .lbs_ipc = sizeof(struct smack_known *), .lbs_msg_msg = sizeof(struct smack_known *), + .lbs_superblock = sizeof(struct superblock_smack), }; static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { @@ -4664,7 +4648,6 @@ static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(fs_context_parse_param, smack_fs_context_parse_param), LSM_HOOK_INIT(sb_alloc_security, smack_sb_alloc_security), - LSM_HOOK_INIT(sb_free_security, smack_sb_free_security), LSM_HOOK_INIT(sb_free_mnt_opts, smack_free_mnt_opts), LSM_HOOK_INIT(sb_eat_lsm_opts, smack_sb_eat_lsm_opts), LSM_HOOK_INIT(sb_statfs, smack_sb_statfs), From patchwork Mon Apr 18 09:45:47 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kozhevnikov X-Patchwork-Id: 12816440 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 34689C433EF for ; Mon, 18 Apr 2022 09:46:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237554AbiDRJtN (ORCPT ); Mon, 18 Apr 2022 05:49:13 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60296 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236991AbiDRJtE (ORCPT ); Mon, 18 Apr 2022 05:49:04 -0400 Received: from szxga02-in.huawei.com (szxga02-in.huawei.com [45.249.212.188]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id AEED5165A2; Mon, 18 Apr 2022 02:46:25 -0700 (PDT) Received: from dggpemm500022.china.huawei.com (unknown [172.30.72.55]) by szxga02-in.huawei.com (SkyGuard) with ESMTP id 4KhhpM02G1zFq5F; Mon, 18 Apr 2022 17:43:55 +0800 (CST) Received: from dggpemm500011.china.huawei.com (7.185.36.110) by dggpemm500022.china.huawei.com (7.185.36.162) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:20 +0800 Received: from mscphmkozh00002.huawei.com (10.219.174.70) by dggpemm500011.china.huawei.com (7.185.36.110) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:18 +0800 From: Alexander Kozhevnikov To: CC: , , , , , , , , , , Subject: [RFC PATCH 2/7] selinux: support per-namespace superblock security structures Date: Mon, 18 Apr 2022 17:45:47 +0800 Message-ID: <20220418094552.128898-3-alexander.kozhevnikov@huawei.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> References: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.219.174.70] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To dggpemm500011.china.huawei.com (7.185.36.110) X-CFilter-Loop: Reflected Precedence: bulk List-ID: From: Igor Baranov This port of commit https://github.com/stephensmalley/selinux-kernel/commit/3378718ef7d4a837f32c63bdfcc0b70342cdd55d Rebased to working-selinuxns and refactored. Extend the superblock security structure to include a reference to the associated selinux namespace, and turn it into a list so that we can maintain per-superblock security state for each namespace. This is necessary because the superblock SIDs and labeling behavior are per selinux namespace. It further enables one to context-mount a filesystem with a particular context in one namespace while using xattrs in another, e.g. one might context mount a container filesystem in the init selinux namespace to provide MCS-style isolation of the containers while using per-file xattrs within the container to support conventional SELinux targeted policy. Introduce a superblock_security() helper to return the superblock security blob for the current selinux namespace and replace direct uses of sb->s_security with calls to it. Also revert the changes made by commit a64c54cf0811b8032fdab8c9d52576f0370837fa ("SELinux: pass a superblock to security_fs_use") so that access to the superblock security structure is properly encapsulated and we can support per-namespace structures. This change has similar problems as with the inode security structure change, see the list of issues in that commit. Not-signed-off-by: Stephen Smalley Signed-off-by: Alexander Kozhevnikov Signed-off-by: Igor Baranov --- security/selinux/hooks.c | 90 ++++++++++++++++++++++++++--- security/selinux/include/objsec.h | 20 ++++++- security/selinux/include/security.h | 3 +- security/selinux/ss/services.c | 19 +++--- 4 files changed, 112 insertions(+), 20 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 8d88b6bec24b..a1716ce534dd 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -613,7 +613,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, { const struct cred *cred = current_cred(); struct superblock_security_struct *sbsec = selinux_superblock(sb); - struct dentry *root = sbsec->sb->s_root; + struct dentry *root = sb->s_root; struct selinux_mnt_opts *opts = mnt_opts; struct inode_security_struct *root_isec; u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; @@ -730,7 +730,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, * Determine the labeling behavior to use for this * filesystem type. */ - rc = security_fs_use(current_selinux_state, sb); + rc = security_fs_use(current_selinux_state, sb->s_type->name, + &sbsec->behavior, &sbsec->sid); if (rc) { pr_warn("%s: security_fs_use(%s) returned %d\n", __func__, sb->s_type->name, rc); @@ -924,7 +925,8 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, if (newsbsec->behavior == SECURITY_FS_USE_NATIVE && !(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) { - rc = security_fs_use(current_selinux_state, newsb); + rc = security_fs_use(current_selinux_state, newsb->s_type->name, + &newsbsec->behavior, &newsbsec->sid); if (rc) goto out; } @@ -1094,7 +1096,7 @@ static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) return rc; } if (sbsec->flags & ROOTCONTEXT_MNT) { - struct dentry *root = sbsec->sb->s_root; + struct dentry *root = sb->s_root; struct inode_security_struct *isec = backing_inode_security(root); seq_putc(m, ','); seq_puts(m, ROOTCONTEXT_STR); @@ -2564,21 +2566,94 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) /* superblock security operations */ -static int selinux_sb_alloc_security(struct super_block *sb) +static void sbsec_head_init(struct super_block *sb, + struct superblock_security_head *sbsech) { - struct superblock_security_struct *sbsec = selinux_superblock(sb); + INIT_LIST_HEAD(&sbsech->head); + spin_lock_init(&sbsech->lock); +} +static struct superblock_security_struct * +sbsec_alloc(struct superblock_security_head *sbsech) +{ + struct superblock_security_struct *sbsec; + + sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_NOFS); + if (!sbsec) + return NULL; mutex_init(&sbsec->lock); INIT_LIST_HEAD(&sbsec->isec_head); spin_lock_init(&sbsec->isec_lock); - sbsec->sb = sb; + sbsec->sbsech = sbsech; sbsec->sid = SECINITSID_UNLABELED; sbsec->def_sid = SECINITSID_FILE; sbsec->mntpoint_sid = SECINITSID_UNLABELED; + sbsec->state = get_selinux_state(current_selinux_state); + + return sbsec; +} + +struct superblock_security_struct * +find_sbsec(struct superblock_security_head *sbsech) +{ + struct superblock_security_struct *cur, *new; + + cur = container_of(sbsech->head.next, struct superblock_security_struct, sbsec_list); + if (cur->state == current_selinux_state) + return cur; + spin_lock(&sbsech->lock); + list_for_each_entry(cur, &sbsech->head, sbsec_list) { + if (cur->state == current_selinux_state) + goto unlock; + } + spin_unlock(&sbsech->lock); + + new = sbsec_alloc(sbsech); + if (!new) { + cur = NULL; + goto out; + } + + spin_lock(&sbsech->lock); + list_for_each_entry(cur, &sbsech->head, sbsec_list) { + if (cur->state == current_selinux_state) + goto unlock; + } + list_add(&new->sbsec_list, &sbsech->head); + cur = new; +unlock: + spin_unlock(&sbsech->lock); +out: + return cur; +} + +static int selinux_sb_alloc_security(struct super_block *sb) +{ + struct superblock_security_struct *sbsec; + struct superblock_security_head *sbsech = selinux_head_of_superblock(sb); + + sbsec_head_init(sb, sbsech); + sbsec = sbsec_alloc(sbsech); + if (!sbsec) + return -ENOMEM; + spin_lock(&sbsech->lock); + list_add(&sbsec->sbsec_list, &sbsech->head); + spin_unlock(&sbsech->lock); return 0; } +static void selinux_sb_free_security(struct super_block *sb) +{ + struct superblock_security_struct *entry, *tmp; + struct superblock_security_head *sbsech = selinux_head_of_superblock(sb); + + list_for_each_entry_safe(entry, tmp, &sbsech->head, sbsec_list) { + put_selinux_state(entry->state); + kfree(entry); + } +} + static inline int opt_len(const char *s) { bool open_quote = false; @@ -7224,6 +7299,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { selinux_msg_queue_alloc_security), LSM_HOOK_INIT(shm_alloc_security, selinux_shm_alloc_security), LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security), + LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), LSM_HOOK_INIT(inode_alloc_security, selinux_inode_alloc_security), LSM_HOOK_INIT(sem_alloc_security, selinux_sem_alloc_security), LSM_HOOK_INIT(secid_to_secctx, selinux_secid_to_secctx), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index f456bb5782a3..6ad1db45b0d9 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -51,8 +51,13 @@ struct file_security_struct { u32 pseqno; /* Policy seqno at the time of file open */ }; +struct superblock_security_head { + struct list_head head; /* list head of superblock_security_struct */ + spinlock_t lock; +}; + struct superblock_security_struct { - struct super_block *sb; /* back pointer to sb object */ + struct superblock_security_head *sbsech; /* back pointer to superbock_security_head */ u32 sid; /* SID of file system superblock */ u32 def_sid; /* default SID for labeling */ u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ @@ -61,6 +66,8 @@ struct superblock_security_struct { struct mutex lock; struct list_head isec_head; spinlock_t isec_lock; + struct selinux_state *state; /* pointer to selinux_state */ + struct list_head sbsec_list; }; struct msg_security_struct { @@ -176,10 +183,19 @@ static inline u32 current_sid(void) return tsec->sid; } -static inline struct superblock_security_struct *selinux_superblock( +static inline struct superblock_security_head *selinux_head_of_superblock( const struct super_block *superblock) { return superblock->s_security + selinux_blob_sizes.lbs_superblock; } +extern struct superblock_security_struct * +find_sbsec(struct superblock_security_head *sbsech); + +static inline struct superblock_security_struct *selinux_superblock( + const struct super_block *superblock) +{ + return find_sbsec(selinux_head_of_superblock(superblock)); +} + #endif /* _SELINUX_OBJSEC_H_ */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 1a7d76f0faf3..a5b698aae38c 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -410,7 +410,8 @@ int security_get_allow_unknown(struct selinux_state *state); #define SECURITY_FS_USE_NATIVE 7 /* use native label support */ #define SECURITY_FS_USE_MAX 7 /* Highest SECURITY_FS_USE_XXX */ -int security_fs_use(struct selinux_state *state, struct super_block *sb); +int security_fs_use(struct selinux_state *state, const char *fstype, + unsigned short *behavior, u32 *sid); int security_genfs_sid(struct selinux_state *state, const char *fstype, char *name, u16 sclass, diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 2c9e77ca4754..548acdecafa8 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2868,19 +2868,18 @@ int selinux_policy_genfs_sid(struct selinux_policy *policy, * security_fs_use - Determine how to handle labeling for a filesystem. * @sb: superblock in question */ -int security_fs_use(struct selinux_state *state, struct super_block *sb) +int security_fs_use(struct selinux_state *state, const char *fstype, + unsigned short *behavior, u32 *sid) { struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; int rc = 0; struct ocontext *c; - struct superblock_security_struct *sbsec = selinux_superblock(sb); - const char *fstype = sb->s_type->name; if (!selinux_initialized(state)) { - sbsec->behavior = SECURITY_FS_USE_NONE; - sbsec->sid = SECINITSID_UNLABELED; + *behavior = SECURITY_FS_USE_NONE; + *sid = SECINITSID_UNLABELED; return 0; } @@ -2897,22 +2896,22 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) } if (c) { - sbsec->behavior = c->v.behavior; + *behavior = c->v.behavior; if (!c->sid[0]) { rc = sidtab_context_to_sid(sidtab, &c->context[0], &c->sid[0]); if (rc) goto out; } - sbsec->sid = c->sid[0]; + *sid = c->sid[0]; } else { rc = __security_genfs_sid(policy, fstype, "/", - SECCLASS_DIR, &sbsec->sid); + SECCLASS_DIR, sid); if (rc) { - sbsec->behavior = SECURITY_FS_USE_NONE; + *behavior = SECURITY_FS_USE_NONE; rc = 0; } else { - sbsec->behavior = SECURITY_FS_USE_GENFS; + *behavior = SECURITY_FS_USE_GENFS; } } From patchwork Mon Apr 18 09:45:48 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kozhevnikov X-Patchwork-Id: 12816439 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id BB5D1C4332F for ; Mon, 18 Apr 2022 09:46:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237254AbiDRJtD (ORCPT ); Mon, 18 Apr 2022 05:49:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60280 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236991AbiDRJtD (ORCPT ); Mon, 18 Apr 2022 05:49:03 -0400 Received: from szxga08-in.huawei.com (szxga08-in.huawei.com [45.249.212.255]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EC2E11659D; Mon, 18 Apr 2022 02:46:23 -0700 (PDT) Received: from dggpemm500020.china.huawei.com (unknown [172.30.72.56]) by szxga08-in.huawei.com (SkyGuard) with ESMTP id 4KhhrN08FXz1GCNq; Mon, 18 Apr 2022 17:45:40 +0800 (CST) Received: from dggpemm500011.china.huawei.com (7.185.36.110) by dggpemm500020.china.huawei.com (7.185.36.49) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:22 +0800 Received: from mscphmkozh00002.huawei.com (10.219.174.70) by dggpemm500011.china.huawei.com (7.185.36.110) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:20 +0800 From: Alexander Kozhevnikov To: CC: , , , , , , , , , , Subject: [RFC PATCH 3/7] SELINUXNS: Fix initilization of the superblock security under spinlock Date: Mon, 18 Apr 2022 17:45:48 +0800 Message-ID: <20220418094552.128898-4-alexander.kozhevnikov@huawei.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> References: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.219.174.70] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To dggpemm500011.china.huawei.com (7.185.36.110) X-CFilter-Loop: Reflected Precedence: bulk List-ID: From: Igor Baranov After porting 3378718ef7d4a837f32c63bdfcc0b70342cdd55d, selinux_superblock() started being called from under spinlock, along with kzalloc(GFP_NOFS), which causes a scheduling while atomic error. The solution is to take the call to selinux_superblock() out from under the spinlock. Signed-off-by: Alexander Kozhevnikov Signed-off-by: Igor Baranov --- security/selinux/hooks.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index a1716ce534dd..f02e31edae7c 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1408,13 +1408,6 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent if (isec->initialized == LABEL_INITIALIZED) return 0; - spin_lock(&isec->lock); - if (isec->initialized == LABEL_INITIALIZED) - goto out_unlock; - - if (isec->sclass == SECCLASS_FILE) - isec->sclass = inode_mode_to_security_class(inode->i_mode); - sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SE_SBINITIALIZED)) { /* Defer initialization until selinux_complete_init, @@ -1424,9 +1417,15 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent if (list_empty(&isec->list)) list_add(&isec->list, &sbsec->isec_head); spin_unlock(&sbsec->isec_lock); - goto out_unlock; + return 0; } + spin_lock(&isec->lock); + if (isec->initialized == LABEL_INITIALIZED) + goto out_unlock; + + if (isec->sclass == SECCLASS_FILE) + isec->sclass = inode_mode_to_security_class(inode->i_mode); sclass = isec->sclass; task_sid = isec->task_sid; sid = isec->sid; From patchwork Mon Apr 18 09:45:49 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kozhevnikov X-Patchwork-Id: 12816441 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 676E7C4332F for ; Mon, 18 Apr 2022 09:46:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237545AbiDRJtN (ORCPT ); Mon, 18 Apr 2022 05:49:13 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60314 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237544AbiDRJtG (ORCPT ); Mon, 18 Apr 2022 05:49:06 -0400 Received: from szxga02-in.huawei.com (szxga02-in.huawei.com [45.249.212.188]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B50B2165AD; Mon, 18 Apr 2022 02:46:25 -0700 (PDT) Received: from dggpemm500024.china.huawei.com (unknown [172.30.72.56]) by szxga02-in.huawei.com (SkyGuard) with ESMTP id 4Khhs70KhHzhXXL; Mon, 18 Apr 2022 17:46:19 +0800 (CST) Received: from dggpemm500011.china.huawei.com (7.185.36.110) by dggpemm500024.china.huawei.com (7.185.36.203) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:24 +0800 Received: from mscphmkozh00002.huawei.com (10.219.174.70) by dggpemm500011.china.huawei.com (7.185.36.110) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:22 +0800 From: Alexander Kozhevnikov To: CC: , , , , , , , , , , Subject: [RFC PATCH 4/7] SELINUXNS: Namespacing for xattrs Date: Mon, 18 Apr 2022 17:45:49 +0800 Message-ID: <20220418094552.128898-5-alexander.kozhevnikov@huawei.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> References: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.219.174.70] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To dggpemm500011.china.huawei.com (7.185.36.110) X-CFilter-Loop: Reflected Precedence: bulk List-ID: From: Igor Baranov In the original S. Smalley's inode_security_struct turned into a list (see https://github.com/stephensmalley/selinux-kernel/commit/efb2ddadfdd0e10e75b6aa5da2ed9841df6ef2f6) This approach has a number of problems described in the commit-message there. Besides, it dramatically increases implementation complexity (the height of the Tower of Babel) and the resulting code had a number of bugs, such as memory leaks. We decided to try a completely different approach as an experiment. So we introduced a unique identifier into the namespace. It is a 64-bit number which grows monotonically each time you create a new namespace. In inode_security_structure the pointer to the selinux state is replaced by this ID, which is needed only for comparison: we need to determine whether some inode_security_struct belongs to "our" namespace or not. This got rid of the pointers. Next is the most interesting part. Instead of making a list of inode_security_struct, we decided to keep only one instance of this structure. It stores information about inode security from the namespace we last read it from. When we want to get the SID of a structure, we first compare its namespace id with the id of the current namespace. If they match, we just use the stored SID. If not, we re-read the SID from the xattr of our namespace. All this logic is implemented in the update_sid() function and its variations. We call this mechanism "carousel switch" and it is somewhat similar to LRU-cache. Among other changes in this commit: * Instead of current_creds(), we decided to use current_real_creds(). This is because the credentials of the task can be spoofed (e.g. ovl_xattr_get). Although objective and subjective credentials have different purposes, they are almost always the same (see task_struct->real_creds). * We drastically changed the way xattr's suffixes are handled. While the original patchset modified the VFS subsystem code for this (see https://github.com/stephensmalley/selinux-kernel/commit/cf673d0f46ae88241f49f3d193403d40405d03a9), we decided to intercept the set() and get() handlers in super_block->s_xattr. * Added README.SELINUX-NAMESPACES Signed-off-by: Alexander Kozhevnikov Signed-off-by: Igor Baranov --- README.SELINUX-NAMESPACES | 22 ++ security/selinux/hooks.c | 349 +++++++++++++++++++++++----- security/selinux/include/objsec.h | 7 +- security/selinux/include/security.h | 8 +- security/selinux/selinuxfs.c | 98 +++++--- 5 files changed, 388 insertions(+), 96 deletions(-) create mode 100644 README.SELINUX-NAMESPACES diff --git a/README.SELINUX-NAMESPACES b/README.SELINUX-NAMESPACES new file mode 100644 index 000000000000..401f702bb583 --- /dev/null +++ b/README.SELINUX-NAMESPACES @@ -0,0 +1,22 @@ +Introduction. +This experimental patch is designed to enable namespaces mode for LSM SELinux. + +This work is based on Stephen Smalley's patches https://github.com/stephensmalley/selinux-kernel/tree/working-selinuxns +Some of which have been applied here, some of which have been reworked/fixed. + +Usage +# create new namespace with name "ns", unshare previous one +echo "ns" > /sys/fs/selinux/unshare; unshare --fork -m + +# remount selinuxfs, load policy +umount /sys/fs/selinux; mount -t selinuxfs none /sys/fs/selinux/; load_policy + +# relabel everything +restorecon / -Rv + +# check xattr's +ls -Z + +# now enter enforcing mode +setenforce 1 + diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index f02e31edae7c..b618c4e0ef36 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -106,6 +106,9 @@ /* SECMARK reference count */ static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); +/* Sequential namespace id */ +static atomic64_t selinux_namespace_id = ATOMIC_INIT(0); + #ifdef CONFIG_SECURITY_SELINUX_DEVELOP static int selinux_enforcing_boot __initdata; @@ -279,7 +282,8 @@ static int __inode_security_revalidate(struct inode *inode, might_sleep_if(may_sleep); if (selinux_initialized(current_selinux_state) && - isec->initialized != LABEL_INITIALIZED) { + (isec->initialized != LABEL_INITIALIZED || + current_selinux_state->id != isec->namespace_id)) { if (!may_sleep) return -ECHILD; @@ -288,6 +292,7 @@ static int __inode_security_revalidate(struct inode *inode, * @opt_dentry is NULL and no dentry for this inode can be * found; in that case, continue using the old label. */ + isec->initialized = LABEL_INVALID; inode_doinit_with_dentry(inode, dentry); } return 0; @@ -497,6 +502,44 @@ static int selinux_is_sblabel_mnt(struct super_block *sb) } } +#define for_each_xattr_handler(handlers, handler) \ + for ((handler) = *(handlers)++; \ + (handler) != NULL; \ + (handler) = *(handlers)++) +static const char * +strcmp_prefix(const char *a, const char *a_prefix) +{ + while (*a_prefix && *a == *a_prefix) { + a++; + a_prefix++; + } + return *a_prefix ? NULL : a; +} + +static int +selinux_xattr_get(const struct xattr_handler *handler, struct dentry *unused, + struct inode *inode, const char *name, void *value, size_t size) +{ + struct superblock_security_head *sbsech = selinux_head_of_superblock(inode->i_sb); + + if (strcmp(name, XATTR_SELINUX_SUFFIX) == 0 && selinux_initialized(current_selinux_state)) + name = current_selinux_state->xattr_name; + + return sbsech->xattr_handler->get(sbsech->xattr_handler, unused, inode, name, value, size); +} + +static int selinux_xattr_set(const struct xattr_handler *handler, struct dentry *unused, + struct inode *inode, const char *name, const void *value, + size_t size, int flags) +{ + struct superblock_security_head *sbsech = selinux_head_of_superblock(inode->i_sb); + + if (strcmp(name, XATTR_SELINUX_SUFFIX) == 0 && selinux_initialized(current_selinux_state)) + name = current_selinux_state->xattr_name; + + return sbsech->xattr_handler->set(sbsech->xattr_handler, unused, inode, name, value, size, flags); +} + static int sb_finish_set_opts(struct super_block *sb) { struct superblock_security_struct *sbsec = selinux_superblock(sb); @@ -505,29 +548,77 @@ static int sb_finish_set_opts(struct super_block *sb) int rc = 0; if (sbsec->behavior == SECURITY_FS_USE_XATTR) { - /* Make sure that the xattr handler exists and that no - error other than -ENODATA is returned by getxattr on - the root directory. -ENODATA is ok, as this may be - the first boot of the SELinux kernel before we have - assigned xattr values to the filesystem. */ - if (!(root_inode->i_opflags & IOP_XATTR)) { - pr_warn("SELinux: (dev %s, type %s) has no " - "xattr support\n", sb->s_id, sb->s_type->name); - rc = -EOPNOTSUPP; - goto out; - } + struct superblock_security_head *sbsech = selinux_head_of_superblock(sb); + + /* Should check xattr support only once */ + if (!sbsech->xattr_handler) { + const struct xattr_handler **handlers, *handler; + const struct xattr_handler *sec_handler = NULL; + const struct xattr_handler **s_xattr; + size_t count = 0; + + /* Make sure that the xattr handler exists and that no + error other than -ENODATA is returned by getxattr on + the root directory. -ENODATA is ok, as this may be + the first boot of the SELinux kernel before we have + assigned xattr values to the filesystem. */ + if (!(root_inode->i_opflags & IOP_XATTR)) { + pr_warn("SELinux: (dev %s, type %s) has no " + "xattr support\n", sb->s_id, sb->s_type->name); + rc = -EOPNOTSUPP; + goto out; + } - rc = __vfs_getxattr(root, root_inode, XATTR_NAME_SELINUX, NULL, 0); - if (rc < 0 && rc != -ENODATA) { - if (rc == -EOPNOTSUPP) - pr_warn("SELinux: (dev %s, type " - "%s) has no security xattr handler\n", - sb->s_id, sb->s_type->name); - else - pr_warn("SELinux: (dev %s, type " - "%s) getxattr errno %d\n", sb->s_id, - sb->s_type->name, -rc); - goto out; + rc = __vfs_getxattr(root, root_inode, XATTR_NAME_SELINUX, NULL, 0); + if (rc < 0 && rc != -ENODATA) { + if (rc == -EOPNOTSUPP) + pr_warn("SELinux: (dev %s, type " + "%s) has no security xattr handler\n", + sb->s_id, sb->s_type->name); + else + pr_warn("SELinux: (dev %s, type " + "%s) getxattr errno %d\n", sb->s_id, + sb->s_type->name, -rc); + goto out; + } + + handlers = sb->s_xattr; + for_each_xattr_handler(handlers, handler) { + count++; + } + + s_xattr = kzalloc((1 + count) * sizeof(struct xattr_handler *) + + sizeof(struct xattr_handler), + GFP_KERNEL); + memcpy(s_xattr, sb->s_xattr, count * sizeof(struct xattr_handler *)); + handlers = s_xattr; + for_each_xattr_handler(handlers, handler) { + const char *n = strcmp_prefix(XATTR_NAME_SELINUX, xattr_prefix(handler)); + if (n) { + if (!handler->prefix ^ !*n) { + if (*n) + continue; + break; + } + sec_handler = handler; + break; + } + } + + if (sec_handler) { + struct xattr_handler *hooked_handler = (void *)(s_xattr + count + 1); + + *hooked_handler = *sec_handler; + hooked_handler->set = selinux_xattr_set; + hooked_handler->get = selinux_xattr_get; + sbsech->xattr_handler = sec_handler; + sbsech->old_s_xattr = sb->s_xattr; + *--handlers = hooked_handler; + sb->s_xattr = s_xattr; + pr_info("SELinux: (dev %s, type %s) installed xattr hook\n", sb->s_id, sb->s_type->name); + } else { + kfree(s_xattr); + } } } @@ -812,6 +903,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, goto out; root_isec->sid = rootcontext_sid; + root_isec->namespace_id = current_selinux_state->id; root_isec->initialized = LABEL_INITIALIZED; } @@ -1395,6 +1487,102 @@ static int inode_doinit_use_xattr(struct inode *inode, struct dentry *dentry, return 0; } +static int read_sid(struct inode *inode, u32 *psid) +{ + int rc; + struct superblock_security_struct *sbsec; + struct dentry *dentry; + + sbsec = selinux_superblock(inode->i_sb); + + if (sbsec->behavior == 0) { + *psid = SECINITSID_UNLABELED; + return 0; + } + if (sbsec->behavior != SECURITY_FS_USE_XATTR) + return -EINVAL; + + dentry = d_find_alias(inode); + + if (!dentry) + dentry = d_find_any_alias(inode); + + if (!dentry) + return -EINVAL; + + rc = inode_doinit_use_xattr(inode, dentry, sbsec->def_sid, psid); + + dput(dentry); + return rc; +} + +static void update_sid_and_lock(struct inode_security_struct *isec) +{ + u32 sid; + u64 nsid, cnsid = current_selinux_state->id; + +// TODO: atomic??? + spin_lock(&isec->lock); + nsid = isec->namespace_id; + sid = isec->sid; + if (nsid != cnsid) { + spin_unlock(&isec->lock); + if (read_sid(isec->inode, &sid) == 0) { + spin_lock(&isec->lock); + isec->namespace_id = cnsid; + isec->sid = sid; + } + } +} +static u32 update_sid(struct inode_security_struct *isec) +{ + u32 sid; + u64 nsid, cnsid = current_selinux_state->id; + +// TODO: atomic??? + spin_lock(&isec->lock); + nsid = isec->namespace_id; + sid = isec->sid; + spin_unlock(&isec->lock); + if (nsid != cnsid) { + if (read_sid(isec->inode, &sid) == 0) { + spin_lock(&isec->lock); + isec->namespace_id = cnsid; + isec->sid = sid; + spin_unlock(&isec->lock); + } + } + return sid; +} + +static int update_sid_rcu(struct inode_security_struct *isec, u32 *psid, bool rcu) +{ + u64 nsid, cnsid = current_selinux_state->id; + +// TODO: atomic??? + spin_lock(&isec->lock); + nsid = isec->namespace_id; + *psid = isec->sid; + spin_unlock(&isec->lock); + if (nsid != cnsid) { + int err; + + if (rcu) + return -ECHILD; + + err = read_sid(isec->inode, psid); + if (err == 0) { + spin_lock(&isec->lock); + isec->namespace_id = cnsid; + isec->sid = *psid; + spin_unlock(&isec->lock); + } else { + return err; + } + } + return 0; +} + /* The inode's security attributes must be initialized before first use. */ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry) { @@ -1555,6 +1743,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent isec->initialized = LABEL_INITIALIZED; isec->sid = sid; + isec->namespace_id = current_selinux_state->id; } out_unlock: @@ -1651,7 +1840,7 @@ static int inode_has_perm(const struct cred *cred, isec = selinux_inode(inode); return avc_has_perm(cred_selinux_state(cred), - sid, isec->sid, isec->sclass, perms, adp); + sid, update_sid(isec), isec->sclass, perms, adp); } /* Same as inode_has_perm, but pass explicit audit data containing @@ -1781,7 +1970,7 @@ static int may_create(struct inode *dir, struct dentry *dentry, u16 tclass) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct task_security_struct *tsec = selinux_cred(current_real_cred()); struct inode_security_struct *dsec; struct superblock_security_struct *sbsec; u32 sid, newsid; @@ -1844,7 +2033,7 @@ static int may_link(struct inode *dir, av = DIR__SEARCH; av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); rc = avc_has_perm(current_selinux_state, - sid, dsec->sid, SECCLASS_DIR, av, &ad); + sid, update_sid(dsec), SECCLASS_DIR, av, &ad); if (rc) return rc; @@ -1865,7 +2054,7 @@ static int may_link(struct inode *dir, } rc = avc_has_perm(current_selinux_state, - sid, isec->sid, isec->sclass, av, &ad); + sid, update_sid(isec), isec->sclass, av, &ad); return rc; } @@ -1890,18 +2079,18 @@ static inline int may_rename(struct inode *old_dir, ad.u.dentry = old_dentry; rc = avc_has_perm(current_selinux_state, - sid, old_dsec->sid, SECCLASS_DIR, + sid, update_sid(old_dsec), SECCLASS_DIR, DIR__REMOVE_NAME | DIR__SEARCH, &ad); if (rc) return rc; rc = avc_has_perm(current_selinux_state, - sid, old_isec->sid, + sid, update_sid(old_isec), old_isec->sclass, FILE__RENAME, &ad); if (rc) return rc; if (old_is_dir && new_dir != old_dir) { rc = avc_has_perm(current_selinux_state, - sid, old_isec->sid, + sid, update_sid(old_isec), old_isec->sclass, DIR__REPARENT, &ad); if (rc) return rc; @@ -1912,14 +2101,14 @@ static inline int may_rename(struct inode *old_dir, if (d_is_positive(new_dentry)) av |= DIR__REMOVE_NAME; rc = avc_has_perm(current_selinux_state, - sid, new_dsec->sid, SECCLASS_DIR, av, &ad); + sid, update_sid(new_dsec), SECCLASS_DIR, av, &ad); if (rc) return rc; if (d_is_positive(new_dentry)) { new_isec = backing_inode_security(new_dentry); new_is_dir = d_is_dir(new_dentry); rc = avc_has_perm(current_selinux_state, - sid, new_isec->sid, + sid, update_sid(new_isec), new_isec->sclass, (new_is_dir ? DIR__RMDIR : FILE__UNLINK), &ad); if (rc) @@ -2089,7 +2278,7 @@ static int selinux_binder_transfer_file(struct task_struct *from, isec = backing_inode_security(dentry); return avc_has_perm(current_selinux_state, - sid, isec->sid, isec->sclass, file_to_av(file), + sid, update_sid(isec), isec->sclass, file_to_av(file), &ad); } @@ -2340,7 +2529,7 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) } else { /* Check for a default transition on this program. */ rc = security_transition_sid(current_selinux_state, old_tsec->sid, - isec->sid, SECCLASS_PROCESS, NULL, + update_sid(isec), SECCLASS_PROCESS, NULL, &new_tsec->sid); if (rc) return rc; @@ -2359,7 +2548,7 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) if (new_tsec->sid == old_tsec->sid) { rc = avc_has_perm(current_selinux_state, - old_tsec->sid, isec->sid, + old_tsec->sid, update_sid(isec), SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad); if (rc) return rc; @@ -2372,7 +2561,7 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) return rc; rc = avc_has_perm(current_selinux_state, - new_tsec->sid, isec->sid, + new_tsec->sid, update_sid(isec), SECCLASS_FILE, FILE__ENTRYPOINT, &ad); if (rc) return rc; @@ -2570,6 +2759,8 @@ static void sbsec_head_init(struct super_block *sb, { INIT_LIST_HEAD(&sbsech->head); spin_lock_init(&sbsech->lock); + sbsech->xattr_handler = NULL; + sbsech->old_s_xattr = NULL; } static struct superblock_security_struct * @@ -2651,6 +2842,12 @@ static void selinux_sb_free_security(struct super_block *sb) put_selinux_state(entry->state); kfree(entry); } + if (sbsech->old_s_xattr) { + const struct xattr_handler **s_xattr = sb->s_xattr; + + sb->s_xattr = sbsech->old_s_xattr; + kfree(s_xattr); + } } static inline int opt_len(const char *s) @@ -2910,6 +3107,7 @@ static int selinux_inode_alloc_security(struct inode *inode) isec->sclass = SECCLASS_FILE; isec->task_sid = sid; isec->initialized = LABEL_INVALID; + isec->namespace_id = current_selinux_state->id; return 0; } @@ -2984,6 +3182,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, struct inode_security_struct *isec = selinux_inode(inode); isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; + isec->namespace_id = current_selinux_state->id; isec->initialized = LABEL_INITIALIZED; } @@ -3060,7 +3259,8 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct inode *inode, const struct cred *cred = current_cred(); struct common_audit_data ad; struct inode_security_struct *isec; - u32 sid; + u32 sid, isid; + int rc; validate_creds(cred); @@ -3071,8 +3271,12 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct inode *inode, if (IS_ERR(isec)) return PTR_ERR(isec); + rc = update_sid_rcu(isec, &isid, rcu); + if (rc != 0) + return rc; + return avc_has_perm_flags(cred_selinux_state(cred), - sid, isec->sid, isec->sclass, FILE__READ, &ad, + sid, isid, isec->sclass, FILE__READ, &ad, rcu ? MAY_NOT_BLOCK : 0); } @@ -3088,7 +3292,7 @@ static noinline int audit_inode_permission(struct inode *inode, ad.u.inode = inode; rc = slow_avc_audit(current_selinux_state, - current_sid(), isec->sid, isec->sclass, perms, + current_sid(), update_sid(isec), isec->sclass, perms, audited, denied, result, &ad); if (rc) return rc; @@ -3102,7 +3306,7 @@ static int selinux_inode_permission(struct inode *inode, int mask) bool from_access; bool no_block = mask & MAY_NOT_BLOCK; struct inode_security_struct *isec; - u32 sid; + u32 sid, isid; struct av_decision avd; int rc, rc2; u32 audited, denied; @@ -3126,8 +3330,12 @@ static int selinux_inode_permission(struct inode *inode, int mask) if (IS_ERR(isec)) return PTR_ERR(isec); + rc = update_sid_rcu(isec, &isid, no_block); + if (rc != 0) + return rc; + rc = avc_has_perm_noaudit(cred_selinux_state(cred), - sid, isec->sid, isec->sclass, perms, + sid, isid, isec->sclass, perms, no_block ? AVC_NONBLOCKING : 0, &avd); audited = avc_audit_required(perms, &avd, rc, @@ -3198,7 +3406,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, struct inode_security_struct *isec; struct superblock_security_struct *sbsec; struct common_audit_data ad; - u32 newsid, sid = current_sid(); + u32 newsid, backsid, sid = current_sid(); int rc = 0; if (strcmp(name, XATTR_NAME_SELINUX)) { @@ -3225,8 +3433,9 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, ad.u.dentry = dentry; isec = backing_inode_security(dentry); + backsid = update_sid(isec); rc = avc_has_perm(current_selinux_state, - sid, isec->sid, isec->sclass, + sid, backsid, isec->sclass, FILE__RELABELFROM, &ad); if (rc) return rc; @@ -3270,7 +3479,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, if (rc) return rc; - rc = security_validate_transition(current_selinux_state, isec->sid, newsid, + rc = security_validate_transition(current_selinux_state, backsid, newsid, sid, isec->sclass); if (rc) return rc; @@ -3319,6 +3528,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, spin_lock(&isec->lock); isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; + isec->namespace_id = current_selinux_state->id; isec->initialized = LABEL_INITIALIZED; spin_unlock(&isec->lock); @@ -3432,13 +3642,14 @@ static int selinux_inode_getsecurity(struct inode *inode, const char *name, void * and lack of permission just means that we fall back to the * in-core context value, not a denial. */ + //TODO: optimize sequence. need to read xattr only once isec = inode_security(inode); if (has_cap_mac_admin(false)) error = security_sid_to_context_force(current_selinux_state, - isec->sid, &context, + update_sid(isec), &context, &size); else - error = security_sid_to_context(current_selinux_state, isec->sid, + error = security_sid_to_context(current_selinux_state, update_sid(isec), &context, &size); if (error) return error; @@ -3478,6 +3689,7 @@ static int selinux_inode_setsecurity(struct inode *inode, const char *name, spin_lock(&isec->lock); isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; + isec->namespace_id = current_selinux_state->id; isec->initialized = LABEL_INITIALIZED; spin_unlock(&isec->lock); return 0; @@ -3494,7 +3706,7 @@ static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t static void selinux_inode_getsecid(struct inode *inode, u32 *secid) { struct inode_security_struct *isec = inode_security_novalidate(inode); - *secid = isec->sid; + *secid = update_sid(isec); } static int selinux_inode_copy_up(struct dentry *src, struct cred **new) @@ -3620,7 +3832,7 @@ static int selinux_file_permission(struct file *file, int mask) return 0; isec = inode_security(inode); - if (sid == fsec->sid && fsec->isid == isec->sid && + if (sid == fsec->sid && fsec->isid == update_sid(isec) && fsec->pseqno == avc_policy_seqno(current_selinux_state)) /* No change since file_open check. */ return 0; @@ -3676,7 +3888,7 @@ static int ioctl_has_perm(const struct cred *cred, struct file *file, isec = inode_security(inode); rc = avc_has_extended_perms(current_selinux_state, - ssid, isec->sid, isec->sclass, + ssid, update_sid(isec), isec->sclass, requested, driver, xperm, &ad); out: return rc; @@ -3947,6 +4159,7 @@ static int selinux_file_open(struct file *file) * Task label is already saved in the file security * struct as its SID. */ + //TODO: namespace? fsec->isid = isec->sid; fsec->pseqno = avc_policy_seqno(current_selinux_state); /* @@ -4046,21 +4259,23 @@ static int selinux_kernel_act_as(struct cred *new, u32 secid) * set the file creation context in a security record to the same as the * objective context of the specified inode */ +//TODO: namespace? static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) { struct inode_security_struct *isec = inode_security(inode); struct task_security_struct *tsec = selinux_cred(new); u32 sid = current_sid(); + u32 isid = update_sid(isec); int ret; ret = avc_has_perm(tsec->state, - sid, isec->sid, + sid, isid, SECCLASS_KERNEL_SERVICE, KERNEL_SERVICE__CREATE_FILES_AS, NULL); if (ret == 0) - tsec->create_sid = isec->sid; + tsec->create_sid = isid; return ret; } @@ -4105,7 +4320,7 @@ static int selinux_kernel_module_from_file(struct file *file) isec = inode_security(file_inode(file)); return avc_has_perm(current_selinux_state, - sid, isec->sid, SECCLASS_SYSTEM, + sid, update_sid(isec), SECCLASS_SYSTEM, SYSTEM__MODULE_LOAD, &ad); } @@ -4267,6 +4482,7 @@ static void selinux_task_to_inode(struct task_struct *p, spin_lock(&isec->lock); isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = sid; + isec->namespace_id = current_selinux_state->id; isec->initialized = LABEL_INITIALIZED; spin_unlock(&isec->lock); } @@ -4633,6 +4849,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, isec->sclass = sclass; isec->sid = sid; + isec->namespace_id = current_selinux_state->id; isec->initialized = LABEL_INITIALIZED; if (sock->sk) { @@ -4921,7 +5138,7 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock) return err; isec = inode_security_novalidate(SOCK_INODE(sock)); - spin_lock(&isec->lock); + update_sid_and_lock(isec); sclass = isec->sclass; sid = isec->sid; spin_unlock(&isec->lock); @@ -4929,6 +5146,7 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock) newisec = inode_security_novalidate(SOCK_INODE(newsock)); newisec->sclass = sclass; newisec->sid = sid; + newisec->namespace_id = current_selinux_state->id; newisec->initialized = LABEL_INITIALIZED; return 0; @@ -5213,7 +5431,7 @@ static int selinux_socket_getpeersec_dgram(struct socket *sock, struct sk_buff * if (sock && family == PF_UNIX) { isec = inode_security_novalidate(SOCK_INODE(sock)); - peer_secid = isec->sid; + peer_secid = update_sid(isec); } else if (skb) selinux_skb_peerlbl_sid(skb, family, &peer_secid); @@ -5280,8 +5498,11 @@ static void selinux_sock_graft(struct sock *sk, struct socket *parent) struct sk_security_struct *sksec = sk->sk_security; if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 || - sk->sk_family == PF_UNIX) + sk->sk_family == PF_UNIX) { isec->sid = sksec->sid; + //TODO: namespace? + //isec->namespace_id = ? + } sksec->sclass = isec->sclass; } @@ -7331,7 +7552,13 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { static void selinux_state_free(struct work_struct *work); -int selinux_state_create(struct selinux_state *parent, struct selinux_state **state) +u64 selinux_new_state_id(void) +{ + return atomic64_fetch_add(1, &selinux_namespace_id); +} + +int selinux_state_create(struct selinux_state *parent, struct selinux_state **state, + const char *suffix) { struct selinux_state *newstate; int rc; @@ -7340,6 +7567,7 @@ int selinux_state_create(struct selinux_state *parent, struct selinux_state **st if (!newstate) return -ENOMEM; + newstate->id = selinux_new_state_id(); refcount_set(&newstate->count, 1); INIT_WORK(&newstate->work, selinux_state_free); @@ -7352,7 +7580,12 @@ int selinux_state_create(struct selinux_state *parent, struct selinux_state **st if (parent) newstate->parent = get_selinux_state(parent); - + strcpy(newstate->xattr_name, XATTR_SELINUX_SUFFIX); + rc = strlen(suffix); + if (rc) { + strcat(newstate->xattr_name, "."); + strncat(newstate->xattr_name, suffix, rc); + } *state = newstate; return 0; err: @@ -7385,7 +7618,7 @@ static __init int selinux_init(void) { pr_info("SELinux: Initializing.\n"); - if (selinux_state_create(NULL, &init_selinux_state)) + if (selinux_state_create(NULL, &init_selinux_state, "")) panic("SELinux: Could not create initial namespace\n"); enforcing_set(init_selinux_state, selinux_enforcing_boot); init_selinux_state->checkreqprot = selinux_checkreqprot_boot; diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 6ad1db45b0d9..21566a1541f4 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -42,8 +42,10 @@ struct inode_security_struct { u16 sclass; /* security class of this object */ unsigned char initialized; /* initialization flag */ spinlock_t lock; + u64 namespace_id; /* pointer to selinux_state */ }; +//TODO: namespace? struct file_security_struct { u32 sid; /* SID of open file description */ u32 fown_sid; /* SID of file owner (for SIGIO) */ @@ -54,6 +56,9 @@ struct file_security_struct { struct superblock_security_head { struct list_head head; /* list head of superblock_security_struct */ spinlock_t lock; + + const struct xattr_handler **old_s_xattr; + const struct xattr_handler *xattr_handler; }; struct superblock_security_struct { @@ -178,7 +183,7 @@ static inline struct ipc_security_struct *selinux_ipc( */ static inline u32 current_sid(void) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct task_security_struct *tsec = selinux_cred(current_real_cred()); return tsec->sid; } diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index a5b698aae38c..0ab6b433dc9c 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -111,9 +111,13 @@ struct selinux_state { struct selinux_policy __rcu *policy; struct mutex policy_mutex; struct selinux_state *parent; + u64 id; + char xattr_name[XATTR_NAME_MAX]; } __randomize_layout; -int selinux_state_create(struct selinux_state *parent, struct selinux_state **state); +u64 selinux_new_state_id(void); +int selinux_state_create(struct selinux_state *parent, struct selinux_state **state, + const char *suffix); void __put_selinux_state(struct selinux_state *state); void selinux_policy_free(struct selinux_policy *policy); @@ -151,7 +155,7 @@ static inline struct task_security_struct *selinux_cred(const struct cred *cred) return cred->security + selinux_blob_sizes.lbs_cred; } -#define current_selinux_state (selinux_cred(current_cred())->state) +#define current_selinux_state (selinux_cred(current_real_cred())->state) #define cred_selinux_state(cred) (selinux_cred(cred)->state) diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 9892527f2269..2da27f2fc2e3 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -31,6 +31,7 @@ #include #include #include +#include /* selinuxfs pseudo filesystem for exporting the security policy API. Based on the proc code and the fs/nfsd/nfsctl.c code. */ @@ -347,10 +348,10 @@ static ssize_t sel_write_unshare(struct file *file, const char __user *buf, { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; struct selinux_state *state = fsi->state; - char *page; - ssize_t length; - bool set; - int rc; + struct cred *cred; + struct task_security_struct *tsec; + char *suffix, *p; + ssize_t len; if (state != current_selinux_state) return -EPERM; @@ -362,49 +363,76 @@ static ssize_t sel_write_unshare(struct file *file, const char __user *buf, if (*ppos != 0) return -EINVAL; - rc = avc_has_perm(current_selinux_state, current_sid(), + len = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__UNSHARE, NULL); - if (rc) - return rc; + if (len) + return len; - page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); + if (count > XATTR_NAME_MAX - sizeof(XATTR_NAME_SELINUX) - 2) + return -EINVAL; - length = -EINVAL; - if (kstrtobool(page, &set)) - goto out; + suffix = memdup_user_nul(buf, count); + if (IS_ERR(suffix)) + return PTR_ERR(suffix); - if (set) { - struct cred *cred = prepare_creds(); - struct task_security_struct *tsec; + len = strnlen(suffix, count); - if (!cred) { - length = -ENOMEM; - goto out; - } - tsec = selinux_cred(cred); - if (selinux_state_create(state, &tsec->state)) { - abort_creds(cred); - length = -ENOMEM; - goto out; - } - tsec->osid = tsec->sid = SECINITSID_KERNEL; - tsec->exec_sid = tsec->create_sid = tsec->keycreate_sid = - tsec->sockcreate_sid = SECSID_NULL; - tsec->parent_cred = get_current_cred(); - commit_creds(cred); + /* cleanup some trash at the end of string */ + for (p = suffix + len - 1; p >= suffix && isspace(*p); p--) + *p = 0; + + len = p - suffix + 1; + + + /* TODO: check for uniqueness! */ +/* + if (!strcmp("", suffix)) { + len = -EINVAL; + goto out; } +*/ + + cred = prepare_creds(); + if (!cred) { + len = -ENOMEM; + goto out; + } + tsec = cred->security; + if (selinux_state_create(state, &tsec->state, suffix)) { + abort_creds(cred); + len = -ENOMEM; + goto out; + } + + tsec->osid = tsec->sid = SECINITSID_KERNEL; + tsec->exec_sid = tsec->create_sid = tsec->keycreate_sid = + tsec->sockcreate_sid = SECSID_NULL; + tsec->parent_cred = get_current_cred(); + commit_creds(cred); - length = count; out: - kfree(page); - return length; + kfree(suffix); + return len < 0 ? len : count; +} + +static ssize_t sel_read_unshare(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + size_t slen = sizeof(XATTR_SELINUX_SUFFIX); + + if (state->id != current_selinux_state->id) + return -EPERM; + + return simple_read_from_buffer(buf, count, ppos, state->xattr_name + slen, + strlen(state->xattr_name + slen)); } static const struct file_operations sel_unshare_ops = { .write = sel_write_unshare, + .read = sel_read_unshare, .llseek = generic_file_llseek, }; @@ -2174,7 +2202,7 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO}, [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops, S_IWUGO}, - [SEL_UNSHARE] = {"unshare", &sel_unshare_ops, 0200}, + [SEL_UNSHARE] = {"unshare", &sel_unshare_ops, 0600}, /* last one */ {""} }; From patchwork Mon Apr 18 09:45:50 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kozhevnikov X-Patchwork-Id: 12816442 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 15DB7C433F5 for ; Mon, 18 Apr 2022 09:46:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237544AbiDRJtO (ORCPT ); Mon, 18 Apr 2022 05:49:14 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60322 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237546AbiDRJtH (ORCPT ); Mon, 18 Apr 2022 05:49:07 -0400 Received: from szxga01-in.huawei.com (szxga01-in.huawei.com [45.249.212.187]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1A56F165AF; Mon, 18 Apr 2022 02:46:28 -0700 (PDT) Received: from dggpemm500021.china.huawei.com (unknown [172.30.72.55]) by szxga01-in.huawei.com (SkyGuard) with ESMTP id 4KhhsB2W5YzdZQt; Mon, 18 Apr 2022 17:46:22 +0800 (CST) Received: from dggpemm500011.china.huawei.com (7.185.36.110) by dggpemm500021.china.huawei.com (7.185.36.109) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:26 +0800 Received: from mscphmkozh00002.huawei.com (10.219.174.70) by dggpemm500011.china.huawei.com (7.185.36.110) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:24 +0800 From: Alexander Kozhevnikov To: CC: , , , , , , , , , , Subject: [RFC PATCH 5/7] SELINUXNS: Migrate all open files and all vma to new namespace Date: Mon, 18 Apr 2022 17:45:50 +0800 Message-ID: <20220418094552.128898-6-alexander.kozhevnikov@huawei.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> References: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.219.174.70] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To dggpemm500011.china.huawei.com (7.185.36.110) X-CFilter-Loop: Reflected Precedence: bulk List-ID: From: Igor Baranov When process switched to another namespace and loads a new policy, the following problem occurs: the current open files in their file_security_struct contain the sid's relevant to the loaded policy in the old namespace. Under the new policy, they have completely random (incorrect) values, and, as a rule, accessing such descriptors leads to failure. For example, a process gets EACCES when writing to its own stdout. Our solution: reinitialize sid's and isid's to actual values in new policy of all opened files, as well as of files referenced by process's VMA. Signed-off-by: Alexander Kozhevnikov Signed-off-by: Igor Baranov --- security/selinux/hooks.c | 94 +++++++++++++++++++++++++++++++++++- security/selinux/selinuxfs.c | 2 +- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b618c4e0ef36..74d32b6a4855 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -91,6 +91,7 @@ #include #include #include +#include #include "avc.h" #include "objsec.h" @@ -4159,8 +4160,7 @@ static int selinux_file_open(struct file *file) * Task label is already saved in the file security * struct as its SID. */ - //TODO: namespace? - fsec->isid = isec->sid; + fsec->isid = update_sid(isec); fsec->pseqno = avc_policy_seqno(current_selinux_state); /* * Since the inode label or policy seqno may have changed @@ -7659,6 +7659,8 @@ static void delayed_superblock_init(struct super_block *sb, void *unused) selinux_set_mnt_opts(sb, NULL, 0, NULL); } +static void migrate_files(void); + void selinux_complete_init(void) { pr_debug("SELinux: Completing initialization.\n"); @@ -7666,6 +7668,9 @@ void selinux_complete_init(void) /* Set up any superblocks initialized prior to the policy load. */ pr_debug("SELinux: Setting up existing superblocks.\n"); iterate_supers(delayed_superblock_init, NULL); + + if (current_selinux_state->id != 0) + migrate_files(); } /* SELinux requires early initialization in order to label @@ -7816,3 +7821,88 @@ int selinux_disable(struct selinux_state *state) return 0; } #endif + + +/* TODO: check&return errors? */ +static void migrate_fds(void) +{ + unsigned int fd; + struct files_struct *files = current->files; + u32 tsid = current_sid(); + + rcu_read_lock(); + for (fd = 0; fd < files_fdtable(files)->max_fds; fd++) { + struct inode *inode; + struct file_security_struct *fsec; + struct file *file = fcheck_files(files, fd); + + if (!file) + continue; + + fsec = selinux_file(file); + + fsec->sid = fsec->fown_sid = tsid; + inode = file_inode(file); + if (inode) { + get_file(file); + rcu_read_unlock(); + fsec->isid = update_sid(inode_security(inode)); + rcu_read_lock(); + fput(file); + } + } + rcu_read_unlock(); +} + +static int migrate_vmas(void) +{ + int ret; + struct mempolicy *task_mempolicy; + struct vm_area_struct *vma; + u32 tsid = current_sid(); + struct mm_struct *mm = mm_access(current, PTRACE_MODE_READ | PTRACE_MODE_FSCREDS); + + if (IS_ERR_OR_NULL(mm)) + return PTR_ERR(mm); + + ret = mmap_read_lock_killable(mm); + if (ret) + goto out_put_mm; + + task_mempolicy = get_task_policy(current); + mpol_get(task_mempolicy); + + for (vma = mm->mmap; vma; vma = vma->vm_next) { + struct file *file = vma->vm_file; + struct file_security_struct *fsec; + struct inode *inode; + + if (!file) + continue; + + fsec = selinux_file(file); + + inode = file_inode(file); + if (inode) + fsec->isid = update_sid(inode_security(inode)); + + fsec->sid = fsec->fown_sid = tsid; + } + + mpol_put(task_mempolicy); + mmap_read_unlock(mm); + +out_put_mm: + mmput(mm); + + return ret; +} + +/* TODO return error? */ +void migrate_files(void) +{ + migrate_fds(); + +/* TODO: return error */ + migrate_vmas(); +} diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 2da27f2fc2e3..18c5383b87a9 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -2306,7 +2306,7 @@ static int selinuxfs_compare(struct super_block *sb, struct fs_context *fc) { struct selinux_fs_info *fsi = sb->s_fs_info; - return (current_selinux_state == fsi->state); + return (current_selinux_state->id == fsi->state->id); } static int sel_get_tree(struct fs_context *fc) From patchwork Mon Apr 18 09:45:51 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kozhevnikov X-Patchwork-Id: 12816443 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id EE88DC4321E for ; Mon, 18 Apr 2022 09:46:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237546AbiDRJtQ (ORCPT ); Mon, 18 Apr 2022 05:49:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60368 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237549AbiDRJtM (ORCPT ); Mon, 18 Apr 2022 05:49:12 -0400 Received: from szxga01-in.huawei.com (szxga01-in.huawei.com [45.249.212.187]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A0737165B2; Mon, 18 Apr 2022 02:46:29 -0700 (PDT) Received: from dggpemm500022.china.huawei.com (unknown [172.30.72.53]) by szxga01-in.huawei.com (SkyGuard) with ESMTP id 4KhhrT44HPzfYy4; Mon, 18 Apr 2022 17:45:45 +0800 (CST) Received: from dggpemm500011.china.huawei.com (7.185.36.110) by dggpemm500022.china.huawei.com (7.185.36.162) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:28 +0800 Received: from mscphmkozh00002.huawei.com (10.219.174.70) by dggpemm500011.china.huawei.com (7.185.36.110) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:26 +0800 From: Alexander Kozhevnikov To: CC: , , , , , , , , , , Subject: [RFC PATCH 6/7] SELINUXNS: Fixing superblock security structure memory leakage Date: Mon, 18 Apr 2022 17:45:51 +0800 Message-ID: <20220418094552.128898-7-alexander.kozhevnikov@huawei.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> References: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.219.174.70] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To dggpemm500011.china.huawei.com (7.185.36.110) X-CFilter-Loop: Reflected Precedence: bulk List-ID: List of sbsec per state added. All sbsec attached to state are cleared at namespace creator process exit. Sbsec state is also replaced with namespace id. Original code provided by Igor Baranov with refactoring to ajust current changes in sbsec handling. Signed-off-by: Alexander Kozhevnikov Signed-off-by: Igor Baranov --- security/selinux/hooks.c | 76 ++++++++++++++++++++++------- security/selinux/include/objsec.h | 7 ++- security/selinux/include/security.h | 2 + security/selinux/selinuxfs.c | 3 ++ 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 74d32b6a4855..e17175ff707d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -104,6 +104,9 @@ #include "audit.h" #include "avc_ss.h" +/* SUPERBLOCK STATE LOCK */ +static spinlock_t sb_state_lock; + /* SECMARK reference count */ static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); @@ -2755,11 +2758,21 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) /* superblock security operations */ +static void sbsec_counting_init(struct list_head *sb_count_head) +{ + //* INIT Superblocks counting list + INIT_LIST_HEAD(sb_count_head); +} + +static void sbsec_counting_add(struct superblock_security_struct *sbsec, struct selinux_state *state) +{ + list_add(&sbsec->sbsec_count, &state->sb_count_head); +} + static void sbsec_head_init(struct super_block *sb, struct superblock_security_head *sbsech) { INIT_LIST_HEAD(&sbsech->head); - spin_lock_init(&sbsech->lock); sbsech->xattr_handler = NULL; sbsech->old_s_xattr = NULL; } @@ -2779,7 +2792,7 @@ sbsec_alloc(struct superblock_security_head *sbsech) sbsec->sid = SECINITSID_UNLABELED; sbsec->def_sid = SECINITSID_FILE; sbsec->mntpoint_sid = SECINITSID_UNLABELED; - sbsec->state = get_selinux_state(current_selinux_state); + sbsec->namespace_id = current_selinux_state->id; return sbsec; } @@ -2787,18 +2800,18 @@ sbsec_alloc(struct superblock_security_head *sbsech) struct superblock_security_struct * find_sbsec(struct superblock_security_head *sbsech) { - struct superblock_security_struct *cur, *new; + struct superblock_security_struct *cur, *new, *tmp; cur = container_of(sbsech->head.next, struct superblock_security_struct, sbsec_list); - if (cur->state == current_selinux_state) + if (cur->namespace_id == current_selinux_state->id) return cur; - spin_lock(&sbsech->lock); - list_for_each_entry(cur, &sbsech->head, sbsec_list) { - if (cur->state == current_selinux_state) + spin_lock(&sb_state_lock); + list_for_each_entry_safe(cur, tmp, &sbsech->head, sbsec_list) { + if (cur->namespace_id == current_selinux_state->id) goto unlock; } - spin_unlock(&sbsech->lock); + spin_unlock(&sb_state_lock); new = sbsec_alloc(sbsech); if (!new) { @@ -2806,15 +2819,22 @@ find_sbsec(struct superblock_security_head *sbsech) goto out; } - spin_lock(&sbsech->lock); - list_for_each_entry(cur, &sbsech->head, sbsec_list) { - if (cur->state == current_selinux_state) - goto unlock; + spin_lock(&sb_state_lock); + list_for_each_entry_safe(cur, tmp, &sbsech->head, sbsec_list) { + if (cur->namespace_id == current_selinux_state->id) + goto free_unlock; } list_add(&new->sbsec_list, &sbsech->head); + sbsec_counting_add(new, current_selinux_state); cur = new; + goto unlock; + +free_unlock: + spin_unlock(&sb_state_lock); + kfree(new); + return cur; unlock: - spin_unlock(&sbsech->lock); + spin_unlock(&sb_state_lock); out: return cur; } @@ -2828,9 +2848,10 @@ static int selinux_sb_alloc_security(struct super_block *sb) sbsec = sbsec_alloc(sbsech); if (!sbsec) return -ENOMEM; - spin_lock(&sbsech->lock); + spin_lock(&sb_state_lock); list_add(&sbsec->sbsec_list, &sbsech->head); - spin_unlock(&sbsech->lock); + sbsec_counting_add(sbsec, current_selinux_state); + spin_unlock(&sb_state_lock); return 0; } @@ -2840,7 +2861,7 @@ static void selinux_sb_free_security(struct super_block *sb) struct superblock_security_head *sbsech = selinux_head_of_superblock(sb); list_for_each_entry_safe(entry, tmp, &sbsech->head, sbsec_list) { - put_selinux_state(entry->state); + list_del(&entry->sbsec_count); // Remove sbsec from list of sb-s for this namespace kfree(entry); } if (sbsech->old_s_xattr) { @@ -2851,6 +2872,16 @@ static void selinux_sb_free_security(struct super_block *sb) } } +static void selinux_state_sb_free_list(struct selinux_state *state) +{ + struct superblock_security_struct *cur, *tmp; + + list_for_each_entry_safe(cur, tmp, &state->sb_count_head, sbsec_count) { + list_del(&cur->sbsec_list); + kfree(cur); + }; +} + static inline int opt_len(const char *s) { bool open_quote = false; @@ -4191,6 +4222,12 @@ static void selinux_cred_free(struct cred *cred) { struct task_security_struct *tsec = selinux_cred(cred); + if (tsec->state->owner_cred == cred) { + /* Free all sb security for this state here - do only once */ + selinux_state_sb_free_list(tsec->state); + tsec->state->owner_cred = NULL; + }; + put_selinux_state(tsec->state); if (tsec->parent_cred) put_cred(tsec->parent_cred); @@ -7573,7 +7610,7 @@ int selinux_state_create(struct selinux_state *parent, struct selinux_state **st mutex_init(&newstate->status_lock); mutex_init(&newstate->policy_mutex); - + sbsec_counting_init(&newstate->sb_count_head); rc = selinux_avc_create(&newstate->avc); if (rc) goto err; @@ -7587,6 +7624,7 @@ int selinux_state_create(struct selinux_state *parent, struct selinux_state **st strncat(newstate->xattr_name, suffix, rc); } *state = newstate; + pr_info("SELinux: State create.\n"); return 0; err: kfree(newstate); @@ -7598,6 +7636,8 @@ static void selinux_state_free(struct work_struct *work) struct selinux_state *parent, *state = container_of(work, struct selinux_state, work); + pr_info("SELinux: State free.\n"); + do { parent = state->parent; if (state->status_page) @@ -7623,6 +7663,8 @@ static __init int selinux_init(void) enforcing_set(init_selinux_state, selinux_enforcing_boot); init_selinux_state->checkreqprot = selinux_checkreqprot_boot; + spin_lock_init(&sb_state_lock); + /* Set the security state for the initial task. */ cred_init_security(); diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 21566a1541f4..6d9c63c57620 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -55,8 +55,6 @@ struct file_security_struct { struct superblock_security_head { struct list_head head; /* list head of superblock_security_struct */ - spinlock_t lock; - const struct xattr_handler **old_s_xattr; const struct xattr_handler *xattr_handler; }; @@ -69,10 +67,11 @@ struct superblock_security_struct { unsigned short behavior; /* labeling behavior */ unsigned short flags; /* which mount options were specified */ struct mutex lock; - struct list_head isec_head; spinlock_t isec_lock; - struct selinux_state *state; /* pointer to selinux_state */ + struct list_head isec_head; + u64 namespace_id; /* id of selinux_state */ struct list_head sbsec_list; + struct list_head sbsec_count; }; struct msg_security_struct { diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 0ab6b433dc9c..48021a7fb92f 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -111,6 +111,8 @@ struct selinux_state { struct selinux_policy __rcu *policy; struct mutex policy_mutex; struct selinux_state *parent; + const struct cred *owner_cred; /* cred of process created this state */ + struct list_head sb_count_head; u64 id; char xattr_name[XATTR_NAME_MAX]; } __randomize_layout; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 18c5383b87a9..5a29516d81a8 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -410,6 +410,9 @@ static ssize_t sel_write_unshare(struct file *file, const char __user *buf, tsec->sockcreate_sid = SECSID_NULL; tsec->parent_cred = get_current_cred(); commit_creds(cred); + tsec->state->owner_cred = current_cred(); + pr_debug("SELINUXNS: State init finished owner cred pntr = %p - %p\n", + &tsec->state, tsec->state->owner_cred); out: kfree(suffix); From patchwork Mon Apr 18 09:45:52 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Kozhevnikov X-Patchwork-Id: 12816444 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7C034C43217 for ; Mon, 18 Apr 2022 09:46:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237548AbiDRJtR (ORCPT ); Mon, 18 Apr 2022 05:49:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60374 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237552AbiDRJtM (ORCPT ); Mon, 18 Apr 2022 05:49:12 -0400 Received: from szxga02-in.huawei.com (szxga02-in.huawei.com [45.249.212.188]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8EF25165B5; Mon, 18 Apr 2022 02:46:31 -0700 (PDT) Received: from dggpemm500020.china.huawei.com (unknown [172.30.72.55]) by szxga02-in.huawei.com (SkyGuard) with ESMTP id 4KhhsD6bSYzhXZd; Mon, 18 Apr 2022 17:46:24 +0800 (CST) Received: from dggpemm500011.china.huawei.com (7.185.36.110) by dggpemm500020.china.huawei.com (7.185.36.49) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:30 +0800 Received: from mscphmkozh00002.huawei.com (10.219.174.70) by dggpemm500011.china.huawei.com (7.185.36.110) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 18 Apr 2022 17:46:28 +0800 From: Alexander Kozhevnikov To: CC: , , , , , , , , , , Subject: [RFC PATCH 7/7] SELINUXNS: Fixing concurrency issues Date: Mon, 18 Apr 2022 17:45:52 +0800 Message-ID: <20220418094552.128898-8-alexander.kozhevnikov@huawei.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> References: <20220418094552.128898-1-alexander.kozhevnikov@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.219.174.70] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To dggpemm500011.china.huawei.com (7.185.36.110) X-CFilter-Loop: Reflected Precedence: bulk List-ID: 1) Fix of filesystem info ZERO pointer dereference due to concurrency race. It was possible for selinuxfs info structure to be cleared when it is still in use. The decision was not to use locks to avoid this but just add zero pointer checks in the code and assign NULL to the pointer before actual structure clearance. 2) Fix for selinuxfs mutex concurrency. As it is possible to have the same selinuxfs filesystem mounted in different states we need a global mutex lock on all write operations in selinuxns. 3) Inode deferred initialization list removed. This list attached to sbsec was used to do deferred isec init after selinux state init was complete. This functionality is anyway implemented now through revalidate procedure. List looks obsolete now. Signed-off-by: Alexander Kozhevnikov Signed-off-by: Igor Baranov --- security/selinux/hooks.c | 56 +----- security/selinux/include/objsec.h | 1 - security/selinux/selinuxfs.c | 275 +++++++++++++++++++++++------- security/selinux/ss/services.c | 12 +- 4 files changed, 227 insertions(+), 117 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e17175ff707d..db5bdfd7c3fb 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -346,27 +346,7 @@ static struct inode_security_struct *backing_inode_security(struct dentry *dentr static void inode_free_security(struct inode *inode) { - struct inode_security_struct *isec = selinux_inode(inode); - struct superblock_security_struct *sbsec; - - if (!isec) - return; - sbsec = selinux_superblock(inode->i_sb); - /* - * As not all inode security structures are in a list, we check for - * empty list outside of the lock to make sure that we won't waste - * time taking a lock doing nothing. - * - * The list_del_init() function can be safely called more than once. - * It should not be possible for this function to be called with - * concurrent list_add(), but for better safety against future changes - * in the code, we use list_empty_careful() here. - */ - if (!list_empty_careful(&isec->list)) { - spin_lock(&sbsec->isec_lock); - list_del_init(&isec->list); - spin_unlock(&sbsec->isec_lock); - } + /* NOTHING TO DO NOW */ } struct selinux_mnt_opts { @@ -641,27 +621,6 @@ static int sb_finish_set_opts(struct super_block *sb) /* Initialize the root inode. */ rc = inode_doinit_with_dentry(root_inode, root); - /* Initialize any other inodes associated with the superblock, e.g. - inodes created prior to initial policy load or inodes created - during get_sb by a pseudo filesystem that directly - populates itself. */ - spin_lock(&sbsec->isec_lock); - while (!list_empty(&sbsec->isec_head)) { - struct inode_security_struct *isec = - list_first_entry(&sbsec->isec_head, - struct inode_security_struct, list); - struct inode *inode = isec->inode; - list_del_init(&isec->list); - spin_unlock(&sbsec->isec_lock); - inode = igrab(inode); - if (inode) { - if (!IS_PRIVATE(inode)) - inode_doinit_with_dentry(inode, NULL); - iput(inode); - } - spin_lock(&sbsec->isec_lock); - } - spin_unlock(&sbsec->isec_lock); out: return rc; } @@ -1602,13 +1561,6 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SE_SBINITIALIZED)) { - /* Defer initialization until selinux_complete_init, - after the initial policy is loaded and the security - server is ready to handle calls. */ - spin_lock(&sbsec->isec_lock); - if (list_empty(&isec->list)) - list_add(&isec->list, &sbsec->isec_head); - spin_unlock(&sbsec->isec_lock); return 0; } @@ -2786,8 +2738,6 @@ sbsec_alloc(struct superblock_security_head *sbsech) if (!sbsec) return NULL; mutex_init(&sbsec->lock); - INIT_LIST_HEAD(&sbsec->isec_head); - spin_lock_init(&sbsec->isec_lock); sbsec->sbsech = sbsech; sbsec->sid = SECINITSID_UNLABELED; sbsec->def_sid = SECINITSID_FILE; @@ -3133,7 +3083,6 @@ static int selinux_inode_alloc_security(struct inode *inode) u32 sid = current_sid(); spin_lock_init(&isec->lock); - INIT_LIST_HEAD(&isec->list); isec->inode = inode; isec->sid = SECINITSID_UNLABELED; isec->sclass = SECCLASS_FILE; @@ -7654,6 +7603,8 @@ void __put_selinux_state(struct selinux_state *state) schedule_work(&state->work); } +extern struct mutex selinuxfs_mutex; + static __init int selinux_init(void) { pr_info("SELinux: Initializing.\n"); @@ -7664,6 +7615,7 @@ static __init int selinux_init(void) init_selinux_state->checkreqprot = selinux_checkreqprot_boot; spin_lock_init(&sb_state_lock); + mutex_init(&selinuxfs_mutex); /* Set the security state for the initial task. */ cred_init_security(); diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 6d9c63c57620..22b577540224 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -36,7 +36,6 @@ enum label_initialized { struct inode_security_struct { struct inode *inode; /* back pointer to inode object */ - struct list_head list; /* list of inode_security_struct */ u32 task_sid; /* SID of creating task */ u32 sid; /* SID of this object */ u16 sclass; /* security class of this object */ diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 5a29516d81a8..0cd609744c98 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -43,6 +43,8 @@ #include "objsec.h" #include "conditional.h" +struct mutex selinuxfs_mutex; /* GLobal mutex for all selinuxfs access */ + enum sel_inos { SEL_ROOT_INO = 2, SEL_LOAD, /* load policy */ @@ -102,15 +104,16 @@ static void selinux_fs_info_free(struct super_block *sb) struct selinux_fs_info *fsi = sb->s_fs_info; int i; + sb->s_fs_info = NULL; if (fsi) { put_selinux_state(fsi->state); for (i = 0; i < fsi->bool_num; i++) kfree(fsi->bool_pending_names[i]); kfree(fsi->bool_pending_names); kfree(fsi->bool_pending_values); + kfree(fsi); } - kfree(sb->s_fs_info); - sb->s_fs_info = NULL; + } #define SEL_INITCON_INO_OFFSET 0x01000000 @@ -131,6 +134,9 @@ static ssize_t sel_read_enforce(struct file *filp, char __user *buf, char tmpbuf[TMPBUFLEN]; ssize_t length; + if (fsi == NULL) + return -EINVAL; + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", enforcing_enabled(fsi->state)); return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); @@ -142,11 +148,15 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *page = NULL; ssize_t length; int old_value, new_value; + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; if (state != current_selinux_state) return -EPERM; @@ -157,9 +167,13 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, if (*ppos != 0) return -EINVAL; + mutex_lock(&selinuxfs_mutex); + page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); + if (IS_ERR(page)) { + length = PTR_ERR(page); + goto out_unlock; + } length = -EINVAL; if (sscanf(page, "%d", &new_value) != 1) @@ -192,6 +206,8 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, length = count; out: kfree(page); +out_unlock: + mutex_unlock(&selinuxfs_mutex); return length; } #else @@ -208,11 +224,17 @@ static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char tmpbuf[TMPBUFLEN]; ssize_t length; ino_t ino = file_inode(filp)->i_ino; - int handle_unknown = (ino == SEL_REJECT_UNKNOWN) ? + int handle_unknown; + + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; + handle_unknown = (ino == SEL_REJECT_UNKNOWN) ? security_get_reject_unknown(state) : !security_get_allow_unknown(state); @@ -228,7 +250,12 @@ static const struct file_operations sel_handle_unknown_ops = { static int sel_open_handle_status(struct inode *inode, struct file *filp) { struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; - struct page *status = selinux_kernel_status_page(fsi->state); + struct page *status; + + if (fsi == NULL) + return -EINVAL; + + status = selinux_kernel_status_page(fsi->state); if (!status) return -ENOMEM; @@ -290,6 +317,9 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, int new_value; int enforcing; + if (fsi == NULL) + return -EINVAL; + if (fsi->state != current_selinux_state) return -EPERM; @@ -316,8 +346,10 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, goto out; if (new_value) { + mutex_lock(&selinuxfs_mutex); enforcing = enforcing_enabled(fsi->state); length = selinux_disable(fsi->state); + mutex_unlock(&selinuxfs_mutex); if (length) goto out; audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, @@ -347,31 +379,33 @@ static ssize_t sel_write_unshare(struct file *file, const char __user *buf, { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; struct cred *cred; struct task_security_struct *tsec; char *suffix, *p; ssize_t len; + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; if (state != current_selinux_state) return -EPERM; - if (count >= PAGE_SIZE) - return -ENOMEM; - /* No partial writes. */ if (*ppos != 0) return -EINVAL; + if ((count >= PAGE_SIZE) || + (count > XATTR_NAME_MAX - sizeof(XATTR_NAME_SELINUX) - 2)) + return -EINVAL; + len = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__UNSHARE, NULL); if (len) return len; - if (count > XATTR_NAME_MAX - sizeof(XATTR_NAME_SELINUX) - 2) - return -EINVAL; - suffix = memdup_user_nul(buf, count); if (IS_ERR(suffix)) return PTR_ERR(suffix); @@ -423,14 +457,19 @@ static ssize_t sel_read_unshare(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; size_t slen = sizeof(XATTR_SELINUX_SUFFIX); + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; + if (state->id != current_selinux_state->id) return -EPERM; return simple_read_from_buffer(buf, count, ppos, state->xattr_name + slen, - strlen(state->xattr_name + slen)); + strlen(state->xattr_name + slen)); } static const struct file_operations sel_unshare_ops = { @@ -480,6 +519,9 @@ static ssize_t sel_read_mls(struct file *filp, char __user *buf, char tmpbuf[TMPBUFLEN]; ssize_t length; + if (fsi == NULL) + return -EINVAL; + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", security_mls_enabled(fsi->state)); return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); @@ -498,13 +540,17 @@ struct policy_load_memory { static int sel_open_policy(struct inode *inode, struct file *filp) { struct selinux_fs_info *fsi = inode->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; struct policy_load_memory *plm = NULL; int rc; + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; BUG_ON(filp->private_data); - mutex_lock(&fsi->state->policy_mutex); + mutex_lock(&selinuxfs_mutex); rc = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, @@ -535,11 +581,11 @@ static int sel_open_policy(struct inode *inode, struct file *filp) filp->private_data = plm; - mutex_unlock(&fsi->state->policy_mutex); + mutex_unlock(&selinuxfs_mutex); return 0; err: - mutex_unlock(&fsi->state->policy_mutex); + mutex_unlock(&selinuxfs_mutex); if (plm) vfree(plm->data); @@ -552,6 +598,9 @@ static int sel_release_policy(struct inode *inode, struct file *filp) struct selinux_fs_info *fsi = inode->i_sb->s_fs_info; struct policy_load_memory *plm = filp->private_data; + if (fsi == NULL) + return -EINVAL; + BUG_ON(!plm); fsi->policy_opened = 0; @@ -726,10 +775,13 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf, ssize_t length; void *data = NULL; + if (fsi == NULL) + return -EINVAL; + if (fsi->state != current_selinux_state) return -EPERM; - mutex_lock(&fsi->state->policy_mutex); + mutex_lock(&selinuxfs_mutex); length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, @@ -773,7 +825,7 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf, from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); out: - mutex_unlock(&fsi->state->policy_mutex); + mutex_unlock(&selinuxfs_mutex); vfree(data); return length; } @@ -786,16 +838,20 @@ static const struct file_operations sel_load_ops = { static ssize_t sel_write_context(struct file *file, char *buf, size_t size) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *canon = NULL; u32 sid, len; ssize_t length; + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__CHECK_CONTEXT, NULL); if (length) - goto out; + return length; length = security_context_to_sid(state, buf, size, &sid, GFP_KERNEL); if (length) @@ -826,6 +882,9 @@ static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, char tmpbuf[TMPBUFLEN]; ssize_t length; + if (fsi == NULL) + return -EINVAL; + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", fsi->state->checkreqprot); return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); } @@ -838,6 +897,9 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, ssize_t length; unsigned int new_value; + if (fsi == NULL) + return -EINVAL; + if (fsi->state != current_selinux_state) return -EPERM; @@ -855,9 +917,13 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, if (*ppos != 0) return -EINVAL; + mutex_lock(&selinuxfs_mutex); + page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); + if (IS_ERR(page)) { + length = PTR_ERR(page); + goto out_unlock; + } length = -EINVAL; if (sscanf(page, "%u", &new_value) != 1) @@ -875,6 +941,8 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, length = count; out: kfree(page); +out_unlock: + mutex_unlock(&selinuxfs_mutex); return length; } static const struct file_operations sel_checkreqprot_ops = { @@ -888,13 +956,17 @@ static ssize_t sel_write_validatetrans(struct file *file, size_t count, loff_t *ppos) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *oldcon = NULL, *newcon = NULL, *taskcon = NULL; char *req = NULL; u32 osid, nsid, tsid; u16 tclass; int rc; + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; if (state != current_selinux_state) return -EPERM; @@ -902,16 +974,16 @@ static ssize_t sel_write_validatetrans(struct file *file, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__VALIDATE_TRANS, NULL); if (rc) - goto out; + return rc; rc = -ENOMEM; if (count >= PAGE_SIZE) - goto out; + return rc; /* No partial writes. */ rc = -EINVAL; if (*ppos != 0) - goto out; + return rc; req = memdup_user_nul(buf, count); if (IS_ERR(req)) { @@ -953,10 +1025,10 @@ static ssize_t sel_write_validatetrans(struct file *file, if (!rc) rc = count; out: - kfree(req); - kfree(oldcon); - kfree(newcon); kfree(taskcon); + kfree(newcon); + kfree(oldcon); + kfree(req); return rc; } @@ -990,6 +1062,9 @@ static ssize_t selinux_transaction_write(struct file *file, const char __user *b char *data; ssize_t rv; + if (fsi == NULL) + return -EINVAL; + if (fsi->state != current_selinux_state) return -EPERM; @@ -1005,6 +1080,7 @@ static ssize_t selinux_transaction_write(struct file *file, const char __user *b simple_transaction_set(file, rv); rv = size; } + return rv; } @@ -1024,18 +1100,23 @@ static const struct file_operations transaction_ops = { static ssize_t sel_write_access(struct file *file, char *buf, size_t size) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *scon = NULL, *tcon = NULL; u32 ssid, tsid; u16 tclass; struct av_decision avd; ssize_t length; + if (fsi == NULL) + return -EINVAL; + + state = fsi->state; length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__COMPUTE_AV, NULL); if (length) - goto out; + return length; + length = -ENOMEM; scon = kzalloc(size + 1, GFP_KERNEL); @@ -1075,7 +1156,7 @@ static ssize_t sel_write_access(struct file *file, char *buf, size_t size) static ssize_t sel_write_create(struct file *file, char *buf, size_t size) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *scon = NULL, *tcon = NULL; char *namebuf = NULL, *objname = NULL; u32 ssid, tsid, newsid; @@ -1085,6 +1166,10 @@ static ssize_t sel_write_create(struct file *file, char *buf, size_t size) u32 len; int nargs; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__COMPUTE_CREATE, @@ -1179,7 +1264,7 @@ static ssize_t sel_write_create(struct file *file, char *buf, size_t size) static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *scon = NULL, *tcon = NULL; u32 ssid, tsid, newsid; u16 tclass; @@ -1187,6 +1272,10 @@ static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size) char *newcon = NULL; u32 len; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__COMPUTE_RELABEL, @@ -1240,7 +1329,7 @@ static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size) static ssize_t sel_write_user(struct file *file, char *buf, size_t size) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *con = NULL, *user = NULL, *ptr; u32 sid, *sids = NULL; ssize_t length; @@ -1248,6 +1337,10 @@ static ssize_t sel_write_user(struct file *file, char *buf, size_t size) int i, rc; u32 len, nsids; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__COMPUTE_USER, @@ -1305,7 +1398,7 @@ static ssize_t sel_write_user(struct file *file, char *buf, size_t size) static ssize_t sel_write_member(struct file *file, char *buf, size_t size) { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *scon = NULL, *tcon = NULL; u32 ssid, tsid, newsid; u16 tclass; @@ -1313,6 +1406,10 @@ static ssize_t sel_write_member(struct file *file, char *buf, size_t size) char *newcon = NULL; u32 len; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__COMPUTE_MEMBER, @@ -1388,7 +1485,10 @@ static ssize_t sel_read_bool(struct file *filep, char __user *buf, unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK; const char *name = filep->f_path.dentry->d_name.name; - mutex_lock(&fsi->state->policy_mutex); + if (fsi == NULL) + return -EINVAL; + + mutex_lock(&selinuxfs_mutex); ret = -EINVAL; if (index >= fsi->bool_num || strcmp(name, @@ -1407,14 +1507,14 @@ static ssize_t sel_read_bool(struct file *filep, char __user *buf, } length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing, fsi->bool_pending_values[index]); - mutex_unlock(&fsi->state->policy_mutex); + mutex_unlock(&selinuxfs_mutex); ret = simple_read_from_buffer(buf, count, ppos, page, length); out_free: free_page((unsigned long)page); return ret; out_unlock: - mutex_unlock(&fsi->state->policy_mutex); + mutex_unlock(&selinuxfs_mutex); goto out_free; } @@ -1428,6 +1528,9 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf, unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK; const char *name = filep->f_path.dentry->d_name.name; + if (fsi == NULL) + return -EINVAL; + if (fsi->state != current_selinux_state) return -EPERM; @@ -1438,11 +1541,13 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf, if (*ppos != 0) return -EINVAL; - page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); + mutex_lock(&selinuxfs_mutex); - mutex_lock(&fsi->state->policy_mutex); + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) { + length = PTR_ERR(page); + goto out_unlock; + } length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, @@ -1467,8 +1572,10 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf, length = count; out: - mutex_unlock(&fsi->state->policy_mutex); kfree(page); +out_unlock: + mutex_unlock(&selinuxfs_mutex); + return length; } @@ -1487,6 +1594,9 @@ static ssize_t sel_commit_bools_write(struct file *filep, ssize_t length; int new_value; + if (fsi == NULL) + return -EINVAL; + if (fsi->state != current_selinux_state) return -EPERM; @@ -1497,11 +1607,13 @@ static ssize_t sel_commit_bools_write(struct file *filep, if (*ppos != 0) return -EINVAL; - page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); + mutex_lock(&selinuxfs_mutex); - mutex_lock(&fsi->state->policy_mutex); + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) { + length = PTR_ERR(page); + goto out_unlock; + } length = avc_has_perm(current_selinux_state, current_sid(), SECINITSID_SECURITY, @@ -1523,8 +1635,9 @@ static ssize_t sel_commit_bools_write(struct file *filep, length = count; out: - mutex_unlock(&fsi->state->policy_mutex); kfree(page); +out_unlock: + mutex_unlock(&selinuxfs_mutex); return length; } @@ -1622,10 +1735,14 @@ static ssize_t sel_read_avc_cache_threshold(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char tmpbuf[TMPBUFLEN]; ssize_t length; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", avc_get_cache_threshold(state->avc)); return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); @@ -1637,11 +1754,15 @@ static ssize_t sel_write_avc_cache_threshold(struct file *file, { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *page; ssize_t ret; unsigned int new_value; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + if (state != current_selinux_state) return -EPERM; @@ -1659,9 +1780,13 @@ static ssize_t sel_write_avc_cache_threshold(struct file *file, if (*ppos != 0) return -EINVAL; + mutex_lock(&selinuxfs_mutex); + page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); + if (IS_ERR(page)) { + ret = PTR_ERR(page); + goto out_unlock; + } ret = -EINVAL; if (sscanf(page, "%u", &new_value) != 1) @@ -1672,6 +1797,8 @@ static ssize_t sel_write_avc_cache_threshold(struct file *file, ret = count; out: kfree(page); +out_unlock: + mutex_unlock(&selinuxfs_mutex); return ret; } @@ -1679,10 +1806,14 @@ static ssize_t sel_read_avc_hash_stats(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *page; ssize_t length; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + page = (char *)__get_free_page(GFP_KERNEL); if (!page) return -ENOMEM; @@ -1699,10 +1830,14 @@ static ssize_t sel_read_sidtab_hash_stats(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; - struct selinux_state *state = fsi->state; + struct selinux_state *state; char *page; ssize_t length; + if (fsi == NULL) + return -EINVAL; + state = fsi->state; + page = (char *)__get_free_page(GFP_KERNEL); if (!page) return -ENOMEM; @@ -1817,6 +1952,9 @@ static int sel_make_avc_files(struct dentry *dir) #endif }; + if (fsi == NULL) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(files); i++) { struct inode *inode; struct dentry *dentry; @@ -1848,6 +1986,9 @@ static int sel_make_ss_files(struct dentry *dir) { "sidtab_hash_stats", &sel_sidtab_hash_stats_ops, S_IRUGO }, }; + if (fsi == NULL) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(files); i++) { struct inode *inode; struct dentry *dentry; @@ -1878,6 +2019,9 @@ static ssize_t sel_read_initcon(struct file *file, char __user *buf, u32 sid, len; ssize_t ret; + if (fsi == NULL) + return -EINVAL; + sid = file_inode(file)->i_ino&SEL_INO_MASK; ret = security_sid_to_context(fsi->state, sid, &con, &len); if (ret) @@ -1979,6 +2123,9 @@ static ssize_t sel_read_policycap(struct file *file, char __user *buf, ssize_t length; unsigned long i_ino = file_inode(file)->i_ino; + if (fsi == NULL) + return -EINVAL; + value = security_policycap_supported(fsi->state, i_ino & SEL_INO_MASK); length = scnprintf(tmpbuf, TMPBUFLEN, "%d", value); @@ -2040,6 +2187,9 @@ static int sel_make_class_dir_entries(struct selinux_policy *newpolicy, struct inode *inode = NULL; int rc; + if (fsi == NULL) + return -EINVAL; + dentry = d_alloc_name(dir, "index"); if (!dentry) return -ENOMEM; @@ -2218,6 +2368,10 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) goto err; fsi = sb->s_fs_info; + + if (fsi == NULL) + goto err; + fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino); if (IS_ERR(fsi->bool_dir)) { ret = PTR_ERR(fsi->bool_dir); @@ -2309,6 +2463,9 @@ static int selinuxfs_compare(struct super_block *sb, struct fs_context *fc) { struct selinux_fs_info *fsi = sb->s_fs_info; + if (fsi == NULL) + return 0; + return (current_selinux_state->id == fsi->state->id); } diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 548acdecafa8..5e1f0486add1 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -67,6 +67,8 @@ #include "audit.h" #include "policycap_names.h" +extern struct mutex selinuxfs_mutex; /* GLobal mutex for all selinuxfs access */ + /* Forward declaration. */ static int context_struct_to_string(struct policydb *policydb, struct context *context, @@ -2165,7 +2167,7 @@ void selinux_policy_cancel(struct selinux_state *state, struct selinux_policy *oldpolicy; oldpolicy = rcu_dereference_protected(state->policy, - lockdep_is_held(&state->policy_mutex)); + lockdep_is_held(&selinuxfs_mutex)); sidtab_cancel_convert(oldpolicy->sidtab); selinux_policy_free(policy); @@ -2189,7 +2191,7 @@ void selinux_policy_commit(struct selinux_state *state, u32 seqno; oldpolicy = rcu_dereference_protected(state->policy, - lockdep_is_held(&state->policy_mutex)); + lockdep_is_held(&selinuxfs_mutex)); /* If switching between different policy types, log MLS status */ if (oldpolicy) { @@ -2283,7 +2285,7 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len, } oldpolicy = rcu_dereference_protected(state->policy, - lockdep_is_held(&state->policy_mutex)); + lockdep_is_held(&selinuxfs_mutex)); /* Preserve active boolean values from the old policy */ rc = security_preserve_bools(oldpolicy, newpolicy); @@ -2983,7 +2985,7 @@ int security_set_bools(struct selinux_state *state, u32 len, int *values) return -EINVAL; oldpolicy = rcu_dereference_protected(state->policy, - lockdep_is_held(&state->policy_mutex)); + lockdep_is_held(&selinuxfs_mutex)); /* Consistency check on number of booleans, should never fail */ if (WARN_ON(len != oldpolicy->policydb.p_bools.nprim)) @@ -3888,7 +3890,7 @@ int security_read_policy(struct selinux_state *state, struct policy_file fp; policy = rcu_dereference_protected( - state->policy, lockdep_is_held(&state->policy_mutex)); + state->policy, lockdep_is_held(&selinuxfs_mutex)); if (!policy) return -EINVAL;