From patchwork Sat Nov 27 16:45:47 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Bottomley X-Patchwork-Id: 12642433 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 6E6CBC433EF for ; Sat, 27 Nov 2021 16:48:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237232AbhK0QwD (ORCPT ); Sat, 27 Nov 2021 11:52:03 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38594 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231158AbhK0QuD (ORCPT ); Sat, 27 Nov 2021 11:50:03 -0500 Received: from bedivere.hansenpartnership.com (bedivere.hansenpartnership.com [IPv6:2607:fcd0:100:8a00::2]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 85175C06173E for ; Sat, 27 Nov 2021 08:46:48 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=hansenpartnership.com; s=20151216; t=1638031608; bh=5ah6AxdfuuD0MZT2VqRIK0hSzcNTKo4O78noGVJ2uJQ=; h=From:To:Subject:Date:Message-Id:In-Reply-To:References:From; b=njGCo5Tdm9jijSkAdLP70yvLqq5iEYYKkXsiTQiojxEQawWokxQ16RbMYS6hBSoZx paiIamBvzue628J0V/8o/tIIhVeXRXDX0h5QfqdAcNmBWJhEAr/oMrAMuHJPTGY5as n4f5SHQIRADQ3QTiveSF7alzcC+Mq12w+xcxGvCc= Received: from localhost (localhost [127.0.0.1]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id 5B0BE12809DF; Sat, 27 Nov 2021 11:46:48 -0500 (EST) Received: from bedivere.hansenpartnership.com ([127.0.0.1]) by localhost (bedivere.hansenpartnership.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id M61YihtRPkeo; Sat, 27 Nov 2021 11:46:48 -0500 (EST) Received: from rainbow.int.hansenpartnership.com (unknown [153.66.140.204]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id 0F5F71280693; Sat, 27 Nov 2021 11:46:46 -0500 (EST) From: James Bottomley To: linux-integrity@vger.kernel.org Cc: containers@lists.linux.dev, Mimi Zohar , Dmitry Kasatkin , Stefan Berger , "Eric W . Biederman" , krzysztof.struczynski@huawei.com, Roberto Sassu , "Serge E . Hallyn" , Michael Peters , Luke Hinds , Lily Sturmann , Patrick Uiterwijk , Christian Brauner Subject: [RFC 1/3] userns: add uuid field Date: Sat, 27 Nov 2021 16:45:47 +0000 Message-Id: <20211127164549.2571457-2-James.Bottomley@HansenPartnership.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20211127164549.2571457-1-James.Bottomley@HansenPartnership.com> References: <20211127164549.2571457-1-James.Bottomley@HansenPartnership.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org As a precursor to namespacing IMA a way of uniquely identifying the namespace to appear in the IMA log is needed. This log may be transported away from the running system and may be analyzed even after the system has been rebooted. Thus we need a way of identifying namespaces in the log which is unique. UUID, being designed probabilistically never to repeat, fits this bill so add it to the user_namespace which we'll also use for namespacing IMA. uuid_gen() is used to create each uuid uniquely. It feeds off the pseudo random number generator, but this should be as unique as we need for probabilistic non repeats without depleting the entropy pool. Since there is no random initializer for a uuid, this is done in user_namespaces_init(). This should be safe because IMA is a late initcall. This patch contains no exposure mechanisms, and the subsequent patches only add uuid entries in the IMA log. However, it is not unlikely that eventually orchestration systems will want to know what the uuid is (to tie their container ID to the one in the IMA log), so additional patches exposing this via NSIO and /proc//ns could be added. For checkpoint/restore, the uuid should not be a property that transports because otherwise we'll have to have a set mechanism with a uniqueness check. Signed-off-by: James Bottomley --- include/linux/user_namespace.h | 2 ++ kernel/user.c | 1 + kernel/user_namespace.c | 3 +++ 3 files changed, 6 insertions(+) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 33a4240e6a6f..d155788abdc1 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -10,6 +10,7 @@ #include #include #include +#include #define UID_GID_MAP_MAX_BASE_EXTENTS 5 #define UID_GID_MAP_MAX_EXTENTS 340 @@ -99,6 +100,7 @@ struct user_namespace { #endif struct ucounts *ucounts; long ucount_max[UCOUNT_COUNTS]; + uuid_t uuid; } __randomize_layout; struct ucounts { diff --git a/kernel/user.c b/kernel/user.c index e2cf8c22b539..bf9ae1d0b670 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -67,6 +67,7 @@ struct user_namespace init_user_ns = { .keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list), .keyring_sem = __RWSEM_INITIALIZER(init_user_ns.keyring_sem), #endif + /* .uuid is initialized in user_namespaces_init() */ }; EXPORT_SYMBOL_GPL(init_user_ns); diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 6b2e3ca7ee99..8ce57c16ddd3 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -141,6 +141,8 @@ int create_user_ns(struct cred *new) if (!setup_userns_sysctls(ns)) goto fail_keyring; + uuid_gen(&ns->uuid); + set_cred_user_ns(new, ns); return 0; fail_keyring: @@ -1386,6 +1388,7 @@ const struct proc_ns_operations userns_operations = { static __init int user_namespaces_init(void) { user_ns_cachep = KMEM_CACHE(user_namespace, SLAB_PANIC | SLAB_ACCOUNT); + uuid_gen(&init_user_ns.uuid); return 0; } subsys_initcall(user_namespaces_init); From patchwork Sat Nov 27 16:45:48 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Bottomley X-Patchwork-Id: 12642435 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 993A4C433EF for ; Sat, 27 Nov 2021 16:49:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238160AbhK0Qwc (ORCPT ); Sat, 27 Nov 2021 11:52:32 -0500 Received: from bedivere.hansenpartnership.com ([96.44.175.130]:36144 "EHLO bedivere.hansenpartnership.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233676AbhK0Quc (ORCPT ); Sat, 27 Nov 2021 11:50:32 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=hansenpartnership.com; s=20151216; t=1638031637; bh=BnMaFRu2u9lYF5O/Dz40XEb/zenFZ/1AZ2LtdiU9XmI=; h=From:To:Subject:Date:Message-Id:In-Reply-To:References:From; b=JMhhAOi63QwMeNZe+ZueSZIv4gf1F6ipxJ04Oqvg8vDo4pzFhXUaFljLPAJPtQJ3x XU+J8lI0RVjVAPcKyd/LauYOanV72KgtM97eNybVwvtx/2kSbaO3edf0ln7/00Kkkg 608WNGvnETk6ShTe2ApJvh9E0gfgkaN1N6Z+RMQE= Received: from localhost (localhost [127.0.0.1]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id E766312809ED; Sat, 27 Nov 2021 11:47:17 -0500 (EST) Received: from bedivere.hansenpartnership.com ([127.0.0.1]) by localhost (bedivere.hansenpartnership.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id bprGBakR3mfb; Sat, 27 Nov 2021 11:47:17 -0500 (EST) Received: from rainbow.int.hansenpartnership.com (unknown [153.66.140.204]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id A29E31280693; Sat, 27 Nov 2021 11:47:16 -0500 (EST) From: James Bottomley To: linux-integrity@vger.kernel.org Cc: containers@lists.linux.dev, Mimi Zohar , Dmitry Kasatkin , Stefan Berger , "Eric W . Biederman" , krzysztof.struczynski@huawei.com, Roberto Sassu , "Serge E . Hallyn" , Michael Peters , Luke Hinds , Lily Sturmann , Patrick Uiterwijk , Christian Brauner Subject: [RFC 2/3] ima: Namespace IMA Date: Sat, 27 Nov 2021 16:45:48 +0000 Message-Id: <20211127164549.2571457-3-James.Bottomley@HansenPartnership.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20211127164549.2571457-1-James.Bottomley@HansenPartnership.com> References: <20211127164549.2571457-1-James.Bottomley@HansenPartnership.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Use the user namespace as the basis for namespacing IMA. The implementation is very simple: add a new template called 'ima-ns' which exports the uuid of the user namespace into the IMA log. This can be used to uniquely separate every container in the IMA log. Note that the admin of the user namespace still cannot read back the IMA log because the IMA securityfs entries are not yet namespace aware. However, root in the init_user_ns can read the log and see the containers. You can get the uuid of init_user_ns from the boot_aggregate entry which is always the first one recorded in the log. Any execution with a different uuid is in a new IMA namespace. A sample of the log showing entry into a container is: 10 7766c926c9db8dd4c923f96be5635b04593029c1 ima-ns sha256:0000000000000000000000000000000000000000000000000000000000000000 boot_aggregate 6582e360-1354-42b9-a6ef-ee1993d982da [...] 10 e0355132472d4d0ae1cc044412b4033bd5e1a48a ima-ns sha256:353e4d6b807056757fb5df31bafe7df80605bec20b445d5e9afd949ca4147d49 /usr/bin/unshare 6582e360-1354-42b9-a6ef-ee1993d982da 10 f257f5a12fd6e28d32b367a2e453c3badd0e8774 ima-ns sha256:2a7c66fc7e19acc100ee2b777b71179043fade8b81968828522cf31e6a96eaa7 /usr/bin/bash e496e384-4133-4d57-b93a-1812b83badf2 10 1bb206dbdf18f75e4515aeef378ba50e555a9291 ima-ns sha256:795fb52db49b211450c7242dbcad00d782a7b8174f669c259f74a7ccabe03a90 /usr/bin/id e496e384-4133-4d57-b93a-1812b83badf2 Signed-off-by: James Bottomley Acked-by: Serge Hallyn --- include/linux/ima.h | 1 + security/integrity/ima/Kconfig | 6 +++++- security/integrity/ima/ima_template.c | 6 +++++- security/integrity/ima/ima_template_lib.c | 24 ++++++++++++++++++++++- security/integrity/ima/ima_template_lib.h | 4 ++++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/include/linux/ima.h b/include/linux/ima.h index b6ab66a546ae..09b14b73889e 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -11,6 +11,7 @@ #include #include #include +#include #include struct linux_binprm; diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index f3a9cc201c8c..4f0ce241b585 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -69,7 +69,8 @@ choice hash, defined as 20 bytes, and a null terminated pathname, limited to 255 characters. The 'ima-ng' measurement list template permits both larger hash digests and longer - pathnames. + pathnames. The 'ima-ns' adds the namespace uuid to the + 'ima-ng' template. config IMA_TEMPLATE bool "ima" @@ -77,6 +78,8 @@ choice bool "ima-ng (default)" config IMA_SIG_TEMPLATE bool "ima-sig" + config IMA_NS_TEMPLATE + bool "ima-ns" endchoice config IMA_DEFAULT_TEMPLATE @@ -85,6 +88,7 @@ config IMA_DEFAULT_TEMPLATE default "ima" if IMA_TEMPLATE default "ima-ng" if IMA_NG_TEMPLATE default "ima-sig" if IMA_SIG_TEMPLATE + default "ima-ns" if IMA_NS_TEMPLATE choice prompt "Default integrity hash algorithm" diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 694560396be0..14e02eb3d0f3 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -24,6 +24,7 @@ static struct ima_template_desc builtin_templates[] = { {.name = "ima-modsig", .fmt = "d-ng|n-ng|sig|d-modsig|modsig"}, {.name = "evm-sig", .fmt = "d-ng|n-ng|evmsig|xattrnames|xattrlengths|xattrvalues|iuid|igid|imode"}, + {.name = "ima-ns", .fmt = "d-ng|n-ng|ns"}, {.name = "", .fmt = ""}, /* placeholder for a custom format */ }; @@ -64,6 +65,9 @@ static const struct ima_template_field supported_fields[] = { {.field_id = "xattrvalues", .field_init = ima_eventinodexattrvalues_init, .field_show = ima_show_template_sig}, + {.field_id = "ns", + .field_init = ima_ns_init, + .field_show = ima_show_template_uuid}, }; /* @@ -72,7 +76,7 @@ static const struct ima_template_field supported_fields[] = { * description as 'd-ng' and 'n-ng' respectively. */ #define MAX_TEMPLATE_NAME_LEN \ - sizeof("d-ng|n-ng|evmsig|xattrnames|xattrlengths|xattrvalues|iuid|igid|imode") + sizeof("d-ng|n-ng|evmsig|xattrnames|xattrlengths|xattrvalues|iuid|igid|imode|ns") static struct ima_template_desc *ima_template; static struct ima_template_desc *ima_buf_template; diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index ca017cae73eb..ebd54c1b5206 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -26,7 +26,8 @@ enum data_formats { DATA_FMT_DIGEST_WITH_ALGO, DATA_FMT_STRING, DATA_FMT_HEX, - DATA_FMT_UINT + DATA_FMT_UINT, + DATA_FMT_UUID, }; static int ima_write_template_field_data(const void *data, const u32 datalen, @@ -120,6 +121,9 @@ static void ima_show_template_data_ascii(struct seq_file *m, break; } break; + case DATA_FMT_UUID: + seq_printf(m, "%pU", buf_ptr); + break; default: break; } @@ -202,6 +206,12 @@ void ima_show_template_uint(struct seq_file *m, enum ima_show_type show, ima_show_template_field_data(m, show, DATA_FMT_UINT, field_data); } +void ima_show_template_uuid(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_UUID, field_data); +} + /** * ima_parse_buf() - Parses lengths and data from an input buffer * @bufstartp: Buffer start address. @@ -685,3 +695,15 @@ int ima_eventinodexattrvalues_init(struct ima_event_data *event_data, { return ima_eventinodexattrs_init_common(event_data, field_data, 'v'); } + +/* + * ima_ns_init - include the namespace UUID as part of the template + * data + */ +int ima_ns_init(struct ima_event_data *event_data, + struct ima_field_data *field_data) +{ + return ima_write_template_field_data(¤t_user_ns()->uuid, + UUID_SIZE, DATA_FMT_UUID, + field_data); +} diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h index c71f1de95753..6ea2156271ae 100644 --- a/security/integrity/ima/ima_template_lib.h +++ b/security/integrity/ima/ima_template_lib.h @@ -29,6 +29,8 @@ void ima_show_template_buf(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_uint(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); +void ima_show_template_uuid(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp, int maxfields, struct ima_field_data *fields, int *curfields, unsigned long *len_mask, int enforce_mask, char *bufname); @@ -62,4 +64,6 @@ int ima_eventinodexattrlengths_init(struct ima_event_data *event_data, struct ima_field_data *field_data); int ima_eventinodexattrvalues_init(struct ima_event_data *event_data, struct ima_field_data *field_data); +int ima_ns_init(struct ima_event_data *event_data, + struct ima_field_data *field_data); #endif /* __LINUX_IMA_TEMPLATE_LIB_H */ From patchwork Sat Nov 27 16:45:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Bottomley X-Patchwork-Id: 12642437 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 A8C15C433F5 for ; Sat, 27 Nov 2021 16:50:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1355566AbhK0Qxu (ORCPT ); Sat, 27 Nov 2021 11:53:50 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38974 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237131AbhK0Qvu (ORCPT ); Sat, 27 Nov 2021 11:51:50 -0500 Received: from bedivere.hansenpartnership.com (bedivere.hansenpartnership.com [IPv6:2607:fcd0:100:8a00::2]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A512FC061748 for ; Sat, 27 Nov 2021 08:48:35 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=hansenpartnership.com; s=20151216; t=1638031715; bh=zHhAbPG3Otnbb5Z5gpk3LKyj3V2/qq/UA1JZushr+i8=; h=From:To:Subject:Date:Message-Id:In-Reply-To:References:From; b=VZNA7vqRMc26BffHLPKaNJAzpuyFQrTkhNEzf7fzQ1gDn2QjDecdIiuxv0cCAnPEr VXe7UCCM3jiTJwc54cNOAdHwpr0r1mOfRvnoEGlh7e9yODL++mCOFjt/RN4zIBLnkw GgKEaqLNSvPSLZ/ELUKrHfZ88/EuGm3RiiSpbdv4= Received: from localhost (localhost [127.0.0.1]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id 7B2BE1280A6F; Sat, 27 Nov 2021 11:48:35 -0500 (EST) Received: from bedivere.hansenpartnership.com ([127.0.0.1]) by localhost (bedivere.hansenpartnership.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id zYbxipdlyS1r; Sat, 27 Nov 2021 11:48:35 -0500 (EST) Received: from rainbow.int.hansenpartnership.com (unknown [153.66.140.204]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id 201071280693; Sat, 27 Nov 2021 11:48:34 -0500 (EST) From: James Bottomley To: linux-integrity@vger.kernel.org Cc: containers@lists.linux.dev, Mimi Zohar , Dmitry Kasatkin , Stefan Berger , "Eric W . Biederman" , krzysztof.struczynski@huawei.com, Roberto Sassu , "Serge E . Hallyn" , Michael Peters , Luke Hinds , Lily Sturmann , Patrick Uiterwijk , Christian Brauner Subject: [RFC 3/3] ima: make the integrity inode cache per namespace Date: Sat, 27 Nov 2021 16:45:49 +0000 Message-Id: <20211127164549.2571457-4-James.Bottomley@HansenPartnership.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20211127164549.2571457-1-James.Bottomley@HansenPartnership.com> References: <20211127164549.2571457-1-James.Bottomley@HansenPartnership.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Currently we get one entry in the IMA log per unique file event. So, if you have a measurement policy and it measures a particular binary it will not get measured again if it is subsequently executed. For Namespaced IMA, the correct behaviour seems to be to log once per inode per namespace (so every unique execution in a namespace gets a separate log entry). Since logging once per inode per namespace is different from current behaviour, it is only activated if the namespace appears in the log template (so there's no behaviour change for any of the original templates). Expand the iint cache to have a list of namespaces, per iint entry, the inode has been seen in by moving the action flags and the measured_pcrs into a per-namespace structure. The lifetime of these additional list entries is tied to the lifetime of the iint entry and the namespace, so if either is deleted, the new entry is. Signed-off-by: James Bottomley --- include/linux/ima.h | 13 ++- include/linux/user_namespace.h | 5 + kernel/user.c | 1 + kernel/user_namespace.c | 6 ++ security/integrity/iint.c | 4 +- security/integrity/ima/Makefile | 2 +- security/integrity/ima/ima.h | 21 +++- security/integrity/ima/ima_api.c | 7 +- security/integrity/ima/ima_main.c | 21 ++-- security/integrity/ima/ima_ns.c | 115 ++++++++++++++++++++++ security/integrity/ima/ima_policy.c | 2 +- security/integrity/ima/ima_template_lib.c | 2 + security/integrity/integrity.h | 11 ++- 13 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 security/integrity/ima/ima_ns.c diff --git a/include/linux/ima.h b/include/linux/ima.h index 09b14b73889e..dbbc0257d065 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -40,7 +40,8 @@ extern int ima_measure_critical_data(const char *event_label, const char *event_name, const void *buf, size_t buf_len, bool hash, u8 *digest, size_t digest_len); - +extern void ima_init_user_ns(struct user_namespace *ns); +extern void ima_free_user_ns(struct user_namespace *ns); #ifdef CONFIG_IMA_APPRAISE_BOOTPARAM extern void ima_appraise_parse_cmdline(void); #else @@ -154,6 +155,16 @@ static inline int ima_measure_critical_data(const char *event_label, return -ENOENT; } +static inline void ima_init_user_ns(struct user_namespace *ns) +{ + return; +} + +static inline void ima_free_user_ns(struct user_namespace *ns) +{ + return; +} + #endif /* CONFIG_IMA */ #ifndef CONFIG_IMA_KEXEC diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index d155788abdc1..52968764b195 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -101,6 +102,10 @@ struct user_namespace { struct ucounts *ucounts; long ucount_max[UCOUNT_COUNTS]; uuid_t uuid; +#ifdef CONFIG_IMA + struct list_head ima_inode_list; + rwlock_t ima_inode_list_lock; +#endif } __randomize_layout; struct ucounts { diff --git a/kernel/user.c b/kernel/user.c index bf9ae1d0b670..670d3c41c817 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -68,6 +68,7 @@ struct user_namespace init_user_ns = { .keyring_sem = __RWSEM_INITIALIZER(init_user_ns.keyring_sem), #endif /* .uuid is initialized in user_namespaces_init() */ + /* all IMA fields are initialized by ima_init_user_ns() */ }; EXPORT_SYMBOL_GPL(init_user_ns); diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 8ce57c16ddd3..8afa5dc0b992 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include #include @@ -144,6 +145,9 @@ int create_user_ns(struct cred *new) uuid_gen(&ns->uuid); set_cred_user_ns(new, ns); + + ima_init_user_ns(ns); + return 0; fail_keyring: #ifdef CONFIG_PERSISTENT_KEYRINGS @@ -200,6 +204,7 @@ static void free_user_ns(struct work_struct *work) } retire_userns_sysctls(ns); key_free_user_ns(ns); + ima_free_user_ns(ns); ns_free_inum(&ns->ns); kmem_cache_free(user_ns_cachep, ns); dec_user_namespaces(ucounts); @@ -1389,6 +1394,7 @@ static __init int user_namespaces_init(void) { user_ns_cachep = KMEM_CACHE(user_namespace, SLAB_PANIC | SLAB_ACCOUNT); uuid_gen(&init_user_ns.uuid); + ima_init_user_ns(&init_user_ns); return 0; } subsys_initcall(user_namespaces_init); diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 8638976f7990..f714532feb7d 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -71,6 +71,7 @@ struct integrity_iint_cache *integrity_iint_find(struct inode *inode) static void iint_free(struct integrity_iint_cache *iint) { kfree(iint->ima_hash); + ima_ns_iint_list_free(iint); iint->ima_hash = NULL; iint->version = 0; iint->flags = 0UL; @@ -81,7 +82,7 @@ static void iint_free(struct integrity_iint_cache *iint) iint->ima_read_status = INTEGRITY_UNKNOWN; iint->ima_creds_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; - iint->measured_pcrs = 0; + INIT_LIST_HEAD(&iint->ns_list); kmem_cache_free(iint_cache, iint); } @@ -170,6 +171,7 @@ static void init_once(void *foo) iint->ima_creds_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; mutex_init(&iint->mutex); + INIT_LIST_HEAD(&iint->ns_list); } static int __init integrity_iintcache_init(void) diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 2499f2485c04..1741aa5d97bc 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -7,7 +7,7 @@ obj-$(CONFIG_IMA) += ima.o ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ - ima_policy.o ima_template.o ima_template_lib.o + ima_policy.o ima_template.o ima_template_lib.o ima_ns.o ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o ima-$(CONFIG_IMA_APPRAISE_MODSIG) += ima_modsig.o ima-$(CONFIG_HAVE_IMA_KEXEC) += ima_kexec.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index be965a8715e4..047256be7195 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -119,6 +119,16 @@ struct ima_kexec_hdr { u64 count; }; +/* IMA cache of per-user-namespace flags */ +struct ima_ns_cache { + struct user_namespace *ns; + struct integrity_iint_cache *iint; + struct list_head ns_list; + struct list_head iint_list; + unsigned long measured_pcrs; + unsigned long flags; +}; + extern const int read_idmap[]; #ifdef CONFIG_HAVE_IMA_KEXEC @@ -263,7 +273,8 @@ int ima_must_measure(struct inode *inode, int mask, enum ima_hooks func); int ima_collect_measurement(struct integrity_iint_cache *iint, struct file *file, void *buf, loff_t size, enum hash_algo algo, struct modsig *modsig); -void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, +void ima_store_measurement(struct integrity_iint_cache *iint, + struct ima_ns_cache *nsc, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, @@ -301,6 +312,14 @@ void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos); void ima_policy_stop(struct seq_file *m, void *v); int ima_policy_show(struct seq_file *m, void *v); +/* IMA Namespace related functions */ +extern bool ima_ns_in_template; +struct ima_ns_cache *ima_ns_cache_get(struct integrity_iint_cache *iint, + struct user_namespace *ns); +void ima_ns_cache_clear(struct integrity_iint_cache *iint); +void ima_init_ns(void); + + /* Appraise integrity measurements */ #define IMA_APPRAISE_ENFORCE 0x01 #define IMA_APPRAISE_FIX 0x02 diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index a64fb0130b01..d613ea1ee378 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -298,6 +298,7 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, * Must be called with iint->mutex held. */ void ima_store_measurement(struct integrity_iint_cache *iint, + struct ima_ns_cache *nsc, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, @@ -322,7 +323,7 @@ void ima_store_measurement(struct integrity_iint_cache *iint, * appraisal, but a file measurement from earlier might already exist in * the measurement list. */ - if (iint->measured_pcrs & (0x1 << pcr) && !modsig) + if (nsc->measured_pcrs & (0x1 << pcr) && !modsig) return; result = ima_alloc_init_template(&event_data, &entry, template_desc); @@ -334,8 +335,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, result = ima_store_template(entry, violation, inode, filename, pcr); if ((!result || result == -EEXIST) && !(file->f_flags & O_DIRECT)) { - iint->flags |= IMA_MEASURED; - iint->measured_pcrs |= (0x1 << pcr); + nsc->flags |= IMA_MEASURED; + nsc->measured_pcrs |= (0x1 << pcr); } if (result < 0) ima_free_template_entry(entry); diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 465865412100..049710203fac 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -168,8 +168,8 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint, if (!IS_I_VERSION(inode) || !inode_eq_iversion(inode, iint->version) || (iint->flags & IMA_NEW_FILE)) { - iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE); - iint->measured_pcrs = 0; + iint->flags &= ~IMA_NEW_FILE; + ima_ns_cache_clear(iint); if (update) ima_update_xattr(iint, file); } @@ -204,6 +204,7 @@ static int process_measurement(struct file *file, const struct cred *cred, { struct inode *inode = file_inode(file); struct integrity_iint_cache *iint = NULL; + struct ima_ns_cache *nsc = NULL; struct ima_template_desc *template_desc = NULL; char *pathbuf = NULL; char filename[NAME_MAX]; @@ -274,20 +275,20 @@ static int process_measurement(struct file *file, const struct cred *cred, ((inode->i_sb->s_iflags & SB_I_IMA_UNVERIFIABLE_SIGNATURE) && !(inode->i_sb->s_iflags & SB_I_UNTRUSTED_MOUNTER) && !(action & IMA_FAIL_UNVERIFIABLE_SIGS))) { - iint->flags &= ~IMA_DONE_MASK; - iint->measured_pcrs = 0; + ima_ns_cache_clear(iint); } + nsc = ima_ns_cache_get(iint, current_user_ns()); /* Determine if already appraised/measured based on bitmask * (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED, * IMA_AUDIT, IMA_AUDITED) */ - iint->flags |= action; + nsc->flags |= action; action &= IMA_DO_MASK; - action &= ~((iint->flags & (IMA_DONE_MASK ^ IMA_MEASURED)) >> 1); + action &= ~((nsc->flags & (IMA_DONE_MASK ^ IMA_MEASURED)) >> 1); /* If target pcr is already measured, unset IMA_MEASURE action */ - if ((action & IMA_MEASURE) && (iint->measured_pcrs & (0x1 << pcr))) + if ((action & IMA_MEASURE) && (nsc->measured_pcrs & (0x1 << pcr))) action ^= IMA_MEASURE; /* HASH sets the digital signature and update flags, nothing else */ @@ -297,7 +298,7 @@ static int process_measurement(struct file *file, const struct cred *cred, if ((xattr_value && xattr_len > 2) && (xattr_value->type == EVM_IMA_XATTR_DIGSIG)) set_bit(IMA_DIGSIG, &iint->atomic_flags); - iint->flags |= IMA_HASHED; + nsc->flags |= IMA_HASHED; action ^= IMA_HASH; set_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); } @@ -342,7 +343,7 @@ static int process_measurement(struct file *file, const struct cred *cred, pathname = ima_d_path(&file->f_path, &pathbuf, filename); if (action & IMA_MEASURE) - ima_store_measurement(iint, file, pathname, + ima_store_measurement(iint, nsc, file, pathname, xattr_value, xattr_len, modsig, pcr, template_desc); if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) { @@ -1047,6 +1048,8 @@ static int __init init_ima(void) if (error) return error; + ima_init_ns(); + error = register_blocking_lsm_notifier(&ima_lsm_policy_notifier); if (error) pr_warn("Couldn't register LSM notifier, error %d\n", error); diff --git a/security/integrity/ima/ima_ns.c b/security/integrity/ima/ima_ns.c new file mode 100644 index 000000000000..7134ba7f3544 --- /dev/null +++ b/security/integrity/ima/ima_ns.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * IMA Namespace Support routines + */ + +#include +#include + +#include "ima.h" + +static struct kmem_cache *ns_cache __read_mostly; +bool ima_ns_in_template __read_mostly; + +void __init ima_init_ns(void) +{ + ns_cache = KMEM_CACHE(ima_ns_cache, SLAB_PANIC); +} + +void ima_init_user_ns(struct user_namespace *ns) +{ + INIT_LIST_HEAD(&ns->ima_inode_list); +} + +void ima_free_user_ns(struct user_namespace *ns) +{ + struct ima_ns_cache *entry; + + /* no refs to ns left, so no need to lock */ + while (!list_empty(&ns->ima_inode_list)) { + entry = list_entry(ns->ima_inode_list.next, struct ima_ns_cache, + ns_list); + + /* iint cache entry is still active to lock to delete */ + write_lock(&entry->iint->ns_list_lock); + list_del(&entry->iint_list); + write_unlock(&entry->iint->ns_list_lock); + + list_del(&entry->ns_list); + kmem_cache_free(ns_cache, entry); + } +} + +struct ima_ns_cache *ima_ns_cache_get(struct integrity_iint_cache *iint, + struct user_namespace *ns) +{ + struct ima_ns_cache *entry = NULL; + + if (!ima_ns_in_template) + /* + * if we're not logging the namespace, don't separate the + * iint cache per namespace. This preserves original + * behaviour for the non-ns case. + */ + ns = &init_user_ns; + + read_lock(&iint->ns_list_lock); + list_for_each_entry(entry, &iint->ns_list, ns_list) + if (entry->ns == ns) + break; + read_unlock(&iint->ns_list_lock); + + if (entry && entry->ns == ns) + return entry; + + entry = kmem_cache_zalloc(ns_cache, GFP_NOFS); + if (!entry) + return NULL; + + /* no refs taken: entry is freed on either ns delete or iint delete */ + entry->ns = ns; + entry->iint = iint; + + write_lock(&iint->ns_list_lock); + list_add_tail(&entry->iint_list, &iint->ns_list); + write_unlock(&iint->ns_list_lock); + + write_lock(&ns->ima_inode_list_lock); + list_add_tail(&entry->ns_list, &ns->ima_inode_list); + write_unlock(&ns->ima_inode_list_lock); + + return entry; +} + +/* clear the flags and measured PCR for every entry in the iint */ +void ima_ns_cache_clear(struct integrity_iint_cache *iint) +{ + struct ima_ns_cache *entry; + + read_lock(&iint->ns_list_lock); + list_for_each_entry(entry, &iint->ns_list, ns_list) { + entry->flags = 0; + entry->measured_pcrs = 0; + } + read_unlock(&iint->ns_list_lock); +} + +void ima_ns_iint_list_free(struct integrity_iint_cache *iint) +{ + struct ima_ns_cache *entry; + + /* iint locking unnecessary; no-one should have acces to the list */ + while (!list_empty(&iint->ns_list)) { + entry = list_entry(iint->ns_list.next, struct ima_ns_cache, + iint_list); + + /* namespace is still active to lock to delete */ + write_lock(&entry->ns->ima_inode_list_lock); + list_del(&entry->ns_list); + write_unlock(&entry->ns->ima_inode_list_lock); + + list_del(&entry->iint_list); + kmem_cache_free(ns_cache, entry); + } +} diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 320ca80aacab..9434a1064da6 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -50,7 +50,7 @@ #define DONT_HASH 0x0200 #define INVALID_PCR(a) (((a) < 0) || \ - (a) >= (sizeof_field(struct integrity_iint_cache, measured_pcrs) * 8)) + (a) >= (sizeof_field(struct ima_ns_cache, measured_pcrs) * 8)) int ima_policy_flag; static int temp_ima_appraise; diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index ebd54c1b5206..d1a381add127 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -703,6 +703,8 @@ int ima_eventinodexattrvalues_init(struct ima_event_data *event_data, int ima_ns_init(struct ima_event_data *event_data, struct ima_field_data *field_data) { + if (unlikely(!ima_ns_in_template)) + ima_ns_in_template = true; return ima_write_template_field_data(¤t_user_ns()->uuid, UUID_SIZE, DATA_FMT_UUID, field_data); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 547425c20e11..8755635da3ff 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -129,7 +129,6 @@ struct integrity_iint_cache { struct inode *inode; /* back pointer to inode in question */ u64 version; /* track inode changes */ unsigned long flags; - unsigned long measured_pcrs; unsigned long atomic_flags; enum integrity_status ima_file_status:4; enum integrity_status ima_mmap_status:4; @@ -138,6 +137,8 @@ struct integrity_iint_cache { enum integrity_status ima_creds_status:4; enum integrity_status evm_status:4; struct ima_digest_data *ima_hash; + struct list_head ns_list; /* list of namespaces inode seen in */ + rwlock_t ns_list_lock; }; /* rbtree tree calls to lookup, insert, delete @@ -225,6 +226,14 @@ static inline void ima_load_x509(void) } #endif +#ifdef CONFIG_IMA +void ima_ns_iint_list_free(struct integrity_iint_cache *iint); +#else +void ima_ns_iint_list_free(struct integrity_iint_cache *iint) +{ +} +#endif + #ifdef CONFIG_EVM_LOAD_X509 void __init evm_load_x509(void); #else