From patchwork Sat Aug 12 10:46:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351775 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DFFED20F6 for ; Sat, 12 Aug 2023 10:47:47 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 840BB2133; Sat, 12 Aug 2023 03:47:46 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4RNHBV6P44z9yydx; Sat, 12 Aug 2023 18:36:02 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S3; Sat, 12 Aug 2023 11:47:17 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 01/13] ima: Introduce hook DIGEST_LIST_CHECK Date: Sat, 12 Aug 2023 12:46:04 +0200 Message-Id: <20230812104616.2190095-2-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S3 X-Coremail-Antispam: 1UD129KBjvJXoWxZF13Wry5Gr4Uuw15Cw18uFg_yoW5Kr1rpa 1DKa40kry5XFy2kFZ3C3W29FW8K3yfKF4UG39093WvyF13AF18Xryayr9F9F1rGr4YkFna qrnIgr43Aa1jy37anT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBYb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUGw A2048vs2IY020Ec7CjxVAFwI0_Gr0_Xr1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxS w2x7M28EF7xvwVC0I7IYx2IY67AKxVWUJVWUCwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxV W8JVWxJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x0267AKxVW8 Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F40Ex7xfMc Ij6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC6x0Yz7v_ Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1l42xK82IYc2Ij64 vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAqx4xG67AKxVWUJVWUGwC20s026x8G jcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r4a6rW5MIIYrxkI7VAKI48JMIIF0xvE2I x0cI8IcVAFwI0_Jr0_JF4lIxAIcVC0I7IYx2IY6xkF7I0E14v26r4j6F4UMIIF0xvE42xK 8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I 0E14v26r4UJVWxJrUvcSsGvfC2KfnxnUUI43ZEXa7IU8-TmDUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAHBF1jj46UrgABsF X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Introduce a new hook to check the integrity of digest lists, whose digests are added to the digest cache. Signed-off-by: Roberto Sassu --- Documentation/ABI/testing/ima_policy | 1 + include/linux/kernel_read_file.h | 1 + security/integrity/ima/ima.h | 1 + security/integrity/ima/ima_main.c | 3 ++- security/integrity/ima/ima_policy.c | 3 +++ 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index 49db0ff288e..14d92c687ef 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -36,6 +36,7 @@ Description: [KEXEC_KERNEL_CHECK] [KEXEC_INITRAMFS_CHECK] [KEXEC_CMDLINE] [KEY_CHECK] [CRITICAL_DATA] [SETXATTR_CHECK][MMAP_CHECK_REQPROT] + [DIGEST_LIST_CHECK] mask:= [[^]MAY_READ] [[^]MAY_WRITE] [[^]MAY_APPEND] [[^]MAY_EXEC] fsmagic:= hex value diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 90451e2e12b..85f602e49e2 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -14,6 +14,7 @@ id(KEXEC_INITRAMFS, kexec-initramfs) \ id(POLICY, security-policy) \ id(X509_CERTIFICATE, x509-certificate) \ + id(DIGEST_LIST, digest-list) \ id(MAX_ID, ) #define __fid_enumify(ENUM, dummy) READING_ ## ENUM, diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index c29db699c99..3aef3d8fb57 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -203,6 +203,7 @@ static inline unsigned int ima_hash_key(u8 *digest) hook(KEY_CHECK, key) \ hook(CRITICAL_DATA, critical_data) \ hook(SETXATTR_CHECK, setxattr_check) \ + hook(DIGEST_LIST_CHECK, digest_list_check) \ hook(MAX_CHECK, none) #define __ima_hook_enumify(ENUM, str) ENUM, diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 365db0e43d7..81abdc8b233 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -771,7 +771,8 @@ const int read_idmap[READING_MAX_ID] = { [READING_MODULE] = MODULE_CHECK, [READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK, [READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK, - [READING_POLICY] = POLICY_CHECK + [READING_POLICY] = POLICY_CHECK, + [READING_DIGEST_LIST] = DIGEST_LIST_CHECK, }; /** diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index c9b3bd8f1bb..b32c83d8a72 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -1287,6 +1287,7 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) case MODULE_CHECK: case KEXEC_KERNEL_CHECK: case KEXEC_INITRAMFS_CHECK: + case DIGEST_LIST_CHECK: if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC | IMA_UID | IMA_FOWNER | IMA_FSUUID | IMA_INMASK | IMA_EUID | IMA_PCR | @@ -1530,6 +1531,8 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->func = CRITICAL_DATA; else if (strcmp(args[0].from, "SETXATTR_CHECK") == 0) entry->func = SETXATTR_CHECK; + else if (strcmp(args[0].from, "DIGEST_LIST_CHECK") == 0) + entry->func = DIGEST_LIST_CHECK; else result = -EINVAL; if (!result) From patchwork Sat Aug 12 10:46:05 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351776 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F07CC370 for ; Sat, 12 Aug 2023 10:47:58 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CF7F42712; Sat, 12 Aug 2023 03:47:55 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4RNHBh1Sp4z9yydw; Sat, 12 Aug 2023 18:36:12 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S4; Sat, 12 Aug 2023 11:47:26 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 02/13] integrity: Introduce a digest cache Date: Sat, 12 Aug 2023 12:46:05 +0200 Message-Id: <20230812104616.2190095-3-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S4 X-Coremail-Antispam: 1UD129KBjvAXoWfGFW8Xr13KFWUCFy8WF47XFb_yoW8CryDZo ZIva13Jw18WFy7uF4kCF17Za1Uuw4Fq34fAr4kXrWDZ3WSvFyUtasFkFs8JFy5Xr48Gr93 Aw18X3yUJa1Utr93n29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUYA7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jr yl82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Jr0_JF4l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Cr0_Gr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrVC2j2 WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE7xkE bVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20xvY0x 0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I3I0E 7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIxAIcV C0I7IYx2IY67AKxVWUJVWUCwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Cr0_Gr1UMIIF0xvE 42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67AKxVW8JVWxJwCI42IY6I8E87Iv6x kF7I0E14v26r4UJVWxJrUvcSsGvfC2KfnxnUUI43ZEXa7IU1sa9DUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAHBF1jj46UrgACsG X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Introduce the digest cache, a structure holding a hash table of digests, extracted from a digest list. Its pointer is stored in the iint of the digest list the digest cache was created from (dig_owner field), and in the iint of the inodes for which the digest cache is used (dig_user field). The digest cache has also a reference count to track how many iints are referencing it. For simplicity, the digest cache is created only once, from the first read. Further modifications of the digest lists, if they are ever allowed, are ignored. Introduce two methods to manage the digest cache: digest_cache_get() and digest_cache_free(). The first creates and returns a digest cache created from the digest list whose path is stored in the security.digest_list xattr of the inode being measured/appraised. The second is called at the time an iint is freed. When the digest cache reference count reaches zero, the digest cache is also freed. Each digest cache pointer in the iint is protected by the respective mutex. Dig_owner_mutex ensures that the first caller of digest_cache_get() creates and initializes dig_owner, and ensures that the other callers wait until the creation and initialization is complete. Dig_user_mutex serializes calls to digest_cache_get() by processes accessing the same inode, and ensures that only the first assigns dig_user. The digest cache also stores which IMA actions have been done to the digest list, and is usable for the same actions done to an inode. Signed-off-by: Roberto Sassu --- include/uapi/linux/xattr.h | 3 + security/integrity/Kconfig | 12 ++ security/integrity/Makefile | 1 + security/integrity/digest_cache.c | 317 ++++++++++++++++++++++++++++++ security/integrity/digest_cache.h | 81 ++++++++ security/integrity/iint.c | 12 ++ security/integrity/integrity.h | 8 + 7 files changed, 434 insertions(+) create mode 100644 security/integrity/digest_cache.c create mode 100644 security/integrity/digest_cache.h diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h index 9463db2dfa9..8a58cf4bce6 100644 --- a/include/uapi/linux/xattr.h +++ b/include/uapi/linux/xattr.h @@ -54,6 +54,9 @@ #define XATTR_IMA_SUFFIX "ima" #define XATTR_NAME_IMA XATTR_SECURITY_PREFIX XATTR_IMA_SUFFIX +#define XATTR_DIGEST_LIST_SUFFIX "digest_list" +#define XATTR_NAME_DIGEST_LIST XATTR_SECURITY_PREFIX XATTR_DIGEST_LIST_SUFFIX + #define XATTR_SELINUX_SUFFIX "selinux" #define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index ec6e0d789da..df8a1f7e6e2 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -130,6 +130,18 @@ config INTEGRITY_AUDIT be enabled by specifying 'integrity_audit=1' on the kernel command line. +config INTEGRITY_DIGEST_CACHE + bool "Enable the integrity digest cache" + depends on INTEGRITY + default n + help + This option enables a cache of digests from a digest list, possibly + authenticated with a signature. + + The digest cache can be used to make a TPM PCR predictable + (by skipping the measurement of cached digests), or for appraisal + with already available sources (e.g. RPM packages). + source "security/integrity/ima/Kconfig" source "security/integrity/evm/Kconfig" diff --git a/security/integrity/Makefile b/security/integrity/Makefile index d0ffe37dc1d..0c175a567ac 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -11,6 +11,7 @@ integrity-$(CONFIG_INTEGRITY_SIGNATURE) += digsig.o integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyring.o integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o +integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \ platform_certs/load_uefi.o \ platform_certs/keyring_handler.o diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c new file mode 100644 index 00000000000..4201c68171a --- /dev/null +++ b/security/integrity/digest_cache.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 IBM Corporation + * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the integrity digest cache. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "integrity.h" + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "DIGEST CACHE: "fmt + +/** + * digest_cache_alloc - Allocate and initialize a new digest cache + * @path_str: Path of the digest list + * @digest_cache_mask: Actions done by IMA on the digest list + * + * This function allocates a new digest cache and initializes all fields of + * the digest_cache structure. + * + * Return: A digest_cache structure on success, NULL on error. + */ +static struct digest_cache *digest_cache_alloc(char *path_str, + u64 digest_cache_mask) +{ + struct digest_cache *digest_cache; + + digest_cache = kmalloc(sizeof(*digest_cache), GFP_KERNEL); + if (!digest_cache) + return digest_cache; + + digest_cache->algo = HASH_ALGO__LAST; + digest_cache->path_str = path_str; + digest_cache->mask = digest_cache_mask; + digest_cache->slots = NULL; + digest_cache->num_slots = 0; + /* + * One for dig_owner of the digest list iint, one for dig_user of the + * iint of the inode for which the digest cache is used. + */ + atomic_set(&digest_cache->ref_count, 2); + return digest_cache; +} + +/** + * digest_cache_free - Free all memory occupied by a digest cache + * @digest_cache: Digest cache + * + * This function frees the digests associated to the digest cache and the + * digest cache itself. + */ +void digest_cache_free(struct digest_cache *digest_cache) +{ + struct digest_cache_entry *p; + struct hlist_node *q; + int digest_len, i; + + if (!digest_cache) + return; + + pr_debug("Free cache (ref count: %d), algo: %s, digest list: %s", + atomic_read(&digest_cache->ref_count), + hash_algo_name[digest_cache->algo], digest_cache->path_str); + + if (!atomic_dec_and_test(&digest_cache->ref_count)) + return; + + digest_len = hash_digest_size[digest_cache->algo]; + + for (i = 0; i < digest_cache->num_slots; i++) { + hlist_for_each_entry_safe(p, q, &digest_cache->slots[i], + hnext) { + hlist_del(&p->hnext); + pr_debug("Remove digest %s:%*phN from digest list %s\n", + hash_algo_name[digest_cache->algo], + digest_len, p->digest, digest_cache->path_str); + kfree(p); + } + } + + pr_debug("Freed cache (ref count: %d), algo: %s, digest list: %s", + atomic_read(&digest_cache->ref_count), + hash_algo_name[digest_cache->algo], digest_cache->path_str); + + kfree(digest_cache->path_str); + kfree(digest_cache->slots); + kfree(digest_cache); +} + +/** + * digest_cache_parse_digest_list - Parse a digest list + * @digest_cache: Digest cache + * @digest_list_path: Path of the digest list + * @data: Data to parse + * @data_len: Length of @data + * + * This function parses a digest list. First, it strips the module-style + * appended signature, if present. Then, it selects the parser to call from + * the beginning of the file name, which is expected to be in the format: + * -. + * + * Return: Zero on success, a negative value on error. + */ +static int digest_cache_parse_digest_list(struct digest_cache *digest_cache, + struct path *digest_list_path, + void *data, size_t data_len) +{ + const size_t marker_len = strlen(MODULE_SIG_STRING); + const struct module_signature *sig; + size_t sig_len; + const void *p; + int ret = -EINVAL; + + /* From ima_modsig.c */ + if (data_len <= marker_len + sizeof(*sig)) + goto parse; + + p = data + data_len - marker_len; + if (memcmp(p, MODULE_SIG_STRING, marker_len)) + goto parse; + + data_len -= marker_len; + sig = (const struct module_signature *)(p - sizeof(*sig)); + + sig_len = be32_to_cpu(sig->sig_len); + data_len -= sig_len + sizeof(*sig); +parse: + pr_debug("Parsing %s, size: %ld\n", digest_cache->path_str, data_len); + + return ret; +} + +/** + * digest_cache_new - Create a new digest cache + * @dentry: Dentry of the file being measured/appraised + * + * This function retrieves the path of the digest list from the + * security.digest_list xattr of the file being measured/appraised. It then + * opens and parses the digest list, and finally instantiates a new digest + * cache. + * + * After read, the IMA actions done on the digest list are recorded in the + * digest cache. The use of the digest cache is allowed for measuring/appraising + * a file, only if the same action has been done on the digest list itself. + * + * The invoked parser will in turn set the digest algorithm, initialize the + * hash table and add the extracted digests to the digest cache. + * + * Return: A new digest cache on success, NULL on error. + */ +static struct digest_cache *digest_cache_new(struct dentry *dentry) +{ + struct integrity_iint_cache *digest_list_iint; + struct digest_cache *digest_cache = NULL; + struct path digest_list_path; + char *path_str = NULL; + struct file *file; + void *data = NULL; + size_t data_len = 0; + struct inode *inode; + u64 digest_cache_mask = 0; + int ret; + + ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_DIGEST_LIST, + &path_str, 0, GFP_NOFS); + if (ret <= 0) { + pr_debug("%s xattr not found in %s\n", XATTR_NAME_DIGEST_LIST, + dentry->d_name.name); + return digest_cache; + } + + pr_debug("Found %s xattr in %s, digest list: %s\n", + XATTR_NAME_DIGEST_LIST, dentry->d_name.name, path_str); + + ret = kern_path(path_str, 0, &digest_list_path); + if (ret < 0) { + pr_debug("Cannot open digest list %s\n", path_str); + goto out; + } + + inode = d_backing_inode(digest_list_path.dentry); + + digest_list_iint = integrity_inode_get(inode); + if (!digest_list_iint) { + pr_debug("Cannot get integrity metadata for digest list %s\n", + path_str); + goto out_path; + } + + if (digest_list_iint->dig_owner) { + pr_debug("Cache for digest list %s exists\n", path_str); + digest_cache = digest_list_iint->dig_owner; + atomic_inc(&digest_cache->ref_count); + goto out_path; + } + + mutex_lock(&digest_list_iint->dig_owner_mutex); + + if (digest_list_iint->dig_owner) { + pr_debug("Cache for digest list %s exists\n", path_str); + digest_cache = digest_list_iint->dig_owner; + atomic_inc(&digest_cache->ref_count); + goto out_unlock; + } + + file = dentry_open(&digest_list_path, O_RDONLY, &init_cred); + if (IS_ERR(file)) { + pr_debug("Unable to open digest list %s\n", path_str); + goto out_unlock; + } + + /* Write-lock the file to avoid getting outdated iint->flags. */ + ret = deny_write_access(file); + if (ret < 0) { + pr_err("Unable to write-lock digest list %s", path_str); + goto out_fput; + } + + ret = kernel_read_file(file, 0, &data, INT_MAX, NULL, + READING_DIGEST_LIST); + if (ret < 0) { + pr_debug("Unable to read digest list %s\n", path_str); + goto out_allow; + } + + if (digest_list_iint->flags & IMA_MEASURED) + digest_cache_mask |= DIGEST_CACHE_MEASURE; + if (digest_list_iint->flags & IMA_APPRAISED_SUBMASK) + digest_cache_mask |= DIGEST_CACHE_APPRAISE_CONTENT; + + if (!digest_cache_mask) { + pr_debug("No actions done on digest list %s\n", path_str); + ret = -ENOENT; + goto out_vfree; + } + + data_len = ret; + + digest_cache = digest_cache_alloc(path_str, digest_cache_mask); + if (!digest_cache) + goto out_vfree; + + /* Freed by digest_cache_free(). */ + path_str = NULL; + + /* + * Digest list parsers must set the digest algorithm, initialize the + * hash table and add the digests. + */ + ret = digest_cache_parse_digest_list(digest_cache, &digest_list_path, + data, data_len); + if (ret < 0) { + pr_debug("Error parsing digest list %s, ret: %d\n", + digest_cache->path_str, ret); + digest_cache_free(digest_cache); + digest_cache = NULL; + goto out_vfree; + } + + digest_list_iint->dig_owner = digest_cache; + + pr_debug("New cache (ref count: %d), algo: %s, digest list: %s, mask: %llu\n", + atomic_read(&digest_cache->ref_count), + hash_algo_name[digest_cache->algo], digest_cache->path_str, + digest_cache->mask); +out_vfree: + vfree(data); +out_allow: + allow_write_access(file); +out_fput: + fput(file); +out_unlock: + mutex_unlock(&digest_list_iint->dig_owner_mutex); +out_path: + path_put(&digest_list_path); +out: + kfree(path_str); + return digest_cache; +} + +/** + * digest_cache_get - Obtain a digest cache and set it in the iint + * @dentry: Dentry of the file being measured/appraised + * @iint: Integrity inode cache of the file being measured/appraised + * + * Obtain a digest cache, and set it in the dig_user field of the passed iint, + * if not already done. + * + * Return: A digest cache on success, NULL otherwise. + */ +struct digest_cache *digest_cache_get(struct dentry *dentry, + struct integrity_iint_cache *iint) +{ + if (iint->dig_user) + return iint->dig_user; + + mutex_lock(&iint->dig_user_mutex); + if (!iint->dig_user) + iint->dig_user = digest_cache_new(dentry); + mutex_unlock(&iint->dig_user_mutex); + + return iint->dig_user; +} diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h new file mode 100644 index 00000000000..ff88e8593c6 --- /dev/null +++ b/security/integrity/digest_cache.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header of the integrity digest cache. + */ + +#ifndef _DIGEST_CACHE_H +#define _DIGEST_CACHE_H + +#include +#include +#include + +/* Depth if elements were uniformly distributed in the hash table slots. */ +#define DIGEST_CACHE_HTABLE_DEPTH 30 + +/* There is no explicit concept of metadata measurement in IMA. */ +#define DIGEST_CACHE_MEASURE 0x01 +#define DIGEST_CACHE_APPRAISE_CONTENT 0x02 + +struct integrity_iint_cache; + +/** + * struct digest_cache - Digest cache + * @slots: Hash table slots + * @num_slots: Number of slots + * @ref_count: Number of references to the digest cache + * @algo: Algorithm of digests stored in the cache + * @path_str: Path of the digest list the cache was created from + * @mask: For which IMA actions and purpose the digest cache can be used + * + * This structure represents a cache of digests extracted from a file, to be + * primarily used for IMA measurement and appraisal. + */ +struct digest_cache { + struct hlist_head *slots; + unsigned int num_slots; + atomic_t ref_count; + enum hash_algo algo; + char *path_str; + u64 mask; +}; + +/** + * struct digest_cache_entry - Entry of a digest cache + * @hnext: Pointer to the next element in the collision list + * @digest: Stored digest + * + * This structure represents an entry of a digest cache, storing a digest. + */ +struct digest_cache_entry { + struct hlist_node hnext; + u8 digest[]; +} __packed; + +static inline unsigned int digest_cache_hash_key(u8 *digest, + unsigned int num_slots) +{ + return (digest[0] | digest[1] << 8) % num_slots; +} + +#ifdef CONFIG_INTEGRITY_DIGEST_CACHE +void digest_cache_free(struct digest_cache *digest_cache); +struct digest_cache *digest_cache_get(struct dentry *dentry, + struct integrity_iint_cache *iint); +#else +static inline void digest_cache_free(struct digest_cache *digest_cache) +{ +} + +static inline struct digest_cache * +digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint) +{ + return NULL; +} + +#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */ +#endif /* _DIGEST_CACHE_H */ diff --git a/security/integrity/iint.c b/security/integrity/iint.c index a462df827de..68ec73172e3 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -80,6 +80,12 @@ static void iint_free(struct integrity_iint_cache *iint) iint->ima_creds_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; iint->measured_pcrs = 0; +#ifdef CONFIG_INTEGRITY_DIGEST_CACHE + digest_cache_free(iint->dig_owner); + digest_cache_free(iint->dig_user); + iint->dig_owner = NULL; + iint->dig_user = NULL; +#endif kmem_cache_free(iint_cache, iint); } @@ -165,6 +171,12 @@ static void init_once(void *foo) iint->ima_creds_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; mutex_init(&iint->mutex); +#ifdef CONFIG_INTEGRITY_DIGEST_CACHE + iint->dig_owner = NULL; + iint->dig_user = NULL; + mutex_init(&iint->dig_owner_mutex); + mutex_init(&iint->dig_user_mutex); +#endif } static int __init integrity_iintcache_init(void) diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7167a6e99bd..0192f81c67f 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -19,6 +19,8 @@ #include #include +#include "digest_cache.h" + /* iint action cache flags */ #define IMA_MEASURE 0x00000001 #define IMA_MEASURED 0x00000002 @@ -171,6 +173,12 @@ struct integrity_iint_cache { enum integrity_status ima_creds_status:4; enum integrity_status evm_status:4; struct ima_digest_data *ima_hash; +#ifdef CONFIG_INTEGRITY_DIGEST_CACHE + struct digest_cache *dig_owner; /* created from this inode */ + struct mutex dig_owner_mutex; /* protects dig_owner */ + struct digest_cache *dig_user; /* user of the digest cache */ + struct mutex dig_user_mutex; /* protects dig_user */ +#endif }; /* rbtree tree calls to lookup, insert, delete From patchwork Sat Aug 12 10:46:06 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351777 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8C30B370 for ; Sat, 12 Aug 2023 10:48:07 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 441AB271E; Sat, 12 Aug 2023 03:48:04 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4RNHBs3cjgz9yydZ; Sat, 12 Aug 2023 18:36:21 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S5; Sat, 12 Aug 2023 11:47:36 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search Date: Sat, 12 Aug 2023 12:46:06 +0200 Message-Id: <20230812104616.2190095-4-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S5 X-Coremail-Antispam: 1UD129KBjvJXoWxtr43XFyDZr17Wr48Gr43trb_yoWxXrW5pa s7Cr1Utr4rZF13Cw17AF1ayr1SvrWqqF47Gw45Wr1ayr4DZr1jy3W8Aw1UXFy5Jr48Wa17 tF4jgr1UCr1UXaUanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUB2b4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUWw A2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxS w2x7M28EF7xvwVC0I7IYx2IY67AKxVWUJVWUCwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxV WxJVW8Jr1l84ACjcxK6I8E87Iv67AKxVW8JVWxJwA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_ Gr1j6F4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc02F40EFcxC0VAKzVAqx4xG6I80ew Av7VC0I7IYx2IY67AKxVWUJVWUGwAv7VC2z280aVAFwI0_Jr0_Gr1lOx8S6xCaFVCjc4AY 6r1j6r4UM4x0Y48IcxkI7VAKI48JM4IIrI8v6xkF7I0E8cxan2IY04v7MxAIw28IcxkI7V AKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCj r7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVW8ZVWrXwCIc40Y0x0EwIxGrwCI42IY6x IIjxv20xvE14v26r1j6r1xMIIF0xvE2Ix0cI8IcVCY1x0267AKxVWxJVW8Jr1lIxAIcVCF 04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r4j6F4UMIIF0xvEx4A2jsIEc7 CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuYvjxUFYFCUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAHBF1jj5KVZQAAsR X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Add digest_cache_init_htable(), to size a hash table depending on the number of digests to be added to the cache. Add digest_cache_add() and digest_cache_lookup() to respectively add and lookup a digest in the digest cache. Signed-off-by: Roberto Sassu --- security/integrity/digest_cache.c | 131 ++++++++++++++++++++++++++++++ security/integrity/digest_cache.h | 24 ++++++ 2 files changed, 155 insertions(+) diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c index 4201c68171a..d14d84b804b 100644 --- a/security/integrity/digest_cache.c +++ b/security/integrity/digest_cache.c @@ -315,3 +315,134 @@ struct digest_cache *digest_cache_get(struct dentry *dentry, return iint->dig_user; } + +/** + * digest_cache_init_htable - Allocate and initialize the hash table + * @digest_cache: Digest cache + * @num_digests: Number of digests to add to the digest cache + * + * This function allocates and initializes the hash table. Its size is + * determined by the number of digests to add to the digest cache, known + * at this point by the parser calling this function. + * + * Return: Zero on success, a negative value otherwise. + */ +int digest_cache_init_htable(struct digest_cache *digest_cache, + u64 num_digests) +{ + int i; + + if (!digest_cache) + return 0; + + digest_cache->num_slots = num_digests / DIGEST_CACHE_HTABLE_DEPTH; + if (!digest_cache->num_slots) + digest_cache->num_slots = 1; + + digest_cache->slots = kmalloc_array(num_digests, + sizeof(*digest_cache->slots), + GFP_KERNEL); + if (!digest_cache->slots) + return -ENOMEM; + + for (i = 0; i < digest_cache->num_slots; i++) + INIT_HLIST_HEAD(&digest_cache->slots[i]); + + pr_debug("Initialized %d hash table slots for digest list %s\n", + digest_cache->num_slots, digest_cache->path_str); + return 0; +} + +/** + * digest_cache_add - Add a new digest to the digest cache + * @digest_cache: Digest cache + * @digest: Digest to add + * + * This function, invoked by a digest list parser, adds a digest extracted + * from a digest list to the digest cache. + * + * Return: Zero on success, a negative value on error. + */ +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest) +{ + struct digest_cache_entry *entry; + unsigned int key; + int digest_len; + + if (!digest_cache) + return 0; + + digest_len = hash_digest_size[digest_cache->algo]; + + entry = kmalloc(sizeof(*entry) + digest_len, GFP_KERNEL); + if (!entry) + return -ENOMEM; + + memcpy(entry->digest, digest, digest_len); + + key = digest_cache_hash_key(digest, digest_cache->num_slots); + hlist_add_head(&entry->hnext, &digest_cache->slots[key]); + pr_debug("Add digest %s:%*phN from digest list %s\n", + hash_algo_name[digest_cache->algo], digest_len, digest, + digest_cache->path_str); + return 0; +} + +/** + * digest_cache_lookup - Searches a digest in the digest cache + * @digest_cache: Digest cache + * @digest: Digest to search + * @algo: Algorithm of the digest to search + * @pathname: Path of the file whose digest is looked up + * + * This function, invoked by IMA or EVM, searches the calculated digest of + * a file or file metadata in the digest cache acquired with + * digest_cache_get(). + * + * Return: Zero if the digest is found, a negative value if not. + */ +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest, + enum hash_algo algo, const char *pathname) +{ + struct digest_cache_entry *entry; + unsigned int key; + int digest_len; + int search_depth = 0; + + if (!digest_cache) + return -ENOENT; + + if (digest_cache->algo == HASH_ALGO__LAST) { + pr_debug("Algorithm not set for digest list %s\n", + digest_cache->path_str); + return -ENOENT; + } + + digest_len = hash_digest_size[digest_cache->algo]; + + if (algo != digest_cache->algo) { + pr_debug("Algo mismatch for file %s, digest %s:%*phN in digest list %s (%s)\n", + pathname, hash_algo_name[algo], digest_len, digest, + digest_cache->path_str, + hash_algo_name[digest_cache->algo]); + return -ENOENT; + } + + key = digest_cache_hash_key(digest, digest_cache->num_slots); + + hlist_for_each_entry_rcu(entry, &digest_cache->slots[key], hnext) { + if (!memcmp(entry->digest, digest, digest_len)) { + pr_debug("Cache hit at depth %d for file %s, digest %s:%*phN in digest list %s\n", + search_depth, pathname, hash_algo_name[algo], + digest_len, digest, digest_cache->path_str); + return 0; + } + + search_depth++; + } + + pr_debug("Cache miss for file %s, digest %s:%*phN in digest list %s\n", + pathname, hash_algo_name[algo], digest_len, digest, + digest_cache->path_str); + return -ENOENT; +} diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h index ff88e8593c6..01cd70f9850 100644 --- a/security/integrity/digest_cache.h +++ b/security/integrity/digest_cache.h @@ -66,6 +66,11 @@ static inline unsigned int digest_cache_hash_key(u8 *digest, void digest_cache_free(struct digest_cache *digest_cache); struct digest_cache *digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint); +int digest_cache_init_htable(struct digest_cache *digest_cache, + u64 num_digests); +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest); +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest, + enum hash_algo algo, const char *pathname); #else static inline void digest_cache_free(struct digest_cache *digest_cache) { @@ -77,5 +82,24 @@ digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint) return NULL; } +static inline int digest_cache_init_htable(struct digest_cache *digest_cache, + u64 num_digests) +{ + return -EOPNOTSUPP; +} + +static inline int digest_cache_add(struct digest_cache *digest_cache, + u8 *digest) +{ + return -EOPNOTSUPP; +} + +static inline int digest_cache_lookup(struct digest_cache *digest_cache, + u8 *digest, enum hash_algo algo, + const char *pathname) +{ + return -ENOENT; +} + #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */ #endif /* _DIGEST_CACHE_H */ From patchwork Sat Aug 12 10:46:07 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351778 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 03569370 for ; Sat, 12 Aug 2023 10:48:30 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A97142D61; Sat, 12 Aug 2023 03:48:14 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.227]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4RNHC25m77z9yydv; Sat, 12 Aug 2023 18:36:30 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S6; Sat, 12 Aug 2023 11:47:45 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 04/13] integrity/digest_cache: Prefetch digest lists in a directory Date: Sat, 12 Aug 2023 12:46:07 +0200 Message-Id: <20230812104616.2190095-5-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S6 X-Coremail-Antispam: 1UD129KBjvJXoW3Xr1rCFy8tF4UJrW7ZrW8Zwb_yoWxuFW7pa 9Ik3WUtr4rZw1fC397AF43CF4Fg39Ygr47Gw45uw1Yyw4DZr1jv3WxCryUZry5Jr4Uu3W7 tF4UKr1UCr4DXaDanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBSb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUJVWUCwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVWxJVW8Jr1l84ACjcxK6I8E87Iv67AKxVW8JVWxJwA2z4x0Y4vEx4A2jsIEc7CjxVAF wI0_Gr1j6F4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc02F40EFcxC0VAKzVAqx4xG6I 80ewAv7VC0I7IYx2IY67AKxVWUJVWUGwAv7VC2z280aVAFwI0_Jr0_Gr1lOx8S6xCaFVCj c4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JM4IIrI8v6xkF7I0E8cxan2IY04v7MxAIw28Icx kI7VAKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2Iq xVCjr7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVW8ZVWrXwCIc40Y0x0EwIxGrwCI42 IY6xIIjxv20xvE14v26r1j6r1xMIIF0xvE2Ix0cI8IcVCY1x0267AKxVWxJVW8Jr1lIxAI cVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r4j6F4UMIIF0xvEx4A2js IEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuYvjxUFgAwUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAHBF1jj5KVZQABsQ X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Prefetch the digest lists in the same directory as the requested one, to measure them in a deterministic way, and obtain a predictable PCR. Prefetching does not imply parsing, so there won't be significant memory consumption. However, since all PCR extend operations are done all at the same time, this iteration is expected to introduce a noticeable delay to the first file operation for which the digest cache is used. This patch assumes for now that all digest lists are stored in the same directory. Signed-off-by: Roberto Sassu --- security/integrity/Makefile | 3 +- security/integrity/digest_cache.h | 5 + security/integrity/digest_cache_iter.c | 160 +++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 security/integrity/digest_cache_iter.c diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 0c175a567ac..c856ed10fba 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -11,7 +11,8 @@ integrity-$(CONFIG_INTEGRITY_SIGNATURE) += digsig.o integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyring.o integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o -integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o +integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o \ + digest_cache_iter.o integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \ platform_certs/load_uefi.o \ platform_certs/keyring_handler.o diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h index 01cd70f9850..cd5c913cf7a 100644 --- a/security/integrity/digest_cache.h +++ b/security/integrity/digest_cache.h @@ -71,6 +71,7 @@ int digest_cache_init_htable(struct digest_cache *digest_cache, int digest_cache_add(struct digest_cache *digest_cache, u8 *digest); int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest, enum hash_algo algo, const char *pathname); +void digest_cache_iter_dir(struct dentry *repo_dentry); #else static inline void digest_cache_free(struct digest_cache *digest_cache) { @@ -101,5 +102,9 @@ static inline int digest_cache_lookup(struct digest_cache *digest_cache, return -ENOENT; } +static inline void digest_cache_iter_dir(struct dentry *repo_dentry) +{ +} + #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */ #endif /* _DIGEST_CACHE_H */ diff --git a/security/integrity/digest_cache_iter.c b/security/integrity/digest_cache_iter.c new file mode 100644 index 00000000000..f7641e7b365 --- /dev/null +++ b/security/integrity/digest_cache_iter.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement a digest list iterator. + */ + +#include +#include +#include +#include + +#include "integrity.h" + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "DIGEST CACHE ITER: "fmt + +static bool iterated; +/* Ensure there is only one iteration over digest lists, make others wait. */ +DEFINE_MUTEX(iterate_mutex); + +struct dir_entry { + struct list_head list; + char name[]; +} __packed; + +struct readdir_callback { + struct dir_context ctx; + struct list_head *head; +}; + +/** + * digest_cache_iter_digest_list - Callback func to get digest lists in a dir + * @__ctx: iterate_dir() context + * @name: Name of file in the accessed dir + * @namelen: String length of @name + * @offset: Current position in the directory stream (see man readdir) + * @ino: Inode number + * @d_type: File type + * + * This function stores the names of the files in the containing directory in + * a linked list. Those files will be opened to trigger a measurement. + * + * Return: True to continue processing, false to stop. + */ +static bool digest_cache_iter_digest_list(struct dir_context *__ctx, + const char *name, int namelen, + loff_t offset, u64 ino, + unsigned int d_type) +{ + struct readdir_callback *ctx = container_of(__ctx, typeof(*ctx), ctx); + struct dir_entry *new_entry; + + if (!strcmp(name, ".") || !strcmp(name, "..")) + return true; + + if (d_type != DT_REG) + return true; + + new_entry = kmalloc(sizeof(*new_entry) + namelen + 1, GFP_KERNEL); + if (!new_entry) + return true; + + memcpy(new_entry->name, name, namelen); + new_entry->name[namelen] = '\0'; + list_add_tail(&new_entry->list, ctx->head); + return true; +} + +/** + * digest_cache_iter_dir - Iterate over all files in the same digest list dir + * @digest_list_dentry: Digest list dentry + * + * This function iterates over all files in the directory containing the digest + * list provided as argument. It helps to measure digest lists in a + * deterministic order and make a TPM PCR predictable. + */ +void digest_cache_iter_dir(struct dentry *digest_list_dentry) +{ + struct file *dir_file; + struct readdir_callback buf = { + .ctx.actor = digest_cache_iter_digest_list, + }; + struct dir_entry *p, *q; + struct file *file; + char *path_str = NULL, *dir_path_str = NULL; + void *data; + LIST_HEAD(head); + char *ptr; + int ret; + + if (iterated) + return; + + mutex_lock(&iterate_mutex); + if (iterated) + goto out; + + iterated = true; + + ret = vfs_getxattr_alloc(&nop_mnt_idmap, digest_list_dentry, + XATTR_NAME_DIGEST_LIST, &path_str, 0, + GFP_NOFS); + if (ret <= 0) { + pr_debug("%s xattr not found in %s\n", XATTR_NAME_DIGEST_LIST, + digest_list_dentry->d_name.name); + goto out; + } + + pr_debug("Found %s xattr in %s, digest list: %s\n", + XATTR_NAME_DIGEST_LIST, digest_list_dentry->d_name.name, + path_str); + + ptr = strrchr(path_str, '/'); + if (!ptr) + goto out; + + dir_path_str = kstrndup(path_str, ptr - path_str, GFP_KERNEL); + if (!dir_path_str) + goto out; + + dir_file = filp_open(dir_path_str, O_RDONLY, 0); + + if (IS_ERR(dir_file)) { + pr_debug("Cannot access %s\n", dir_path_str); + goto out; + } + + buf.head = &head; + iterate_dir(dir_file, &buf.ctx); + list_for_each_entry_safe(p, q, &head, list) { + pr_debug("Prefetching digest list %s/%s\n", dir_path_str, + p->name); + + file = file_open_root(&dir_file->f_path, p->name, O_RDONLY, 0); + if (IS_ERR(file)) + continue; + + data = NULL; + + ret = kernel_read_file(file, 0, &data, INT_MAX, NULL, + READING_DIGEST_LIST); + if (ret >= 0) + vfree(data); + + fput(file); + list_del(&p->list); + kfree(p); + } + + fput(dir_file); +out: + mutex_unlock(&iterate_mutex); + kfree(path_str); + kfree(dir_path_str); +} From patchwork Sat Aug 12 10:46:08 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351779 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 68DFA370 for ; Sat, 12 Aug 2023 10:48:44 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B8EBF3593; Sat, 12 Aug 2023 03:48:22 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4RNHCD0SRLz9yydy; Sat, 12 Aug 2023 18:36:40 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S7; Sat, 12 Aug 2023 11:47:54 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 05/13] integrity/digest_cache: Parse tlv digest lists Date: Sat, 12 Aug 2023 12:46:08 +0200 Message-Id: <20230812104616.2190095-6-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S7 X-Coremail-Antispam: 1UD129KBjvJXoWfGr47Jr17tFWDGw4xJFWkZwb_yoWDCry5pa sxKF18KrW7WF1fCw4xAF17Cr4fKrZ09rW7KFWruw1ayrWDZr1qk3Z2kFy8Zry5tr4DX3W7 Jw4agF909r4DXaUanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBvb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUCVW8JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20x vY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I 3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIx AIcVC0I7IYx2IY67AKxVWUCVW8JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr1j6F4UJwCI 42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z2 80aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x07UZo7tUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAHBF1jj5KVZQACsT X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Add a parser for TLV-formatted (Type Length Value) digest lists. Their structure is: [header: DIGEST_LIST_FILE, num fields, total len] [field: DIGEST_LIST_ALGO, length, value] [field: DIGEST_LIST_ENTRY#1, length, value (below)] |- [header: DIGEST_LIST_FILE, num fields, total len] |- [ENTRY#1_DIGEST, length, file digest] |- [ENTRY#1_PATH, length, file path] [field: DIGEST_LIST_ENTRY#N, length, value (below)] |- [header: DIGEST_LIST_FILE, num fields, total len] |- [ENTRY#N_DIGEST, length, file digest] |- [ENTRY#N_PATH, length, file path] Defined fields are sufficient for measurement/appraisal of file content. More fields can be introduced later (e.g. for appraisal of file metadata). This patch defines only the callbacks (handlers) for the defined fields. The parsing logic is already introduced in lib/tlv_parser.c. Signed-off-by: Roberto Sassu --- include/uapi/linux/tlv_digest_list.h | 59 ++++++ security/integrity/Makefile | 3 +- security/integrity/digest_cache.c | 4 + .../integrity/digest_list_parsers/parsers.h | 13 ++ security/integrity/digest_list_parsers/tlv.c | 188 ++++++++++++++++++ 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 include/uapi/linux/tlv_digest_list.h create mode 100644 security/integrity/digest_list_parsers/parsers.h create mode 100644 security/integrity/digest_list_parsers/tlv.c diff --git a/include/uapi/linux/tlv_digest_list.h b/include/uapi/linux/tlv_digest_list.h new file mode 100644 index 00000000000..52987b63877 --- /dev/null +++ b/include/uapi/linux/tlv_digest_list.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Export definitions of the tlv digest list. + */ + +#ifndef _UAPI_LINUX_TLV_DIGEST_LIST_H +#define _UAPI_LINUX_TLV_DIGEST_LIST_H + +#include + +#define FOR_EACH_DIGEST_LIST_TYPE(DIGEST_LIST_TYPE) \ + DIGEST_LIST_TYPE(DIGEST_LIST_FILE) \ + DIGEST_LIST_TYPE(DIGEST_LIST__LAST) + +#define FOR_EACH_FIELD(FIELD) \ + FIELD(DIGEST_LIST_ALGO) \ + FIELD(DIGEST_LIST_ENTRY) \ + FIELD(FIELD__LAST) + +#define FOR_EACH_ENTRY_FIELD(ENTRY_FIELD) \ + ENTRY_FIELD(ENTRY_DIGEST) \ + ENTRY_FIELD(ENTRY_PATH) \ + ENTRY_FIELD(ENTRY__LAST) + +#define GENERATE_ENUM(ENUM) ENUM, +#define GENERATE_STRING(STRING) #STRING, + +/** + * enum digest_list_types - Type of digest list + * + * Enumerates the types of digest lists to parse. + */ +enum digest_list_types { + FOR_EACH_DIGEST_LIST_TYPE(GENERATE_ENUM) +}; + +/** + * enum fields - Digest list fields + * + * Enumerates the digest list fields. + */ +enum digest_list_fields { + FOR_EACH_FIELD(GENERATE_ENUM) +}; + +/** + * enum entry_fields - Entry-specific fields + * + * Enumerates the digest list entry-specific fields. + */ +enum entry_fields { + FOR_EACH_ENTRY_FIELD(GENERATE_ENUM) +}; + +#endif /* _UAPI_LINUX_TLV_DIGEST_LIST_H */ diff --git a/security/integrity/Makefile b/security/integrity/Makefile index c856ed10fba..3765b004e66 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -12,7 +12,8 @@ integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyring.o integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o \ - digest_cache_iter.o + digest_cache_iter.o \ + digest_list_parsers/tlv.o integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \ platform_certs/load_uefi.o \ platform_certs/keyring_handler.o diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c index d14d84b804b..818ac0ac0bf 100644 --- a/security/integrity/digest_cache.c +++ b/security/integrity/digest_cache.c @@ -18,6 +18,7 @@ #include #include "integrity.h" +#include "digest_list_parsers/parsers.h" #ifdef pr_fmt #undef pr_fmt @@ -141,6 +142,9 @@ static int digest_cache_parse_digest_list(struct digest_cache *digest_cache, parse: pr_debug("Parsing %s, size: %ld\n", digest_cache->path_str, data_len); + if (!strncmp(digest_list_path->dentry->d_name.name, "tlv-", 4)) + ret = digest_list_parse_tlv(digest_cache, data, data_len); + return ret; } diff --git a/security/integrity/digest_list_parsers/parsers.h b/security/integrity/digest_list_parsers/parsers.h new file mode 100644 index 00000000000..e8fff2374d8 --- /dev/null +++ b/security/integrity/digest_list_parsers/parsers.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Digest list parsers. + */ + +#include "../digest_cache.h" + +int digest_list_parse_tlv(struct digest_cache *digest_cache, const u8 *data, + size_t data_len); diff --git a/security/integrity/digest_list_parsers/tlv.c b/security/integrity/digest_list_parsers/tlv.c new file mode 100644 index 00000000000..239400f5786 --- /dev/null +++ b/security/integrity/digest_list_parsers/tlv.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Parse a tlv digest list. + */ + +#define pr_fmt(fmt) "TLV DIGEST LIST: "fmt +#include +#include +#include +#include + +#include "parsers.h" + +#define kenter(FMT, ...) \ + pr_debug("==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + pr_debug("<== %s()"FMT"\n", __func__, ##__VA_ARGS__) + +const char *digest_list_types_str[] = { + FOR_EACH_DIGEST_LIST_TYPE(GENERATE_STRING) +}; + +const char *digest_list_fields_str[] = { + FOR_EACH_FIELD(GENERATE_STRING) +}; + +const char *entry_fields_str[] = { + FOR_EACH_ENTRY_FIELD(GENERATE_STRING) +}; + +static int parse_digest_list_algo(struct digest_cache *digest_cache, + enum digest_list_fields field, + const u8 *field_data, u64 field_data_len) +{ + u8 algo; + int ret = 0; + + kenter(",%u,%llu", field, field_data_len); + + if (digest_cache->algo != HASH_ALGO__LAST) { + pr_debug("Digest algorithm already set to %s\n", + hash_algo_name[digest_cache->algo]); + ret = -EBADMSG; + goto out; + } + + if (field_data_len != sizeof(u8)) { + pr_debug("Unexpected data length %llu, expected %lu\n", + field_data_len, sizeof(u8)); + ret = -EBADMSG; + goto out; + } + + algo = *field_data; + + if (algo >= HASH_ALGO__LAST) { + pr_debug("Unexpected digest algo %u\n", algo); + ret = -EBADMSG; + goto out; + } + + digest_cache->algo = algo; + pr_debug("Digest algo: %s\n", hash_algo_name[algo]); +out: + kleave(" = %d", ret); + return ret; +} + +static int parse_entry_digest(struct digest_cache *digest_cache, + enum entry_fields field, const u8 *field_data, + u64 field_data_len) +{ + int ret = 0; + + kenter(",%u,%llu", field, field_data_len); + + if (field_data_len != hash_digest_size[digest_cache->algo]) { + pr_debug("Unexpected data length %llu, expected %d\n", + field_data_len, hash_digest_size[digest_cache->algo]); + ret = -EBADMSG; + goto out; + } + + digest_cache_add(digest_cache, (u8 *)field_data); +out: + kleave(" = %d", ret); + return ret; +} + +static int entry_callback(void *callback_data, u64 field, const u8 *field_data, + u64 field_data_len) +{ + struct digest_cache *digest_cache; + int ret; + + digest_cache = (struct digest_cache *)callback_data; + + switch (field) { + case ENTRY_DIGEST: + ret = parse_entry_digest(digest_cache, field, field_data, + field_data_len); + break; + case ENTRY_PATH: + ret = 0; + break; + default: + pr_debug("Unhandled field %s\n", entry_fields_str[field]); + /* Just ignore non-relevant fields. */ + ret = 0; + break; + } + + return ret; +} + +static int parse_digest_list_entry(struct digest_cache *digest_cache, + enum digest_list_fields field, + const u8 *field_data, u64 field_data_len) +{ + int ret; + + kenter(",%u,%llu", field, field_data_len); + + ret = tlv_parse(DIGEST_LIST_FILE, entry_callback, digest_cache, + field_data, field_data_len, digest_list_types_str, + DIGEST_LIST__LAST, entry_fields_str, ENTRY__LAST); + + kleave(" = %d", ret); + return ret; +} + +static int digest_list_callback(void *callback_data, u64 field, + const u8 *field_data, u64 field_data_len) +{ + struct digest_cache *digest_cache; + int ret; + + digest_cache = (struct digest_cache *)callback_data; + + switch (field) { + case DIGEST_LIST_ALGO: + ret = parse_digest_list_algo(digest_cache, field, field_data, + field_data_len); + break; + case DIGEST_LIST_ENTRY: + ret = parse_digest_list_entry(digest_cache, field, field_data, + field_data_len); + break; + default: + pr_debug("Unhandled field %s\n", + digest_list_fields_str[field]); + /* Just ignore non-relevant fields. */ + ret = 0; + break; + } + + return ret; +} + +int digest_list_parse_tlv(struct digest_cache *digest_cache, const u8 *data, + size_t data_len) +{ + u64 parsed_data_type; + u64 parsed_num_fields; + u64 parsed_total_len; + int ret; + + ret = tlv_parse_hdr(&data, &data_len, &parsed_data_type, + &parsed_num_fields, &parsed_total_len, + digest_list_types_str, DIGEST_LIST__LAST); + if (ret < 0) + return ret; + + if (parsed_data_type != DIGEST_LIST_FILE) + return 0; + + ret = digest_cache_init_htable(digest_cache, parsed_num_fields); + if (ret < 0) + return ret; + + return tlv_parse_data(digest_list_callback, digest_cache, + parsed_num_fields, data, data_len, + digest_list_fields_str, FIELD__LAST); +} From patchwork Sat Aug 12 10:46:09 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351780 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 51B751FB7 for ; Sat, 12 Aug 2023 10:48:53 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1844A2D5B; Sat, 12 Aug 2023 03:48:32 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4RNHCP2p8vz9yyf1; Sat, 12 Aug 2023 18:36:49 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S8; Sat, 12 Aug 2023 11:48:04 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 06/13] integrity/digest_cache: Parse rpm digest lists Date: Sat, 12 Aug 2023 12:46:09 +0200 Message-Id: <20230812104616.2190095-7-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S8 X-Coremail-Antispam: 1UD129KBjvJXoW3Gry7tFW5KryrWw1rXry8uFg_yoW3trWkpa 4DKFy8trWkXF1Skws7AF12kr1Sq3yqgFnFqrZ8uFn0yFZIvryjva18AryxZryrJr4DZFy7 Gr4YqF129F4DtaDanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBvb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUCVW8JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20x vY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I 3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIx AIcVC0I7IYx2IY67AKxVWUCVW8JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr1j6F4UJwCI 42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z2 80aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x07UZo7tUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAHBF1jj5KVZQADsS X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Implement a simple parser of RPM headers, that extracts the digest and the algorithm of the packaged files from the RPMTAG_FILEDIGESTS and RPMTAG_FILEDIGESTALGO section, and add them to the digest cache. Signed-off-by: Roberto Sassu --- security/integrity/Makefile | 3 +- security/integrity/digest_cache.c | 2 + .../integrity/digest_list_parsers/parsers.h | 2 + security/integrity/digest_list_parsers/rpm.c | 215 ++++++++++++++++++ 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 security/integrity/digest_list_parsers/rpm.c diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 3765b004e66..c4c17a57d84 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -13,7 +13,8 @@ integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyrin integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o \ digest_cache_iter.o \ - digest_list_parsers/tlv.o + digest_list_parsers/tlv.o \ + digest_list_parsers/rpm.o integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \ platform_certs/load_uefi.o \ platform_certs/keyring_handler.o diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c index 818ac0ac0bf..fc392b925a5 100644 --- a/security/integrity/digest_cache.c +++ b/security/integrity/digest_cache.c @@ -144,6 +144,8 @@ static int digest_cache_parse_digest_list(struct digest_cache *digest_cache, if (!strncmp(digest_list_path->dentry->d_name.name, "tlv-", 4)) ret = digest_list_parse_tlv(digest_cache, data, data_len); + else if (!strncmp(digest_list_path->dentry->d_name.name, "rpm-", 4)) + ret = digest_list_parse_rpm(digest_cache, data, data_len); return ret; } diff --git a/security/integrity/digest_list_parsers/parsers.h b/security/integrity/digest_list_parsers/parsers.h index e8fff2374d8..f86e58e9806 100644 --- a/security/integrity/digest_list_parsers/parsers.h +++ b/security/integrity/digest_list_parsers/parsers.h @@ -11,3 +11,5 @@ int digest_list_parse_tlv(struct digest_cache *digest_cache, const u8 *data, size_t data_len); +int digest_list_parse_rpm(struct digest_cache *digest_cache, const u8 *data, + size_t data_len); diff --git a/security/integrity/digest_list_parsers/rpm.c b/security/integrity/digest_list_parsers/rpm.c new file mode 100644 index 00000000000..df2029d042f --- /dev/null +++ b/security/integrity/digest_list_parsers/rpm.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Parse an rpm digest list (RPM package header). + */ + +#define pr_fmt(fmt) "RPM DIGEST LIST: "fmt +#include + +#include "parsers.h" + +#define RPMTAG_FILEDIGESTS 1035 +#define RPMTAG_FILEDIGESTALGO 5011 + +#define RPM_INT32_TYPE 4 +#define RPM_STRING_ARRAY_TYPE 8 + +struct rpm_hdr { + u32 magic; + u32 reserved; + u32 tags; + u32 datasize; +} __packed; + +struct rpm_entryinfo { + s32 tag; + u32 type; + s32 offset; + u32 count; +} __packed; + +enum pgp_algos { + DIGEST_ALGO_MD5 = 1, + DIGEST_ALGO_SHA1 = 2, + DIGEST_ALGO_RMD160 = 3, + /* 4, 5, 6, and 7 are reserved. */ + DIGEST_ALGO_SHA256 = 8, + DIGEST_ALGO_SHA384 = 9, + DIGEST_ALGO_SHA512 = 10, + DIGEST_ALGO_SHA224 = 11, +}; + +static const enum hash_algo pgp_algo_mapping[DIGEST_ALGO_SHA224 + 1] = { + [DIGEST_ALGO_MD5] = HASH_ALGO_MD5, + [DIGEST_ALGO_SHA1] = HASH_ALGO_SHA1, + [DIGEST_ALGO_RMD160] = HASH_ALGO_RIPE_MD_160, + [4] = HASH_ALGO__LAST, + [5] = HASH_ALGO__LAST, + [6] = HASH_ALGO__LAST, + [7] = HASH_ALGO__LAST, + [DIGEST_ALGO_SHA256] = HASH_ALGO_SHA256, + [DIGEST_ALGO_SHA384] = HASH_ALGO_SHA384, + [DIGEST_ALGO_SHA512] = HASH_ALGO_SHA512, + [DIGEST_ALGO_SHA224] = HASH_ALGO_SHA224, +}; + +int digest_list_parse_rpm(struct digest_cache *digest_cache, const u8 *data, + size_t data_len) +{ + const unsigned char rpm_header_magic[8] = { + 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00 + }; + const struct rpm_hdr *hdr; + const struct rpm_entryinfo *entry; + uint32_t tags, max_tags, datasize; + uint32_t digests_count, max_digests_count; + uint32_t digests_offset, algo_offset; + uint32_t digest_len, pkg_pgp_algo, i; + bool algo_offset_set = false, digests_offset_set = false; + enum hash_algo pkg_kernel_algo = HASH_ALGO_MD5; + u8 rpm_digest[SHA512_DIGEST_SIZE]; + int ret; + + if (data_len < sizeof(*hdr)) { + pr_debug("Not enough data for RPM header, current %ld, expected: %ld\n", + data_len, sizeof(*hdr)); + return -EINVAL; + } + + if (memcmp(data, rpm_header_magic, sizeof(rpm_header_magic))) { + pr_debug("RPM header magic mismatch\n"); + return -EINVAL; + } + + hdr = (const struct rpm_hdr *)data; + data += sizeof(*hdr); + data_len -= sizeof(*hdr); + + tags = __be32_to_cpu(hdr->tags); + max_tags = data_len / sizeof(*entry); + + /* Finite termination on tags loop. */ + if (tags > max_tags) + return -EINVAL; + + datasize = __be32_to_cpu(hdr->datasize); + if (datasize != data_len - tags * sizeof(*entry)) + return -EINVAL; + + pr_debug("Scanning %d RPM header sections\n", tags); + for (i = 0; i < tags; i++) { + if (data_len < sizeof(*entry)) + return -EINVAL; + + entry = (const struct rpm_entryinfo *)data; + data += sizeof(*entry); + data_len -= sizeof(*entry); + + switch (__be32_to_cpu(entry->tag)) { + case RPMTAG_FILEDIGESTS: + if (__be32_to_cpu(entry->type) != RPM_STRING_ARRAY_TYPE) + return -EINVAL; + + digests_offset = __be32_to_cpu(entry->offset); + digests_count = __be32_to_cpu(entry->count); + digests_offset_set = true; + + pr_debug("Found RPMTAG_FILEDIGESTS at offset %u, count: %u\n", + digests_offset, digests_count); + break; + case RPMTAG_FILEDIGESTALGO: + if (__be32_to_cpu(entry->type) != RPM_INT32_TYPE) + return -EINVAL; + + algo_offset = __be32_to_cpu(entry->offset); + algo_offset_set = true; + + pr_debug("Found RPMTAG_FILEDIGESTALGO at offset %u\n", + algo_offset); + break; + default: + break; + } + } + + if (!digests_offset_set) + return -EINVAL; + + if (algo_offset_set) { + if (algo_offset >= data_len) + return -EINVAL; + + if (data_len - algo_offset < sizeof(uint32_t)) + return -EINVAL; + + pkg_pgp_algo = *(uint32_t *)&data[algo_offset]; + pkg_pgp_algo = __be32_to_cpu(pkg_pgp_algo); + if (pkg_pgp_algo > DIGEST_ALGO_SHA224) { + pr_debug("Unknown PGP algo %d\n", pkg_pgp_algo); + return -EINVAL; + } + + pkg_kernel_algo = pgp_algo_mapping[pkg_pgp_algo]; + if (pkg_kernel_algo >= HASH_ALGO__LAST) { + pr_debug("Unknown mapping for PGP algo %d\n", + pkg_pgp_algo); + return -EINVAL; + } + + pr_debug("Found mapping for PGP algo %d: %s\n", pkg_pgp_algo, + hash_algo_name[pkg_kernel_algo]); + } + + digest_cache->algo = pkg_kernel_algo; + digest_len = hash_digest_size[pkg_kernel_algo]; + + if (digests_offset > data_len) + return -EINVAL; + + /* Worst case, every digest is a \0. */ + max_digests_count = data_len - digests_offset; + + /* Finite termination on digests_count loop. */ + if (digests_count > max_digests_count) + return -EINVAL; + + ret = digest_cache_init_htable(digest_cache, digests_count); + if (ret < 0) + return ret; + + ret = -ENOENT; + + for (i = 0; i < digests_count; i++) { + if (digests_offset == data_len) + return -EINVAL; + + if (!data[digests_offset]) { + digests_offset++; + continue; + } + + if (data_len - digests_offset < digest_len * 2 + 1) + return -EINVAL; + + ret = hex2bin(rpm_digest, (const char *)&data[digests_offset], + digest_len); + if (ret < 0) { + pr_debug("Invalid hex format for digest %s\n", + &data[digests_offset]); + ret = -EINVAL; + break; + } + + ret = digest_cache_add(digest_cache, rpm_digest); + if (ret < 0) + return ret; + + digests_offset += digest_len * 2 + 1; + } + + return ret; +} From patchwork Sat Aug 12 10:46:10 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351781 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CF371370 for ; Sat, 12 Aug 2023 10:49:19 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DE83D3593; Sat, 12 Aug 2023 03:48:44 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4RNHCZ4v8gz9yydv; Sat, 12 Aug 2023 18:36:58 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S9; Sat, 12 Aug 2023 11:48:13 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 07/13] ima: Add digest_cache policy keyword Date: Sat, 12 Aug 2023 12:46:10 +0200 Message-Id: <20230812104616.2190095-8-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S9 X-Coremail-Antispam: 1UD129KBjvJXoW3CrWruw17uFyrAr15trW8WFg_yoWkCF17pa yvg3WUCr48ZryS9r1xAay29r4FgrWfta1UA398X342yanxXr10vw1fJr13AFy5ArW5CF92 yF1Ygr4DuF4jvaDanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBvb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUCVW8JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20x vY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I 3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIx AIcVC0I7IYx2IY67AKxVWUCVW8JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr1j6F4UJwCI 42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z2 80aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x07UZo7tUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAHBF1jj5KVZQAEsV X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Add the digest_cache policy keyword, to enable the use of the digest cache for specific IMA actions and purpose. At the moment, the digest cache can be used for measurement and appraisal of file content. They are reflected respectively with the new flags DIGEST_CACHE_MEASURE and DIGEST_CACHE_APPRAISE_CONTENT. Depending on the IMA action of the parsed rule, the new flags are set in the digest_cache_mask variable and passed back to process_measurement(), so that the latter can determine whether or not it can use the digest cache. The digest cache cannot be used for measurement on the default PCR. It cannot also be used for appraisal together with the other appraisal methods (imasig, sigv3, modsig). Also, currently, the digest cache cannot be used to measure/appraise digest lists. This functionality will be added in the future. Signed-off-by: Roberto Sassu --- Documentation/ABI/testing/ima_policy | 5 ++- security/integrity/ima/ima.h | 6 ++- security/integrity/ima/ima_api.c | 6 ++- security/integrity/ima/ima_appraise.c | 2 +- security/integrity/ima/ima_main.c | 8 ++-- security/integrity/ima/ima_policy.c | 56 ++++++++++++++++++++++++++- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index 14d92c687ef..7792e65b35c 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -29,7 +29,7 @@ Description: [obj_user=] [obj_role=] [obj_type=]] option: [digest_type=] [template=] [permit_directio] [appraise_type=] [appraise_flag=] - [appraise_algos=] [keyrings=] + [appraise_algos=] [keyrings=] [digest_cache=] base: func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK] [FIRMWARE_CHECK] @@ -77,6 +77,9 @@ Description: For example, "sha256,sha512" to only accept to appraise files where the security.ima xattr was hashed with one of these two algorithms. + digest_cache:= [content] + "content" means that the digest cache is used only + for file content measurement and/or appraisal. default policy: # PROC_SUPER_MAGIC diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 3aef3d8fb57..f8f91d5c04a 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -260,7 +260,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, u32 secid, int mask, enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, - const char *func_data, unsigned int *allowed_algos); + const char *func_data, unsigned int *allowed_algos, + u64 *digest_cache_mask); 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, @@ -291,7 +292,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, u32 secid, enum ima_hooks func, int mask, int flags, int *pcr, struct ima_template_desc **template_desc, - const char *func_data, unsigned int *allowed_algos); + const char *func_data, unsigned int *allowed_algos, + u64 *digest_cache_mask); void ima_init_policy(void); void ima_update_policy(void); void ima_update_policy_flags(void); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 452e80b541e..c591b093bb5 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -173,6 +173,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename, * @template_desc: pointer filled in if matched measure policy sets template= * @func_data: func specific data, may be NULL * @allowed_algos: allowlist of hash algorithms for the IMA xattr + * @digest_cache_mask: For which actions and purpose the digest cache is usable * * The policy is defined in terms of keypairs: * subj=, obj=, type=, func=, mask=, fsmagic= @@ -190,7 +191,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, u32 secid, int mask, enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, - const char *func_data, unsigned int *allowed_algos) + const char *func_data, unsigned int *allowed_algos, + u64 *digest_cache_mask) { int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE | IMA_HASH; @@ -198,7 +200,7 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode, return ima_match_policy(idmap, inode, cred, secid, func, mask, flags, pcr, template_desc, func_data, - allowed_algos); + allowed_algos, digest_cache_mask); } static bool ima_get_verity_digest(struct integrity_iint_cache *iint, diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 491c1aca0b1..10dbafdae3d 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -81,7 +81,7 @@ int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, security_current_getsecid_subj(&secid); return ima_match_policy(idmap, inode, current_cred(), secid, func, mask, IMA_APPRAISE | IMA_HASH, NULL, - NULL, NULL, NULL); + NULL, NULL, NULL, NULL); } static int ima_fix_xattr(struct dentry *dentry, diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 81abdc8b233..4fdfc399fa6 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -231,7 +231,7 @@ static int process_measurement(struct file *file, const struct cred *cred, */ action = ima_get_action(file_mnt_idmap(file), inode, cred, secid, mask, func, &pcr, &template_desc, NULL, - &allowed_algos); + &allowed_algos, NULL); violation_check = ((func == FILE_CHECK || func == MMAP_CHECK || func == MMAP_CHECK_REQPROT) && (ima_policy_flag & IMA_MEASURE)); @@ -473,11 +473,11 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot) inode = file_inode(vma->vm_file); action = ima_get_action(file_mnt_idmap(vma->vm_file), inode, current_cred(), secid, MAY_EXEC, MMAP_CHECK, - &pcr, &template, NULL, NULL); + &pcr, &template, NULL, NULL, NULL); action |= ima_get_action(file_mnt_idmap(vma->vm_file), inode, current_cred(), secid, MAY_EXEC, MMAP_CHECK_REQPROT, &pcr, &template, NULL, - NULL); + NULL, NULL); /* Is the mmap'ed file in policy? */ if (!(action & (IMA_MEASURE | IMA_APPRAISE_SUBMASK))) @@ -958,7 +958,7 @@ int process_buffer_measurement(struct mnt_idmap *idmap, security_current_getsecid_subj(&secid); action = ima_get_action(idmap, inode, current_cred(), secid, 0, func, &pcr, &template, - func_data, NULL); + func_data, NULL, NULL); if (!(action & IMA_MEASURE) && !digest) return -ENOENT; } diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index b32c83d8a72..8583479112d 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -122,6 +122,7 @@ struct ima_rule_entry { struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */ struct ima_rule_opt_list *label; /* Measure data grouped under this label */ struct ima_template_desc *template; + u64 digest_cache_mask; /* For which actions and purpose the digest cache is usable */ }; /* @@ -726,6 +727,7 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func) * @template_desc: the template that should be used for this rule * @func_data: func specific data, may be NULL * @allowed_algos: allowlist of hash algorithms for the IMA xattr + * @digest_cache_mask: For which actions and purpose the digest cache is usable * * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type) * conditions. @@ -738,7 +740,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, u32 secid, enum ima_hooks func, int mask, int flags, int *pcr, struct ima_template_desc **template_desc, - const char *func_data, unsigned int *allowed_algos) + const char *func_data, unsigned int *allowed_algos, + u64 *digest_cache_mask) { struct ima_rule_entry *entry; int action = 0, actmask = flags | (flags << 1); @@ -783,6 +786,9 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, if (template_desc && entry->template) *template_desc = entry->template; + if (digest_cache_mask) + *digest_cache_mask |= entry->digest_cache_mask; + if (!actmask) break; } @@ -1073,7 +1079,7 @@ enum policy_opt { Opt_digest_type, Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos, Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings, - Opt_label, Opt_err + Opt_label, Opt_digest_cache, Opt_err }; static const match_table_t policy_tokens = { @@ -1122,6 +1128,7 @@ static const match_table_t policy_tokens = { {Opt_template, "template=%s"}, {Opt_keyrings, "keyrings=%s"}, {Opt_label, "label=%s"}, + {Opt_digest_cache, "digest_cache=%s"}, {Opt_err, NULL} }; @@ -1886,6 +1893,26 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) &(template_desc->num_fields)); entry->template = template_desc; break; + case Opt_digest_cache: + ima_log_string(ab, "template", args[0].from); + + result = -EINVAL; + + if (!strcmp(args[0].from, "content")) { + switch (entry->action) { + case MEASURE: + entry->digest_cache_mask |= DIGEST_CACHE_MEASURE; + result = 0; + break; + case APPRAISE: + entry->digest_cache_mask |= DIGEST_CACHE_APPRAISE_CONTENT; + result = 0; + break; + default: + break; + } + } + break; case Opt_err: ima_log_string(ab, "UNKNOWN", p); result = -EINVAL; @@ -1912,6 +1939,28 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) "verity rules should include d-ngv2"); } + /* New-style measurements with digest cache cannot be on default PCR. */ + if (entry->action == MEASURE && + (entry->digest_cache_mask & DIGEST_CACHE_MEASURE)) { + if (!(entry->flags & IMA_PCR) || + entry->pcr == CONFIG_IMA_MEASURE_PCR_IDX) + result = -EINVAL; + } + + /* Digest cache should not conflict with other appraisal methods. */ + if (entry->action == APPRAISE && + (entry->digest_cache_mask & DIGEST_CACHE_APPRAISE_CONTENT)) { + if ((entry->flags & IMA_DIGSIG_REQUIRED) || + (entry->flags & IMA_VERITY_REQUIRED) || + (entry->flags & IMA_MODSIG_ALLOWED)) + result = -EINVAL; + } + + /* Digest cache cannot be used to measure/appraise digest lists. */ + if ((entry->flags & IMA_FUNC) && entry->func == DIGEST_LIST_CHECK && + entry->digest_cache_mask) + result = -EINVAL; + audit_log_format(ab, "res=%d", !result); audit_log_end(ab); return result; @@ -2278,6 +2327,9 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, "appraise_flag=check_blacklist "); if (entry->flags & IMA_PERMIT_DIRECTIO) seq_puts(m, "permit_directio "); + if ((entry->digest_cache_mask & DIGEST_CACHE_MEASURE) || + (entry->digest_cache_mask & DIGEST_CACHE_APPRAISE_CONTENT)) + seq_puts(m, "digest_cache=content "); rcu_read_unlock(); seq_puts(m, "\n"); return 0; From patchwork Sat Aug 12 10:46:11 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351782 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6ADCB370 for ; Sat, 12 Aug 2023 10:49:35 +0000 (UTC) Received: from frasgout12.his.huawei.com (unknown [14.137.139.154]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4475A3A8F; Sat, 12 Aug 2023 03:48:57 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4RNH9M1rR3z9ygHK; Sat, 12 Aug 2023 18:35:03 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S10; Sat, 12 Aug 2023 11:48:22 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 08/13] ima: Use digest cache for measurement Date: Sat, 12 Aug 2023 12:46:11 +0200 Message-Id: <20230812104616.2190095-9-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S10 X-Coremail-Antispam: 1UD129KBjvJXoW3Gr1rtFWfZrW8uFW7Aw1kZrb_yoWxJr1Upa 9xuF1Fkrs5ZFyfCrn3A3W7Aa1S9rZ7tF4UGws8X34Skay3Xr10va1rAw129Fy5Jry5Xa4x ta1jgr4Uuw4jyaDanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBvb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUCVW8JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20x vY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I 3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIx AIcVC0I7IYx2IY67AKxVWUCVW8JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr1j6F4UJwCI 42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z2 80aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x07UZo7tUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAHBF1jj46UrwAAsF X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu If a measure rule contains 'digest_cache=content', get the digest cache (if available) associated to the file being measured. AND the digest list mask from the IMA policy with the digest list mask of the digest cache (set depending on the actions done on the digest lists), to determine if the digest cache can be used for the measurement action. If the digest cache is enabled, lookup the calculated digest of the file being accessed and if found, pass the ANDed masks to ima_store_measurement(). Otherwise, reset the mask to zero. Finally, if the DIGEST_CACHE_MEASURE flag is set in the mask, mark the file as measured for the supplied PCR (which cannot be the default one). At the first digest list accessed, iterate over all digest lists in the same directory, and measure them to make the PCR predictable. However, don't parse those digest lists except the requested one, to avoid too much memory pressure. Skipping the measurement of cached digests causes less information to be available to remote verifiers. In particular, they would know that a subset or all files in the measured digest list could have been accessed, but they won't know if and when. Signed-off-by: Roberto Sassu --- security/integrity/ima/ima.h | 3 ++- security/integrity/ima/ima_api.c | 16 +++++++++++++++- security/integrity/ima/ima_main.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index f8f91d5c04a..bb75cc3d2fd 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -270,7 +270,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, - struct ima_template_desc *template_desc); + struct ima_template_desc *template_desc, + u64 digest_cache_mask); int process_buffer_measurement(struct mnt_idmap *idmap, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index c591b093bb5..77b1ecff45a 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -339,7 +339,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, - struct ima_template_desc *template_desc) + struct ima_template_desc *template_desc, + u64 digest_cache_mask) { static const char op[] = "add_template_measure"; static const char audit_cause[] = "ENOMEM"; @@ -363,6 +364,19 @@ void ima_store_measurement(struct integrity_iint_cache *iint, if (iint->measured_pcrs & (0x1 << pcr) && !modsig) return; + /* + * If the file digest was found in the digest cache, the digest cache + * is enabled for measurement, and the digest list was measured, mark + * the file as measured, so that it does not appear in the measurement + * list (known digest), and the same action is not repeated at the next + * access. + */ + if (digest_cache_mask & DIGEST_CACHE_MEASURE) { + iint->flags |= IMA_MEASURED; + iint->measured_pcrs |= (0x1 << pcr); + return; + } + result = ima_alloc_init_template(&event_data, &entry, template_desc); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 4fdfc399fa6..54d006fc490 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -221,6 +221,8 @@ static int process_measurement(struct file *file, const struct cred *cred, bool violation_check; enum hash_algo hash_algo; unsigned int allowed_algos = 0; + u64 digest_cache_mask = 0; + struct digest_cache *digest_cache = NULL; if (!ima_policy_flag || !S_ISREG(inode->i_mode)) return 0; @@ -231,7 +233,7 @@ static int process_measurement(struct file *file, const struct cred *cred, */ action = ima_get_action(file_mnt_idmap(file), inode, cred, secid, mask, func, &pcr, &template_desc, NULL, - &allowed_algos, NULL); + &allowed_algos, &digest_cache_mask); violation_check = ((func == FILE_CHECK || func == MMAP_CHECK || func == MMAP_CHECK_REQPROT) && (ima_policy_flag & IMA_MEASURE)); @@ -263,6 +265,21 @@ static int process_measurement(struct file *file, const struct cred *cred, if (!action) goto out; + if (digest_cache_mask) { + /* + * Prefetch the digest lists to measure them in a deterministic + * way, and make the PCR predictable. + */ + if (digest_cache_mask & DIGEST_CACHE_MEASURE) + digest_cache_iter_dir(file_dentry(file)); + + digest_cache = digest_cache_get(file_dentry(file), iint); + if (digest_cache) + digest_cache_mask &= digest_cache->mask; + else + digest_cache_mask = 0; + } + mutex_lock(&iint->mutex); if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags)) @@ -349,10 +366,17 @@ static int process_measurement(struct file *file, const struct cred *cred, if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */ pathname = ima_d_path(&file->f_path, &pathbuf, filename); + if (rc == 0 && digest_cache_mask) { + if (digest_cache_lookup(digest_cache, iint->ima_hash->digest, + iint->ima_hash->algo, pathname)) + /* Reset the mask, the file digest was not found. */ + digest_cache_mask = 0; + } + if (action & IMA_MEASURE) ima_store_measurement(iint, file, pathname, xattr_value, xattr_len, modsig, pcr, - template_desc); + template_desc, digest_cache_mask); if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) { rc = ima_check_blacklist(iint, modsig, pcr); if (rc != -EPERM) { From patchwork Sat Aug 12 10:46:12 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351783 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BF1E4370 for ; Sat, 12 Aug 2023 10:49:54 +0000 (UTC) Received: from frasgout12.his.huawei.com (unknown [14.137.139.154]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E2C2730DA; Sat, 12 Aug 2023 03:49:12 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.227]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4RNH9X3ly0z9ygHG; Sat, 12 Aug 2023 18:35:12 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S11; Sat, 12 Aug 2023 11:48:31 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 09/13] ima: Use digest cache for appraisal Date: Sat, 12 Aug 2023 12:46:12 +0200 Message-Id: <20230812104616.2190095-10-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S11 X-Coremail-Antispam: 1UD129KBjvJXoWxuF13ur1UJF1rZFWktFy3twb_yoW5Kw17pa 98KF15GryrWFWa9rZ8Aa9I9ayFk3yvgF4DW398JwnFyFZxXr1jvryrJ342vFy5Xr1rJrn7 twnFgr1UAa1rt3DanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBvb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUCVW8JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20x vY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I 3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIx AIcVC0I7IYx2IY67AKxVWUCVW8JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr1j6F4UJwCI 42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z2 80aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x07UZo7tUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAHBF1jj5KVaQAAsd X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu If the digest of the accessed file is found in the digest cache, pass the ANDed masks from the IMA policy and from the digest cache to ima_appraise_measurement(). If the DIGEST_CACHE_APPRAISE_CONTENT flag is set in the mask, security.ima is not available, and the modsig method is disabled, grant access in read-only mode (except for new files). Since xattrs were not verified with EVM, writes need to be prevented to avoid the HMAC to be updated from an unverified one. Signed-off-by: Roberto Sassu --- security/integrity/ima/ima.h | 6 ++++-- security/integrity/ima/ima_appraise.c | 14 +++++++++++++- security/integrity/ima/ima_main.c | 3 ++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index bb75cc3d2fd..06887f8f1bc 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -322,7 +322,8 @@ int ima_appraise_measurement(enum ima_hooks func, struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, - int xattr_len, const struct modsig *modsig); + int xattr_len, const struct modsig *modsig, + u64 digest_cache_mask); int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func); void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); @@ -346,7 +347,8 @@ static inline int ima_appraise_measurement(enum ima_hooks func, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, - const struct modsig *modsig) + const struct modsig *modsig, + u8 digest_cache_mask) { return INTEGRITY_UNKNOWN; } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 10dbafdae3d..969a02802b9 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -479,7 +479,8 @@ int ima_appraise_measurement(enum ima_hooks func, struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, - int xattr_len, const struct modsig *modsig) + int xattr_len, const struct modsig *modsig, + u64 digest_cache_mask) { static const char op[] = "appraise_data"; const char *cause = "unknown"; @@ -514,6 +515,17 @@ int ima_appraise_measurement(enum ima_hooks func, (!(iint->flags & IMA_DIGSIG_REQUIRED) || (inode->i_size == 0))) status = INTEGRITY_PASS; + /* + * Except for new files, use the digest cache to appraise the + * file content and, at the same time, mark the file as + * immutable to prevent file updates and transitioning from an + * unverified HMAC to a valid HMAC. + */ + if (status != INTEGRITY_PASS && + (digest_cache_mask & DIGEST_CACHE_APPRAISE_CONTENT)) { + set_bit(IMA_DIGSIG, &iint->atomic_flags); + status = INTEGRITY_PASS; + } goto out; } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 54d006fc490..b458ef62c4c 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -383,7 +383,8 @@ static int process_measurement(struct file *file, const struct cred *cred, inode_lock(inode); rc = ima_appraise_measurement(func, iint, file, pathname, xattr_value, - xattr_len, modsig); + xattr_len, modsig, + digest_cache_mask); inode_unlock(inode); } if (!rc) From patchwork Sat Aug 12 10:46:13 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351784 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED4D3370 for ; Sat, 12 Aug 2023 10:50:18 +0000 (UTC) Received: from frasgout13.his.huawei.com (unknown [14.137.139.46]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E8B0F3A8B; Sat, 12 Aug 2023 03:49:42 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4RNHD90CZszB03LR; Sat, 12 Aug 2023 18:37:29 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S12; Sat, 12 Aug 2023 11:48:41 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 10/13] tools: Add tool to manage digest lists Date: Sat, 12 Aug 2023 12:46:13 +0200 Message-Id: <20230812104616.2190095-11-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S12 X-Coremail-Antispam: 1UD129KBjvAXoWftryrWF1rCw4rXF1rCFyrCrg_yoW8Kw43Ko Z2qF45Gw1ftr17CF4kuFn3Xa17GwnYkrWkCry8JrWDZF1rJF1rKanFkFW5uFy3Wr4rKFy3 ur40q34xur48JrZ7n29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUYK7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F4 0Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC 6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1l42xK82 IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAqx4xG67AKxVWUJVWUGwC2 0s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r4a6rW5MIIYrxkI7VAKI48JMI IF0xvE2Ix0cI8IcVAFwI0_Gr0_Xr1lIxAIcVC0I7IYx2IY6xkF7I0E14v26r4UJVWxJr1l IxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r4j6F4UMIIF0xvEx4 A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuYvjxUFgAwUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAHBF1jj46UsAAAsa X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RDNS_DYNAMIC,SPF_HELO_NONE, SPF_NONE,URIBL_BLOCKED autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Add a tool to generate and manage the digest lists. Digest lists can be generated from a directory, an individual file, or from a list. Once generated, digest list content can be showed (digest algorithm and value, file path). Also, the tool can add/remove the security.digest_list xattr to/from each file in the generated digest lists. To select the proper generator and parser, each digest list file name must start with '-'. Signed-off-by: Roberto Sassu --- MAINTAINERS | 1 + tools/Makefile | 16 +- tools/digest-lists/.gitignore | 3 + tools/digest-lists/Makefile | 48 +++ tools/digest-lists/common.c | 163 ++++++++++ tools/digest-lists/common.h | 90 ++++++ tools/digest-lists/manage_digest_lists.c | 342 +++++++++++++++++++++ tools/digest-lists/manage_digest_lists.txt | 82 +++++ 8 files changed, 739 insertions(+), 6 deletions(-) create mode 100644 tools/digest-lists/.gitignore create mode 100644 tools/digest-lists/Makefile create mode 100644 tools/digest-lists/common.c create mode 100644 tools/digest-lists/common.h create mode 100644 tools/digest-lists/manage_digest_lists.c create mode 100644 tools/digest-lists/manage_digest_lists.txt diff --git a/MAINTAINERS b/MAINTAINERS index e5a325137e9..2bd85dcfd23 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10295,6 +10295,7 @@ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: security/integrity/ F: security/integrity/ima/ +F: tools/digest-lists/ INTEL 810/815 FRAMEBUFFER DRIVER M: Antonino Daplas diff --git a/tools/Makefile b/tools/Makefile index 37e9f680483..3789b5f292e 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -15,6 +15,7 @@ help: @echo ' counter - counter tools' @echo ' cpupower - a tool for all things x86 CPU power' @echo ' debugging - tools for debugging' + @echo ' digest-lists - tools for managing digest lists' @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer' @echo ' firmware - Firmware tools' @echo ' freefall - laptop accelerometer program for disk protection' @@ -69,7 +70,7 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) -cgroup counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi pci firmware debugging tracing: FORCE +cgroup counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi pci firmware debugging tracing digest-lists: FORCE $(call descend,$@) bpf/%: FORCE @@ -120,7 +121,8 @@ all: acpi cgroup counter cpupower gpio hv firewire \ perf selftests bootconfig spi turbostat usb \ virtio mm bpf x86_energy_perf_policy \ tmon freefall iio objtool kvm_stat wmi \ - pci debugging tracing thermal thermometer thermal-engine + pci debugging tracing thermal thermometer thermal-engine \ + digest-lists acpi_install: $(call descend,power/$(@:_install=),install) @@ -128,7 +130,7 @@ acpi_install: cpupower_install: $(call descend,power/$(@:_install=),install) -cgroup_install counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install pci_install debugging_install tracing_install: +cgroup_install counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install pci_install debugging_install tracing_install digest-lists_install: $(call descend,$(@:_install=),install) selftests_install: @@ -161,7 +163,8 @@ install: acpi_install cgroup_install counter_install cpupower_install gpio_insta virtio_install mm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ wmi_install pci_install debugging_install intel-speed-select_install \ - tracing_install thermometer_install thermal-engine_install + tracing_install thermometer_install thermal-engine_install \ + digest-lists_install acpi_clean: $(call descend,power/acpi,clean) @@ -169,7 +172,7 @@ acpi_clean: cpupower_clean: $(call descend,power/cpupower,clean) -cgroup_clean counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean: +cgroup_clean counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean digest-lists_clean: $(call descend,$(@:_clean=),clean) libapi_clean: @@ -214,6 +217,7 @@ clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firewire_cl mm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \ - intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean + intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean \ + digest-lists_clean .PHONY: FORCE diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore new file mode 100644 index 00000000000..1b8a7b9c205 --- /dev/null +++ b/tools/digest-lists/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +manage_digest_lists +manage_digest_lists.1 diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile new file mode 100644 index 00000000000..05af3a91c06 --- /dev/null +++ b/tools/digest-lists/Makefile @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../scripts/Makefile.include +include ../scripts/utilities.mak +BINDIR=usr/bin +MANDIR=usr/share/man +MAN1DIR=$(MANDIR)/man1 +CFLAGS=-ggdb -Wall + +PROGS=manage_digest_lists + +MAN1=manage_digest_lists.1 + +A2X=a2x +a2x_path := $(call get-executable,$(A2X)) + +all: man $(PROGS) + +manage_digest_lists: manage_digest_lists.c common.c + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) -lcrypto + +ifneq ($(findstring $(MAKEFLAGS),s),s) + ifneq ($(V),1) + QUIET_A2X = @echo ' A2X '$@; + endif +endif + +%.1: %.txt +ifeq ($(a2x_path),) + $(error "You need to install asciidoc for man pages") +else + $(QUIET_A2X)$(A2X) --doctype manpage --format manpage $< +endif + +clean: + rm -f $(MAN1) $(PROGS) + +man: $(MAN1) + +install-man: man + install -d -m 755 $(INSTALL_ROOT)/$(MAN1DIR) + install -m 644 $(MAN1) $(INSTALL_ROOT)/$(MAN1DIR) + +install-tools: $(PROGS) + install -d -m 755 $(INSTALL_ROOT)/$(BINDIR) + install -m 755 -p $(PROGS) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" + +install: install-tools install-man +.PHONY: all clean man install-tools install-man install diff --git a/tools/digest-lists/common.c b/tools/digest-lists/common.c new file mode 100644 index 00000000000..5378e677c09 --- /dev/null +++ b/tools/digest-lists/common.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Common functions and data. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +const char *const hash_algo_name[HASH_ALGO__LAST] = { + [HASH_ALGO_MD4] = "md4", + [HASH_ALGO_MD5] = "md5", + [HASH_ALGO_SHA1] = "sha1", + [HASH_ALGO_RIPE_MD_160] = "rmd160", + [HASH_ALGO_SHA256] = "sha256", + [HASH_ALGO_SHA384] = "sha384", + [HASH_ALGO_SHA512] = "sha512", + [HASH_ALGO_SHA224] = "sha224", + [HASH_ALGO_RIPE_MD_128] = "rmd128", + [HASH_ALGO_RIPE_MD_256] = "rmd256", + [HASH_ALGO_RIPE_MD_320] = "rmd320", + [HASH_ALGO_WP_256] = "wp256", + [HASH_ALGO_WP_384] = "wp384", + [HASH_ALGO_WP_512] = "wp512", + [HASH_ALGO_TGR_128] = "tgr128", + [HASH_ALGO_TGR_160] = "tgr160", + [HASH_ALGO_TGR_192] = "tgr192", + [HASH_ALGO_SM3_256] = "sm3", + [HASH_ALGO_STREEBOG_256] = "streebog256", + [HASH_ALGO_STREEBOG_512] = "streebog512", +}; + +const int hash_digest_size[HASH_ALGO__LAST] = { + [HASH_ALGO_MD4] = MD5_DIGEST_SIZE, + [HASH_ALGO_MD5] = MD5_DIGEST_SIZE, + [HASH_ALGO_SHA1] = SHA1_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_160] = RMD160_DIGEST_SIZE, + [HASH_ALGO_SHA256] = SHA256_DIGEST_SIZE, + [HASH_ALGO_SHA384] = SHA384_DIGEST_SIZE, + [HASH_ALGO_SHA512] = SHA512_DIGEST_SIZE, + [HASH_ALGO_SHA224] = SHA224_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_128] = RMD128_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_256] = RMD256_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_320] = RMD320_DIGEST_SIZE, + [HASH_ALGO_WP_256] = WP256_DIGEST_SIZE, + [HASH_ALGO_WP_384] = WP384_DIGEST_SIZE, + [HASH_ALGO_WP_512] = WP512_DIGEST_SIZE, + [HASH_ALGO_TGR_128] = TGR128_DIGEST_SIZE, + [HASH_ALGO_TGR_160] = TGR160_DIGEST_SIZE, + [HASH_ALGO_TGR_192] = TGR192_DIGEST_SIZE, + [HASH_ALGO_SM3_256] = SM3256_DIGEST_SIZE, + [HASH_ALGO_STREEBOG_256] = STREEBOG256_DIGEST_SIZE, + [HASH_ALGO_STREEBOG_512] = STREEBOG512_DIGEST_SIZE, +}; + +int read_file(const char *path, size_t *len, unsigned char **data) +{ + struct stat st; + int rc = 0, fd; + + if (stat(path, &st) == -1) + return -ENOENT; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -EACCES; + + *len = st.st_size; + + *data = mmap(NULL, *len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (*data == MAP_FAILED) + rc = -ENOMEM; + + close(fd); + return rc; +} + +int calc_digest(__u8 *digest, void *data, __u64 len, enum hash_algo algo) +{ + EVP_MD_CTX *mdctx; + const EVP_MD *md; + int ret = -EINVAL; + + OpenSSL_add_all_algorithms(); + + md = EVP_get_digestbyname(hash_algo_name[algo]); + if (!md) + goto out; + + mdctx = EVP_MD_CTX_create(); + if (!mdctx) + goto out; + + if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + goto out_mdctx; + + if (EVP_DigestUpdate(mdctx, data, len) != 1) + goto out_mdctx; + + if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1) + goto out_mdctx; + + ret = 0; +out_mdctx: + EVP_MD_CTX_destroy(mdctx); +out: + EVP_cleanup(); + return ret; +} + +int calc_file_digest(__u8 *digest, const char *path, enum hash_algo algo) +{ + unsigned char *data; + size_t len; + int ret; + + ret = read_file(path, &len, &data); + if (ret < 0) + return ret; + + ret = calc_digest(digest, data, len, algo); + + munmap(data, len); + return ret; +} + +ssize_t _write(int fd, void *buf, size_t buf_len) +{ + ssize_t len; + loff_t offset = 0; + + while (offset < buf_len) { + len = write(fd, buf + offset, buf_len - offset); + if (len < 0) + return -errno; + + offset += len; + } + + return buf_len; +} diff --git a/tools/digest-lists/common.h b/tools/digest-lists/common.h new file mode 100644 index 00000000000..d65168e2932 --- /dev/null +++ b/tools/digest-lists/common.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header of common.c + */ + +#include +#include +#include +#include + +#define MD5_DIGEST_SIZE 16 +#define SHA1_DIGEST_SIZE 20 +#define RMD160_DIGEST_SIZE 20 +#define SHA256_DIGEST_SIZE 32 +#define SHA384_DIGEST_SIZE 48 +#define SHA512_DIGEST_SIZE 64 +#define SHA224_DIGEST_SIZE 28 +#define RMD128_DIGEST_SIZE 16 +#define RMD256_DIGEST_SIZE 32 +#define RMD320_DIGEST_SIZE 40 +#define WP256_DIGEST_SIZE 32 +#define WP384_DIGEST_SIZE 48 +#define WP512_DIGEST_SIZE 64 +#define TGR128_DIGEST_SIZE 16 +#define TGR160_DIGEST_SIZE 20 +#define TGR192_DIGEST_SIZE 24 +#define SM3256_DIGEST_SIZE 32 +#define STREEBOG256_DIGEST_SIZE 32 +#define STREEBOG512_DIGEST_SIZE 64 + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) + +#define DIGEST_LIST_SIZE_MAX (64 * 1024 * 1024 - 1) + +/* In stripped ARM and x86-64 modules, ~ is surprisingly rare. */ +#define MODULE_SIG_STRING "~Module signature appended~\n" + +enum pkey_id_type { + PKEY_ID_PGP, /* OpenPGP generated key ID */ + PKEY_ID_X509, /* X.509 arbitrary subjectKeyIdentifier */ + PKEY_ID_PKCS7, /* Signature in PKCS#7 message */ +}; + +/* + * Module signature information block. + * + * The constituents of the signature section are, in order: + * + * - Signer's name + * - Key identifier + * - Signature data + * - Information block + */ +struct module_signature { + __u8 algo; /* Public-key crypto algorithm [0] */ + __u8 hash; /* Digest algorithm [0] */ + __u8 id_type; /* Key identifier type [PKEY_ID_PKCS7] */ + __u8 signer_len; /* Length of signer's name [0] */ + __u8 key_id_len; /* Length of key identifier [0] */ + __u8 __pad[3]; + __be32 sig_len; /* Length of signature data */ +}; + +enum ops { OP_GEN, OP_SHOW, OP_ADD_XATTR, OP_RM_XATTR, OP__LAST }; + +struct generator { + const char *name; + void *(*new)(int dirfd, char *input, enum hash_algo algo); + int (*add)(int dirfd, void *ptr, char *input); + void (*close)(void *ptr); +}; + +struct parser { + const char *name; + int (*parse)(const char *digest_list_path, enum ops op); +}; + +extern const char *ops_str[OP__LAST]; +extern const char *const hash_algo_name[HASH_ALGO__LAST]; +extern const int hash_digest_size[HASH_ALGO__LAST]; + +int read_file(const char *path, size_t *len, unsigned char **data); +int calc_digest(__u8 *digest, void *data, __u64 len, enum hash_algo algo); +int calc_file_digest(__u8 *digest, const char *path, enum hash_algo algo); +ssize_t _write(int fd, void *buf, size_t buf_len); diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c new file mode 100644 index 00000000000..bc425da5317 --- /dev/null +++ b/tools/digest-lists/manage_digest_lists.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement a tool to manage digest lists.. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +const char *ops_str[OP__LAST] = { + [OP_GEN] = "gen", + [OP_SHOW] = "show", + [OP_ADD_XATTR] = "add-xattr", + [OP_RM_XATTR] = "rm-xattr", +}; + +struct generator generators[] = { +}; + +struct parser parsers[] = { +}; + +static int generator_add(struct generator *generator, int dirfd, + void *ptr, char *input) +{ + char *full_path = input; + int ret; + + if (!generator->add) + return -ENOENT; + + if (strncmp(input, "rpmdb", 5)) { + full_path = realpath(input, NULL); + if (!full_path) { + printf("Error generating full path of %s\n", full_path); + return -ENOMEM; + } + } + + ret = generator->add(dirfd, ptr, full_path); + + if (full_path != input) + free(full_path); + + return ret; +} + +static int gen_digest_list(char *digest_list_format, char *digest_list_dir, + char *input, int input_is_list, __u8 algo) +{ + struct generator *generator; + void *ptr; + FTS *fts = NULL; + FTSENT *ftsent; + FILE *fp; + int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV); + char *paths[2] = { input, NULL }; + char line[1024], *p; + int ret, i, dirfd; + + for (i = 0; i < ARRAY_SIZE(generators); i++) + if (!strcmp(generators[i].name, digest_list_format)) + break; + + if (i == ARRAY_SIZE(generators)) { + printf("Cannot find generator for %s\n", digest_list_format); + return -ENOENT; + } + + generator = &generators[i]; + + dirfd = open(digest_list_dir, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) { + printf("Unable to open %s, ret: %d\n", digest_list_dir, -errno); + return -errno; + } + + if (generator->new) { + ptr = generator->new(dirfd, input, algo); + if (!ptr) { + ret = -ENOMEM; + goto out; + } + } + + if (input_is_list) { + fp = fopen(input, "r"); + if (!fp) { + ret = -EACCES; + goto out_close; + } + + while ((fgets(line, sizeof(line), fp))) { + p = strrchr(line, '\n'); + *p = '\0'; + + ret = generator_add(generator, dirfd, ptr, line); + if (ret < 0) { + printf("Error generating entry for %s, ret: %d\n", + line, ret); + fclose(fp); + goto out_close; + } + } + + fclose(fp); + goto out_close; + } else if (!strncmp(input, "rpmdb", 5)) { + ret = generator_add(generator, dirfd, ptr, input); + if (ret < 0) { + printf("Error generating entry for %s, ret: %d\n", + input, ret); + goto out_close; + } + } + + fts = fts_open(paths, fts_flags, NULL); + if (!fts) { + printf("Unable to open %s\n", input); + ret = -EACCES; + goto out_close; + } + + while ((ftsent = fts_read(fts)) != NULL) { + switch (ftsent->fts_info) { + case FTS_F: + ret = generator_add(generator, dirfd, ptr, + ftsent->fts_path); + if (ret < 0) { + printf("Error generating entry for %s, ret: %d\n", + ftsent->fts_path, ret); + goto out_fts_close; + } + default: + break; + } + } + +out_fts_close: + fts_close(fts); +out_close: + if (generator->close) + generator->close(ptr); +out: + close(dirfd); + return ret; +} + +static struct parser *get_parser(const char *filename) +{ + const char *separator; + int i; + + separator = strchr(filename, '-'); + if (!separator) + return NULL; + + for (i = 0; i < ARRAY_SIZE(parsers); i++) + if (!strncmp(parsers[i].name, filename, separator - filename)) + break; + + if (i == ARRAY_SIZE(parsers)) { + printf("Cannot find parser for %s\n", filename); + return NULL; + } + + return &parsers[i]; +} + +static int parse_digest_list(char *digest_list_format, char *digest_list_path, + enum ops op) +{ + struct parser *parser; + FTS *fts = NULL; + FTSENT *ftsent; + int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV); + char *paths[2] = { NULL, NULL }; + char *full_path = NULL; + int ret; + + full_path = realpath(digest_list_path, NULL); + if (!full_path) + return -ENOMEM; + + paths[0] = full_path; + + fts = fts_open(paths, fts_flags, NULL); + if (!fts) { + printf("Unable to open %s\n", digest_list_path); + free(full_path); + return -EACCES; + } + + while ((ftsent = fts_read(fts)) != NULL) { + switch (ftsent->fts_info) { + case FTS_F: + parser = get_parser(ftsent->fts_name); + if (!parser) + continue; + + ret = parser->parse(ftsent->fts_path, op); + if (ret < 0) { + printf("Error parsing entry %s, ret: %d\n", + ftsent->fts_path, ret); + goto out_fts_close; + } + + break; + default: + break; + } + } + +out_fts_close: + fts_close(fts); + free(full_path); + return ret; +} + +static void usage(char *progname) +{ + printf("Usage: %s \n", progname); + printf("Options:\n"); + printf("\t-d : directory digest lists are written to\n" + "\t-i : input digest list for an operation" + "\t-L: input is a list of files/directories\n" + "\t-a : digest list algorithm\n" + "\t-f : digest list format\n" + "\t-o : operation to perform\n" + "\t\tgen: generate a digest list\n" + "\t\tshow: show the content of a digest list\n" + "\t\tadd-xattr: set the " XATTR_NAME_DIGEST_LIST " xattr to the digest list path\n" + "\t\trm-xattr: remove the " XATTR_NAME_DIGEST_LIST " xattr\n" + "\t-h: display help\n"); +} + +int main(int argc, char *argv[]) +{ + char *digest_list_dir = NULL, *digest_list_format = NULL, *input = NULL; + enum hash_algo algo = HASH_ALGO_SHA256; + enum ops op = OP__LAST; + struct stat st; + int c, i; + int ret, input_is_list = 0; + + while ((c = getopt(argc, argv, "d:i:La:f:o:h")) != -1) { + switch (c) { + case 'd': + digest_list_dir = optarg; + break; + case 'i': + input = optarg; + break; + case 'L': + input_is_list = 1; + break; + case 'a': + for (i = 0; i < HASH_ALGO__LAST; i++) + if (!strcmp(hash_algo_name[i], optarg)) + break; + if (i == HASH_ALGO__LAST) { + printf("Invalid algo %s\n", optarg); + return -EINVAL; + } + algo = i; + break; + case 'f': + digest_list_format = optarg; + break; + case 'o': + for (op = 0; op < OP__LAST; op++) + if (!strcmp(ops_str[op], optarg)) + break; + if (op == OP__LAST) { + printf("Invalid op %s\n", optarg); + return -EINVAL; + } + break; + case 'h': + usage(argv[0]); + return 0; + default: + printf("Invalid option %c\n", c); + return -EINVAL; + } + } + + if (op == OP__LAST) { + printf("Operation not specified\n"); + return -ENOENT; + } + + switch (op) { + case OP_GEN: + if (!digest_list_format || !input || !digest_list_dir) { + printf("Missing format/input/digest list directory\n"); + return -ENOENT; + } + + if (stat(digest_list_dir, &st) == -1) { + ret = mkdir(digest_list_dir, 0755); + if (ret < 0) { + printf("Unable to create %s, ret: %d\n", + digest_list_dir, -errno); + return -errno; + } + } + + ret = gen_digest_list(digest_list_format, digest_list_dir, + input, input_is_list, algo); + break; + case OP_SHOW: + case OP_ADD_XATTR: + case OP_RM_XATTR: + if (!input) { + printf("Missing input\n"); + return -ENOENT; + } + + ret = parse_digest_list(digest_list_format, input, op); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} diff --git a/tools/digest-lists/manage_digest_lists.txt b/tools/digest-lists/manage_digest_lists.txt new file mode 100644 index 00000000000..62d655516e8 --- /dev/null +++ b/tools/digest-lists/manage_digest_lists.txt @@ -0,0 +1,82 @@ +manage_digest_lists(1) +====================== + +NAME +---- +manage_digest_lists - manage digest lists lifecycle + + +SYNOPSIS +-------- +manage_digest_lists [options] + + +DESCRIPTION +------------ +manage_digest_lists can be used to manage the lifecycle of digest lists (e.g. generate, show). + + +OPTIONS +------- +-d :: + directory digest lists are written to + +-i :: + input digest list for an operation + +-L:: + input is a list of files/directories + +-a :: + digest list algorithm + +-f :: + digest list format + +-o :: + operation to perform::: + gen:::: + generate a digest list + show:::: + show the content of a digest list + add-xattr:::: + set the security.digest_list xattr to the digest list path + rm-xattr:::: + remove the security.digest_list xattr + +-h:: + display help + + +EXAMPLES +-------- +Generate digest lists from the RPM database: + +# manage_digest_lists -d /etc/digest_lists -i rpmdb -o gen -f rpm + + +Generate digest lists for the kernel modules (for custom kernels): + +# manage_digest_lists -d /etc/digest_lists -i /lib/modules/`uname -r` -o gen -f tlv + + +Show digest lists content in /etc/digest_lists + +# manage_digest_lists -i /etc/digest_lists -o show + + +Add security.digest_list xattr for digest lists in /etc/digest_lists + +# manage_digest_lists -i /etc/digest_lists -o add-xattr + + +AUTHOR +------ +Written by Roberto Sassu, . + + +COPYING +------- +Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH. Free use of +this software is granted under the terms of the GNU Public License 2.0 +(GPLv2). From patchwork Sat Aug 12 10:46:14 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351785 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C4CCE370 for ; Sat, 12 Aug 2023 10:50:44 +0000 (UTC) Received: from frasgout12.his.huawei.com (unknown [14.137.139.154]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7ED073594; Sat, 12 Aug 2023 03:50:12 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4RNH9v2RGQz9ygHH; Sat, 12 Aug 2023 18:35:31 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S13; Sat, 12 Aug 2023 11:48:50 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 11/13] tools/digest-lists: Add tlv digest list generator and parser Date: Sat, 12 Aug 2023 12:46:14 +0200 Message-Id: <20230812104616.2190095-12-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S13 X-Coremail-Antispam: 1UD129KBjvAXoWfXr1xCr18urykKr43AFW5Wrg_yoW8WFWxZo ZaqF43Gw48Jr129F4kuF43ZF47Wa9Yqay5Aw1rGrWDX3WFyF18Ka1qka13Ja13Xw18trWj v3W0q3yagw48KrZ7n29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUYK7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F4 0Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC 6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1l42xK82 IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAqx4xG67AKxVWUJVWUGwC2 0s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r4a6rW5MIIYrxkI7VAKI48JMI IF0xvE2Ix0cI8IcVAFwI0_Gr0_Xr1lIxAIcVC0I7IYx2IY6xkF7I0E14v26r4UJVWxJr1l IxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r4j6F4UMIIF0xvEx4 A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuYvjxUFgAwUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAHBF1jj5KVagAAse X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE,URIBL_BLOCKED autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Add a generator of tlv digest lists. It will store the digest algorithm, the digest and path of each file provided as input. Also add a parser of tlv digest lists. It will display the content (digest algorithm and value, and file path), and will add/remove the security.digest_list xattr to/from each file in the digest list. Signed-off-by: Roberto Sassu --- tools/digest-lists/.gitignore | 2 + tools/digest-lists/Makefile | 22 ++- tools/digest-lists/generators/generators.h | 16 ++ tools/digest-lists/generators/tlv.c | 168 ++++++++++++++++++ tools/digest-lists/manage_digest_lists.c | 5 + tools/digest-lists/parsers/parsers.h | 14 ++ tools/digest-lists/parsers/tlv.c | 195 +++++++++++++++++++++ tools/digest-lists/parsers/tlv_parser.h | 38 ++++ 8 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 tools/digest-lists/generators/generators.h create mode 100644 tools/digest-lists/generators/tlv.c create mode 100644 tools/digest-lists/parsers/parsers.h create mode 100644 tools/digest-lists/parsers/tlv.c create mode 100644 tools/digest-lists/parsers/tlv_parser.h diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore index 1b8a7b9c205..9a75ae766ff 100644 --- a/tools/digest-lists/.gitignore +++ b/tools/digest-lists/.gitignore @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 manage_digest_lists manage_digest_lists.1 +libgen-tlv-list.so +libparse-tlv-list.so diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile index 05af3a91c06..23f9fa3b588 100644 --- a/tools/digest-lists/Makefile +++ b/tools/digest-lists/Makefile @@ -1,13 +1,23 @@ # SPDX-License-Identifier: GPL-2.0 include ../scripts/Makefile.include +include ../scripts/Makefile.arch include ../scripts/utilities.mak + BINDIR=usr/bin +ifeq ($(LP64), 1) + LIBDIR=usr/lib64 +else + LIBDIR=usr/lib +endif MANDIR=usr/share/man MAN1DIR=$(MANDIR)/man1 CFLAGS=-ggdb -Wall PROGS=manage_digest_lists +GENERATORS=libgen-tlv-list.so +PARSERS=libparse-tlv-list.so + MAN1=manage_digest_lists.1 A2X=a2x @@ -15,9 +25,15 @@ a2x_path := $(call get-executable,$(A2X)) all: man $(PROGS) -manage_digest_lists: manage_digest_lists.c common.c +manage_digest_lists: manage_digest_lists.c common.c $(GENERATORS) $(PARSERS) $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) -lcrypto +libgen-tlv-list.so: generators/tlv.c common.c + $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-tlv-list.so $^ -o $@ + +libparse-tlv-list.so: parsers/tlv.c common.c ../../lib/tlv_parser.c + $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-tlv-list.so $^ -o $@ -I parsers + ifneq ($(findstring $(MAKEFLAGS),s),s) ifneq ($(V),1) QUIET_A2X = @echo ' A2X '$@; @@ -32,7 +48,7 @@ else endif clean: - rm -f $(MAN1) $(PROGS) + rm -f $(MAN1) $(PROGS) $(GENERATORS) $(PARSERS) man: $(MAN1) @@ -43,6 +59,8 @@ install-man: man install-tools: $(PROGS) install -d -m 755 $(INSTALL_ROOT)/$(BINDIR) install -m 755 -p $(PROGS) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" + install -m 755 -p $(GENERATORS) "$(INSTALL_ROOT)/$(LIBDIR)/$(TARGET)" + install -m 755 -p $(PARSERS) "$(INSTALL_ROOT)/$(LIBDIR)/$(TARGET)" install: install-tools install-man .PHONY: all clean man install-tools install-man install diff --git a/tools/digest-lists/generators/generators.h b/tools/digest-lists/generators/generators.h new file mode 100644 index 00000000000..9830b791667 --- /dev/null +++ b/tools/digest-lists/generators/generators.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header for all digest list generators. + */ + +#include +#include +#include + +void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo); +int tlv_list_gen_add(int dirfd, void *ptr, char *input); +void tlv_list_gen_close(void *ptr); diff --git a/tools/digest-lists/generators/tlv.c b/tools/digest-lists/generators/tlv.c new file mode 100644 index 00000000000..cbc29a49f51 --- /dev/null +++ b/tools/digest-lists/generators/tlv.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Generate tlv digest lists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#include "../../../include/uapi/linux/tlv_parser.h" +#include "../../../include/uapi/linux/tlv_digest_list.h" +#include "../common.h" + +struct tlv_struct { + __u8 *digest_list; + struct tlv_hdr *outer_hdr; + struct tlv_entry *outer_entry; + __u8 algo; + int fd; +}; + +static int new_digest_list(int dirfd, const char *input, struct tlv_struct *tlv) +{ + char filename[NAME_MAX + 1]; + struct tlv_hdr *hdr; + const char *input_ptr; + + input_ptr = strrchr(input, '/'); + if (input_ptr) + input_ptr++; + else + input_ptr = input; + + snprintf(filename, sizeof(filename), "tlv-%s", input_ptr); + + tlv->fd = openat(dirfd, filename, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (tlv->fd < 0) { + printf("Unable to create %s\n", filename); + return -errno; + } + + ftruncate(tlv->fd, DIGEST_LIST_SIZE_MAX); + tlv->digest_list = mmap(NULL, DIGEST_LIST_SIZE_MAX, + PROT_READ | PROT_WRITE, MAP_SHARED, tlv->fd, 0); + + if (tlv->digest_list == MAP_FAILED) { + printf("Cannot allocate buffer\n"); + close(tlv->fd); + return -ENOMEM; + } + + hdr = (struct tlv_hdr *)tlv->digest_list; + memset(hdr, 0, sizeof(*hdr)); + + hdr->data_type = __cpu_to_be64(DIGEST_LIST_FILE); + hdr->num_fields = 0; + hdr->total_len = 0; + return 0; +} + +static void write_entry(struct tlv_hdr *hdr, struct tlv_entry **entry, + __u16 field, __u8 *data, __u32 data_len, + bool update_data) +{ + __u16 num_fields; + __u64 total_len; + __u64 entry_len; + + num_fields = __be64_to_cpu(hdr->num_fields); + total_len = __be64_to_cpu(hdr->total_len); + + (*entry)->field = __cpu_to_be64(field); + (*entry)->length = __cpu_to_be64(data_len); + + if (update_data) + memcpy((*entry)->data, data, data_len); + + num_fields++; + entry_len = sizeof(*(*entry)) + data_len; + total_len += entry_len; + + hdr->num_fields = __cpu_to_be64(num_fields); + hdr->total_len = __cpu_to_be64(total_len); + (*entry) = (struct tlv_entry *)((__u8 *)*entry + entry_len); +} + +void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo) +{ + struct tlv_struct *tlv; + int ret; + + tlv = malloc(sizeof(*tlv)); + if (!tlv) + return NULL; + + ret = new_digest_list(dirfd, input, tlv); + if (ret < 0) { + free(tlv); + return NULL; + } + + tlv->outer_hdr = (struct tlv_hdr *)tlv->digest_list; + tlv->outer_entry = (struct tlv_entry *)(tlv->outer_hdr + 1); + tlv->algo = algo; + + write_entry(tlv->outer_hdr, &tlv->outer_entry, DIGEST_LIST_ALGO, + &tlv->algo, sizeof(tlv->algo), true); + return tlv; +} + +int tlv_list_gen_add(int dirfd, void *ptr, char *input) +{ + struct tlv_struct *tlv = (struct tlv_struct *)ptr; + __u8 digest[SHA512_DIGEST_SIZE]; + struct tlv_hdr *inner_hdr; + struct tlv_entry *inner_entry; + int ret; + + ret = calc_file_digest(digest, input, tlv->algo); + if (ret < 0) { + printf("Cannot calculate digest of %s\n", input); + return ret; + } + + inner_hdr = (struct tlv_hdr *)(tlv->outer_entry + 1); + inner_hdr->data_type = __cpu_to_be64(DIGEST_LIST_FILE); + + inner_entry = (struct tlv_entry *)(inner_hdr + 1); + + write_entry(inner_hdr, &inner_entry, ENTRY_DIGEST, digest, + hash_digest_size[tlv->algo], true); + write_entry(inner_hdr, &inner_entry, ENTRY_PATH, (__u8 *)input, + strlen(input) + 1, true); + + write_entry(tlv->outer_hdr, &tlv->outer_entry, DIGEST_LIST_ENTRY, NULL, + (__u8 *)inner_entry - (__u8 *)inner_hdr, false); + return 0; +} + +void tlv_list_gen_close(void *ptr) +{ + struct tlv_struct *tlv = (struct tlv_struct *)ptr; + + munmap(tlv->digest_list, DIGEST_LIST_SIZE_MAX); + ftruncate(tlv->fd, (__u8 *)tlv->outer_entry - (__u8 *)tlv->outer_hdr); + close(tlv->fd); + free(tlv); +} diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c index bc425da5317..7caad681eee 100644 --- a/tools/digest-lists/manage_digest_lists.c +++ b/tools/digest-lists/manage_digest_lists.c @@ -20,6 +20,8 @@ #include #include "common.h" +#include "generators/generators.h" +#include "parsers/parsers.h" const char *ops_str[OP__LAST] = { [OP_GEN] = "gen", @@ -29,9 +31,12 @@ const char *ops_str[OP__LAST] = { }; struct generator generators[] = { + { .name = "tlv", .new = tlv_list_gen_new, .add = tlv_list_gen_add, + .close = tlv_list_gen_close }, }; struct parser parsers[] = { + { .name = "tlv", .parse = tlv_list_parse }, }; static int generator_add(struct generator *generator, int dirfd, diff --git a/tools/digest-lists/parsers/parsers.h b/tools/digest-lists/parsers/parsers.h new file mode 100644 index 00000000000..708da7eac3b --- /dev/null +++ b/tools/digest-lists/parsers/parsers.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header for all digest list parsers. + */ + +#include +#include +#include + +int tlv_list_parse(const char *digest_list_path, enum ops op); diff --git a/tools/digest-lists/parsers/tlv.c b/tools/digest-lists/parsers/tlv.c new file mode 100644 index 00000000000..1c9909e80b9 --- /dev/null +++ b/tools/digest-lists/parsers/tlv.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Parse tlv digest lists. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#include "../../../include/uapi/linux/tlv_digest_list.h" +#include "../common.h" + +struct tlv_parse_ctx { + const char *digest_list_path; + size_t digest_list_path_len; + enum hash_algo algo; + enum ops op; +}; + +const char *digest_list_types_str[] = { + FOR_EACH_DIGEST_LIST_TYPE(GENERATE_STRING) +}; + +const char *digest_list_fields_str[] = { + FOR_EACH_FIELD(GENERATE_STRING) +}; + +const char *entry_fields_str[] = { + FOR_EACH_ENTRY_FIELD(GENERATE_STRING) +}; + +static int parse_digest_list_algo(struct tlv_parse_ctx *ctx, + enum digest_list_fields field, + const __u8 *field_data, __u64 field_data_len) +{ + ctx->algo = *field_data; + return 0; +} + +static int parse_entry_digest(struct tlv_parse_ctx *ctx, + enum entry_fields field, const __u8 *field_data, + __u64 field_data_len) +{ + int i; + + if (ctx->op != OP_SHOW) + return 0; + + printf("%s:", hash_algo_name[ctx->algo]); + + for (i = 0; i < hash_digest_size[ctx->algo]; i++) + printf("%02x", field_data[i]); + + return 0; +} + +static int parse_entry_path(struct tlv_parse_ctx *ctx, enum entry_fields field, + const __u8 *field_data, __u64 field_data_len) +{ + char *entry_path = (char *)field_data; + int ret; + + switch (ctx->op) { + case OP_SHOW: + printf(" %s\n", entry_path); + ret = 0; + break; + case OP_ADD_XATTR: + ret = lsetxattr(entry_path, XATTR_NAME_DIGEST_LIST, + ctx->digest_list_path, + ctx->digest_list_path_len, 0); + if (ret < 0 && errno == ENODATA) + ret = 0; + + if (ret < 0) + printf("Error setting %s on %s, %s\n", + XATTR_NAME_DIGEST_LIST, entry_path, + strerror(errno)); + break; + case OP_RM_XATTR: + ret = lremovexattr(entry_path, XATTR_NAME_DIGEST_LIST); + if (ret < 0 && errno == ENODATA) + ret = 0; + + if (ret < 0) + printf("Error removing %s from %s, %s\n", + XATTR_NAME_DIGEST_LIST, entry_path, + strerror(errno)); + break; + default: + break; + } + + return 0; +} + +static int entry_callback(void *callback_data, __u64 field, + const __u8 *field_data, __u64 field_data_len) +{ + struct tlv_parse_ctx *ctx = (struct tlv_parse_ctx *)callback_data; + int ret; + + switch (field) { + case ENTRY_DIGEST: + ret = parse_entry_digest(ctx, field, field_data, + field_data_len); + break; + case ENTRY_PATH: + ret = parse_entry_path(ctx, field, field_data, field_data_len); + break; + default: + pr_debug("Unhandled field %llu\n", field); + /* Just ignore non-relevant fields. */ + ret = 0; + break; + } + + return ret; +} + +static int parse_digest_list_entry(struct tlv_parse_ctx *ctx, + enum digest_list_fields field, + const __u8 *field_data, __u64 field_data_len) +{ + return tlv_parse(DIGEST_LIST_FILE, entry_callback, ctx, field_data, + field_data_len, digest_list_types_str, + DIGEST_LIST__LAST, entry_fields_str, ENTRY__LAST); +} + +static int digest_list_callback(void *callback_data, __u64 field, + const __u8 *field_data, __u64 field_data_len) +{ + struct tlv_parse_ctx *ctx = (struct tlv_parse_ctx *)callback_data; + int ret; + + switch (field) { + case DIGEST_LIST_ALGO: + ret = parse_digest_list_algo(ctx, field, field_data, + field_data_len); + break; + case DIGEST_LIST_ENTRY: + ret = parse_digest_list_entry(ctx, field, field_data, + field_data_len); + break; + default: + pr_debug("Unhandled field %llu\n", field); + /* Just ignore non-relevant fields. */ + ret = 0; + break; + } + + return ret; +} + +int tlv_list_parse(const char *digest_list_path, enum ops op) +{ + struct tlv_parse_ctx ctx = { + .op = op, .digest_list_path = digest_list_path, + .digest_list_path_len = strlen(digest_list_path) + }; + unsigned char *data; + size_t data_len; + int ret; + + ret = read_file(digest_list_path, &data_len, &data); + if (ret < 0) + return ret; + + ret = tlv_parse(DIGEST_LIST_FILE, digest_list_callback, &ctx, data, + data_len, digest_list_types_str, DIGEST_LIST__LAST, + digest_list_fields_str, FIELD__LAST); + + munmap(data, data_len); + return ret; +} diff --git a/tools/digest-lists/parsers/tlv_parser.h b/tools/digest-lists/parsers/tlv_parser.h new file mode 100644 index 00000000000..3c9f54a97b3 --- /dev/null +++ b/tools/digest-lists/parsers/tlv_parser.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header file of TLV parser. + */ + +#ifndef _TLV_PARSER_H +#define _TLV_PARSER_H + +#include +#include +#include +#include +#include + +#ifdef TLV_DEBUG +#define pr_debug(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +#define pr_debug(fmt, ...) { } +#endif + +typedef int (*parse_callback)(void *, __u64, const __u8 *, __u64); + +int tlv_parse_hdr(const __u8 **data, size_t *data_len, __u64 *parsed_data_type, + __u64 *parsed_num_fields, __u64 *parsed_total_len, + const char **data_types, __u64 num_data_types); +int tlv_parse_data(parse_callback callback, void *callback_data, + __u64 parsed_num_fields, const __u8 *data, size_t data_len, + const char **fields, __u64 num_fields); +int tlv_parse(__u64 expected_data_type, parse_callback callback, + void *callback_data, const __u8 *data, size_t data_len, + const char **data_types, __u64 num_data_types, + const char **fields, __u64 num_fields); + +#endif /* _TLV_PARSER_H */ From patchwork Sat Aug 12 10:46:15 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351786 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0EE6F370 for ; Sat, 12 Aug 2023 10:50:53 +0000 (UTC) Received: from frasgout13.his.huawei.com (unknown [14.137.139.46]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 614DE30F7; Sat, 12 Aug 2023 03:50:19 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.227]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4RNHDW45mCzB03Lp; Sat, 12 Aug 2023 18:37:47 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S14; Sat, 12 Aug 2023 11:49:00 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 12/13] tools/digest-lists: Add rpm digest list generator and parser Date: Sat, 12 Aug 2023 12:46:15 +0200 Message-Id: <20230812104616.2190095-13-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S14 X-Coremail-Antispam: 1UD129KBjvAXoWfJr17Jw1DuF1UWFWkurykKrg_yoW8XF45uo Zaga13Gan0kr18uF4vkFy3Xa1ayanYya1UA3yrWryqq3W8AFy0g3Z5KanrXrW7ur4rJryS qr4Iq343Aw4xW3s5n29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUYt7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVWxJr0_GcWle2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20x vY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I 3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIx AIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr1j6F4UJwCI 42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z2 80aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x07UZo7tUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAHBF1jj46UtAAAse X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RDNS_DYNAMIC,SPF_HELO_NONE, SPF_NONE,URIBL_BLOCKED autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Add a generator to generate an rpm digest list from one or multiple RPM package headers. The digest list contains the RPM magic string, the content of the RPMTAG_IMMUTABLE section, and the user asymmetric key signature (module-style) converted from the PGP signature in the RPMTAG_RSAHEADER section. This generator has as prerequisite gpg support for a new command --conv-kernel, which converts the PGP format to a user asymmetric key signature. Also add a parser of rpm digest list, to show the content (digest algorithm and value, and file path), and to add/remove the security.digest_list xattr to/from each file in the RPM packages. Signed-off-by: Roberto Sassu --- tools/digest-lists/.gitignore | 2 + tools/digest-lists/Makefile | 10 +- tools/digest-lists/generators/generators.h | 2 + tools/digest-lists/generators/rpm.c | 257 +++++++++++++++++++++ tools/digest-lists/manage_digest_lists.c | 2 + tools/digest-lists/parsers/parsers.h | 2 + tools/digest-lists/parsers/rpm.c | 169 ++++++++++++++ 7 files changed, 442 insertions(+), 2 deletions(-) create mode 100644 tools/digest-lists/generators/rpm.c create mode 100644 tools/digest-lists/parsers/rpm.c diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore index 9a75ae766ff..51ca25f3b50 100644 --- a/tools/digest-lists/.gitignore +++ b/tools/digest-lists/.gitignore @@ -3,3 +3,5 @@ manage_digest_lists manage_digest_lists.1 libgen-tlv-list.so libparse-tlv-list.so +libgen-rpm-list.so +libparse-rpm-list.so diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile index 23f9fa3b588..2c8089affb8 100644 --- a/tools/digest-lists/Makefile +++ b/tools/digest-lists/Makefile @@ -15,8 +15,8 @@ CFLAGS=-ggdb -Wall PROGS=manage_digest_lists -GENERATORS=libgen-tlv-list.so -PARSERS=libparse-tlv-list.so +GENERATORS=libgen-tlv-list.so libgen-rpm-list.so +PARSERS=libparse-tlv-list.so libparse-rpm-list.so MAN1=manage_digest_lists.1 @@ -31,9 +31,15 @@ manage_digest_lists: manage_digest_lists.c common.c $(GENERATORS) $(PARSERS) libgen-tlv-list.so: generators/tlv.c common.c $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-tlv-list.so $^ -o $@ +libgen-rpm-list.so: generators/rpm.c common.c + $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-rpm-list.so $^ -o $@ -lrpm -lrpmio + libparse-tlv-list.so: parsers/tlv.c common.c ../../lib/tlv_parser.c $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-tlv-list.so $^ -o $@ -I parsers +libparse-rpm-list.so: parsers/rpm.c common.c + $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-rpm-list.so $^ -o $@ -I parsers -lrpm -lrpmio + ifneq ($(findstring $(MAKEFLAGS),s),s) ifneq ($(V),1) QUIET_A2X = @echo ' A2X '$@; diff --git a/tools/digest-lists/generators/generators.h b/tools/digest-lists/generators/generators.h index 9830b791667..ff3ed6ac8d4 100644 --- a/tools/digest-lists/generators/generators.h +++ b/tools/digest-lists/generators/generators.h @@ -14,3 +14,5 @@ void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo); int tlv_list_gen_add(int dirfd, void *ptr, char *input); void tlv_list_gen_close(void *ptr); + +int rpm_list_gen_add(int dirfd, void *ptr, char *input); diff --git a/tools/digest-lists/generators/rpm.c b/tools/digest-lists/generators/rpm.c new file mode 100644 index 00000000000..29e7a6eb0ca --- /dev/null +++ b/tools/digest-lists/generators/rpm.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Generate rpm digest lists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common.h" + +static int gen_filename(Header rpm, char *filename, int filename_len) +{ + char *_filename = headerFormat(rpm, "rpm-%{nvra}", NULL); + + if (!_filename) + return -ENOMEM; + + strncpy(filename, _filename, filename_len); + free(_filename); + return 0; +} + +static int write_rpm_header(Header rpm, int dirfd, char *filename) +{ + rpmtd immutable; + ssize_t ret; + int fd; + + fd = openat(dirfd, filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -EACCES; + + ret = _write(fd, (void *)rpm_header_magic, sizeof(rpm_header_magic)); + if (ret != sizeof(rpm_header_magic)) { + ret = -EIO; + goto out; + } + + immutable = rpmtdNew(); + headerGet(rpm, RPMTAG_HEADERIMMUTABLE, immutable, 0); + ret = _write(fd, immutable->data, immutable->count); + if (ret != immutable->count) { + ret = -EIO; + goto out; + } + + rpmtdFree(immutable); +out: + close(fd); + + if (ret < 0) + unlinkat(dirfd, filename, 0); + + return ret; +} + +static int write_rpm_header_signature(Header rpm, int dirfd, char *filename) +{ + char sig_to_convert[] = "/tmp/sig_to_convert_XXXXXX"; + char uasym_sig[] = "/tmp/uasym_sig_XXXXXX"; + struct module_signature modsig = { 0 }; + rpmtd signature = rpmtdNew(); + __u8 buf[1024]; + struct stat st; + int ret, n_read, status, fd, fd_sig_to_convert, fd_uasym_sig; + + fd_sig_to_convert = mkstemp(sig_to_convert); + if (fd_sig_to_convert == -1) + return -errno; + + fd_uasym_sig = mkstemp(uasym_sig); + if (fd_uasym_sig == -1) { + ret = -errno; + goto out; + } + + headerGet(rpm, RPMTAG_RSAHEADER, signature, 0); + if (!signature->count) { + printf("Warning: no RPM signature for %s\n", filename); + ret = 0; + goto out_get; + } + + ret = _write(fd_sig_to_convert, signature->data, signature->count); + if (ret != signature->count) + goto out_get; + + close(fd_sig_to_convert); + fd_sig_to_convert = -1; + + if (fork() == 0) + return execlp("gpg", "gpg", "--no-keyring", "--conv-kernel", + "-o", uasym_sig, sig_to_convert, NULL); + + wait(&status); + if (WEXITSTATUS(status)) { + ret = WEXITSTATUS(status); + goto out_get; + } + + if (stat(uasym_sig, &st) == -1) + goto out_get; + + fd = openat(dirfd, filename, O_WRONLY | O_APPEND); + if (fd < 0) { + ret = -errno; + goto out_get; + } + + modsig.id_type = PKEY_ID_PGP; + modsig.sig_len = st.st_size; + modsig.sig_len = __cpu_to_be32(modsig.sig_len); + + while ((n_read = read(fd_uasym_sig, buf, sizeof(buf))) > 0) { + ret = _write(fd, buf, n_read); + if (ret != n_read) + goto out_fd; + } + + ret = _write(fd, &modsig, sizeof(modsig)); + if (ret != sizeof(modsig)) + goto out_fd; + + ret = _write(fd, MODULE_SIG_STRING, sizeof(MODULE_SIG_STRING) - 1); + if (ret != sizeof(MODULE_SIG_STRING) - 1) + goto out_fd; + + ret = 0; +out_fd: + close(fd); + + if (ret < 0) + unlinkat(dirfd, filename, 0); +out_get: + rpmtdFree(signature); +out: + close(fd_sig_to_convert); + unlink(sig_to_convert); + close(fd_uasym_sig); + unlink(uasym_sig); + + return ret; +} + +static void write_rpm_digest_list(Header rpm, int dirfd, char *filename) +{ + int ret; + + ret = write_rpm_header(rpm, dirfd, filename); + if (ret < 0) { + printf("Cannot dump RPM header of %s\n", filename); + return; + } + + ret = write_rpm_header_signature(rpm, dirfd, filename); + if (ret < 0) + printf("Cannot add signature to %s\n", filename); +} + +int rpm_list_gen_add(int dirfd, void *ptr, char *input) +{ + char filename[NAME_MAX + 1], *selection; + rpmts ts = NULL; + Header hdr; + FD_t fd; + rpmdbMatchIterator mi; + rpmVSFlags vsflags = 0; + int ret; + + ts = rpmtsCreate(); + if (!ts) { + rpmlog(RPMLOG_NOTICE, "rpmtsCreate() error..\n"); + ret = -EACCES; + goto out; + } + + ret = rpmReadConfigFiles(NULL, NULL); + if (ret != RPMRC_OK) { + rpmlog(RPMLOG_NOTICE, "Unable to read RPM configuration.\n"); + ret = -EACCES; + goto out_ts; + } + + if (strncmp(input, "rpmdb", 5)) { + vsflags |= _RPMVSF_NODIGESTS; + vsflags |= _RPMVSF_NOSIGNATURES; + rpmtsSetVSFlags(ts, vsflags); + + fd = Fopen(input, "r.ufdio"); + if (!fd || Ferror(fd)) { + rpmlog(RPMLOG_NOTICE, + "Failed to open package file %s, %s\n", input, + Fstrerror(fd)); + ret = -EACCES; + goto out_rpm; + } + + ret = rpmReadPackageFile(ts, fd, "rpm", &hdr); + Fclose(fd); + + if (ret != RPMRC_OK) { + rpmlog(RPMLOG_NOTICE, + "Could not read package file %s\n", input); + goto out_rpm; + } + + gen_filename(hdr, filename, sizeof(filename)); + + write_rpm_digest_list(hdr, dirfd, filename); + headerFree(hdr); + goto out_rpm; + } + + mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0); + while ((hdr = rpmdbNextIterator(mi)) != NULL) { + gen_filename(hdr, filename, sizeof(filename)); + + /* Skip rpm- */ + if (strstr(filename + 4, "gpg-pubkey")) + continue; + + selection = strchr(input, ':'); + if (selection && !strstr(filename + 4, selection + 1)) + continue; + + write_rpm_digest_list(hdr, dirfd, filename); + } + + rpmdbFreeIterator(mi); +out_rpm: + rpmFreeRpmrc(); + rpmFreeCrypto(); + rpmFreeMacros(NULL); +out_ts: + rpmtsFree(ts); +out: + return ret; +} diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c index 7caad681eee..3985bfcb827 100644 --- a/tools/digest-lists/manage_digest_lists.c +++ b/tools/digest-lists/manage_digest_lists.c @@ -33,10 +33,12 @@ const char *ops_str[OP__LAST] = { struct generator generators[] = { { .name = "tlv", .new = tlv_list_gen_new, .add = tlv_list_gen_add, .close = tlv_list_gen_close }, + { .name = "rpm", .add = rpm_list_gen_add }, }; struct parser parsers[] = { { .name = "tlv", .parse = tlv_list_parse }, + { .name = "rpm", .parse = rpm_list_gen_parse }, }; static int generator_add(struct generator *generator, int dirfd, diff --git a/tools/digest-lists/parsers/parsers.h b/tools/digest-lists/parsers/parsers.h index 708da7eac3b..ecefb2ec79b 100644 --- a/tools/digest-lists/parsers/parsers.h +++ b/tools/digest-lists/parsers/parsers.h @@ -12,3 +12,5 @@ #include int tlv_list_parse(const char *digest_list_path, enum ops op); + +int rpm_list_gen_parse(const char *digest_list_path, enum ops op); diff --git a/tools/digest-lists/parsers/rpm.c b/tools/digest-lists/parsers/rpm.c new file mode 100644 index 00000000000..7dd063b64ac --- /dev/null +++ b/tools/digest-lists/parsers/rpm.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Parse rpm digest lists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common.h" + +static const enum hash_algo pgp_hash_algorithms[PGPHASHALGO_SHA224 + 1] = { + [PGPHASHALGO_MD5] = HASH_ALGO_MD5, + [PGPHASHALGO_SHA1] = HASH_ALGO_SHA1, + [PGPHASHALGO_RIPEMD160] = HASH_ALGO_RIPE_MD_160, + [PGPHASHALGO_SHA256] = HASH_ALGO_SHA256, + [PGPHASHALGO_SHA384] = HASH_ALGO_SHA384, + [PGPHASHALGO_SHA512] = HASH_ALGO_SHA512, + [PGPHASHALGO_SHA224] = HASH_ALGO_SHA224, +}; + +int rpm_list_gen_parse(const char *digest_list_path, enum ops op) +{ + rpmtd filedigestalgo, filedigests, basenames, dirnames, dirindexes; + rpmts ts = NULL; + Header hdr; + FD_t fd; + rpmVSFlags vsflags = 0; + char file_path[PATH_MAX]; + enum hash_algo algo = HASH_ALGO_MD5; + const char *digest_str, *basename, *dirname; + __u32 dirindex, *pgp_algo_ptr; + size_t digest_list_path_len = strlen(digest_list_path); + int ret; + + ts = rpmtsCreate(); + if (!ts) { + rpmlog(RPMLOG_NOTICE, "rpmtsCreate() error..\n"); + ret = -EACCES; + goto out; + } + + ret = rpmReadConfigFiles(NULL, NULL); + if (ret != RPMRC_OK) { + rpmlog(RPMLOG_NOTICE, "Unable to read RPM configuration.\n"); + ret = -EACCES; + goto out_ts; + } + + vsflags |= _RPMVSF_NODIGESTS; + vsflags |= _RPMVSF_NOSIGNATURES; + rpmtsSetVSFlags(ts, vsflags); + + fd = Fopen(digest_list_path, "r.ufdio"); + if (!fd || Ferror(fd)) { + rpmlog(RPMLOG_NOTICE, "Failed to open package file %s, %s\n", + digest_list_path, Fstrerror(fd)); + ret = -EACCES; + goto out_rpm; + } + + ret = rpmReadHeader(ts, fd, &hdr, NULL); + Fclose(fd); + + if (ret != RPMRC_OK) { + rpmlog(RPMLOG_NOTICE, "Could not read package file %s\n", + digest_list_path); + goto out_rpm; + } + + filedigestalgo = rpmtdNew(); + filedigests = rpmtdNew(); + basenames = rpmtdNew(); + dirnames = rpmtdNew(); + dirindexes = rpmtdNew(); + + headerGet(hdr, RPMTAG_FILEDIGESTALGO, filedigestalgo, 0); + headerGet(hdr, RPMTAG_FILEDIGESTS, filedigests, 0); + headerGet(hdr, RPMTAG_BASENAMES, basenames, 0); + headerGet(hdr, RPMTAG_DIRNAMES, dirnames, 0); + headerGet(hdr, RPMTAG_DIRINDEXES, dirindexes, 0); + + pgp_algo_ptr = rpmtdGetUint32(filedigestalgo); + if (pgp_algo_ptr && *pgp_algo_ptr <= PGPHASHALGO_SHA224) + algo = pgp_hash_algorithms[*pgp_algo_ptr]; + + while ((digest_str = rpmtdNextString(filedigests))) { + basename = rpmtdNextString(basenames); + dirindex = *rpmtdNextUint32(dirindexes); + + rpmtdSetIndex(dirnames, dirindex); + dirname = rpmtdGetString(dirnames); + + snprintf(file_path, sizeof(file_path), "%s%s", dirname, + basename); + + if (!strlen(digest_str)) + continue; + + switch (op) { + case OP_SHOW: + printf("%s:%s %s\n", hash_algo_name[algo], digest_str, + file_path); + ret = 0; + break; + case OP_ADD_XATTR: + ret = lsetxattr(file_path, XATTR_NAME_DIGEST_LIST, + digest_list_path, + digest_list_path_len, 0); + if (ret < 0 && errno == ENODATA) + ret = 0; + + if (ret < 0) + printf("Error setting %s on %s, %s\n", + XATTR_NAME_DIGEST_LIST, file_path, + strerror(errno)); + break; + case OP_RM_XATTR: + ret = lremovexattr(file_path, XATTR_NAME_DIGEST_LIST); + if (ret < 0 && errno == ENODATA) + ret = 0; + + if (ret < 0) + printf("Error removing %s from %s, %s\n", + XATTR_NAME_DIGEST_LIST, file_path, + strerror(errno)); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + if (ret < 0) + break; + } + + rpmtdFree(filedigestalgo); + rpmtdFree(filedigests); + rpmtdFree(basenames); + rpmtdFree(dirnames); + rpmtdFree(dirindexes); + headerFree(hdr); +out_rpm: + rpmFreeRpmrc(); + rpmFreeCrypto(); + rpmFreeMacros(NULL); +out_ts: + rpmtsFree(ts); +out: + return ret; +} From patchwork Sat Aug 12 10:46:16 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13351814 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D6D211868 for ; Sat, 12 Aug 2023 11:07:10 +0000 (UTC) Received: from frasgout13.his.huawei.com (unknown [14.137.139.46]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9F6B1AF; Sat, 12 Aug 2023 04:07:07 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4RNHDh6w4SzB03Lj; Sat, 12 Aug 2023 18:37:56 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwBXC7scY9dkThi9AA--.8440S15; Sat, 12 Aug 2023 11:49:09 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, bpf@vger.kernel.org, jarkko@kernel.org, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, Roberto Sassu Subject: [RFC][PATCH v2 13/13] docs: Add documentation of the integrity digest cache Date: Sat, 12 Aug 2023 12:46:16 +0200 Message-Id: <20230812104616.2190095-14-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> References: <20230812104616.2190095-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwBXC7scY9dkThi9AA--.8440S15 X-Coremail-Antispam: 1UD129KBjvAXoWfGr1fWrWxtF4DtFyDtFW3trb_yoW8ZrWkJo ZIvw45Cw45Kry5uF4UCFnrJF47G3ZYgwn7AF48tr45WF1aqF45Ka1DC3WUWFW5Jr4rGa4x A348Jw4xJF1ktrn3n29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUYu7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVWxJr0_GcWle2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jr0_Jr4lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCF04k20x vY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I 3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIx AIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr1j6F4UJwCI 42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z2 80aVCY1x0267AKxVWxJr0_GcJvcSsGvfC2KfnxnUUI43ZEXa7IU13l1DUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAHBF1jj46UtAABsf X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, PDS_RDNS_DYNAMIC_FP,RCVD_IN_DNSWL_BLOCKED,RDNS_DYNAMIC,SPF_HELO_NONE, SPF_NONE,URIBL_BLOCKED autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net X-Patchwork-State: RFC From: Roberto Sassu Add the documentation of the integrity digest cache in Documentation/security. Signed-off-by: Roberto Sassu --- Documentation/security/index.rst | 1 + .../security/integrity-digest-cache.rst | 484 ++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 486 insertions(+) create mode 100644 Documentation/security/integrity-digest-cache.rst diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 6ed8d2fa6f9..3316d50c839 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -18,3 +18,4 @@ Security Documentation digsig landlock secrets/index + integrity-digest-cache diff --git a/Documentation/security/integrity-digest-cache.rst b/Documentation/security/integrity-digest-cache.rst new file mode 100644 index 00000000000..371b3f84780 --- /dev/null +++ b/Documentation/security/integrity-digest-cache.rst @@ -0,0 +1,484 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================== +Integrity Digest Cache +====================== + +Introduction +============ + +The main goal of Integrity Measurement Architecture (IMA) is to perform a +measurement of the file content and use it for remote attestation, to +report a possibly compromised system, using the TPM as a root of trust. It +can also prevent a system compromise from happening by checking the +calculated file digest against a known-good reference value and by denying +the current operation if there is a mismatch. + + +Motivation +========== + +This patch set aims to address two important shortcomings: predictability +of the Platform Configuration Registers (PCRs), and the provisioning of +reference values to compare the calculated file digest against. + +Remote attestation, according to Trusted Computing Group (TCG) +specifications, is done by replicating the PCR extend operation in +software with the digests in the event log (in this case the IMA +measurement list), and by comparing the obtained value with the PCR value +signed by the TPM with the quote operation. + +Due to how the extend operation is performed, if measurements are done in +a different order, the final PCR value will be different. That means that +if measurements are done in parallel, there is no way to predict what the +final PCR value will be, making impossible to seal data to a PCR value. If +the PCR value was predictable, a system could for example prove its +integrity by unsealing and using its private key, without sending every +time the full list of measurements. + +Provisioning reference values for file digests is also a difficult task. +The solution so far was to add file signatures to RPM packages, and +possibly to DEB packages, so that IMA can verify them. While this undoubtly +works, it also requires Linux distribution vendors to support the feature +by rebuilding all their packages, and eventually extending their PKI to +perform the additional signatures. It could also require developers extra +work to deal with the additional data. + +On the other hand, since often packages carry the file digests themselves, +it won't be actually needed to add file signatures. If the kernel was able +to extract the file digests by itself, all the tasks mentioned above for +the Linux distribution vendors won't be needed too. All current and past +Linux distributions can be easily retrofitted to enable IMA appraisal with +the file digests from the packages. + +Narrowing down the scope of a package parser to only extract specific +information makes it small enough to accurately verify that it cannot harm +the kernel. An additional mitigation consists in verifying the signature of +the package first, before attempting to extract the file digests. + + +Solution +======== + +To avoid a PCR is extended in a non-deterministic way, the proposed +solution is to replace individual file measurements with the measurement of +a file (the digest list) containing a set of file digests. If the +calculated digest of a file being measured/appraised matches one digest in +the set, its measurement is skipped. If otherwise there is no match, the +file digest is added to the measurement list. + +The resulting measurement list, which cannot be done on the default IMA PCR +to avoid ambiguity with the default-style measurement, has the following +meaning: none/some/all files represented with the measurement of the digest +lists COULD have been accessed, without knowing IF and WHEN. Any other +measurement (other than boot_aggregate) is of a file whose digest was not +included in the digest list. + +File signatures have a coarser granularity, it is per-signing key and not +per-package. A measurement list containing just the measurement of the +signing keys and the files without/invalid signature (those with valid +signature would be skipped) would be even less accurate. + +To ensure a rapid and smooth deployment of IMA appraisal, the kernel has +been provided with the ability to extract file digests from the RPM +package headers, and add them to the kernel memory on demand (only when a +file from a given package is accessed). This ensures that the memory +consumption for this new feature is directly proportional to the usage of +the system. + + +Scope +===== + +The integrity digest cache enables IMA to extend a PCR (not the default +one) in a deterministic fashion, and to appraise immutable files with file +digests from the packages, when no other appraisal method is available. It +does not yet support metadata verification with Extended Verification +Module (EVM), for which a separate patch set will be provided. + + +Design +====== + +The digest cache is a hash table of file digests, attached to the inode of +the digest list from which file digests are extracted. It is accessible, +when a given file is being measured/appraised, from the new xattr +security.digest_list, containing the path of the digest list itself. + +If the calculated file digest is found in the digest cache, its measurement +is avoided, or read-only access is granted if appraisal is in enforcing +mode. Read-write access is prevented to avoid updating an unverified HMAC +of file metadata. + +The digest cache can be used only if the following conditions are met: + +- The ``digest_cache=content`` keyword is added to the desired IMA policy + rules; +- If the rule action is ``measure``, a PCR different from the default one + is specified; +- If the rule action is ``appraise``, ``digest_cache=content`` and + ``appraise_type`` don't appear at the same time; +- The same action for which the digest cache is used was done also on the + digest list; +- The digest cache (currently) is not used for measurement/appraisal of + other digest lists. + +For performance reasons, the digest cache is attached to every inode using +it, since multiple hooks can be invoked on it before the +measurement/appraisal result is cached. A reference count indicates how +many inodes use it, and only when it reaches zero, the digest cache can be +freed (for example when inodes are evicted from memory). + +Two digest cache pointers have been added to the iint to distinguish for +which purpose they should be used: dig_owner points to the digest cache +created from the same inode the iint refers to, and should be used for +measurement/appraisal of other inodes; dig_user points to the digest +cache created from a different inode, and requested for +measurement/appraisal. One digest cache pointer would be confusing, as +for digest lists the digest cache was created from them, but IMA would +try to use that digest cache for measurement/appraisal of itself. + +Finally, at the first digest list measurement, an iterator is executed to +sequentially read (not parse) all the digest lists in the same directory, +so that the PCR is extended in a deterministic fashion. The other +concurrent users of the digest cache have to wait until the iterator +finishes. + + +API +=== + +Data Structures +~~~~~~~~~~~~~~~ + +.. kernel-doc:: security/integrity/digest_cache.h + + +Functions +~~~~~~~~~ + +.. kernel-doc:: security/integrity/digest_cache.c + +``digest_cache_alloc()``, ``digest_cache_parse_digest_list()`` and +``digest_cache_new()`` are internal functions used during the creation and +initialization of the digest cache. + +``digest_cache_get()`` and ``digest_cache_free()`` are called by the user +of the digest cache (e.g. IMA), to obtain and free a digest cache. + +``digest_cache_init_htable()``, ``digest_cache_add()`` and +``digest_cache_lookup()`` are called by the digest list parsers to populate +and search in a digest cache. + + +Digest List Formats +=================== + +tlv +~~~ + +The Type-Length-Value (TLV) format was chosen for its extensibility. +Additional fields can be added without breaking compatibility with old +versions of the parser. + +The layout of a tlv digest list is the following:: + + [header: DIGEST_LIST_FILE, num fields, total len] + [field: DIGEST_LIST_ALGO, length, value] + [field: DIGEST_LIST_ENTRY#1, length, value (below)] + |- [header: DIGEST_LIST_FILE, num fields, total len] + |- [ENTRY#1_DIGEST, length, file digest] + |- [ENTRY#1_PATH, length, file path] + [field: DIGEST_LIST_ENTRY#N, length, value (below)] + |- [header: DIGEST_LIST_FILE, num fields, total len] + |- [ENTRY#N_DIGEST, length, file digest] + |- [ENTRY#N_PATH, length, file path] + +DIGEST_LIST_ALGO is a field to specify the algorithm of the file digest. +DIGEST_LIST_ENTRY is a nested TLV structure with the following fields: +ENTRY_DIGEST contains the file digest; ENTRY_PATH contains the file path. + + +rpm +~~~ + +The rpm digest list is basically a subset of the RPM package header. +Its format is:: + + [RPM magic number] + [RPMTAG_IMMUTABLE] + +RPMTAG_IMMUTABLE is a section of the full RPM header containing the part +of the header that was signed, and whose signature is stored in the +RPMTAG_RSAHEADER section. + + +Appended Signature +~~~~~~~~~~~~~~~~~~ + +Digest lists can have a module-style appended signature, that can be used +for appraisal with IMA. The signature type can be PKCS#7, as for kernel +modules, or the new user asymmetric key signature. + + +History +======= + +The original name of this work was IMA Digest Lists, which was somehow +considered too invasive. The code was moved to a separate component named +DIGLIM (DIGest Lists Integrity Module), with the purpose of removing the +complexity away of IMA, and also add the possibility of using it with other +kernel components (e.g. Integrity Policy Enforcement, or IPE). + +Since it was originally proposed, in 2017, this work grew up a lot thanks +to various comments/suggestions. It became integrally part of the openEuler +distribution since end of 2020. + +There are significant differences between this and the previous versions. +The most important one is moving from a centralized repository of file +digests to a per-package repository. This significantly reduces the memory +pressure, since digest lists are loaded into kernel memory only when they +are actually needed. Also, file digests are automatically unloaded from +kernel memory at the same time inodes are evicted from memory during +reclamation. + + +Performance +=========== + +The tests have been performed on a Fedora 38 virtual machine, with 8 cores +(AMD EPYC-Rome), 4GB of RAM, TPM passthrough. The signing key is an ECDSA +NIST P-384. + +IMA measurement policy: no cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + dont_measure fsmagic=0x01021994 + measure func=BPRM_CHECK + measure func=MMAP_CHECK + + +IMA measurement policy: cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + dont_measure fsmagic=0x01021994 + measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11 + measure func=BPRM_CHECK digest_cache=content pcr=11 + measure func=MMAP_CHECK digest_cache=content pcr=11 + + +IMA Measurement Results +~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + +-----------+-----------+-----------+ + | # measur. | boot time | slab | + +-----------------------------+-----------+-----------+-----------+ + | measure (no cache) | 389 | 12.682s | 231453 KB | + +-----------------------------+-----------+-----------+-----------+ + | measure (cache, no iter) | 175 | 12.283s | 234224 KB | + +-----------------------------+-----------+-----------+-----------+ + | measure (cache, iter) | 853 | 16.430s | 238491 KB | + +-----------------------------+-----------+-----------+-----------+ + +With the iterator enabled, all 852 packages are measured. Consequently, the +boot time is longer. One possible optimization would be to exclude the +packages that don't include measured files. By disabling the iterator, it +can be seen that the packages actually used are 174 (one measurement is for +boot_aggregate). + + +IMA appraisal policy: no cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + dont_appraise fsmagic=0x01021994 + appraise func=BPRM_CHECK appraise_type=imasig + appraise func=MMAP_CHECK appraise_type=imasig + + +IMA appraisal policy: cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + dont_appraise fsmagic=0x01021994 + appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig + appraise func=BPRM_CHECK digest_cache=content + appraise func=MMAP_CHECK digest_cache=content + + +IMA Appraisal Results +~~~~~~~~~~~~~~~~~~~~~ + +:: + + +-----------+-----------+ + | boot time | slab | + +-----------------------------+-----------+-----------+ + | appraise (no cache) | 11.995s | 231145 KB | + +-----------------------------+-----------+-----------+ + | appraise (cache) | 11.879s | 233091 KB | + +-----------------------------+-----------+-----------+ + +In this test, it can be seen that the performance of the two solutions are +comparable, with the digest cache slightly ahead. The difference could be +more substantial with more file appraised. + + +How to Test +=========== + +First, it is necessary to copy the new kernel headers (tlv_parser.h, +uasym_parser.h, tlv_digest_list.h) from usr/include/linux in the kernel +source directory to /usr/include/linux. + +Then, gpg must be rebuilt with the additional patches to convert the PGP +keys of the Linux distribution to the new user asymmetric key format: + +.. code-block:: bash + + $ gpg --conv-kernel >> certs/uasym_keys.bin + +This embeds the converted keys in the kernel image. Then, the following +kernel options must be enabled: + +.. code-block:: bash + + CONFIG_INTEGRITY_DIGEST_CACHE=y + CONFIG_UASYM_KEYS_SIGS=y + CONFIG_UASYM_PRELOAD_PUBLIC_KEYS=y + +and the kernel must be rebuilt with the patches applied. After boot, it is +necessary to build and install the digest list tool in tools/digest-lists, +and to execute (as root): + +.. code-block:: bash + + # manage_digest_lists -o gen -d /etc/digest_lists -i rpmdb -f rpm + +The new gpg must also be installed in the system, as it will be used to +convert the PGP signatures of the RPM headers to the user asymmetric key +format. + +It is recommended to create an additional digest list with the following +files, by creating a file named ``list`` with the content: + +.. code-block:: bash + + /usr/bin/manage_digest_lists + /usr/lib64/libgen-tlv-list.so + /usr/lib64/libgen-rpm-list.so + /usr/lib64/libparse-rpm-list.so + /usr/lib64/libparse-tlv-list.so + +Then, to create the digest list, it is sufficient to execute: + +.. code-block:: bash + + # manage_digest_lists -i list -L -d /etc/digest_lists -o gen -f tlv + +If appraisal is enabled and in enforcing mode, it is necessary to sign the +new digest list, with the sign-file tool in the scripts/ directory of the +kernel sources: + +.. code-block:: bash + + # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/tlv-list + +The final step is to add security.digest_list to each file with: + +.. code-block:: bash + + # manage_digest_lists -i /etc/digest_lists -o add-xattr + +After that, it is possible to test the integrity digest cache with the +following policy written to /etc/ima/ima-policy: + +.. code-block:: bash + + dont_measure fsmagic=0x01021994 + measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11 + measure func=BPRM_CHECK digest_cache=content pcr=11 + measure func=MMAP_CHECK digest_cache=content pcr=11 + dont_appraise fsmagic=0x01021994 + appraise func=BPRM_CHECK digest_cache=content + appraise func=MMAP_CHECK digest_cache=content + appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig + +Tmpfs is excluded for now, until memfd is properly handled. + +Before loading the policy, it is possible to enable dynamic debug to see +which operations are done by the integrity digest cache: + +.. code-block:: bash + + # echo "file tlv* +p" > /sys/kernel/debug/dynamic_debug/control + # echo "file rpm* +p" > /sys/kernel/debug/dynamic_debug/control + # echo "file digest* +p" > /sys/kernel/debug/dynamic_debug/control + +Alternatively, the same strings can be set as value of the dyndbg= option +in the kernel command line. + +A preliminary test, before booting the system with the new policy, is to +supply the policy to IMA in the current system with: + +.. code-block:: bash + + # cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy + +If that worked, the system can be rebooted. Systemd will take care of +loading the IMA policy at boot. The instructions have been tested on a +Fedora 38 OS. + +After boot, it is possible to check the content of the measurement list: + +.. code-block:: bash + + # cat /sys/kernel/security/ima/ascii_runtime_measurements + +If only the files shipped with Fedora 38 have been executed, the +measurement list will contain only the digest lists, and not the individual +files. + +Another test is to ensure that IMA prevents the execution of unknown files: + +.. code-block:: bash + + # cp -a /bin/cat . + # ./cat + +That will work. But not on the modified binary: + +.. code-block:: bash + + # echo 1 >> cat + # cat + -bash: ./cat: Permission denied + +Execution will be denied, and a new entry in the measurement list will +appear (it would be probably ok to not add that entry, as access to the +file was denied): + +.. code-block:: bash + + 11 50b5a68bea0776a84eef6725f17ce474756e51c0 ima-ng sha256:15e1efee080fe54f5d7404af7e913de01671e745ce55215d89f3d6521d3884f0 /root/cat + +Finally, it is possible to test the shrinking of the digest cache, by +forcing the kernel to evict inodes from memory: + +.. code-block:: bash + + # echo 3 > /proc/sys/vm/drop_caches + +The kernel log should have messages like: + +.. code-block:: bash + + [ 313.032536] DIGEST CACHE: Remove digest sha256:102900208eef27b766380135906d431dba87edaa7ec6aa72e6ebd3dd67f3a97b from digest list /etc/digest_lists/rpm-libseccomp-2.5.3-4.fc38.x86_64 diff --git a/MAINTAINERS b/MAINTAINERS index 2bd85dcfd23..af33db344ce 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10293,6 +10293,7 @@ M: Dmitry Kasatkin L: linux-integrity@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git +F: Documentation/security/integrity-digest-cache.rst F: security/integrity/ F: security/integrity/ima/ F: tools/digest-lists/