From patchwork Fri Jul 21 16:33: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: 13322377 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 E6B2B27F00 for ; Fri, 21 Jul 2023 16:56:47 +0000 (UTC) Received: from frasgout13.his.huawei.com (unknown [14.137.139.46]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 50F792D47; Fri, 21 Jul 2023 09:56:42 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4R6vwt4qsfz9xFfY; Sat, 22 Jul 2023 00:22:54 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S3; Fri, 21 Jul 2023 17:33:47 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 01/12] ima: Introduce hook DIGEST_LIST_CHECK Date: Fri, 21 Jul 2023 18:33:15 +0200 Message-Id: <20230721163326.4106089-2-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S3 X-Coremail-Antispam: 1UD129KBjvJXoWxZF13Wry5Gr4Uuw15Cw18uFg_yoW5Kr1rpa 1DKa40kry5XFy2kFZ3C3W29FW8K3yfKF4UG39093WvyF13AF18Xryayr9F9F1rGr4YkFna qrnIgr43Aa1jy37anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPqb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUGw A2048vs2IY020Ec7CjxVAFwI0_Gr0_Xr1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxS w2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxV W8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrVC2j2 WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE7xkE bVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262kKe7 AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02 F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw0_GF ylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvEc7Cj xVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI 0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x 07jfcTPUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAFBF1jj5DJPgABsX X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE, T_SCC_BODY_TEXT_LINE 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 Fri Jul 21 16:33: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: 13322309 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 2AD431F953 for ; Fri, 21 Jul 2023 16:35:43 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 10DA43AB3; Fri, 21 Jul 2023 09:35:20 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4R6vwz6R8Bz9xFQP; Sat, 22 Jul 2023 00:22:59 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S4; Fri, 21 Jul 2023 17:33:54 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 02/12] integrity: Introduce a digest cache Date: Fri, 21 Jul 2023 18:33:16 +0200 Message-Id: <20230721163326.4106089-3-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S4 X-Coremail-Antispam: 1UD129KBjvAXoWfJw4xtF13AF1rCw4rCw1fXrb_yoW8Cryxto Zava17Jw18WFy3uF4kCF17Za1xuw4Fq34fAr4kXrWDZ3WfXFyUJasFkFn8JFy5Xr18Gr93 Aw18Xw4UJFW8tr93n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUO57kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jr yl82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x0267AK xVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F40Ex7 xfMcIj6xIIjxv20xvE14v26r106r15McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC6x0Y z7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxVAaw2 AFwI0_GFv_Wryl42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAq x4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r1q6r 43MIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Gr0_Xr1lIxAIcVC0I7IYx2IY6xkF 7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14 v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuY vjxUxeHqDUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAFBF1jj4zMlAAAsm X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE 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 cache of digests extracted from a (possibly signed) digest list. The cached digests can be used by IMA to skip the measurement of known files (not on the default PCR), or to grant access to a file if appraisal is in enforcing mode. In the future, it can also be used to verify the integrity of file metadata with EVM. The digest cache is a structure holding a hash table of digests, extracted from a digest list. It is accessible by reading the digest list path from the new security.digest_list xattr of the file being measured/appraised, and by retrieving the digest cache pointer from the integrity metadata associated to the digest list file. The digest cache is usable only if the action being performed on an accessed file has been done also on the digest list itself. The DIGEST_CACHE_MEASURE and DIGEST_CACHE_APPRAISE_CONTENT flags, if set, enable the usage of the digest cache respectively for measuring and appraising file content. Introduce the first three methods, get, put and free. The first two are called by IMA, when it processes a file and an action should be done on it. The last is called by the integrity code at the time the inode is freed, causing the digest cache also to be freed. The digest cache is guaranteed to be available throughout the IMA processing of the current file, by acquiring and releasing a reference to the path structure of the digest list file (so that the latter cannot be suddenly freed, e.g. by deleting the file). Another important point is that, 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. The iint mutex is taken only for assigning the digest cache pointer. Since the digest cache is not modified afterwards, there is no need to lock after creation. Signed-off-by: Roberto Sassu --- include/uapi/linux/xattr.h | 3 + security/integrity/Kconfig | 12 ++ security/integrity/Makefile | 1 + security/integrity/digest_cache.c | 300 ++++++++++++++++++++++++++++++ security/integrity/digest_cache.h | 84 +++++++++ security/integrity/iint.c | 7 + security/integrity/ima/ima.h | 1 - security/integrity/integrity.h | 5 + 8 files changed, 412 insertions(+), 1 deletion(-) 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..66c2c4088e9 --- /dev/null +++ b/security/integrity/digest_cache.c @@ -0,0 +1,300 @@ +// 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 + * + * 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) +{ + 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 = 0; + digest_cache->slots = NULL; + digest_cache->num_slots = 0; + 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; + + 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("Free cache, algo: %s, digest list: %s", + 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_get - Get a digest cache + * @dentry_to_check: Dentry of the file being measured/appraised + * @digest_list_path: Path structure of the digest list + * + * This function retrieves the path of the digest list from the + * security.digest_list xattr of the file being measured/appraised. It then + * instantiates a new digest cache, and opens and parses the digest list. + * + * 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. + * + * The caller is responsible to invoke digest_cache_put(), to release + * the reference of the path structure associated to the digest list. + * + * Return: A new digest cache on success, NULL on error. + */ +struct digest_cache *digest_cache_get(struct dentry *dentry_to_check, + struct path *digest_list_path) +{ + struct integrity_iint_cache *digest_list_iint; + struct digest_cache *digest_cache = NULL; + char *path_str = NULL; + struct file *file; + void *data = NULL; + size_t data_len = 0; + struct inode *inode; + int ret; + + ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry_to_check, + 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_to_check->d_name.name); + return digest_cache; + } + + pr_debug("Found %s xattr in %s, digest list: %s\n", + XATTR_NAME_DIGEST_LIST, dentry_to_check->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->digest_cache) { + pr_debug("Cache for digest list %s exists\n", path_str); + digest_cache = digest_list_iint->digest_cache; + goto out_path; + } + + 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_path; + } + + /* 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; + } + + data_len = ret; + + digest_cache = digest_cache_alloc(path_str); + 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; + } + + /* + * Add penalty only for concurrent add, otherwise don't take a lock. + * In the worst case, the lock contenders waste time to create a + * digest cache that is freed. + */ + mutex_lock(&digest_list_iint->mutex); + /* Someone came before us. */ + if (digest_list_iint->digest_cache) { + pr_debug("Cache for digest list %s exists\n", + digest_cache->path_str); + digest_cache_free(digest_cache); + digest_cache = digest_list_iint->digest_cache; + mutex_unlock(&digest_list_iint->mutex); + goto out_vfree; + } + + digest_list_iint->digest_cache = digest_cache; + 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; + mutex_unlock(&digest_list_iint->mutex); + + pr_debug("Get cache, algo: %s, digest list: %s, mask: %d\n", + 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_path: + if (!digest_cache) + path_put(digest_list_path); +out: + kfree(path_str); + return digest_cache; +} + +/** + * digest_cache_put - Release a digest cache + * @digest_cache: Digest cache + * @digest_list_path: Path structure of the digest list + * + * This function releases the path structure of the digest list. + */ +void digest_cache_put(struct digest_cache *digest_cache, + struct path *digest_list_path) +{ + if (!digest_cache) + return; + + /* Pairs with kernel_path() in digest_cache_get(). */ + path_put(digest_list_path); + + pr_debug("Put cache, algo: %s, digest list: %s", + hash_algo_name[digest_cache->algo], digest_cache->path_str); +} diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h new file mode 100644 index 00000000000..fa4a716df65 --- /dev/null +++ b/security/integrity/digest_cache.h @@ -0,0 +1,84 @@ +/* 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 digest_cache - Digest cache + * @slots: Hash table slots + * @num_slots: Number of slots + * @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; + enum hash_algo algo; + char *path_str; + u8 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 path *digest_list_path); +void digest_cache_put(struct digest_cache *digest_cache, + struct path *digest_list_path); +#else +static inline void digest_cache_free(struct digest_cache *digest_cache) +{ +} + +static inline struct digest_cache * +digest_cache_get(struct dentry *dentry, struct path *digest_list_path) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline void digest_cache_put(struct digest_cache *digest_cache, + struct path *digest_list_path) +{ +} + +#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */ +#endif /* _DIGEST_CACHE_H */ diff --git a/security/integrity/iint.c b/security/integrity/iint.c index a462df827de..9a35ae1fb85 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -80,6 +80,10 @@ 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->digest_cache); + iint->digest_cache = NULL; +#endif kmem_cache_free(iint_cache, iint); } @@ -165,6 +169,9 @@ 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->digest_cache = NULL; +#endif } static int __init integrity_iintcache_init(void) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 3aef3d8fb57..859a94bcecb 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -452,5 +452,4 @@ static inline int ima_filter_rule_match(u32 secid, u32 field, u32 op, #else #define POLICY_FILE_FLAGS S_IWUSR #endif /* CONFIG_IMA_READ_POLICY */ - #endif /* __LINUX_IMA_H */ diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7167a6e99bd..bd3f9a27f0d 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,9 @@ 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 *digest_cache; +#endif }; /* rbtree tree calls to lookup, insert, delete From patchwork Fri Jul 21 16:33:17 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322365 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 C22B727F00 for ; Fri, 21 Jul 2023 16:53:36 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1283A2727; Fri, 21 Jul 2023 09:53:35 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4R6vx72s5wz9xFQJ; Sat, 22 Jul 2023 00:23:07 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S5; Fri, 21 Jul 2023 17:34:02 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 03/12] integrity/digest_cache: Add functions to populate and search Date: Fri, 21 Jul 2023 18:33:17 +0200 Message-Id: <20230721163326.4106089-4-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S5 X-Coremail-Antispam: 1UD129KBjvJXoWxtr43Cr1DWFyUtFWUKw4kJFb_yoWxJw1kpa s7Cr1Utr4rZF13Gw1xAF1ayr1FvrWqqF47Jw45Wr1ayr4DXr1jy3W8Aw1UXFy5Jr48Wa17 tF4jgr1Uur1UXaUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPqb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUWw A2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxS w2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxV W8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrVC2j2 WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE7xkE bVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262kKe7 AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02 F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw0_GF ylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvEc7Cj xVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI 0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZFpf9x 07jzE__UUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAFBF1jj5DJQAAAso X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE 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 | 124 ++++++++++++++++++++++++++++++ security/integrity/digest_cache.h | 24 ++++++ 2 files changed, 148 insertions(+) diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c index 66c2c4088e9..7537c7232db 100644 --- a/security/integrity/digest_cache.c +++ b/security/integrity/digest_cache.c @@ -298,3 +298,127 @@ void digest_cache_put(struct digest_cache *digest_cache, pr_debug("Put cache, algo: %s, digest list: %s", hash_algo_name[digest_cache->algo], digest_cache->path_str); } + +/** + * 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 + * + * 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; + + 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 fa4a716df65..5e3997b2723 100644 --- a/security/integrity/digest_cache.h +++ b/security/integrity/digest_cache.h @@ -64,6 +64,11 @@ struct digest_cache *digest_cache_get(struct dentry *dentry, struct path *digest_list_path); void digest_cache_put(struct digest_cache *digest_cache, struct path *digest_list_path); +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) { @@ -80,5 +85,24 @@ static inline void digest_cache_put(struct digest_cache *digest_cache, { } +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 Fri Jul 21 16:33:18 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322308 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 24FA0200A6 for ; Fri, 21 Jul 2023 16:35:43 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 109893AB2; Fri, 21 Jul 2023 09:35:20 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4R6vxG6K9Qz9xFQR; Sat, 22 Jul 2023 00:23:14 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S6; Fri, 21 Jul 2023 17:34:09 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 04/12] integrity/digest_cache: Iterate over digest lists in same dir Date: Fri, 21 Jul 2023 18:33:18 +0200 Message-Id: <20230721163326.4106089-5-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S6 X-Coremail-Antispam: 1UD129KBjvJXoWxKF1UZFyUuF45JFW7XF15CFg_yoWxKF15pa 9Ik3W5Kr48Z34fCws7AF4akF4Fg39YgF47Gw45uw15Aw4DZr1qv3WxCryUZry5Jr4Uua47 tF4Ygr45Cr4DXaDanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPlb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw 0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aV AFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZF pf9x07j7GYLUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAFBF1jj4zMlQAAsn X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE 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 One advantage of the digest cache is the ability of skipping measurements of cached digests. That would allow to accumulate integrity measurements on a PCR in a predictable way, since only the digest lists would be measured. However, since digest lists are accessed on demand, when a file belonging to that repo is measured/appraised, it could happen due to parallel execution that also digest lists are measured not in the same order. Thus, eliminate this possibility by iterating over the directory containing the digest lists and by reading all of them, to trigger a measurement. Read digest lists are not parsed, to avoid too much memory pressure. Signed-off-by: Roberto Sassu --- security/integrity/Makefile | 3 +- security/integrity/digest_cache.h | 5 + security/integrity/digest_cache_iter.c | 163 +++++++++++++++++++++++++ 3 files changed, 170 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 5e3997b2723..d8fd5ce47a7 100644 --- a/security/integrity/digest_cache.h +++ b/security/integrity/digest_cache.h @@ -69,6 +69,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) { @@ -104,5 +105,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..f9c4675d383 --- /dev/null +++ b/security/integrity/digest_cache_iter.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 IBM Corporation + * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement a digest list iterator. + */ + +#include +#include +#include +#include +#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(&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; + 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; + + *ptr = '\0'; + dir_file = filp_open(path_str, O_RDONLY, 0); + *ptr = '/'; + + if (IS_ERR(dir_file)) { + pr_debug("Cannot access parent directory of repo %s\n", + path_str); + goto out; + } + + buf.head = &head; + iterate_dir(dir_file, &buf.ctx); + list_for_each_entry_safe(p, q, &head, list) { + pr_debug("Prereading digest list %s in %s\n", p->name, + path_str); + + 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); +} From patchwork Fri Jul 21 16:33:19 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322311 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 CD26021500 for ; Fri, 21 Jul 2023 16:35:49 +0000 (UTC) Received: from frasgout13.his.huawei.com (unknown [14.137.139.46]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 64D0B3C07; Fri, 21 Jul 2023 09:35:32 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4R6vxS4qdqz9xFfZ; Sat, 22 Jul 2023 00:23:24 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S7; Fri, 21 Jul 2023 17:34:17 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 05/12] integrity/digest_cache: Parse tlv digest lists Date: Fri, 21 Jul 2023 18:33:19 +0200 Message-Id: <20230721163326.4106089-6-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S7 X-Coremail-Antispam: 1UD129KBjvJXoWfGr47Jr17tFWDGw4xJFWkZwb_yoWDCry5pa sxKF18KrW7GF1fCw4xAF17Cr4fKrZ09rW7KFWruw1ayrWDZr1qk3Z2kFy8Zry5tr4DW3W7 Jw4agF909r4DXaUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPlb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw 0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aV AFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZF pf9x07j7GYLUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAFBF1jj5DJQgAAsq X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE, T_SCC_BODY_TEXT_LINE 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 7537c7232db..a486dc1ff50 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 @@ -126,6 +127,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 Fri Jul 21 16:33:20 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322310 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 3C45320FBD for ; Fri, 21 Jul 2023 16:35:46 +0000 (UTC) Received: from frasgout12.his.huawei.com (unknown [14.137.139.154]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8EA8E10CE; Fri, 21 Jul 2023 09:35:29 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4R6vvQ0JSVz9v7cT; Sat, 22 Jul 2023 00:21:38 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S8; Fri, 21 Jul 2023 17:34:24 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 06/12] integrity/digest_cache: Parse rpm digest lists Date: Fri, 21 Jul 2023 18:33:20 +0200 Message-Id: <20230721163326.4106089-7-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S8 X-Coremail-Antispam: 1UD129KBjvJXoW3AFWkAr1xCw17Xr13ZryxXwb_yoWxKw1fpa sxKFy8KrWkXF1Sk3yxAF12kF1Sq3yjg3W2qrW5urn0yFZ7Zr1jvw18GryxZryrJr4DZFy7 Kr4YqF1I9F4qqaDanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPlb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw 0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aV AFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZF pf9x07j7GYLUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAFBF1jj4zMlgAAsk X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE 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 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 | 174 ++++++++++++++++++ 4 files changed, 180 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 a486dc1ff50..3bf0e6d06bf 100644 --- a/security/integrity/digest_cache.c +++ b/security/integrity/digest_cache.c @@ -129,6 +129,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..a4c1d0350ec --- /dev/null +++ b/security/integrity/digest_list_parsers/rpm.c @@ -0,0 +1,174 @@ +// 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 +#include +#include +#include + +#define RPMTAG_FILEDIGESTS 1035 +#define RPMTAG_FILEDIGESTALGO 5011 + +#include "parsers.h" + +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 u8 *bufp = data, *bufendp = data + data_len; + const u8 *datap, *digests = NULL, *algo_buf = NULL; + struct rpm_hdr *hdr = (struct rpm_hdr *)bufp; + struct rpm_entryinfo *entry; + u32 tags = be32_to_cpu(hdr->tags); + u32 digests_count = 0; + enum hash_algo pkg_kernel_algo = HASH_ALGO_MD5; + enum pgp_algos pkg_pgp_algo; + u8 rpm_digest[SHA512_DIGEST_SIZE]; + int ret, i, digest_len; + + const unsigned char rpm_header_magic[8] = { + 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00 + }; + + 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(bufp, rpm_header_magic, sizeof(rpm_header_magic))) { + pr_debug("RPM header magic mismatch\n"); + return -EINVAL; + } + + bufp += sizeof(*hdr); + datap = bufp + tags * sizeof(struct rpm_entryinfo); + + pr_debug("Scanning %d RPM header sections\n", tags); + + for (i = 0; i < tags && (bufp + sizeof(*entry)) <= bufendp; + i++, bufp += sizeof(*entry)) { + entry = (struct rpm_entryinfo *)bufp; + + switch (be32_to_cpu(entry->tag)) { + case RPMTAG_FILEDIGESTS: + digests = datap + be32_to_cpu(entry->offset); + digests_count = be32_to_cpu(entry->count); + pr_debug("Found RPMTAG_FILEDIGESTS at offset %ld, count: %d\n", + digests - data, digests_count); + break; + case RPMTAG_FILEDIGESTALGO: + algo_buf = datap + be32_to_cpu(entry->offset); + pr_debug("Found RPMTAG_FILEDIGESTALGO at offset %ld\n", + algo_buf - data); + break; + } + + if (digests && algo_buf) + break; + } + + if (!digests) + return 0; + + if (algo_buf && algo_buf + sizeof(u32) <= bufendp) { + pkg_pgp_algo = be32_to_cpu(*(u32 *)algo_buf); + 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]; + + ret = digest_cache_init_htable(digest_cache, digests_count); + if (ret < 0) + return ret; + + ret = -ENOENT; + + for (i = 0; i < digests_count && digests < bufendp; i++) { + if (!*digests) { + digests++; + continue; + } + + if (digests + digest_len * 2 + 1 > bufendp) { + pr_debug("Read beyond end\n"); + ret = -EINVAL; + break; + } + + ret = hex2bin(rpm_digest, digests, digest_len); + if (ret < 0) { + pr_debug("Invalid hex format for digest %s\n", digests); + ret = -EINVAL; + break; + } + + ret = digest_cache_add(digest_cache, rpm_digest); + if (ret < 0) + return ret; + + digests += digest_len * 2 + 1; + } + + return ret; +} From patchwork Fri Jul 21 16:33:21 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322317 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 C303F2770A for ; Fri, 21 Jul 2023 16:36:20 +0000 (UTC) Received: from frasgout11.his.huawei.com (unknown [14.137.139.23]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E29E73AA5; Fri, 21 Jul 2023 09:35:56 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.227]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4R6vxj2gZ1z9xFQS; Sat, 22 Jul 2023 00:23:37 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S9; Fri, 21 Jul 2023 17:34:32 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 07/12] ima: Add digest_cache policy keyword Date: Fri, 21 Jul 2023 18:33:21 +0200 Message-Id: <20230721163326.4106089-8-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S9 X-Coremail-Antispam: 1UD129KBjvJXoW3CrWruw17JF4DAFyxCw15Jwb_yoWkXFykpa yvg3WUCr48XryS9r1xAa4q9r4FgrWIqa1UA395X342y3ZxXr10v3WfJr13CFy3ArW5Cr92 yF1Ygr4Dua1jvaDanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPlb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW5JVW7JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw 0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW5JVW7JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aV AFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZF pf9x07j7GYLUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAFBF1jj5DJQwAAsr X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE 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). 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 | 51 +++++++++++++++++++++++++-- 6 files changed, 66 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 859a94bcecb..bdf03525e15 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, + u8 *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, + u8 *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..bc25675264c 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, + u8 *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..701c4f21158 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; + u8 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, + u8 *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,23 @@ 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; + } + audit_log_format(ab, "res=%d", !result); audit_log_end(ab); return result; @@ -2278,6 +2322,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 Fri Jul 21 16:33:22 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322312 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 8A80124191 for ; Fri, 21 Jul 2023 16:35:58 +0000 (UTC) Received: from frasgout12.his.huawei.com (unknown [14.137.139.154]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6042E30F0; Fri, 21 Jul 2023 09:35:40 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4R6vvj014Nz9xFZY; Sat, 22 Jul 2023 00:21:52 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S10; Fri, 21 Jul 2023 17:34:39 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 08/12] ima: Use digest cache for measurement Date: Fri, 21 Jul 2023 18:33:22 +0200 Message-Id: <20230721163326.4106089-9-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S10 X-Coremail-Antispam: 1UD129KBjvJXoW3Gr1rtw1UXFWkKF1fZrWUJwb_yoWxXr1fpa 9xuF15Kr4ruFyfCrn3A3W7Aa1S9ryktF4UGws8Xw1Skay3Xr1jva1rAw129Fy5Jry5Xa4x ta1jgr4Uuw4jvaDanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPlb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW5JVW7JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw 0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW5JVW7JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aV AFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZF pf9x07j7GYLUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAFBF1jj4zMlwAAsl X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE 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 | 27 +++++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index bdf03525e15..4f40e07954d 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, + unsigned int 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 bc25675264c..591d388158b 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, + unsigned int 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..7a5148ac3af 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -221,6 +221,9 @@ static int process_measurement(struct file *file, const struct cred *cred, bool violation_check; enum hash_algo hash_algo; unsigned int allowed_algos = 0; + u8 digest_cache_mask = 0; + struct digest_cache *digest_cache = NULL; + struct path digest_list_path; if (!ima_policy_flag || !S_ISREG(inode->i_mode)) return 0; @@ -231,7 +234,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 +266,18 @@ static int process_measurement(struct file *file, const struct cred *cred, if (!action) goto out; + if (digest_cache_mask) { + if (digest_cache_mask & DIGEST_CACHE_MEASURE) + digest_cache_iter_dir(file_dentry(file)); + + digest_cache = digest_cache_get(file_dentry(file), + &digest_list_path); + 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 +364,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) { @@ -391,6 +413,7 @@ static int process_measurement(struct file *file, const struct cred *cred, out: if (pathbuf) __putname(pathbuf); + digest_cache_put(digest_cache, &digest_list_path); if (must_appraise) { if (rc && (ima_appraise & IMA_APPRAISE_ENFORCE)) return -EACCES; From patchwork Fri Jul 21 16:33:23 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322313 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 CBC6E25158 for ; Fri, 21 Jul 2023 16:36:02 +0000 (UTC) Received: from frasgout13.his.huawei.com (unknown [14.137.139.46]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B5EF24203; Fri, 21 Jul 2023 09:35:43 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4R6vy24Pz1z9xFH1; Sat, 22 Jul 2023 00:23:54 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S11; Fri, 21 Jul 2023 17:34:47 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 09/12] ima: Use digest cache for appraisal Date: Fri, 21 Jul 2023 18:33:23 +0200 Message-Id: <20230721163326.4106089-10-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S11 X-Coremail-Antispam: 1UD129KBjvJXoWxuF13ur1UJF1rZFWktFy3twb_yoW5Kw15pa 98KF15GryrWFWa9FZ8Aanxua1Sk3yqgF4DW398JwnFyFZxXr1jvryrJ342vFy5Xr1rJrn7 twnFgr1UAa1rt3DanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPlb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW5JVW7JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Jw 0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW5JVW7JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aV AFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVW8Jr0_Cr1UYxBIdaVFxhVjvjDU0xZF pf9x07j7GYLUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAFBF1jj5DJRQAAst X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE, T_SCC_BODY_TEXT_LINE 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 4f40e07954d..385aaede15b 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, + u8 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..e6e0ac5e26a 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, + u8 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 7a5148ac3af..9f745b473bc 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -381,7 +381,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 Fri Jul 21 16:33:24 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322314 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 1BA3525923 for ; Fri, 21 Jul 2023 16:36:06 +0000 (UTC) Received: from frasgout12.his.huawei.com (unknown [14.137.139.154]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A4C64420F; Fri, 21 Jul 2023 09:35:45 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.227]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4R6vw01JDVz9xFZw; Sat, 22 Jul 2023 00:22:08 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S12; Fri, 21 Jul 2023 17:34:55 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 10/12] tools: Add tool to manage digest lists Date: Fri, 21 Jul 2023 18:33:24 +0200 Message-Id: <20230721163326.4106089-11-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S12 X-Coremail-Antispam: 1UD129KBjvAXoWftryrWF1rCw4rXF1rCFyrCrg_yoW8Kw4rWo Z2qF43Gw4ftr17CF4kuFn3Xa1UGwnYkrWkCry8JrWDZF1rJF18KanFkFW5uF13Wr4rKFy3 ur40q348ur48JrZ7n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOo7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Xr0_Ar1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F4 0Ex7xfMcIj6xIIjxv20xvE14v26r106r15McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC 6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxV Aaw2AFwI0_GFv_Wryl42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2Iq xVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r 1q6r43MIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Xr0_Ar1lIxAIcVC0I7IYx2IY 6xkF7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2js IE14v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIF yTuYvjxUxrcTDUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAFBF1jj5DJRQABss X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE,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 'format-'. 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 d3af1e179b0..6ee11828f2b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10294,6 +10294,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..9da62bd3570 --- /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); + exit(1); + } + 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); + exit(1); + } + break; + case 'h': + usage(argv[0]); + exit(0); + default: + printf("Invalid option %c\n", c); + exit(1); + } + } + + if (op == OP__LAST) { + printf("Operation not specified\n"); + exit(1); + } + + switch (op) { + case OP_GEN: + if (!digest_list_format || !input || !digest_list_dir) { + printf("Missing format/input/digest list directory\n"); + exit(1); + } + + 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"); + exit(1); + } + + 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 Fri Jul 21 16:33:25 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322315 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 2098F26B61 for ; Fri, 21 Jul 2023 16:36:15 +0000 (UTC) Received: from frasgout13.his.huawei.com (unknown [14.137.139.46]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 14E4A30D2; Fri, 21 Jul 2023 09:35:48 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.229]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4R6vyK645Dz9xFfT; Sat, 22 Jul 2023 00:24:09 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S13; Fri, 21 Jul 2023 17:35:02 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 11/12] tools/digest-lists: Add tlv digest list generator and parser Date: Fri, 21 Jul 2023 18:33:25 +0200 Message-Id: <20230721163326.4106089-12-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S13 X-Coremail-Antispam: 1UD129KBjvAXoWfXr1xCr18urykKr43AFW5Wrg_yoW8WFWxZo ZaqF43Gw48Jr129F4kuF43ZF47Wa9Yqay5Aw1rGrWDX3WFyF18Ka1qka13Ja13Zw18trWj v3W0q3yagw48KrZ7n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOo7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Xr0_Ar1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F4 0Ex7xfMcIj6xIIjxv20xvE14v26r106r15McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC 6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxV Aaw2AFwI0_GFv_Wryl42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2Iq xVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r 1q6r43MIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Xr0_Ar1lIxAIcVC0I7IYx2IY 6xkF7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2js IE14v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIF yTuYvjxUxrcTDUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAFBF1jj5DJRQACsv X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RDNS_DYNAMIC,SPF_HELO_NONE,SPF_NONE, T_SCC_BODY_TEXT_LINE,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 9da62bd3570..db5680506a8 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 Fri Jul 21 16:33:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 13322316 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 03F00275C9 for ; Fri, 21 Jul 2023 16:36:18 +0000 (UTC) Received: from frasgout12.his.huawei.com (unknown [14.137.139.154]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 575D835A0; Fri, 21 Jul 2023 09:35:53 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.18.147.228]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4R6vwH1F1sz9ttD1; Sat, 22 Jul 2023 00:22:23 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwC3hl1bs7pkcDDSBA--.22409S14; Fri, 21 Jul 2023 17:35:10 +0100 (CET) From: Roberto Sassu To: 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-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, Roberto Sassu Subject: [RFC][PATCH 12/12] tools/digest-lists: Add rpm digest list generator and parser Date: Fri, 21 Jul 2023 18:33:26 +0200 Message-Id: <20230721163326.4106089-13-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230721163326.4106089-1-roberto.sassu@huaweicloud.com> References: <20230721163326.4106089-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: GxC2BwC3hl1bs7pkcDDSBA--.22409S14 X-Coremail-Antispam: 1UD129KBjvAXoWfJr17Jw1DuF1UWFWkurykKrg_yoW8XF45uo Zaga13Gan0kr18uF4vkFy3Xa1ayanYya1UA3yrWryqq3W8AFy0g3Z5KanrXrW7ur4rJryS qr4Iq343Aw4xW3s5n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOo7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Xr0_Ar1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F4 0Ex7xfMcIj6xIIjxv20xvE14v26r106r15McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC 6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxV Aaw2AFwI0_GFv_Wryl42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2Iq xVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r 1q6r43MIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Xr0_Ar1lIxAIcVC0I7IYx2IY 6xkF7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2js IE14v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIF yTuYvjxUxrcTDUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAFBF1jj4zMmQAAsr X-CFilter-Loop: Reflected X-Spam-Status: No, score=-0.9 required=5.0 tests=BAYES_00,MAY_BE_FORGED, RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_BL,RCVD_IN_MSPIKE_L3,RDNS_DYNAMIC, SPF_HELO_NONE,SPF_NONE,T_SCC_BODY_TEXT_LINE,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 db5680506a8..75ddb542062 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; +}