From patchwork Thu Oct 27 00:01:30 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Casey Schaufler X-Patchwork-Id: 9398715 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 79776600BA for ; Thu, 27 Oct 2016 00:02:19 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4052A29E4C for ; Thu, 27 Oct 2016 00:02:19 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 3568729E51; Thu, 27 Oct 2016 00:02:19 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2234629E4C for ; Thu, 27 Oct 2016 00:02:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935159AbcJ0ABi (ORCPT ); Wed, 26 Oct 2016 20:01:38 -0400 Received: from nm28-vm1.bullet.mail.ne1.yahoo.com ([98.138.91.35]:45460 "EHLO nm28-vm1.bullet.mail.ne1.yahoo.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935146AbcJ0ABe (ORCPT ); Wed, 26 Oct 2016 20:01:34 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.com; s=s2048; t=1477526493; bh=IAEo54pF/hMAEsa0h6i/mvx3EyK1aFGwSPOCypOT25o=; h=Subject:To:References:Cc:From:Date:In-Reply-To:From:Subject; b=hbZfaPiJb3HdRfqzt5SxdY5WvNgXU64uegAl9L6jhZ+Lv9Q4BanWgS8DnnOiwZfLz+LDoZvDCcSnxvrLK/e4zCR6aPhCMxLwqahypMBIMvzIBeBae5311o5CqVyjp1VyE+3ZTYiU4CiMOyX43tuog/PV8i08uDAYSqGQZWuqyTEOi7Z/AnCPX2UA9tAmvCjYvFSxjKR6SzPc9qQFw+gx21dHIqnepNhf51U5XDlIlAr/86+cXCg6Un5/8o+iHrui1Cj/GC206Tyv3F1slS0L9Nf2xahXZCGZuHS8KMPS90QN063jEQvG0ZIds+WyWYZkqi/9ePZ309ik5ioM4Pgeaw== Received: from [98.138.100.103] by nm28.bullet.mail.ne1.yahoo.com with NNFMP; 27 Oct 2016 00:01:33 -0000 Received: from [98.138.226.58] by tm102.bullet.mail.ne1.yahoo.com with NNFMP; 27 Oct 2016 00:01:33 -0000 Received: from [127.0.0.1] by smtp209.mail.ne1.yahoo.com with NNFMP; 27 Oct 2016 00:01:33 -0000 X-Yahoo-Newman-Id: 54912.45898.bm@smtp209.mail.ne1.yahoo.com X-Yahoo-Newman-Property: ymail-3 X-YMail-OSG: WaEvwmUVM1mfCUqWw26sKMFL7namk5CRKp5Ak_PJB9nQ1jf dBp2ynkAClqMkjNiHHrzsSREg.lG7x.DKJPnthwdodmlmDYtEPVuzjRY.bzT o0EeJ.D_Trz3K6PmlB._hpPh3J00NzzKaA3qu_8rgb3PVZDcWsZ0G2_LYYBE 6w9cGtSSCKw7gtBTZwCyTSydhdM.7iuG.fBAW0YsBPA9Sx76dSFm2ouIkoxe CXOsoRu70YRTj.DKflD1wZL0.hc_FxYgoZtBj65Ls6fLgdsakaHKZNdk1Ast DUFdtXVhGtLx9SpnaHUjYvBbfpX_qXRU8yXzpt3z.k5GbQfOxFMG7jSlCBtC UBe3waJrc46IcAhSXv5mjvu0WNsTTnt9tltjFohT8bPgMYuWMjm_wiDmwxRR p8hd_tkIK6vKrk74sK3rZMF5vM_HPla_wsCfr.QJIHIlBnyejK6Wz0Z01iQ5 6hfbnSg56Jg.ch6MJLYVc8mQgXr0gjKaZbLabNy7XDIhbufTCnpH1_a0FTIX cIdTaWGD7g1XC1prBwDIoW3tdORaKyHT7pe60HP2XdR3zwosDYeeJuv9VPN2 bLKs09J5_SH3snqW.5cro3nc- X-Yahoo-SMTP: OIJXglSswBDfgLtXluJ6wiAYv6_cnw-- Subject: [PATCH v6 3/3] LSM: Add context interface for proc attrs To: LSM , James Morris References: <00f80c77-9623-7e9e-8980-63b362a4f16c@schaufler-ca.com> Cc: John Johansen , Paul Moore , Kees Cook , Stephen Smalley , Tetsuo Handa , LKLM From: Casey Schaufler Message-ID: <234d6610-37d1-6632-3c3d-43b6c167e370@schaufler-ca.com> Date: Wed, 26 Oct 2016 17:01:30 -0700 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Thunderbird/45.4.0 MIME-Version: 1.0 In-Reply-To: <00f80c77-9623-7e9e-8980-63b362a4f16c@schaufler-ca.com> Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP Subject: [PATCH v6 3/3] LSM: Add context interface for proc attrs The /proc/.../attr/current interface is used by all three Linux security modules (SELinux, Smack and AppArmor) to report and modify the process security attribute. This is all fine when there is exactly one of these modules active and the userspace code knows which it module it is. It would require a major change to the "current" interface to provide information about more than one set of process security attributes. Instead, a "context" attribute is added, which identifies the security module that the information applies to. The format is: lsmname='context-value' When multiple concurrent modules are supported the /proc/.../attr/context interface will include the data for all of the active modules. lsmname1='context-value1',lsmname2='context-value2' The module specific subdirectories under attr contain context entries that report the information for that specific module in the same format. Signed-off-by: Casey Schaufler --- Documentation/security/LSM.txt | 8 +++ fs/proc/base.c | 4 ++ security/apparmor/lsm.c | 35 +++++++++++-- security/security.c | 108 +++++++++++++++++++++++++++++++++++++++++ security/selinux/hooks.c | 20 +++++++- security/smack/smack_lsm.c | 20 ++++---- 6 files changed, 180 insertions(+), 15 deletions(-) diff --git a/Documentation/security/LSM.txt b/Documentation/security/LSM.txt index 125c489..af3eb11 100644 --- a/Documentation/security/LSM.txt +++ b/Documentation/security/LSM.txt @@ -36,6 +36,14 @@ for SELinux would be in /proc/.../attr/selinux. Using the files found directly in /proc/.../attr (e.g. current) should be avoided. These files remain as legacy interfaces. +The files named "context" in the attr directories contain the +same information as the "current" files, but formatted to +identify the module it comes from. + +if selinux is the active security module: + /proc/self/attr/context could contain selinux='unconfined_t' + /proc/self/attr/selinux/context could contain selinux='unconfined_t' + Based on https://lkml.org/lkml/2007/10/26/215, a new LSM is accepted into the kernel when its intent (a description of what it tries to protect against and in what cases one would expect to diff --git a/fs/proc/base.c b/fs/proc/base.c index c6dbe81..923e4e2 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -2565,6 +2565,7 @@ static const struct pid_entry selinux_attr_dir_stuff[] = { ATTR("selinux", "fscreate", S_IRUGO|S_IWUGO), ATTR("selinux", "keycreate", S_IRUGO|S_IWUGO), ATTR("selinux", "sockcreate", S_IRUGO|S_IWUGO), + ATTR("selinux", "context", S_IRUGO|S_IWUGO), }; LSM_DIR_OPS(selinux); #endif @@ -2572,6 +2573,7 @@ LSM_DIR_OPS(selinux); #ifdef CONFIG_SECURITY_SMACK static const struct pid_entry smack_attr_dir_stuff[] = { ATTR("smack", "current", S_IRUGO|S_IWUGO), + ATTR("smack", "context", S_IRUGO|S_IWUGO), }; LSM_DIR_OPS(smack); #endif @@ -2581,6 +2583,7 @@ static const struct pid_entry apparmor_attr_dir_stuff[] = { ATTR("apparmor", "current", S_IRUGO|S_IWUGO), ATTR("apparmor", "prev", S_IRUGO), ATTR("apparmor", "exec", S_IRUGO|S_IWUGO), + ATTR("apparmor", "context", S_IRUGO|S_IWUGO), }; LSM_DIR_OPS(apparmor); #endif @@ -2592,6 +2595,7 @@ static const struct pid_entry attr_dir_stuff[] = { ATTR(NULL, "fscreate", S_IRUGO|S_IWUGO), ATTR(NULL, "keycreate", S_IRUGO|S_IWUGO), ATTR(NULL, "sockcreate", S_IRUGO|S_IWUGO), + ATTR(NULL, "context", S_IRUGO|S_IWUGO), #ifdef CONFIG_SECURITY_SELINUX DIR("selinux", S_IRUGO|S_IXUGO, proc_selinux_attr_dir_inode_ops, proc_selinux_attr_dir_ops), diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 1d4843d..b865cf7 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -476,9 +476,13 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, const struct cred *cred = get_task_cred(task); struct aa_task_cxt *cxt = cred_cxt(cred); struct aa_profile *profile = NULL; + char *vp; + char *np; if (strcmp(name, "current") == 0) profile = aa_get_newest_profile(cxt->profile); + else if (strcmp(name, "context") == 0) + profile = aa_get_newest_profile(cxt->profile); else if (strcmp(name, "prev") == 0 && cxt->previous) profile = aa_get_newest_profile(cxt->previous); else if (strcmp(name, "exec") == 0 && cxt->onexec) @@ -486,9 +490,29 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, else error = -EINVAL; - if (profile) - error = aa_getprocattr(profile, value); + if (profile == NULL) + goto put_out; + + error = aa_getprocattr(profile, &vp); + if (error < 0) + goto put_out; + + if (strcmp(name, "context") == 0) { + *value = kasprintf(GFP_KERNEL, "apparmor='%s'", vp); + if (*value == NULL) { + error = -ENOMEM; + goto put_out; + } + np = strchr(*value, '\n'); + if (np != NULL) { + np[0] = '\''; + np[1] = '\0'; + } + error = strlen(*value); + } else + *value = vp; +put_out: aa_put_profile(profile); put_cred(cred); @@ -530,7 +554,7 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, goto out; arg_size = size - (args - (largs ? largs : (char *) value)); - if (strcmp(name, "current") == 0) { + if (strcmp(name, "current") == 0 || strcmp(name, "context") == 0) { if (strcmp(command, "changehat") == 0) { error = aa_setprocattr_changehat(args, arg_size, !AA_DO_TEST); @@ -552,7 +576,10 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, else goto fail; } else - /* only support the "current" and "exec" process attributes */ + /* + * only support the "current", "context" and "exec" + * process attributes + */ goto fail; if (!error) diff --git a/security/security.c b/security/security.c index 23d5868..656984a 100644 --- a/security/security.c +++ b/security/security.c @@ -1207,8 +1207,47 @@ int security_getprocattr(struct task_struct *p, const char *lsm, char *name, char **value) { struct security_hook_list *hp; + char *vp; + char *cp = NULL; int rc = -EINVAL; + int trc; + /* + * "context" requires work here in addition to what + * the modules provide. + */ + if (strcmp(name, "context") == 0) { + *value = NULL; + list_for_each_entry(hp, + &security_hook_heads.getprocattr, list) { + if (lsm != NULL && strcmp(lsm, hp->lsm)) + continue; + trc = hp->hook.getprocattr(p, "context", &vp); + if (trc == -ENOENT) + continue; + if (trc <= 0) { + kfree(*value); + return trc; + } + rc = trc; + if (*value == NULL) { + *value = vp; + } else { + cp = kasprintf(GFP_KERNEL, "%s,%s", *value, vp); + if (cp == NULL) { + kfree(*value); + kfree(vp); + return -ENOMEM; + } + kfree(*value); + kfree(vp); + *value = cp; + } + } + if (rc > 0) + return strlen(*value); + return rc; + } list_for_each_entry(hp, &security_hook_heads.getprocattr, list) { if (lsm != NULL && strcmp(lsm, hp->lsm)) @@ -1225,7 +1264,76 @@ int security_setprocattr(struct task_struct *p, const char *lsm, char *name, { struct security_hook_list *hp; int rc = -EINVAL; + char *local; + char *cp; + int slen; + int failed = 0; + + /* + * If lsm is NULL look at all the modules to find one + * that processes name. If lsm is not NULL only look at + * that module. + * + * "context" is handled directly here. + */ + if (strcmp(name, "context") == 0) { + /* + * First verify that the input is acceptable. + * lsm1='v1'lsm2='v2'lsm3='v3' + * + * A note on the use of strncmp() below. + * The check is for the substring at the beginning of cp. + * The kzalloc of size + 1 ensures a terminated string. + */ + local = kzalloc(size + 1, GFP_KERNEL); + memcpy(local, value, size); + cp = local; + list_for_each_entry(hp, &security_hook_heads.setprocattr, + list) { + if (lsm != NULL && strcmp(lsm, hp->lsm)) + continue; + if (cp[0] == ',') { + if (cp == local) + goto free_out; + cp++; + } + slen = strlen(hp->lsm); + if (strncmp(cp, hp->lsm, slen)) + goto free_out; + cp += slen; + if (cp[0] != '=' || cp[1] != '\'' || cp[2] == '\'') + goto free_out; + for (cp += 2; cp[0] != '\''; cp++) + if (cp[0] == '\0') + goto free_out; + cp++; + } + cp = local; + list_for_each_entry(hp, &security_hook_heads.setprocattr, + list) { + if (lsm != NULL && strcmp(lsm, hp->lsm)) + continue; + if (cp[0] == ',') + cp++; + cp += strlen(hp->lsm) + 2; + for (slen = 0; cp[slen] != '\''; slen++) + ; + cp[slen] = '\0'; + + rc = hp->hook.setprocattr(p, "context", cp, slen); + if (rc < 0) + failed = rc; + cp += slen + 1; + } + if (failed != 0) + rc = failed; + else + rc = size; +free_out: + kfree(local); + return rc; + } list_for_each_entry(hp, &security_hook_heads.setprocattr, list) { if (lsm != NULL && strcmp(lsm, hp->lsm)) continue; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 0d039fc..cc7e95f 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -5795,6 +5795,8 @@ static int selinux_getprocattr(struct task_struct *p, if (!strcmp(name, "current")) sid = __tsec->sid; + else if (!strcmp(name, "context")) + sid = __tsec->sid; else if (!strcmp(name, "prev")) sid = __tsec->osid; else if (!strcmp(name, "exec")) @@ -5812,7 +5814,19 @@ static int selinux_getprocattr(struct task_struct *p, if (!sid) return 0; - error = security_sid_to_context(sid, value, &len); + if (strcmp(name, "context")) { + error = security_sid_to_context(sid, value, &len); + } else { + char *vp; + + error = security_sid_to_context(sid, &vp, &len); + if (!error) { + *value = kasprintf(GFP_KERNEL, "selinux='%s'", vp); + if (*value == NULL) + error = -ENOMEM; + } + } + if (error) return error; return len; @@ -5852,6 +5866,8 @@ static int selinux_setprocattr(struct task_struct *p, error = current_has_perm(p, PROCESS__SETSOCKCREATE); else if (!strcmp(name, "current")) error = current_has_perm(p, PROCESS__SETCURRENT); + else if (!strcmp(name, "context")) + error = current_has_perm(p, PROCESS__SETCURRENT); else error = -EINVAL; if (error) @@ -5911,7 +5927,7 @@ static int selinux_setprocattr(struct task_struct *p, tsec->keycreate_sid = sid; } else if (!strcmp(name, "sockcreate")) { tsec->sockcreate_sid = sid; - } else if (!strcmp(name, "current")) { + } else if (!strcmp(name, "current") || !strcmp(name, "context")) { error = -EINVAL; if (sid == 0) goto abort_change; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 7c12198..54627dd 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -3605,18 +3605,20 @@ static int smack_getprocattr(struct task_struct *p, char *name, char **value) { struct smack_known *skp = smk_of_task_struct(p); char *cp; - int slen; - if (strcmp(name, "current") != 0) + if (strcmp(name, "current") == 0) { + cp = kstrdup(skp->smk_known, GFP_KERNEL); + if (cp == NULL) + return -ENOMEM; + } else if (strcmp(name, "context") == 0) { + cp = kasprintf(GFP_KERNEL, "smack='%s'", skp->smk_known); + if (cp == NULL) + return -ENOMEM; + } else return -EINVAL; - cp = kstrdup(skp->smk_known, GFP_KERNEL); - if (cp == NULL) - return -ENOMEM; - - slen = strlen(cp); *value = cp; - return slen; + return strlen(cp); } /** @@ -3653,7 +3655,7 @@ static int smack_setprocattr(struct task_struct *p, char *name, if (value == NULL || size == 0 || size >= SMK_LONGLABEL) return -EINVAL; - if (strcmp(name, "current") != 0) + if (strcmp(name, "current") != 0 && strcmp(name, "context") != 0) return -EINVAL; skp = smk_import_entry(value, size);