From patchwork Fri Jul 24 10:04:43 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukasz Pawelczyk X-Patchwork-Id: 6858951 Return-Path: X-Original-To: patchwork-linux-fsdevel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 0F90EC05AC for ; Fri, 24 Jul 2015 10:07:53 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id D4FA520632 for ; Fri, 24 Jul 2015 10:07:50 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 7CDB820631 for ; Fri, 24 Jul 2015 10:07:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753389AbbGXKGx (ORCPT ); Fri, 24 Jul 2015 06:06:53 -0400 Received: from mailout3.w1.samsung.com ([210.118.77.13]:38694 "EHLO mailout3.w1.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752563AbbGXKFh (ORCPT ); Fri, 24 Jul 2015 06:05:37 -0400 Received: from eucpsbgm2.samsung.com (unknown [203.254.199.245]) by mailout3.w1.samsung.com (Oracle Communications Messaging Server 7.0.5.31.0 64bit (built May 5 2014)) with ESMTP id <0NRZ007E9LDAYW90@mailout3.w1.samsung.com>; Fri, 24 Jul 2015 11:05:34 +0100 (BST) X-AuditID: cbfec7f5-f794b6d000001495-5a-55b20dee1294 Received: from eusync2.samsung.com ( [203.254.199.212]) by eucpsbgm2.samsung.com (EUCPMTA) with SMTP id AE.2D.05269.EED02B55; Fri, 24 Jul 2015 11:05:34 +0100 (BST) Received: from amdc2143.DIGITAL.local ([106.120.53.33]) by eusync2.samsung.com (Oracle Communications Messaging Server 7.0.5.31.0 64bit (built May 5 2014)) with ESMTPA id <0NRZ00EPNLC23A60@eusync2.samsung.com>; Fri, 24 Jul 2015 11:05:33 +0100 (BST) From: Lukasz Pawelczyk To: "Eric W. Biederman" , "Serge E. Hallyn" , Al Viro , Alexey Dobriyan , Andrew Morton , Andy Lutomirski , Arnd Bergmann , Casey Schaufler , David Howells , Eric Dumazet , Eric Paris , Fabian Frederick , Greg KH , James Morris , Jiri Slaby , Joe Perches , John Johansen , Jonathan Corbet , Kees Cook , Lukasz Pawelczyk , Mauro Carvalho Chehab , NeilBrown , Oleg Nesterov , Paul Moore , Stephen Smalley , Tetsuo Handa , Zefan Li , linux-doc@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, selinux@tycho.nsa.gov Cc: havner@gmail.com Subject: [PATCH v3 09/11] smack: namespace groundwork Date: Fri, 24 Jul 2015 12:04:43 +0200 Message-id: <1437732285-11524-10-git-send-email-l.pawelczyk@samsung.com> X-Mailer: git-send-email 2.4.3 In-reply-to: <1437732285-11524-1-git-send-email-l.pawelczyk@samsung.com> References: <1437732285-11524-1-git-send-email-l.pawelczyk@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAAzWRbUhTYRiGfXfenXO2Gh2m5cEoatAfKbMP4aGyoqhe+lERCbU/NfOwpE1l c6MZ4TTNmtnHimpqZNgI5sdK0lJbxopp+TFDLUhsOTUzdZoZkWvVlvTvuu/rhufHw1LyNhzH pmdkC7oMlUZBS3H779beNQFZXUqi/zUFN/P2QrmzmoaQ1cPAh4Y5GoafFyEI5Acx/GkoYGDE 42fgwmAnBZbgTxrOVjpp+DO+Gi4NHYQy3xCGttlCGq4/mBZBR7EWOqx3RXD33D0MT12vMPQ0 ldMwfdFPg/V8CQNVNWYxVH06Ba47Zgz1LYUI+q03MZQVBMTwormSgmvDbgze7i4GvKFW8faV xDcRwiQ4Z0Wk1FxCkzLzG0waSwcYUlFnIG+bj5CCl5Ni0ljjEJG2W0FMWm5XM6Ty0jUx+Try HpNHl8PTzu9KUuP6jA4sVUq3pAmadKOgW7v1mPTESOd9nFXkRad+ND/GZtRrRxbEsjy3kR8v MViQJIxL+O4PTtqCpKycsyO+2d4qng95Iv79x69MZEVzifyPbhcVETFcvYS/8qAGRQTFxfC/ AvOjaC6Jz/c+EkUYc6v4al89HWEZR/hh3xgzf2453+WcxRGWhPv2npl/vZzbwxeXljNXkKwC RTnQYsFwPEufqtauT9CrtHpDhjrheKa2Ds3/9/sTZPdsciOORYqFsufWhylyscqoN2ndiGcp RYwsyhOuZGkqU46gyzyqM2gEvRstZbEiVmZrmjok59SqbOGkIGQJuv9WxErizGjFMmPfjMlc OpRfuy90pz/2zO7c6aRnsznMudAO643kDbum/Idt3cmmJd8Cg9Q+7x6f0mKM65joXfR4271c U7E6W3naud8WvXm0RJOr7VMssEnexa+vdYxET40dG48ayPY4KvSThZaz1ekvU1Pqk9RD17+M S1z2zKujq4I7H5J8BdafUK2Lp3R61V/rA6Vh2wIAAA== Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org X-Spam-Status: No, score=-8.1 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This commit introduces several changes to Smack to prepare it for namespace implementation. All the changes are related to namespaces. Overview of the changes: - Adds required data structures for mapped labels and functions to operate on them. - Implements the proc interface /proc/$PID/attr/label_map that can be used for remapping of labels for a specific namespace. Also for checking the map. - Modifies handling of special built-in labels. Detects them on import and assigns the same char* pointer regardless whether it's used in a normal or a mapped label. This way we can always compare them by == instead of strcmp(). - Adds User namespace hooks implementation This patch introduces both internal and user-space visible APIs to handle namespaced labels and Smack namespaces but the behaviour of Smack should not be changed. The APIs are there, but they have no impact yet. Signed-off-by: Lukasz Pawelczyk Reviewed-by: Casey Schaufler --- security/smack/Kconfig | 10 ++ security/smack/Makefile | 1 + security/smack/smack.h | 45 ++++- security/smack/smack_access.c | 47 ++++- security/smack/smack_lsm.c | 134 +++++++++++++- security/smack/smack_ns.c | 404 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 626 insertions(+), 15 deletions(-) create mode 100644 security/smack/smack_ns.c diff --git a/security/smack/Kconfig b/security/smack/Kconfig index 271adae..b19a7fb 100644 --- a/security/smack/Kconfig +++ b/security/smack/Kconfig @@ -40,3 +40,13 @@ config SECURITY_SMACK_NETFILTER This enables security marking of network packets using Smack labels. If you are unsure how to answer this question, answer N. + +config SECURITY_SMACK_NS + bool "Smack namespace" + depends on SECURITY_SMACK + depends on USER_NS + help + This enables Smack namespace that makes it possible to map + specific labels within user namespace (analogously to mapping + UIDs) and to gain MAC capabilities over them. + If you are unsure how to answer this question, answer N. diff --git a/security/smack/Makefile b/security/smack/Makefile index ee2ebd5..5faebd7 100644 --- a/security/smack/Makefile +++ b/security/smack/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_SECURITY_SMACK) := smack.o smack-y := smack_lsm.o smack_access.o smackfs.o smack-$(CONFIG_SECURITY_SMACK_NETFILTER) += smack_netfilter.o +smack-$(CONFIG_SECURITY_SMACK_NS) += smack_ns.o diff --git a/security/smack/smack.h b/security/smack/smack.h index 014a7d1..e63cf53 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -21,6 +21,7 @@ #include #include #include +#include /* * Smack labels were limited to 23 characters for a long time. @@ -59,8 +60,36 @@ struct smack_known { struct netlbl_lsm_secattr smk_netlabel; /* on wire labels */ struct list_head smk_rules; /* access rules */ struct mutex smk_rules_lock; /* lock for rules */ +#ifdef CONFIG_SECURITY_SMACK_NS + struct list_head smk_mapped; /* namespaced labels */ + struct mutex smk_mapped_lock; +#endif /* CONFIG_SECURITY_SMACK_NS */ }; +#ifdef CONFIG_SECURITY_SMACK_NS + +/* + * User namespace security pointer content. + */ +struct smack_ns { + struct list_head smk_mapped; /* namespaced labels */ + struct mutex smk_mapped_lock; +}; + +/* + * A single entry for a namespaced/mapped label. + */ +struct smack_known_ns { + struct list_head smk_list_known; + struct list_head smk_list_ns; + struct user_namespace *smk_ns; + char *smk_mapped; + struct smack_known *smk_unmapped; + bool smk_allocated; +}; + +#endif /* CONFIG_SECURITY_SMACK_NS */ + /* * Maximum number of bytes for the levels in a CIPSO IP option. * Why 23? CIPSO is constrained to 30, so a 32 byte buffer is @@ -268,7 +297,7 @@ int smk_tskacc(struct task_struct *, struct smack_known *, u32, struct smk_audit_info *); int smk_curacc(struct smack_known *, u32, struct smk_audit_info *); struct smack_known *smack_from_secid(const u32); -char *smk_parse_smack(const char *string, int len); +char *smk_parse_smack(const char *string, int len, bool *allocated); int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int); struct smack_known *smk_import_entry(const char *, int); void smk_insert_entry(struct smack_known *skp); @@ -283,6 +312,20 @@ char *smk_find_label_name(struct smack_known *skp); struct smack_known *smk_get_label(const char *string, int len, bool import); /* + * These functions are in smack_ns.c + */ +#ifdef CONFIG_SECURITY_SMACK_NS +struct user_namespace *smk_find_mapped_ns(struct user_namespace *ns); +struct smack_known_ns *smk_find_mapped(struct smack_known *skp, + struct user_namespace *ns); +struct smack_known *smk_find_unmapped(const char *string, int len, + struct user_namespace *ns); +extern const struct seq_operations proc_label_map_seq_operations; +ssize_t proc_label_map_write(struct task_struct *p, const struct cred *f_cred, + void *value, size_t size); +#endif /* CONFIG_SECURITY_SMACK_NS */ + +/* * Shared data. */ extern int smack_enabled; diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 5b13d0c..f71ace6 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -452,13 +452,16 @@ struct smack_known *smk_find_entry(const char *string) /** * smk_parse_smack - parse smack label from a text string * @string: a text string that might contain a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * @len: the maximum size, or zero if it is NULL terminated + * @allocated: (out) indicates whether the return string has been + * allocated and has to be freed with kfree() later + * (built-in labels returned are not allocated) * * Returns a pointer to the clean label or an error code. */ -char *smk_parse_smack(const char *string, int len) +char *smk_parse_smack(const char *string, int len, bool *allocated) { - char *smack; + char *smack = NULL; int i; if (len <= 0) @@ -480,11 +483,33 @@ char *smk_parse_smack(const char *string, int len) if (i == 0 || i >= SMK_LONGLABEL) return ERR_PTR(-EINVAL); + /* + * Look for special labels. This way we guarantee that we can compare + * special labels in mapped entries by ==, without strcmp(). + */ + if (len == 1 && !strcmp(string, smack_known_huh.smk_known)) + smack = smack_known_huh.smk_known; + else if (len == 1 && !strcmp(string, smack_known_hat.smk_known)) + smack = smack_known_hat.smk_known; + else if (len == 1 && !strcmp(string, smack_known_star.smk_known)) + smack = smack_known_star.smk_known; + else if (len == 1 && !strcmp(string, smack_known_floor.smk_known)) + smack = smack_known_floor.smk_known; + else if (len == 1 && !strcmp(string, smack_known_web.smk_known)) + smack = smack_known_web.smk_known; + + if (smack) { + *allocated = false; + + return smack; + } + smack = kzalloc(i + 1, GFP_KERNEL); if (smack == NULL) return ERR_PTR(-ENOMEM); strncpy(smack, string, i); + *allocated = true; return smack; } @@ -540,8 +565,9 @@ struct smack_known *smk_import_entry(const char *string, int len) char *smack; int slen; int rc; + bool allocated; - smack = smk_parse_smack(string, len); + smack = smk_parse_smack(string, len, &allocated); if (IS_ERR(smack)) return ERR_CAST(smack); @@ -577,6 +603,10 @@ struct smack_known *smk_import_entry(const char *string, int len) if (rc >= 0) { INIT_LIST_HEAD(&skp->smk_rules); mutex_init(&skp->smk_rules_lock); +#ifdef CONFIG_SECURITY_SMACK_NS + INIT_LIST_HEAD(&skp->smk_mapped); + mutex_init(&skp->smk_mapped_lock); +#endif /* CONFIG_SECURITY_SMACK_NS */ /* * Make sure that the entry is actually * filled before putting it on the list. @@ -590,7 +620,8 @@ struct smack_known *smk_import_entry(const char *string, int len) kfree(skp); skp = ERR_PTR(rc); freeout: - kfree(smack); + if (allocated) + kfree(smack); unlockout: mutex_unlock(&smack_known_lock); @@ -742,17 +773,19 @@ char *smk_find_label_name(struct smack_known *skp) struct smack_known *smk_get_label(const char *string, int len, bool import) { struct smack_known *skp; + bool allocated; char *cp; if (import) { skp = smk_import_entry(string, len); } else { - cp = smk_parse_smack(string, len); + cp = smk_parse_smack(string, len, &allocated); if (IS_ERR(cp)) return ERR_CAST(cp); skp = smk_find_entry(cp); - kfree(cp); + if (allocated) + kfree(cp); } return skp; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index d1beff5..8894115 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -42,6 +42,7 @@ #include #include #include +#include #include "smack.h" #define TRANS_TRUE "TRUE" @@ -3397,6 +3398,27 @@ unlockandout: } /** + * smack_getprocattr_seq - Smack process attribute access through seq + * @p: the object task + * @name: the name of the attribute in /proc/.../attr/ + * @ops: out, seq_operations to handle @name + * + * Returns 0 if @name is to be handled by seq, error otherwise. + */ +int smack_getprocattr_seq(struct task_struct *p, const char *name, + const struct seq_operations **ops) +{ +#ifdef CONFIG_SECURITY_SMACK_NS + if (strcmp(name, "label_map") == 0) { + *ops = &proc_label_map_seq_operations; + return 0; + } +#endif + + return -EOPNOTSUPP; +} + +/** * smack_getprocattr - Smack process attribute access * @p: the object task * @name: the name of the attribute in /proc/.../attr @@ -3428,9 +3450,8 @@ static int smack_getprocattr(struct task_struct *p, char *name, char **value) } /** - * smack_setprocattr - Smack process attribute setting + * proc_current_write - Smack "current" process attribute setting * @p: the object task - * @name: the name of the attribute in /proc/.../attr * @value: the value to set * @size: the size of the value * @@ -3439,8 +3460,7 @@ static int smack_getprocattr(struct task_struct *p, char *name, char **value) * * Returns the length of the smack label or an error code */ -static int smack_setprocattr(struct task_struct *p, const struct cred *f_cred, - char *name, void *value, size_t size) +static int proc_current_write(struct task_struct *p, void *value, size_t size) { struct task_smack *tsp; struct cred *new; @@ -3459,9 +3479,6 @@ static int smack_setprocattr(struct task_struct *p, const struct cred *f_cred, if (value == NULL || size == 0 || size >= SMK_LONGLABEL) return -EINVAL; - if (strcmp(name, "current") != 0) - return -EINVAL; - skp = smk_get_label(value, size, true); if (IS_ERR(skp)) return PTR_ERR(skp); @@ -3484,6 +3501,33 @@ static int smack_setprocattr(struct task_struct *p, const struct cred *f_cred, } /** + * smack_setprocattr - Smack process attribute setting + * @p: the object task + * @cred: the credentials of the file's opener + * @name: the name of the attribute in /proc/.../attr + * @value: the value to set + * @size: the size of the value + * + * Sets the proc attribute + * + * Returns the length of the written data or an error code + */ +static int smack_setprocattr(struct task_struct *p, const struct cred *f_cred, + char *name, void *value, size_t size) +{ +#ifdef CONFIG_SECURITY_SMACK_NS + if (strcmp(name, "label_map") == 0) + return proc_label_map_write(p, f_cred, value, size); +#endif + + if (strcmp(name, "current") == 0) + return proc_current_write(p, value, size); + + return -EINVAL; + +} + +/** * smack_unix_stream_connect - Smack access on UDS * @sock: one sock * @other: the other sock @@ -4324,6 +4368,53 @@ static void smack_audit_rule_free(void *vrule) #endif /* CONFIG_AUDIT */ +#ifdef CONFIG_SECURITY_SMACK_NS + +static inline int smack_userns_create(struct user_namespace *ns) +{ + struct smack_ns *snsp; + + snsp = kzalloc(sizeof(*snsp), GFP_KERNEL); + if (snsp == NULL) + return -ENOMEM; + + INIT_LIST_HEAD(&snsp->smk_mapped); + mutex_init(&snsp->smk_mapped_lock); + + ns->security = snsp; + return 0; +} + +static inline void smack_userns_free(struct user_namespace *ns) +{ + struct smack_ns *snsp = ns->security; + struct smack_known *skp; + struct smack_known_ns *sknp, *n; + + list_for_each_entry_safe(sknp, n, &snsp->smk_mapped, smk_list_ns) { + skp = sknp->smk_unmapped; + + mutex_lock(&skp->smk_mapped_lock); + list_del_rcu(&sknp->smk_list_known); + if (sknp->smk_allocated) + kfree(sknp->smk_mapped); + kfree(sknp); + mutex_unlock(&skp->smk_mapped_lock); + + list_del(&sknp->smk_list_ns); + } + + kfree(snsp); +} + +static inline int smack_userns_setns(struct nsproxy *nsproxy, + struct user_namespace *ns) +{ + return 0; +} + +#endif /* CONFIG_SECURITY_SMACK_NS */ + /** * smack_ismaclabel - check if xattr @name references a smack MAC label * @name: Full xattr name to check. @@ -4500,6 +4591,7 @@ struct security_hook_list smack_hooks[] = { LSM_HOOK_INIT(d_instantiate, smack_d_instantiate), + LSM_HOOK_INIT(getprocattr_seq, smack_getprocattr_seq), LSM_HOOK_INIT(getprocattr, smack_getprocattr), LSM_HOOK_INIT(setprocattr, smack_setprocattr), @@ -4537,6 +4629,13 @@ struct security_hook_list smack_hooks[] = { LSM_HOOK_INIT(audit_rule_free, smack_audit_rule_free), #endif /* CONFIG_AUDIT */ + /* Namespace hooks */ +#ifdef CONFIG_SECURITY_SMACK_NS + LSM_HOOK_INIT(userns_create, smack_userns_create), + LSM_HOOK_INIT(userns_free, smack_userns_free), + LSM_HOOK_INIT(userns_setns, smack_userns_setns), +#endif /* CONFIG_SECURITY_SMACK_NS */ + LSM_HOOK_INIT(ismaclabel, smack_ismaclabel), LSM_HOOK_INIT(secid_to_secctx, smack_secid_to_secctx), LSM_HOOK_INIT(secctx_to_secid, smack_secctx_to_secid), @@ -4549,6 +4648,27 @@ struct security_hook_list smack_hooks[] = { static __init void init_smack_known_list(void) { +#ifdef CONFIG_SECURITY_SMACK_NS + /* + * Initialize mapped list locks + */ + mutex_init(&smack_known_huh.smk_mapped_lock); + mutex_init(&smack_known_hat.smk_mapped_lock); + mutex_init(&smack_known_floor.smk_mapped_lock); + mutex_init(&smack_known_star.smk_mapped_lock); + mutex_init(&smack_known_invalid.smk_mapped_lock); + mutex_init(&smack_known_web.smk_mapped_lock); + /* + * Initialize mapped lists + */ + INIT_LIST_HEAD(&smack_known_huh.smk_mapped); + INIT_LIST_HEAD(&smack_known_hat.smk_mapped); + INIT_LIST_HEAD(&smack_known_star.smk_mapped); + INIT_LIST_HEAD(&smack_known_floor.smk_mapped); + INIT_LIST_HEAD(&smack_known_invalid.smk_mapped); + INIT_LIST_HEAD(&smack_known_web.smk_mapped); +#endif /* CONFIG_SECURITY_SMACK_NS */ + /* * Initialize rule list locks */ diff --git a/security/smack/smack_ns.c b/security/smack/smack_ns.c new file mode 100644 index 0000000..49223c4 --- /dev/null +++ b/security/smack/smack_ns.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2014 Samsung Electronics. + * + * Smack namespaces + * + * Author(s): + * Lukasz Pawelczyk + * + * This program is free software, you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include "smack.h" + +/** + * smk_find_mapped_ns - Finds a first namespace from this one through + * its parrents that has a map. This map is the effective map in this + * namespace. + * @ns: a user namespace for which we search for a mapped ns + * + * Returns a namespace that has a non-NULL map, or NULL if there is + * no mapped namespace. + * + * Can be effectively used to answer a question: "is there a Smack + * map for this namespace?" + */ +struct user_namespace *smk_find_mapped_ns(struct user_namespace *ns) +{ + struct user_namespace *user_ns = ns; + + do { + struct smack_ns *sns = user_ns->security; + + if (sns && !list_empty(&sns->smk_mapped)) + break; + + user_ns = user_ns->parent; + } while (user_ns); + + return user_ns; +} + +/** + * __smk_find_mapped - an internal version of smk_find_mapped + * that doesn't use smk_find_mapped_ns, but + * operates directly on the passed one. + */ +static struct smack_known_ns *__smk_find_mapped(struct smack_known *skp, + struct user_namespace *ns) +{ + struct smack_known_ns *sknp; + + if (ns == NULL) + return NULL; + + list_for_each_entry_rcu(sknp, &skp->smk_mapped, smk_list_known) + if (sknp->smk_ns == ns) + return sknp; + + return NULL; +} + +/** + * smk_find_mapped - Finds a mapped label on the smack_known's mapped list + * @skp: a label which mapped label we look for + * @ns: a user namespace the label we search for is assigned to + * + * Returns a pointer to the mapped label if one exists that is + * assigned to the specified user namespace or NULL if not found. + */ +struct smack_known_ns *smk_find_mapped(struct smack_known *skp, + struct user_namespace *ns) +{ + struct user_namespace *user_ns = smk_find_mapped_ns(ns); + + return __smk_find_mapped(skp, user_ns); +} + +/** + * __smk_find_unmapped - an internal version of smk_find_unmapped + * that doesn't use smk_find_mapped_ns, but + * operates directly on the passed one. + */ +static struct smack_known *__smk_find_unmapped(const char *string, int len, + struct user_namespace *ns) +{ + struct smack_ns *snsp; + struct smack_known *skp = NULL; + struct smack_known_ns *sknp; + char *smack; + bool allocated = false; + + if (ns == NULL) + return NULL; + + snsp = ns->security; + + smack = smk_parse_smack(string, len, &allocated); + if (IS_ERR(smack)) + return ERR_CAST(smack); + + list_for_each_entry_rcu(sknp, &snsp->smk_mapped, smk_list_ns) { + if (strcmp(smack, sknp->smk_mapped) == 0) { + skp = sknp->smk_unmapped; + break; + } + } + + if (allocated) + kfree(smack); + return skp; +} + +/** + * smk_find_unmapped - Finds an original label by a mapped label string + * and the namespace it could be mapped in + * @string: a name of a mapped label we look for + * @len: the string size, or zero if it is NULL terminated. + * @ns: a namespace the looked for label should be mapped in + * + * Returns a smack_known label that is mapped as 'string' in 'ns', + * NULL if not found or an error code. + */ +struct smack_known *smk_find_unmapped(const char *string, int len, + struct user_namespace *ns) +{ + struct user_namespace *user_ns = smk_find_mapped_ns(ns); + + return __smk_find_unmapped(string, len, user_ns); +} + +/** + * smk_import_mapped - Imports a mapped label effectively creating a mapping. + * @skp: a label we map + * @ns: a user namespace this label will be mapped in + * @string: a text string of the mapped label + * @len: the maximum size, or zero if it is NULL terminanted + * + * Returns a pointer to the mapped label entry or an error code. + * + * The mapped label will be added to 2 lists: + * - a list of mapped labels of skp + * - a list of labels mapped in ns + */ +static struct smack_known_ns *smk_import_mapped(struct smack_known *skp, + struct user_namespace *ns, + const char *string, int len) +{ + struct smack_ns *snsp = ns->security; + struct smack_known_ns *sknp; + char *mapped; + bool allocated; + + /* Mapping init_user_ns is against the design and pointless */ + if (ns == &init_user_ns) + return ERR_PTR(-EBADR); + + mapped = smk_parse_smack(string, len, &allocated); + if (IS_ERR(mapped)) + return ERR_CAST(mapped); + + mutex_lock(&skp->smk_mapped_lock); + + /* + * Don't allow one<->many mappings in namespace, rename. + * This code won't get triggered for now as trying to assign + * a duplicate is forbidden in proc_label_map_write(). + * Leaving this as this function might be also used elsewhere. + */ + sknp = smk_find_mapped(skp, ns); + if (sknp != NULL) { + if (sknp->smk_allocated) + kfree(sknp->smk_mapped); + sknp->smk_mapped = mapped; + sknp->smk_allocated = allocated; + goto unlockout; + } + + sknp = kzalloc(sizeof(*sknp), GFP_KERNEL); + if (sknp == NULL) { + sknp = ERR_PTR(-ENOMEM); + if (allocated) + kfree(mapped); + goto unlockout; + } + + sknp->smk_ns = ns; + sknp->smk_mapped = mapped; + sknp->smk_allocated = allocated; + sknp->smk_unmapped = skp; + list_add_rcu(&sknp->smk_list_known, &skp->smk_mapped); + + mutex_lock(&snsp->smk_mapped_lock); + list_add_rcu(&sknp->smk_list_ns, &snsp->smk_mapped); + mutex_unlock(&snsp->smk_mapped_lock); + +unlockout: + mutex_unlock(&skp->smk_mapped_lock); + + return sknp; +} + +static void *proc_label_map_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct smack_known *skp; + struct task_struct *task = seq->private; + struct user_namespace *ns = ns_of_task_struct(task); + loff_t counter = *pos; + + rcu_read_lock(); + list_for_each_entry_rcu(skp, &smack_known_list, list) + if (smk_find_mapped(skp, ns) && counter-- == 0) + return skp; + + return NULL; +} + +static void proc_label_map_seq_stop(struct seq_file *seq, void *v) +{ + rcu_read_unlock(); +} + +static void *proc_label_map_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct smack_known *skp = v; + struct task_struct *task = seq->private; + struct user_namespace *ns = ns_of_task_struct(task); + + list_for_each_entry_continue_rcu(skp, &smack_known_list, list) { + if (smk_find_mapped(skp, ns)) { + (*pos)++; + return skp; + } + } + + return NULL; +} + +static int proc_label_map_seq_show(struct seq_file *seq, void *v) +{ + struct smack_known *skp = v; + struct task_struct *task = seq->private; + struct user_namespace *ns = ns_of_task_struct(task); + struct smack_known_ns *sknp; + + sknp = smk_find_mapped(skp, ns); + if (sknp) + seq_printf(seq, "%s -> %s\n", skp->smk_known, sknp->smk_mapped); + + return 0; +} + +const struct seq_operations proc_label_map_seq_operations = { + .start = proc_label_map_seq_start, + .stop = proc_label_map_seq_stop, + .next = proc_label_map_seq_next, + .show = proc_label_map_seq_show, +}; + +static DEFINE_MUTEX(smk_map_mutex); + +static bool mapping_permitted(const struct cred *f_cred, + struct user_namespace *user_ns) +{ + /* + * Do not allow mapping own label. This is in contrast to user + * namespace where you can always map your own UID. In Smack having + * administrative privileges over your own label (which Smack + * namespace would effectively give you) is not equivalent to user + * namespace. E.g. things like setting exec/transmute labels that + * otherwise would be denied. Hence no own_label param here. + */ + + /* + * Adjusting namespace settings requires capabilities on the target. + */ + if (security_capable(f_cred, user_ns, CAP_MAC_ADMIN) != 0) + return false; + + /* + * And it requires capabilities in the parent. + * + * If the Smack namespace was properly hierarchical the user_ns to + * check against could be 'user_ns->parent'. Right now because of + * security concerns only privileged initial namespace is allowed + * to fill the map. For a hierarchical namespaces one would + * implement mapping (in the child namespaces) of only mapped labels + * (in parent namespace) and change '&init_user_ns' to + * 'user_ns->parent'. This will be added in the future. + */ + if (smack_ns_privileged(&init_user_ns, CAP_MAC_ADMIN) && + security_capable(f_cred, &init_user_ns, CAP_MAC_ADMIN) == 0) + return true; + + return false; +} + +ssize_t proc_label_map_write(struct task_struct *p, const struct cred *f_cred, + void *value, size_t size) +{ + struct user_namespace *ns = ns_of_task_struct(p); + struct user_namespace *cur_ns = ns_of_current(); + struct smack_known *skp; + struct smack_known_ns *sknp; + char *pos, *next_line, *tok[2]; + ssize_t ret; + int i; + + /* Mapping labels for the init ns makes no sense */ + if (ns == &init_user_ns) + return -EBADR; + + if (cur_ns != ns->parent) + return -EPERM; + + if (!mapping_permitted(f_cred, ns)) + return -EPERM; + + if (value == NULL || size == 0 || size >= PAGE_SIZE) + return -EINVAL; + + mutex_lock(&smk_map_mutex); + + /* Parse the user data */ + pos = value; + pos[size] = '\0'; + + for (; pos; pos = next_line) { + ret = -EINVAL; + + /* Find the end of line and ensure I don't look past it */ + next_line = strchr(pos, '\n'); + if (next_line) { + *next_line = '\0'; + next_line++; + if (*next_line == '\0') + next_line = NULL; + } + + /* Find tokens in line */ + for (i = 0; i < 2; ++i) { + while (isspace(*pos)) + *(pos++) = '\0'; + + /* unexpected end of file */ + if (*pos == '\0') + goto out; + + tok[i] = pos; + + /* find the end of the token */ + while (*pos != '\0' && !isspace(*pos)) + ++pos; + } + + /* NUL terminate the last token if not EOL */ + while (isspace(*pos)) + *(pos++) = '\0'; + + /* there should not be any trailing data */ + if (*pos != '\0') + goto out; + + ret = -EEXIST; + + /* do not allow to map 2 different labels to one name */ + skp = __smk_find_unmapped(tok[1], 0, ns); + if (IS_ERR(skp)) { + ret = PTR_ERR(skp); + goto out; + } + if (skp != NULL) + goto out; + + skp = smk_import_entry(tok[0], 0); + if (IS_ERR(skp)) { + ret = PTR_ERR(skp); + goto out; + } + + /* do not allow remapping */ + if (__smk_find_mapped(skp, ns)) + goto out; + + sknp = smk_import_mapped(skp, ns, tok[1], 0); + if (IS_ERR(sknp)) { + ret = PTR_ERR(sknp); + goto out; + } + } + + ret = size; + +out: + mutex_unlock(&smk_map_mutex); + + return ret; +}