From patchwork Wed Sep 15 16:31:37 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496753 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4F796C433F5 for ; Wed, 15 Sep 2021 16:34:56 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2E2C0610D1 for ; Wed, 15 Sep 2021 16:34:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229849AbhIOQeL (ORCPT ); Wed, 15 Sep 2021 12:34:11 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3824 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229841AbhIOQeJ (ORCPT ); Wed, 15 Sep 2021 12:34:09 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m0q4x9Mz67gjm; Thu, 16 Sep 2021 00:30:35 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:32:46 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 1/9] ima: Introduce new hook DIGEST_LIST_CHECK Date: Wed, 15 Sep 2021 18:31:37 +0200 Message-ID: <20210915163145.1046505-2-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce a new hook called DIGEST_LIST_CHECK to measure and appraise files read by the kernel with type READING_DIGEST_LIST. Files with this type are: - digest lists directly read by the kernel - executables of digest list parsers - files read by the parsers (including digest lists) Typical rules with the new hook are: - measure func=DIGEST_LIST_CHECK pcr=11 - appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig Signed-off-by: Roberto Sassu --- security/integrity/ima/ima.h | 1 + security/integrity/ima/ima_main.c | 3 ++- security/integrity/ima/ima_policy.c | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index be965a8715e4..d01ca3566aec 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -202,6 +202,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) \ 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 26a709207ca3..d33765c7aaa7 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -723,7 +723,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 761ae5b63735..8d6f5d85dfa4 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -1222,6 +1222,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 | @@ -1446,6 +1447,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 Wed Sep 15 16:31:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496695 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3B024C4167B for ; Wed, 15 Sep 2021 16:32:54 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 27BBC610D1 for ; Wed, 15 Sep 2021 16:32:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229988AbhIOQeM (ORCPT ); Wed, 15 Sep 2021 12:34:12 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3825 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229854AbhIOQeJ (ORCPT ); Wed, 15 Sep 2021 12:34:09 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m0r2mrNz67h7V; Thu, 16 Sep 2021 00:30:36 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:32:47 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 2/9] diglim: Loader Date: Wed, 15 Sep 2021 18:31:38 +0200 Message-ID: <20210915163145.1046505-3-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Digest lists should be loaded as soon as possible, before files are accessed, so that services like IMA can perform their queries. Implement two methods for loading digest lists at kernel initialization time. Take a directory from CONFIG_DIGLIM_DIGEST_LISTS_DIR and execute digest_list_read() for each file in that directory. Afterwards, execute a digest list uploader at CONFIG_DIGLIM_UPLOADER_PATH with the following syntax: add Signed-off-by: Roberto Sassu --- .../security/diglim/implementation.rst | 16 ++++ MAINTAINERS | 1 + security/integrity/diglim/Kconfig | 14 +++ security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/loader.c | 92 +++++++++++++++++++ security/integrity/iint.c | 1 + security/integrity/integrity.h | 8 ++ 7 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 security/integrity/diglim/loader.c diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index f7b48c093f39..0290b42b014b 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -226,3 +226,19 @@ This section introduces the interfaces in ``/integrity/diglim`` necessary to interact with DIGLIM. .. kernel-doc:: security/integrity/diglim/fs.c + + +Loader +------ + +Digest lists should be loaded as soon as possible, before files are +accessed, so that services like IMA can perform their queries. + +The kernel loader implements two methods for loading digest lists at kernel +initialization time. The first method takes a directory from +CONFIG_DIGLIM_DIGEST_LISTS_DIR and executes digest_list_read() for each +file in that directory. The second method, invoked sequentially after the +first, executes a digest list uploader at CONFIG_DIGLIM_UPLOADER_PATH with +the following syntax: + + add diff --git a/MAINTAINERS b/MAINTAINERS index 033c70014568..0ffceb271803 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5518,6 +5518,7 @@ F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h F: security/integrity/diglim/fs.c F: security/integrity/diglim/ima.c +F: security/integrity/diglim/loader.c F: security/integrity/diglim/methods.c F: security/integrity/diglim/parser.c F: tools/testing/selftests/diglim/ diff --git a/security/integrity/diglim/Kconfig b/security/integrity/diglim/Kconfig index 436a76a14337..6c76a0fefe42 100644 --- a/security/integrity/diglim/Kconfig +++ b/security/integrity/diglim/Kconfig @@ -9,3 +9,17 @@ config DIGLIM help DIGLIM provides reference values for file content and metadata, that can be used for measurement and appraisal with IMA. + +config DIGLIM_DIGEST_LISTS_DIR + string "Path of the directory containing digest lists" + depends on DIGLIM + default "/etc/digest_lists" + help + This option defines the path of the directory containing digest lists. + +config DIGLIM_UPLOADER_PATH + string "Path of the user space digest list uploader" + depends on DIGLIM + default "/usr/libexec/diglim/upload_digest_lists" + help + This option defines the path of the user space digest list uploader. diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile index 5cb1a8bfa0fc..ae79a3317ec8 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGLIM) += diglim.o -diglim-y := methods.o parser.o ima.o fs.o +diglim-y := methods.o parser.o ima.o fs.o loader.o diff --git a/security/integrity/diglim/loader.c b/security/integrity/diglim/loader.c new file mode 100644 index 000000000000..e95d3716c153 --- /dev/null +++ b/security/integrity/diglim/loader.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Functions to load digest lists. + */ + +#include +#include +#include +#include + +#include "diglim.h" + +struct readdir_callback { + struct dir_context ctx; + struct path *path; +}; + +static bool digest_list_supported_by_kernel(const char *filename) +{ + char *type_start, *format_start, *format_end; + + type_start = strchr(filename, '-'); + if (!type_start++) + return false; + + format_start = strchr(type_start, '-'); + if (!format_start++) + return false; + + format_end = strchr(format_start, '-'); + if (!format_end) + return false; + + if (format_end - format_start != strlen("compact") || + strncmp(format_start, "compact", format_end - format_start)) + return false; + + return true; +} + +static int __init digest_list_load(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); + int ret; + + if (!strcmp(name, ".") || !strcmp(name, "..")) + return 0; + + if (!digest_list_supported_by_kernel(name)) + return 0; + + ret = digest_list_read(ctx->path, (char *)name, DIGEST_LIST_ADD); + if (ret < 0) + return ret; + + return 0; +} + +static void digest_list_exec_parser(void) +{ + char *argv[4] = {NULL}, *envp[1] = {NULL}; + + argv[0] = (char *)CONFIG_DIGLIM_UPLOADER_PATH; + argv[1] = "add"; + argv[2] = CONFIG_DIGLIM_DIGEST_LISTS_DIR; + + call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); +} + +void __init digest_lists_load(void) +{ + struct file *file; + struct readdir_callback buf = { + .ctx.actor = digest_list_load, + }; + + file = filp_open(CONFIG_DIGLIM_DIGEST_LISTS_DIR, O_RDONLY, 0); + if (IS_ERR(file)) + return; + + buf.path = &file->f_path; + iterate_dir(file, &buf.ctx); + fput(file); + + digest_list_exec_parser(); +} diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 8638976f7990..3799311cc0c3 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -211,6 +211,7 @@ void __init integrity_load_keys(void) if (!IS_ENABLED(CONFIG_IMA_LOAD_X509)) evm_load_x509(); + digest_lists_load(); } static int __init integrity_fs_init(void) diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index de5dde382f11..2ab85f286d17 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -288,4 +288,12 @@ static inline void __init add_to_platform_keyring(const char *source, { } #endif + +#ifdef CONFIG_DIGLIM +void __init digest_lists_load(void); +#else +static inline void __init digest_lists_load(void) +{ +} +#endif #endif /*__INTEGRITY_H*/ From patchwork Wed Sep 15 16:31:39 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496757 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 152A2C433F5 for ; Wed, 15 Sep 2021 16:34:58 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 00B666121F for ; Wed, 15 Sep 2021 16:34:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229788AbhIOQgP (ORCPT ); Wed, 15 Sep 2021 12:36:15 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3826 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229889AbhIOQeK (ORCPT ); Wed, 15 Sep 2021 12:34:10 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m0s0m94z67jhw; Thu, 16 Sep 2021 00:30:37 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:32:48 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 3/9] diglim: LSM Date: Wed, 15 Sep 2021 18:31:39 +0200 Message-ID: <20210915163145.1046505-4-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce a new LSM to keep track of the operations performed by digest list parsers to convert the digest lists and upload them to the kernel. The motivation and its behavior is described more in detail in Documentation/security/diglim/lsm.rst. Signed-off-by: Roberto Sassu --- Documentation/security/diglim/index.rst | 1 + Documentation/security/diglim/lsm.rst | 65 ++++ MAINTAINERS | 1 + security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/diglim.h | 27 ++ security/integrity/diglim/fs.c | 3 + security/integrity/diglim/hooks.c | 436 ++++++++++++++++++++++++ 7 files changed, 534 insertions(+), 1 deletion(-) create mode 100644 Documentation/security/diglim/lsm.rst create mode 100644 security/integrity/diglim/hooks.c diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst index d4ba4ce50a59..bf3cc4a9a91d 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -12,3 +12,4 @@ Digest Lists Integrity Module (DIGLIM) implementation remote_attestation tests + lsm diff --git a/Documentation/security/diglim/lsm.rst b/Documentation/security/diglim/lsm.rst new file mode 100644 index 000000000000..ce979e6c6dfd --- /dev/null +++ b/Documentation/security/diglim/lsm.rst @@ -0,0 +1,65 @@ +.. SPDX-License-Identifier: GPL-2.0 + +LSM +=== + +When digest lists (in compact format) are directly uploaded by the kernel, +determining their integrity is straightforward, as a file open is the only +operation performed. + +However, if digest lists are first processed by a user space parser, many +operations occur before the converted digest list is uploaded to the +kernel, and any of them may affect the result of the conversion. In this +case, the integrity of all files involved must be evaluated to ensure that +the output is the expected one. + +The new DIGLIM LSM has been introduced with two goals: the first is to +identify user space parsers as soon as they are loaded, in order to monitor +the operations they perform; the second is to avoid interference from other +processes, which are assumed as untrusted. + +Regarding the first goal, user space parsers are identified by calculating +the digest of their executable and searching it in the DIGLIM hash table. +An executable is successfully recognized as a digest list parser if its +digest is found and the associated type is COMPACT_PARSER. Once a parser +has been identified, DIGLIM LSM monitors the integrity of opened files. In +addition, it also denies access to ld.so.cache, to avoid an unknown +measurement or appraisal failure, and to files without content measurable +by IMA (e.g. character devices). + +The integrity status of the parser, a set of flags representing the +operations performed by IMA, is kept in the credentials of the process +identified as parser. Initially, the flags are set from the operations done +on the executable and they are AND-ed with the flags retrieved at each file +open (which themselves are set from the operations done by IMA on that +file). This ensures that even if one file was not processed, this is +reflected in the global integrity status of the parser. Given that the AND +operation prevents the cleared flag to be set again, the only way to upload +a converted digest list with that flag is to restart the parser. + +The flags still set in the process credentials at the time the parser +uploads the converted digest lists are then copied to the converted lists +themselves, so that they can be retrieved by DIGLIM users during a digest +query and evaluated (the query result might be discarded). This mechanism +is reliable against LSM misconfiguration: if for any reason DIGLIM LSM is +turned off, no flags will be set in the converted digest list. + +Regarding the second goal, avoiding interference from other user space +processes is necessary if they are assumed to be untrusted. This threat +model applies if the system is supposed to enforce a mandatory policy where +only files shipped by software vendors are allowed to be accessed. The +mandatory policy could be also defined by system administrators (they could +decide the set of approved software vendors). + +To avoid interference to the user space parsers from other processes, the +following countermeasures are implemented. First, files accessed by user +space parsers are exclusively write-locked until the parsers finish to use +them. A failure when write-locking a file (if the file was already opened +for writing by another process) will result in the file access to be denied +to the parser. Second, ptraces on the parsers are also denied as they might +influence their execution. + +Other than these two limitations (not being able to access files +write-locked by the parsers and to ptrace the parsers), processes which are +not identified as parsers are not subject to the policy enforcement by +DIGLIM LSM. diff --git a/MAINTAINERS b/MAINTAINERS index 0ffceb271803..94220e40b7e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5511,6 +5511,7 @@ F: Documentation/security/diglim/architecture.rst F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: Documentation/security/diglim/lsm.rst F: Documentation/security/diglim/remote_attestation.rst F: Documentation/security/diglim/tests.rst F: include/linux/diglim.h diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile index ae79a3317ec8..37fa6ef2a73c 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGLIM) += diglim.o -diglim-y := methods.o parser.o ima.o fs.o loader.o +diglim-y := methods.o parser.o ima.o fs.o loader.o hooks.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index c597c2e7a52a..b53de803a63c 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "../integrity.h" @@ -28,6 +29,13 @@ #define HASH_BITS 10 #define DIGLIM_HTABLE_SIZE (1 << HASH_BITS) +#define FLAG_PARSER_EXEC 0x01 +#define FLAG_PARSER_FILE_ACCESS 0x02 +#define FLAG_PARSER_FILE_ACCESS_DENY 0x04 + +extern struct lsm_blob_sizes diglim_lsm_blob_sizes; +extern int diglim_lsm_enabled; + /** * struct digest_list_item - a digest list loaded into the kernel * @@ -229,4 +237,23 @@ int diglim_ima_get_info(struct file *file, u8 *buffer, size_t buffer_len, enum hash_algo *algo, u8 *actions); ssize_t digest_list_read(struct path *root, char *path, enum ops op); +static inline u8 *diglim_cred_actions(const struct cred *cred) +{ + return cred->security + diglim_lsm_blob_sizes.lbs_cred; +} + +static inline u8 *diglim_cred_flags(const struct cred *cred) +{ + return diglim_cred_actions(cred) + 1; +} + +static inline u8 *diglim_inode(const struct inode *inode) +{ + return inode->i_security + diglim_lsm_blob_sizes.lbs_inode; +} + +static inline u8 *diglim_file(const struct file *file) +{ + return file->f_security + diglim_lsm_blob_sizes.lbs_file; +} #endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index 467ff4f7c0ce..56c6f7ff2b3c 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -577,6 +577,9 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf, enum hash_algo algo; u8 actions = 0; + if (diglim_lsm_enabled) + actions = *diglim_cred_actions(current_cred()); + /* No partial writes. */ result = -EINVAL; if (*ppos != 0) diff --git a/security/integrity/diglim/hooks.c b/security/integrity/diglim/hooks.c new file mode 100644 index 000000000000..f186ffbac628 --- /dev/null +++ b/security/integrity/diglim/hooks.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Functions to evaluate the integrity of converted digest lists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "diglim.h" + +int diglim_lsm_enabled; + +/** + * diglim_file_read_with_ima - read the file with IMA + * @file: file descriptor of the file being accessed + * + * This function opens the file again so that it can pass the file descriptor to + * IMA for reading the file. It also marks the inode so that diglim_file_open() + * recognizes that the open was made by DIGLIM LSM and does not process it. + * + * NOTE: the second open might be not necessary, depending on the IMA policy; + * however, since the second open is always performed with type + * READING_DIGEST_LIST, it can be easily monitored by IMA with a rule with + * func=DIGEST_LIST_CHECK + * + * Return: 0 on success, a negative value on error + */ +static int diglim_file_read_with_ima(struct file *file) +{ + struct inode *inode = file_inode(file); + u8 *inode_flags = diglim_inode(inode); + void *datap = NULL; + struct file *f; + int ret, flags; + + /* Taken from ima_crypto.c. */ + flags = file->f_flags & ~(O_WRONLY | O_APPEND | O_TRUNC | O_CREAT | + O_NOCTTY | O_EXCL); + flags |= O_RDONLY; + + /* Signal to diglim_file_open() to not evaluate the second open. */ + *inode_flags |= FLAG_PARSER_FILE_ACCESS; + /* Open an additional file descriptor to use with ima_read_file(). */ + f = dentry_open(&file->f_path, flags, file->f_cred); + *inode_flags &= ~FLAG_PARSER_FILE_ACCESS; + if (IS_ERR(f)) + return PTR_ERR(f); + + /* + * Pass the file descriptor to IMA with file type READING_DIGEST_LIST, + * so that this operation can be more easily identified with an IMA rule + * with func=DIGEST_LIST_CHECK. + */ + ret = kernel_read_file(f, 0, &datap, INT_MAX, NULL, + READING_DIGEST_LIST); + if (ret >= 0) + vfree(datap); + + fput(f); + return ret; +} + +/** + * diglim_identify_parser - identify a digest list parser from the executable + * @cred: credentials of the child process + * @file: file descriptor of the child process executable + * + * This function first identifies a digest list parser from the executable, by + * searching the executable digest in the DIGLIM hash table. A parser is + * successfully identified if the digest is found in the hash table and if the + * type of the found digest is COMPACT_PARSER. + * + * If a parser has been successfully identified, this function also sets the + * actions performed by IMA on the executable in the process credentials. These + * initial actions will be AND-ed with the actions performed by IMA on each + * regular file opened by the parser. A missing action for a file will cause + * the corresponding action in the process credentials to be irreversibly + * cleared. + * + * Updating the actions in the process credentials in this way makes it clear + * whether or not the parser is suitable for use for a particular integrity goal + * (measurement or appraisal). If an action is set, it means that the executable + * and the process inputs have been evaluated for a particular goal, and from + * this it can be inferred that the output of the process is also correct. + * + * Return: 0 if a digest list parser has been successfully identified, a + * negative value otherwise + */ +static int diglim_identify_parser(const struct cred *cred, struct file *file) +{ + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + u8 *parser_actions = diglim_cred_actions(cred); + u8 digest[IMA_MAX_DIGEST_SIZE]; + enum hash_algo algo = HASH_ALGO__LAST; + u16 modifiers = 0; + u8 digest_list_actions = 0, file_actions = 0; + int ret = -ENOENT; + + /* Read the file with IMA. */ + ret = diglim_file_read_with_ima(file); + if (ret < 0) + return ret; + + iint = integrity_iint_find(inode); + if (!iint) + return ret; + + /* + * Since executables are write-protected, information obtained from IMA + * (digest and actions performed on the executable) are safe to use + * without the risk of races with writers. + */ + mutex_lock(&iint->mutex); + if (!(iint->flags & IMA_COLLECTED)) { + mutex_unlock(&iint->mutex); + goto out; + } + + /* Query the executable digest to determine if it is a parser. */ + ret = diglim_digest_get_info(iint->ima_hash->digest, + iint->ima_hash->algo, COMPACT_PARSER, + &modifiers, &digest_list_actions); + + mutex_unlock(&iint->mutex); + + if (ret < 0) + goto out; + + /* Obtain the flags from IMA operations on the executable. */ + ret = diglim_ima_get_info(file, NULL, 0, NULL, digest, sizeof(digest), + &algo, &file_actions); + if (!ret) + *parser_actions |= file_actions; + + pr_debug("%s: task: %d(%s), parser initial actions: %d\n", __func__, + current->pid, current->comm, *parser_actions); +out: + return ret; +} + +/** + * diglim_bprm_committing_creds - implement the bprm_committing_creds hook + * @bprm: linux_binprm structure of the file being executed + * + * This function implements the bprm_committing_creds hook, to identify a digest + * list parser from the digest of the executable. After a successful + * identification, the FLAG_PARSER_EXEC flag is set in the process credentials, + * so that the diglim_file_open() hook below knows that it should enforce the + * parser policy. + */ +static void diglim_bprm_committing_creds(struct linux_binprm *bprm) +{ + u8 *parser_flags = diglim_cred_flags(bprm->cred); + int ret; + + /* + * Try to identify the parsers if the parent directory is named diglim. + */ + if (strcmp(file_dentry(bprm->file)->d_parent->d_name.name, "diglim")) + return; + + ret = diglim_identify_parser(bprm->cred, bprm->file); + if (ret < 0) + return; + + *parser_flags |= FLAG_PARSER_EXEC; +} + +/** + * diglim_file_open_check - check file access and if IMA eval is required + * @file: file descriptor of the file being accessed + * + * This function checks a file access and determines whether or not it is safe + * for the parser to access the file. Access (read) is considered safe if the + * file is in a trusted filesystem (procfs, securityfs), does not have content + * to be read or it has content that can be measured/appraised by IMA, and there + * are no concurrent writes. + * + * NOTE: access to ld.so.cache, although it can be allowed, is instead denied to + * avoid an unknown measurement in the measurement list or appraisal + * failure. + * + * This function also determines whether an IMA evaluation is required. + * + * The following table summarizes the policy enforced on the parsers. + * + * Not parser parser + * +-----------+--------------+-----------------------+-----------------------+ + * | operation | file type | | | + * +-----------+--------------+ | | + * | | ld.so.cache | allow | allow | + * | | reg | allow [1] | allow | + * | write | dir/link | allow | allow | + * | | procfs/secfs | allow | allow | + * | | char/block/ | allow | allow | + * | | socket/fifo | | | + * +-----------+--------------+-----------------------+-----------------------+ + * | | ld.so.cache | allow | deny | + * | | reg | allow | allow [2] + IMA eval | + * | read | dir/link | allow | allow | + * | | procfs/secfs | allow | allow | + * | | char/block/ | allow | deny | + * | | socket/fifo | | | + * +-----------+--------------+-----------------------+-----------------------+ + * + * [1]: if not write-locked by the parser + * [2]: if there are no concurrent writes + * + * Return: 0 if access is allowed but IMA eval is not required, -EPERM if access + * is denied and 1 if access is allowed and IMA eval is required. + */ +static int diglim_file_open_check(struct file *file) +{ + struct inode *inode = file_inode(file); + + /* Skip non-read operations. */ + if (!(file->f_mode & FMODE_READ)) + return 0; + + /* Deny access to ld.so.cache. */ + if (!strcmp(file_dentry(file)->d_name.name, "ld.so.cache")) + return -EPERM; + + /* Deny access to files that can have content but cannot be measured. */ + if (!S_ISREG(inode->i_mode) && !S_ISLNK(inode->i_mode) && + !S_ISDIR(inode->i_mode)) + return -EPERM; + + /* Allow access to dirs and symlinks. */ + if (!S_ISREG(inode->i_mode)) + return 0; + + /* Allow access to files in procfs and securityfs. */ + if (inode->i_sb->s_magic == PROC_SUPER_MAGIC || + inode->i_sb->s_magic == SECURITYFS_MAGIC) + return 0; + + return 1; +} + +/** + * diglim_file_lock - write-lock the file before retrieving IMA actions + * @file: file descriptor of the file being locked + * + * This function write-locks the file being accessed in order to safely retrieve + * IMA actions and ensure that there are no concurrent writes, which would cause + * the retrieved actions to be outdated. + * + * Since IMA eventually resets the actions only when the file is closed, getting + * the exclusive write-lock ensures that the actions are up to date (for the + * exclusive write-lock to succeed there must be no pending writes, which means + * that IMA already updated the actions in ima_check_last_writer()). + * + * Return: 0 on success, -ETXTBSY if the file cannot be write-locked + */ +static int diglim_file_lock(struct file *file) +{ + u8 *file_flags = diglim_file(file); + int ret; + + /* Write-lock the file until the parser finishes to use it. */ + ret = deny_write_access(file); + if (ret < 0) + return ret; + + /* Grab a reference to the inode to avoid free before unlocking it. */ + igrab(file_inode(file)); + + /* Record deny_write_access() call in the file descriptor. */ + *file_flags |= FLAG_PARSER_FILE_ACCESS_DENY; + return 0; +} + +/** + * diglim_file_unlock - unlock the file locked by DIGLIM LSM + * @file: file descriptor of the file being unlocked + * + * This function first checks if the file was locked by DIGLIM LSM, and if yes, + * unlocks it. + */ +static void diglim_file_unlock(struct file *file) +{ + u8 *file_flags; + + file_flags = diglim_file(file); + + /* File not write-locked by DIGLIM LSM. */ + if (!(*file_flags & FLAG_PARSER_FILE_ACCESS_DENY)) + return; + + /* Release write-lock. */ + allow_write_access(file); + + /* Clear flag. */ + *file_flags &= ~FLAG_PARSER_FILE_ACCESS_DENY; + + /* Release reference taken in diglim_file_lock(). */ + iput(file_inode(file)); +} + +/** + * diglim_file_open - check file access and update process actions + * @file: file descriptor of the file being accessed + * + * This function checks the file being accessed by the parser and does an AND + * of the actions in the parser process credentials with the actions done by IMA + * on that file. + * + * The actions in the parser process credentials will be then copied to the + * converted digest lists uploaded by the parser. + * + * Return: 0 if access is allowed, a negative value if access is denied + */ +static int diglim_file_open(struct file *file) +{ + u8 file_actions = 0; + u8 *parser_flags = diglim_cred_flags(current_cred()); + u8 *parser_actions = diglim_cred_actions(current_cred()); + u8 initial_parser_actions = *parser_actions; + struct inode *inode = file_inode(file); + u8 *inode_flags = diglim_inode(inode); + int ret; + + /* Skip processes that are not the parser. */ + if (!(*parser_flags & FLAG_PARSER_EXEC)) + return 0; + + /* Skip calls due to dentry_open() in diglim_file_read_with_ima(). */ + if (*inode_flags & FLAG_PARSER_FILE_ACCESS) + return 0; + + /* Check file access. */ + ret = diglim_file_open_check(file); + if (ret <= 0) + return ret; + + /* Try to write-lock the file. */ + ret = diglim_file_lock(file); + if (ret < 0) + return ret; + + /* Read the file with IMA. */ + ret = diglim_file_read_with_ima(file); + if (ret < 0) { + diglim_file_unlock(file); + return ret; + } + + /* Retrieve the actions performed by IMA on the file. */ + diglim_ima_get_info(file, NULL, 0, NULL, NULL, 0, NULL, &file_actions); + + /* Do an AND of the parser process actions with the file actions. */ + *parser_actions &= file_actions; + + if (*parser_actions != initial_parser_actions) + pr_err("%s: task: %d(%s), cleared parser actions, file: %s, old flags: %d, new flags: %d\n", + __func__, current->pid, current->comm, + file_dentry(file)->d_name.name, initial_parser_actions, + *parser_actions); + + return 0; +} + +/** + * diglim_file_free - unlock files accessed by the parser + * @file: file descriptor of the file being monitored + * + * This function unlocks files accessed by the parser. + */ +void diglim_file_free(struct file *file) +{ + diglim_file_unlock(file); +} + +/** + * diglim_ptrace_access_check - deny ptraces on the parser process + * @child: task being ptraced + * @mode: ptrace mode + * + * This function denies ptraces on the parser process. + * + * Return: 0 if the ptrace is not done on the parser process, -EACCES otherwise + */ +static int diglim_ptrace_access_check(struct task_struct *child, + unsigned int mode) +{ + const struct cred *cred = get_task_cred(child); + u8 *flags = diglim_cred_flags(cred); + + /* Deny ptraces to the parser. */ + if (*flags & FLAG_PARSER_EXEC) + return -EACCES; + + return 0; +} + +static struct security_hook_list diglim_lsm_hooks[] __lsm_ro_after_init = { + LSM_HOOK_INIT(bprm_committing_creds, diglim_bprm_committing_creds), + LSM_HOOK_INIT(file_open, diglim_file_open), + LSM_HOOK_INIT(file_free_security, diglim_file_free), + LSM_HOOK_INIT(ptrace_access_check, diglim_ptrace_access_check), +}; + +static int __init diglim_lsm_init(void) +{ + diglim_lsm_enabled = 1; + security_add_hooks(diglim_lsm_hooks, ARRAY_SIZE(diglim_lsm_hooks), + "diglim"); + return 0; +} + +struct lsm_blob_sizes diglim_lsm_blob_sizes __lsm_ro_after_init = { + .lbs_cred = 2 * sizeof(u8), + .lbs_inode = sizeof(u8), + .lbs_file = sizeof(u8), +}; + +DEFINE_LSM(diglim) = { + .name = "diglim", + .init = diglim_lsm_init, + .blobs = &diglim_lsm_blob_sizes, +}; From patchwork Wed Sep 15 16:31:40 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496755 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id BE192C433EF for ; Wed, 15 Sep 2021 16:34:57 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A7EED61029 for ; Wed, 15 Sep 2021 16:34:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229665AbhIOQgP (ORCPT ); Wed, 15 Sep 2021 12:36:15 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3827 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229894AbhIOQeK (ORCPT ); Wed, 15 Sep 2021 12:34:10 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m0c3vhRz67yV7; Thu, 16 Sep 2021 00:30:24 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:32:48 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 4/9] diglim: Tests - LSM Date: Wed, 15 Sep 2021 18:31:40 +0200 Message-ID: <20210915163145.1046505-5-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce more tests to ensure that DIGLIM LSM works as expected: - digest_list_add_del_test_parser_upload; - digest_list_add_del_test_parser_upload_not_measured; - digest_list_add_del_test_parser_upload_write; - digest_list_add_del_test_parser_upload_read; - digest_list_add_del_test_parser_upload_char_dev. The tests are in tools/testing/selftests/diglim/selftest.c. A description of the tests can be found in Documentation/security/diglim/tests.rst. Signed-off-by: Roberto Sassu --- Documentation/security/diglim/tests.rst | 18 +- tools/testing/selftests/diglim/Makefile | 12 +- tools/testing/selftests/diglim/common.h | 9 + tools/testing/selftests/diglim/selftest.c | 357 +++++++++++++++++++++- 4 files changed, 378 insertions(+), 18 deletions(-) diff --git a/Documentation/security/diglim/tests.rst b/Documentation/security/diglim/tests.rst index 899e7d6683cf..21874918433d 100644 --- a/Documentation/security/diglim/tests.rst +++ b/Documentation/security/diglim/tests.rst @@ -14,7 +14,12 @@ expected: - ``digest_list_add_del_test_file_upload_measured``; - ``digest_list_add_del_test_file_upload_measured_chown``; - ``digest_list_check_measurement_list_test_file_upload``; -- ``digest_list_check_measurement_list_test_buffer_upload``. +- ``digest_list_check_measurement_list_test_buffer_upload``; +- ``digest_list_add_del_test_parser_upload``; +- ``digest_list_add_del_test_parser_upload_not_measured`` +- ``digest_list_add_del_test_parser_upload_write``; +- ``digest_list_add_del_test_parser_upload_read``; +- ``digest_list_add_del_test_parser_upload_char_dev``. The tests are in ``tools/testing/selftests/diglim/selftest.c``. @@ -68,3 +73,14 @@ addition. The ``file_upload`` variant uploads a file, while the ``buffer_upload`` variant uploads a buffer. + +The ``digest_list_add_del_test_parser`` tests verify the correctness of +DIGLIM LSM. The ``upload`` variant ensures that files opened by the parser +are evaluated and the actions are copied to the converted digest list. The +``upload_not_measured`` variant ensures that the IMA measure action is not +set to the converted digest list if the parser read a file not measured. +The ``upload_write`` variant ensures that the parser cannot access a file +concurrently written by another process. The ``upload_read`` variant +ensures that files being read by the parser cannot be written by other +processes. Finally, the ``upload_char_dev`` variant ensures that the parser +cannot access files without measurable content (e.g. character device). diff --git a/tools/testing/selftests/diglim/Makefile b/tools/testing/selftests/diglim/Makefile index 100c219955d7..0e68d8363dd3 100644 --- a/tools/testing/selftests/diglim/Makefile +++ b/tools/testing/selftests/diglim/Makefile @@ -7,13 +7,19 @@ LDLIBS += -lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS = selftest -TEST_GEN_PROGS_EXTENDED = libcommon.so +TEST_GEN_PROGS = selftest manage_digest_lists +TEST_GEN_PROGS_EXTENDED = libcommon.so libcommon.a common.o include ../lib.mk $(OUTPUT)/libcommon.so: common.c $(CC) $(CFLAGS) -shared -fPIC $< $(LDLIBS) -o $@ +$(OUTPUT)/libcommon.a: common.o + ar rcs libcommon.a common.o + $(OUTPUT)/selftest: selftest.c $(TEST_GEN_PROGS_EXTENDED) - $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) -lcommon + $(CC) $(CFLAGS) -DOUTPUT=\"$(OUTPUT)\" $< -o $@ $(LDFLAGS) -lcommon + +$(OUTPUT)/manage_digest_lists: manage_digest_lists.c $(TEST_GEN_PROGS_EXTENDED) + $(CC) $(CFLAGS) -static $< -o $@ $(LDFLAGS) -lcommon diff --git a/tools/testing/selftests/diglim/common.h b/tools/testing/selftests/diglim/common.h index 6c7979f4182e..b71031adb830 100644 --- a/tools/testing/selftests/diglim/common.h +++ b/tools/testing/selftests/diglim/common.h @@ -26,6 +26,15 @@ #define BUFFER_SIZE 1024 +#define INTEGRITY_DIR "/sys/kernel/security/integrity" +#define DIGEST_LIST_DIR INTEGRITY_DIR "/diglim" +#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query" +#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_list_label" +#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add" +#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del" +#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded" +#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count" + int write_buffer(char *path, char *buffer, size_t buffer_len, int uid); int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, bool is_char); diff --git a/tools/testing/selftests/diglim/selftest.c b/tools/testing/selftests/diglim/selftest.c index 273ba80c43fd..af7a590c445f 100644 --- a/tools/testing/selftests/diglim/selftest.c +++ b/tools/testing/selftests/diglim/selftest.c @@ -63,16 +63,6 @@ typedef uint64_t u64; #define DIGEST_LIST_PATH_TEMPLATE "/tmp/digest_list.XXXXXX" -#define INTEGRITY_DIR "/sys/kernel/security/integrity" - -#define DIGEST_LIST_DIR INTEGRITY_DIR "/diglim" -#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query" -#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_list_label" -#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add" -#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del" -#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded" -#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count" - #define IMA_POLICY_PATH INTEGRITY_DIR "/ima/policy" #define IMA_MEASUREMENTS_PATH INTEGRITY_DIR "/ima/ascii_runtime_measurements" @@ -94,7 +84,11 @@ typedef uint64_t u64; #define MAX_DIGEST_LIST_SIZE 10000 #define NUM_ITERATIONS 100000 -enum upload_types { UPLOAD_FILE, UPLOAD_FILE_CHOWN, UPLOAD_BUFFER }; +#define DIGEST_LIST_PARSER_PATH OUTPUT "/manage_digest_lists" +#define DIGEST_LIST_PARSER_PATH_COPY "/tmp/diglim/manage_digest_lists" + +enum upload_types { UPLOAD_FILE, UPLOAD_FILE_CHOWN, UPLOAD_BUFFER, + UPLOAD_PARSER, UPLOAD_PARSER_CHOWN, NO_UPLOAD }; const char *const hash_algo_name[HASH_ALGO__LAST] = { [HASH_ALGO_MD4] = "md4", @@ -486,6 +480,69 @@ static struct digest_list_item *digest_list_generate_random(void) return !ret ? digest_list : NULL; } +static struct digest_list_item *digest_list_generate_file(char *file_path, + enum compact_types type) +{ + struct digest_list_item *digest_list; + struct compact_list_hdr *hdr; + u8 digest[64]; + int ret; + + digest_list = calloc(1, sizeof(*digest_list)); + if (!digest_list) + return NULL; + + digest_list->size = sizeof(struct compact_list_hdr) + + hash_digest_size[HASH_ALGO_SHA256]; + digest_list->buf = calloc(digest_list->size, sizeof(unsigned char)); + if (!digest_list->buf) { + ret = -ENOMEM; + goto out; + } + + hdr = (struct compact_list_hdr *)digest_list->buf; + + hdr->version = 1; + hdr->_reserved = 0; + hdr->type = type; + hdr->modifiers = 0; + hdr->algo = HASH_ALGO_SHA256; + hdr->count = 1; + hdr->datalen = hash_digest_size[hdr->algo]; + + hdr->type = __cpu_to_le16(hdr->type); + hdr->algo = __cpu_to_le16(hdr->algo); + hdr->count = __cpu_to_le32(hdr->count); + hdr->datalen = __cpu_to_le32(hdr->datalen); + + ret = calc_file_digest(digest_list->buf + sizeof(*hdr), file_path, + HASH_ALGO_SHA256); + if (ret < 0) + goto out; + + digest_list->algo = get_ima_hash_algo(); + if (digest_list->algo == HASH_ALGO__LAST) { + ret = -ENOENT; + goto out; + } + + ret = calc_digest(digest, digest_list->buf, digest_list->size, + digest_list->algo); + if (ret < 0) + goto out; + + _bin2hex(digest_list->digest_str, digest, + hash_digest_size[digest_list->algo]); +out: + if (ret < 0) { + free(digest_list->buf); + free(digest_list); + return NULL; + } + + return digest_list; +} + static int digest_list_upload(struct digest_list_item *digest_list, enum ops op, enum upload_types upload_type, int uid) { @@ -494,16 +551,20 @@ static int digest_list_upload(struct digest_list_item *digest_list, enum ops op, unsigned char *buffer = digest_list->buf; size_t buffer_len = digest_list->size; unsigned char rnd[3]; - int ret = 0, fd; + int ret = 0, fd, status; if (op == DIGEST_LIST_ADD) { if (upload_type == UPLOAD_FILE || - upload_type == UPLOAD_FILE_CHOWN) { + upload_type == UPLOAD_FILE_CHOWN || + upload_type == UPLOAD_PARSER || + upload_type == UPLOAD_PARSER_CHOWN || + upload_type == NO_UPLOAD) { fd = mkstemp(path_template); if (fd < 0) return -EPERM; - if (upload_type == UPLOAD_FILE_CHOWN) + if (upload_type == UPLOAD_FILE_CHOWN || + upload_type == UPLOAD_PARSER_CHOWN) ret = fchown(fd, 3000, -1); fchmod(fd, 0644); @@ -550,6 +611,25 @@ static int digest_list_upload(struct digest_list_item *digest_list, enum ops op, strlen(basename), -1); if (ret < 0) goto out; + } else if (upload_type == UPLOAD_PARSER || + upload_type == UPLOAD_PARSER_CHOWN) { + if (fork() == 0) { + execlp(DIGEST_LIST_PARSER_PATH_COPY, + DIGEST_LIST_PARSER_PATH_COPY, + path_template, path_upload, NULL); + exit(1); + } + + ret = 0; + + wait(&status); + if (WEXITSTATUS(status) != 0) + ret = -EINVAL; + + goto out; + } else if (upload_type == NO_UPLOAD) { + ret = 0; + goto out; } ret = write_buffer(path_upload, (char *)buffer, buffer_len, uid); @@ -1439,4 +1519,253 @@ TEST_F_TIMEOUT(test_measure, UPLOAD_BUFFER); } +FIXTURE(test_parser) +{ + struct digest_list_item *digest_list; +}; + +#define PARSER_RULES "measure func=DIGEST_LIST_CHECK fowner=3000 \n" + +FIXTURE_SETUP(test_parser) +{ + int ret; + + ret = load_ima_policy(PARSER_RULES); + ASSERT_EQ(0, ret) { + TH_LOG("load_ima_policy() failed"); + } + + unlink(DIGEST_LIST_PARSER_PATH_COPY); + mkdir("/tmp/diglim", 0700); + + ret = copy_file(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH_COPY); + ASSERT_EQ(0, ret) { + TH_LOG("copy_file() failed"); + } + + ret = chown(DIGEST_LIST_PARSER_PATH_COPY, 3000, -1); + ASSERT_EQ(0, ret) { + TH_LOG("chown() failed"); + } + + chmod(DIGEST_LIST_PARSER_PATH_COPY, 0755); + + self->digest_list = digest_list_generate_file( + DIGEST_LIST_PARSER_PATH_COPY, + COMPACT_PARSER); + ASSERT_NE(NULL, self->digest_list) { + TH_LOG("Cannot generate digest list"); + } + + self->digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED); + + ret = digest_list_upload(self->digest_list, DIGEST_LIST_ADD, + UPLOAD_FILE, 1000); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(self->digest_list, DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } +} + +FIXTURE_TEARDOWN(test_parser) +{ + int ret; + + ret = digest_list_upload(self->digest_list, DIGEST_LIST_DEL, + UPLOAD_FILE, 1000); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + free(self->digest_list->buf); + free(self->digest_list); +} + +TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload, UINT_MAX) +{ + struct digest_list_item *digest_list; + int ret; + + digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE); + ASSERT_NE(NULL, digest_list) { + TH_LOG("Cannot generate digest list"); + } + + digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED); + + ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, + UPLOAD_PARSER_CHOWN, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_DEL, + UPLOAD_PARSER_CHOWN, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_list); +} + +TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_not_measured, + UINT_MAX) +{ + struct digest_list_item *digest_list; + int ret; + + digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE); + ASSERT_NE(NULL, digest_list) { + TH_LOG("Cannot generate digest list"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, + UPLOAD_PARSER, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_DEL, + UPLOAD_PARSER, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_list); +} + +TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_write, + UINT_MAX) +{ + char path_template[] = DIGEST_LIST_PATH_TEMPLATE; + struct digest_list_item *digest_list; + int ret, fd, status, fds[2]; + char c = 0; + + digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE); + ASSERT_NE(NULL, digest_list) { + TH_LOG("Cannot generate digest list"); + } + + digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED); + + ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, NO_UPLOAD, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + digest_list->filename_suffix, 6); + + ret = pipe(fds); + ASSERT_EQ(0, ret) { + TH_LOG("pipe() failed"); + } + + if (fork() == 0) { + fd = open(path_template, O_WRONLY); + if (fd < 0) + exit(1); + + close(fds[1]); + ret = read(fds[0], &c, sizeof(c)); + exit(1); + } + + close(fds[0]); + + if (fork() == 0) { + execlp(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH, + path_template, DIGEST_LIST_ADD_PATH, NULL); + exit(1); + } + + wait(&status); + ASSERT_EQ(1, WEXITSTATUS(status)) { + TH_LOG("digest list parser unexpected exit code %d", + WEXITSTATUS(status)); + } + + free(digest_list); +} + +TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_read, + UINT_MAX) +{ + char path_template[] = DIGEST_LIST_PATH_TEMPLATE; + struct digest_list_item *digest_list; + int ret, fd; + + digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE); + ASSERT_NE(NULL, digest_list) { + TH_LOG("Cannot generate digest list"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, NO_UPLOAD, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + digest_list->filename_suffix, 6); + + if (fork() == 0) { + execlp(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH, + path_template, DIGEST_LIST_ADD_PATH, "open_and_wait", + NULL); + exit(1); + } + + fd = open(path_template, O_WRONLY); + ASSERT_LT(0, fd) { + TH_LOG("digest list open success unexpected"); + close(fd); + } + + wait(NULL); + free(digest_list); + unlink(path_template); +} + +TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_char_dev, + UINT_MAX) +{ + int status; + + if (fork() == 0) { + execlp(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH, + "/dev/null", DIGEST_LIST_ADD_PATH, NULL); + exit(1); + } + + wait(&status); + ASSERT_NE(0, WEXITSTATUS(status)) { + TH_LOG("digest list parser success unexpected"); + } +} + TEST_HARNESS_MAIN From patchwork Wed Sep 15 16:31:41 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496743 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.9 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,UNWANTED_LANGUAGE_BODY, URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 073B2C433FE for ; Wed, 15 Sep 2021 16:34:12 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E8BA961242 for ; Wed, 15 Sep 2021 16:34:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229788AbhIOQfa (ORCPT ); Wed, 15 Sep 2021 12:35:30 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3828 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229491AbhIOQf3 (ORCPT ); Wed, 15 Sep 2021 12:35:29 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m2P0HXFz67hPW; Thu, 16 Sep 2021 00:31:57 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:34:08 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 5/9] diglim: Compact digest list generator Date: Wed, 15 Sep 2021 18:31:41 +0200 Message-ID: <20210915163145.1046505-6-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce a generator for the compact digest lists, which can be directly uploaded to the kernel. This tool can be used to generate a digest list from a file or the files in the specified directory. Files with execute permissions, without write permissions, those in /lib/modules (not files starting with modules.) and in /lib/firmware are marked as immutable (IMA with appraisal in enforcing mode will deny writes). Signed-off-by: Roberto Sassu --- MAINTAINERS | 4 + tools/diglim/Makefile | 18 ++ tools/diglim/common.c | 79 +++++++++ tools/diglim/common.h | 59 +++++++ tools/diglim/compact_gen.c | 349 +++++++++++++++++++++++++++++++++++++ 5 files changed, 509 insertions(+) create mode 100644 tools/diglim/Makefile create mode 100644 tools/diglim/common.c create mode 100644 tools/diglim/common.h create mode 100644 tools/diglim/compact_gen.c diff --git a/MAINTAINERS b/MAINTAINERS index 94220e40b7e2..b752790c06ea 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5522,6 +5522,10 @@ F: security/integrity/diglim/ima.c F: security/integrity/diglim/loader.c F: security/integrity/diglim/methods.c F: security/integrity/diglim/parser.c +F: tools/diglim/Makefile +F: tools/diglim/common.c +F: tools/diglim/common.h +F: tools/diglim/compact_gen.c F: tools/testing/selftests/diglim/ DIOLAN U2C-12 I2C DRIVER diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile new file mode 100644 index 000000000000..45efa554449d --- /dev/null +++ b/tools/diglim/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 + +CC := $(CROSS_COMPILE)gcc +CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb + +PROGS := compact_gen +PROGS_EXTENDED := common.o + +all: $(PROGS) + +clean: + rm -fr $(PROGS) $(PROGS_EXTENDED) + +common.o: common.c + $(CC) -c $(CFLAGS) $< -o $@ + +compact_gen: compact_gen.c $(PROGS_EXTENDED) + $(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lcrypto diff --git a/tools/diglim/common.c b/tools/diglim/common.c new file mode 100644 index 000000000000..dd5ff4b186b3 --- /dev/null +++ b/tools/diglim/common.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 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 "common.h" + +char *compact_types_str[COMPACT__LAST] = { + [COMPACT_PARSER] = "parser", + [COMPACT_FILE] = "file", + [COMPACT_METADATA] = "metadata", + [COMPACT_DIGEST_LIST] = "digest_list", +}; + +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, +}; diff --git a/tools/diglim/common.h b/tools/diglim/common.h new file mode 100644 index 000000000000..d33e5082c17c --- /dev/null +++ b/tools/diglim/common.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header of common.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../usr/include/linux/diglim.h" + +#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 COMPACT_LIST_SIZE_MAX (64 * 1024 * 1024 - 1) + +/* kernel types */ +typedef u_int8_t u8; +typedef u_int16_t u16; +typedef u_int32_t u32; +typedef u_int64_t u64; + +extern char *compact_types_str[COMPACT__LAST]; +extern const char *const hash_algo_name[HASH_ALGO__LAST]; +extern const int hash_digest_size[HASH_ALGO__LAST]; diff --git a/tools/diglim/compact_gen.c b/tools/diglim/compact_gen.c new file mode 100644 index 000000000000..0bfe8584de46 --- /dev/null +++ b/tools/diglim/compact_gen.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Generate compact digest lists. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __BYTE_ORDER == __BIG_ENDIAN +#include +#else +#include +#endif + +#include "common.h" + +static int gen_filename_prefix(char *filename, int filename_len, int pos, + const char *format, enum compact_types type) +{ + return snprintf(filename, filename_len, "%d-%s_list-%s-", + (pos >= 0) ? pos : 0, compact_types_str[type], format); +} + +static 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; +} + +static int calc_file_digest(u8 *digest, char *path, enum hash_algo algo) +{ + void *data = MAP_FAILED; + struct stat st; + int fd, ret = 0; + + if (stat(path, &st) == -1) + return -EACCES; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -EACCES; + + if (st.st_size) { + data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + ret = -ENOMEM; + goto out; + } + } + + ret = calc_digest(digest, data, st.st_size, algo); +out: + if (data != MAP_FAILED) + munmap(data, st.st_size); + + close(fd); + return ret; +} + +static u8 *new_digest_list(enum hash_algo algo, enum compact_types type, + u16 modifiers) +{ + u8 *digest_list; + struct compact_list_hdr *hdr; + + digest_list = mmap(NULL, COMPACT_LIST_SIZE_MAX, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + if (digest_list == MAP_FAILED) { + printf("Cannot allocate buffer\n"); + return NULL; + } + + hdr = (struct compact_list_hdr *)digest_list; + memset(hdr, 0, sizeof(*hdr)); + + hdr->version = 1; + hdr->type = __cpu_to_le16(type); + hdr->modifiers = __cpu_to_le16(modifiers); + hdr->algo = __cpu_to_le16(algo); + return digest_list; +} + +static int write_digest_list(int fd, u8 *digest_list) +{ + struct compact_list_hdr *hdr; + u32 datalen; + ssize_t ret; + + hdr = (struct compact_list_hdr *)digest_list; + if (!hdr->count) + return 0; + + datalen = hdr->datalen; + hdr->count = __cpu_to_le32(hdr->count); + hdr->datalen = __cpu_to_le32(hdr->datalen); + + ret = write(fd, digest_list, sizeof(*hdr) + datalen); + if (ret != sizeof(*hdr) + datalen) + return -EIO; + + return ret; +} + +static int gen_compact_digest_list(char *input, enum hash_algo algo, + u8 *digest_list, u8 *digest_list_immutable) +{ + FTS *fts = NULL; + FTSENT *ftsent; + int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV); + char *paths[2] = { input, NULL }; + u8 *digest_list_ptr = digest_list; + struct compact_list_hdr *cur_hdr; + int ret; + + if (!digest_list) + digest_list_ptr = digest_list_immutable; + + fts = fts_open(paths, fts_flags, NULL); + if (!fts) { + printf("Unable to open %s\n", input); + return -EACCES; + } + + while ((ftsent = fts_read(fts)) != NULL) { + switch (ftsent->fts_info) { + case FTS_F: + if (((ftsent->fts_statp->st_mode & 0111) || + !(ftsent->fts_statp->st_mode & 0222)) && + ftsent->fts_statp->st_size) + digest_list_ptr = digest_list_immutable; + + if ((strstr(ftsent->fts_path, "/lib/modules") && + strncmp(ftsent->fts_name, "modules.", 8)) || + strstr(ftsent->fts_path, "/lib/firmware")) + digest_list_ptr = digest_list_immutable; + + cur_hdr = (struct compact_list_hdr *)digest_list_ptr; + + ret = calc_file_digest(digest_list_ptr + + sizeof(*cur_hdr) + cur_hdr->datalen, + ftsent->fts_path, algo); + if (ret < 0) { + printf("Cannot calculate digest of %s\n", + ftsent->fts_path); + continue; + } + + cur_hdr->count++; + cur_hdr->datalen += hash_digest_size[algo]; + break; + default: + break; + } + } + + return 0; +} + +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 : file/directory the digest list is generated from\n" + "\t-t : type of compact list to generate\n" + "\t-a : digest algorithm\n" + "\t-f: force the digest list to be immutable\n" + "\t-h: display help\n"); +} + +int main(int argc, char *argv[]) +{ + char path[PATH_MAX]; + char filename[NAME_MAX + 1]; + char *output_dir = NULL, *input = NULL; + enum compact_types type = COMPACT_FILE; + enum hash_algo algo = HASH_ALGO_SHA256; + u8 *digest_list = NULL, *digest_list_immutable = NULL; + char *input_ptr; + struct stat st; + int c; + int ret, fd = -1, force_immutable = 0; + + while ((c = getopt(argc, argv, "d:i:t:a:fh")) != -1) { + switch (c) { + case 'd': + output_dir = optarg; + break; + case 'i': + input = optarg; + break; + case 't': + for (type = 0; type < COMPACT__LAST; type++) + if (!strcmp(compact_types_str[type], optarg)) + break; + if (type == COMPACT__LAST) { + printf("Invalid type %s\n", optarg); + exit(1); + } + break; + case 'a': + for (algo = 0; algo < HASH_ALGO__LAST; algo++) + if (!strcmp(hash_algo_name[algo], optarg)) + break; + if (algo == HASH_ALGO__LAST) { + printf("Invalid algo %s\n", optarg); + exit(1); + } + break; + case 'f': + force_immutable = 1; + break; + case 'h': + usage(argv[0]); + exit(0); + default: + printf("Invalid option %c\n", c); + exit(1); + } + } + + if (!output_dir) { + printf("Output directory not specified\n"); + exit(1); + } + + if (!input) { + printf("Input file/directory not specified\n"); + exit(1); + } + + if (stat(input, &st) == -1) { + printf("Input file/directory not found or not accessible\n"); + exit(1); + } + + if (stat(output_dir, &st) == -1) + mkdir(output_dir, 0755); + + gen_filename_prefix(filename, sizeof(filename), 0, "compact", type); + + input_ptr = strrchr(input, '/'); + if (input_ptr) + input_ptr++; + else + input_ptr = input; + + snprintf(path, sizeof(path), "%s/%s%s", output_dir, filename, + input_ptr); + + if (!force_immutable) { + digest_list = new_digest_list(algo, type, 0); + if (!digest_list) { + ret = -ENOMEM; + goto out; + } + } + + digest_list_immutable = new_digest_list(algo, type, + (1 << COMPACT_MOD_IMMUTABLE)); + if (!digest_list_immutable) { + ret = -ENOMEM; + goto out; + } + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + printf("Unable to create %s\n", path); + ret = -errno; + goto out; + } + + ret = gen_compact_digest_list(input, algo, digest_list, + digest_list_immutable); + if (ret < 0) { + printf("Unable to generate the digest list from %s\n", input); + goto out; + } + + if (!force_immutable) { + ret = write_digest_list(fd, digest_list); + if (ret < 0) { + printf("Unable to write the digest list to %s\n", path); + goto out; + } + } + + ret = write_digest_list(fd, digest_list_immutable); + if (ret < 0) + printf("Unable to write the digest list to %s\n", path); +out: + if (digest_list) + munmap(digest_list, COMPACT_LIST_SIZE_MAX); + if (digest_list_immutable) + munmap(digest_list_immutable, COMPACT_LIST_SIZE_MAX); + + if (fd >= 0) + close(fd); + + if (ret < 0) + unlink(path); + + return ret; +} From patchwork Wed Sep 15 16:31:42 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496745 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 53962C4321E for ; Wed, 15 Sep 2021 16:34:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3CA7461242 for ; Wed, 15 Sep 2021 16:34:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230161AbhIOQfb (ORCPT ); Wed, 15 Sep 2021 12:35:31 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3829 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229598AbhIOQfa (ORCPT ); Wed, 15 Sep 2021 12:35:30 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m2P53Mfz67j6Y; Thu, 16 Sep 2021 00:31:57 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:34:08 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 6/9] diglim: RPM digest list generator Date: Wed, 15 Sep 2021 18:31:42 +0200 Message-ID: <20210915163145.1046505-7-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce the generator of RPM digest lists, which takes the RPM header from a package or the RPM DB. Optionally, it appends the RPM header signature with the same format of kernel modules and ID PKEY_ID_PGP. This type of digest list can be loaded through the user space parser rpm_parser, which is introduced in a subsequent patch. Signed-off-by: Roberto Sassu --- MAINTAINERS | 1 + tools/diglim/Makefile | 5 +- tools/diglim/rpm_gen.c | 334 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 tools/diglim/rpm_gen.c diff --git a/MAINTAINERS b/MAINTAINERS index b752790c06ea..04b252ebd7e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5526,6 +5526,7 @@ F: tools/diglim/Makefile F: tools/diglim/common.c F: tools/diglim/common.h F: tools/diglim/compact_gen.c +F: tools/diglim/rpm_gen.c F: tools/testing/selftests/diglim/ DIOLAN U2C-12 I2C DRIVER diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile index 45efa554449d..332bcd93af78 100644 --- a/tools/diglim/Makefile +++ b/tools/diglim/Makefile @@ -3,7 +3,7 @@ CC := $(CROSS_COMPILE)gcc CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb -PROGS := compact_gen +PROGS := compact_gen rpm_gen PROGS_EXTENDED := common.o all: $(PROGS) @@ -16,3 +16,6 @@ common.o: common.c compact_gen: compact_gen.c $(PROGS_EXTENDED) $(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lcrypto + +rpm_gen: rpm_gen.c $(PROGS_EXTENDED) + $(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lrpm -lrpmio diff --git a/tools/diglim/rpm_gen.c b/tools/diglim/rpm_gen.c new file mode 100644 index 000000000000..fbee65ea0394 --- /dev/null +++ b/tools/diglim/rpm_gen.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Generate RPM digest lists. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#if __BYTE_ORDER == __BIG_ENDIAN +#include +#else +#include +#endif + +#include "../../usr/include/linux/diglim.h" + +const unsigned char rpm_header_magic[8] = { + 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00 +}; + +/* 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 */ +}; + +static int gen_filename_prefix(char *filename, int filename_len, int pos, + const char *format, enum compact_types type) +{ + return snprintf(filename, filename_len, "%d-%s_list-%s-", + (pos >= 0) ? pos : 0, compact_types_str[type], format); +} + +static void gen_filename(Header rpm, int pos, enum compact_types type, + char *filename, int filename_len, char *output_format) +{ + rpmtd name = rpmtdNew(), version = rpmtdNew(); + rpmtd release = rpmtdNew(), arch = rpmtdNew(); + int prefix_len; + + headerGet(rpm, RPMTAG_NAME, name, 0); + headerGet(rpm, RPMTAG_VERSION, version, 0); + headerGet(rpm, RPMTAG_RELEASE, release, 0); + headerGet(rpm, RPMTAG_ARCH, arch, 0); + + prefix_len = gen_filename_prefix(filename, filename_len, pos, + output_format, type); + + snprintf(filename + prefix_len, filename_len - prefix_len, + "%s-%s-%s.%s", rpmtdGetString(name), rpmtdGetString(version), + rpmtdGetString(release), rpmtdGetString(arch)); + + rpmtdFree(name); + rpmtdFree(version); + rpmtdFree(release); + rpmtdFree(arch); +} + +static int find_package(Header rpm, char *package) +{ + rpmtd name = rpmtdNew(); + int found = 0; + + headerGet(rpm, RPMTAG_NAME, name, 0); + if (!strncmp(rpmtdGetString(name), package, strlen(package))) + found = 1; + + rpmtdFree(name); + return found; +} + +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, 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) +{ + struct module_signature modsig = { 0 }; + rpmtd signature = rpmtdNew(); + int ret, fd; + + headerGet(rpm, RPMTAG_RSAHEADER, signature, 0); + fd = openat(dirfd, filename, O_WRONLY | O_APPEND); + if (fd < 0) { + ret = -errno; + goto out; + } + + modsig.id_type = PKEY_ID_PGP; + modsig.sig_len = signature->count; + modsig.sig_len = __cpu_to_be32(modsig.sig_len); + + ret = write(fd, signature->data, signature->count); + if (ret != signature->count) { + ret = -EIO; + goto out_fd; + } + + ret = write(fd, &modsig, sizeof(modsig)); + if (ret != sizeof(modsig)) { + ret = -EIO; + goto out_fd; + } + + ret = write(fd, MODULE_SIG_STRING, sizeof(MODULE_SIG_STRING) - 1); + if (ret != sizeof(MODULE_SIG_STRING) - 1) { + ret = -EIO; + goto out; + } + + ret = 0; +out_fd: + close(fd); +out: + rpmtdFree(signature); + + if (ret < 0) + unlinkat(dirfd, filename, 0); + + 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-r : RPM package the digest list is generated from (all RPM packages in DB if not specified)\n" + "\t-p : selected RPM package in RPM DB\n" + "\t-h: display help\n"); +} + +static void gen_rpm_digest_list(Header rpm, int dirfd, char *filename) +{ + int ret; + + ret = write_rpm_header(rpm, dirfd, filename); + if (ret < 0) { + printf("Cannot generate %s digest list\n", filename); + return; + } + + ret = write_rpm_header_signature(rpm, dirfd, filename); + if (ret < 0) + printf("Cannot add signature to %s digest list\n", + filename); +} + +int main(int argc, char *argv[]) +{ + char filename[NAME_MAX + 1]; + rpmts ts = NULL; + Header hdr; + FD_t fd; + rpmdbMatchIterator mi; + rpmVSFlags vsflags = 0; + char *input_package = NULL, *selected_package = NULL; + char *output_dir = NULL; + struct stat st; + int c; + int ret, dirfd; + + while ((c = getopt(argc, argv, "d:r:p:h")) != -1) { + switch (c) { + case 'd': + output_dir = optarg; + break; + case 'r': + input_package = optarg; + break; + case 'p': + selected_package = optarg; + break; + case 'h': + usage(argv[0]); + exit(0); + default: + printf("Invalid option %c\n", c); + exit(1); + } + } + + if (!output_dir) { + printf("Output directory not specified\n"); + exit(1); + } + + if (stat(output_dir, &st) == -1) + mkdir(output_dir, 0755); + + dirfd = open(output_dir, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) { + printf("Unable to open %s, ret: %d\n", output_dir, -errno); + ret = -errno; + goto out; + } + + 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; + } + + if (input_package) { + vsflags |= _RPMVSF_NODIGESTS; + vsflags |= _RPMVSF_NOSIGNATURES; + rpmtsSetVSFlags(ts, vsflags); + + fd = Fopen(input_package, "r.ufdio"); + if ((!fd) || Ferror(fd)) { + rpmlog(RPMLOG_NOTICE, + "Failed to open package file %s, %s\n", + input_package, 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_package); + goto out_rpm; + } + + gen_filename(hdr, 0, COMPACT_FILE, filename, sizeof(filename), + "rpm"); + + gen_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, 0, COMPACT_FILE, filename, sizeof(filename), + "rpm"); + + if (strstr(filename, "gpg-pubkey") != NULL) + continue; + + if (selected_package && !find_package(hdr, selected_package)) + continue; + + gen_rpm_digest_list(hdr, dirfd, filename); + } + + rpmdbFreeIterator(mi); +out_rpm: + rpmFreeRpmrc(); + rpmtsFree(ts); +out: + close(dirfd); + return ret; +} From patchwork Wed Sep 15 16:31:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496747 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 17C7BC433F5 for ; Wed, 15 Sep 2021 16:34:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id EC9EC610D1 for ; Wed, 15 Sep 2021 16:34:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230405AbhIOQfh (ORCPT ); Wed, 15 Sep 2021 12:35:37 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3830 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229554AbhIOQfb (ORCPT ); Wed, 15 Sep 2021 12:35:31 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m2Q2bSMz67hKh; Thu, 16 Sep 2021 00:31:58 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:34:09 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 7/9] diglim: Digest list uploader Date: Wed, 15 Sep 2021 18:31:43 +0200 Message-ID: <20210915163145.1046505-8-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce a digest list uploader. Its syntax is: upload_digest_lists Its main function is to determine which digest list parsers must be executed to parse the digest lists in the specified location. The uploader then executes the parsers in sequence, which are expected to be in the same directory as the uploader. Given that this tool is not involved in the conversion of the digest lists, it does not need to be identified as a parser by DIGLIM LSM. It is intentionally compiled as a static binary. Otherwise, a build service of a Linux distribution would need to be modified to generate a digest list for each of the shared library the uploader depends on (e.g. glibc). Instead, with a static binary, only a digest list for that binary has to be generated. Signed-off-by: Roberto Sassu --- MAINTAINERS | 1 + tools/diglim/Makefile | 5 +- tools/diglim/upload_digest_lists.c | 238 +++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 tools/diglim/upload_digest_lists.c diff --git a/MAINTAINERS b/MAINTAINERS index 04b252ebd7e1..148a2a7957b7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5527,6 +5527,7 @@ F: tools/diglim/common.c F: tools/diglim/common.h F: tools/diglim/compact_gen.c F: tools/diglim/rpm_gen.c +F: tools/diglim/upload_digest_lists.c F: tools/testing/selftests/diglim/ DIOLAN U2C-12 I2C DRIVER diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile index 332bcd93af78..a22125ad0281 100644 --- a/tools/diglim/Makefile +++ b/tools/diglim/Makefile @@ -3,7 +3,7 @@ CC := $(CROSS_COMPILE)gcc CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb -PROGS := compact_gen rpm_gen +PROGS := compact_gen rpm_gen upload_digest_lists PROGS_EXTENDED := common.o all: $(PROGS) @@ -19,3 +19,6 @@ compact_gen: compact_gen.c $(PROGS_EXTENDED) rpm_gen: rpm_gen.c $(PROGS_EXTENDED) $(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lrpm -lrpmio + +upload_digest_lists: upload_digest_lists.c + $(CC) $(CFLAGS) -static $< -o $@ $(LDFLAGS) diff --git a/tools/diglim/upload_digest_lists.c b/tools/diglim/upload_digest_lists.c new file mode 100644 index 000000000000..06086dd64aa3 --- /dev/null +++ b/tools/diglim/upload_digest_lists.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Run parsers of digest list formats not recognizable by the kernel. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MOUNT_FLAGS (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME) +#define PROCFS_MNTPOINT "/proc" +#define SYSFS_MNTPOINT "/sys" +#define SECURITYFS_MNTPOINT SYSFS_MNTPOINT "/kernel/security" +#define DIGLIM_DIR SECURITYFS_MNTPOINT "/integrity/diglim" + +struct format_entry { + struct format_entry *next; + char *format; +}; + +struct format_entry *head; +bool procfs_mounted; +bool sysfs_mounted; +bool securityfs_mounted; + +int add_format_parser(char *path) +{ + char *name; + char *type_start, *format_start, *format_end; + struct format_entry *cur, *new; + int ret = 0; + + name = strrchr(path, '/'); + if (!name) + return -EINVAL; + + name++; + + type_start = strchr(name, '-'); + if (!type_start++) + return 0; + + format_start = strchr(type_start, '-'); + if (!format_start++) + return 0; + + format_end = strchr(format_start, '-'); + if (!format_end) + return 0; + + if (!strncmp(format_start, "compact", format_end - format_start)) + return 0; + + cur = head; + + while (cur) { + if (!strncmp(format_start, cur->format, + format_end - format_start)) + goto out; + + cur = cur->next; + } + + new = malloc(sizeof(*new)); + if (!new) { + ret = -ENOMEM; + goto out; + } + + new->format = strndup(format_start, format_end - format_start); + if (!new->format) { + ret = -ENOMEM; + goto out; + } + + new->next = head; + head = new; +out: + if (ret < 0) + free(new); + + return ret; +} + +void free_list(void) +{ + struct format_entry *cur = head, *tmp; + + while (cur) { + tmp = cur; + cur = tmp->next; + free(tmp->format); + free(tmp); + } +} + +static int mount_filesystems(void) +{ + struct stat st; + struct statfs stf; + int ret; + + if (stat("/proc/self", &st) == -1 || + statfs("/proc/self", &stf) == -1 || stf.f_type != 0x9fa0) { + ret = mount(PROCFS_MNTPOINT, PROCFS_MNTPOINT, "proc", + MOUNT_FLAGS, NULL); + if (ret < 0) { + printf("Cannot mount procfs\n"); + return ret; + } + + procfs_mounted = true; + } + + if (stat(SECURITYFS_MNTPOINT, &st) == -1 || + statfs(SYSFS_MNTPOINT, &stf) == -1 || + stf.f_type != 0x62656572) { + ret = mount(SYSFS_MNTPOINT, SYSFS_MNTPOINT, "sysfs", + MOUNT_FLAGS, NULL); + if (ret < 0) { + printf("Cannot mount sysfs\n"); + return ret; + } + + sysfs_mounted = true; + } + + if (stat(DIGLIM_DIR, &st) == -1 || + statfs(SECURITYFS_MNTPOINT, &stf) == -1 || + stf.f_type != 0x73636673) { + ret = mount(SECURITYFS_MNTPOINT, SECURITYFS_MNTPOINT, + "securityfs", MOUNT_FLAGS, NULL); + if (ret < 0) { + printf("Cannot mount securityfs\n"); + return ret; + } + + securityfs_mounted = true; + } + + return 0; +} + +static void umount_filesystems(void) +{ + if (procfs_mounted) + umount(PROCFS_MNTPOINT); + if (securityfs_mounted) + umount(SECURITYFS_MNTPOINT); + if (sysfs_mounted) + umount(SYSFS_MNTPOINT); +} + +int main(int argc, char *argv[]) +{ + char *paths[2] = { NULL, NULL }; + struct format_entry *cur; + char parser_path[PATH_MAX], *sep; + FTS *fts = NULL; + FTSENT *ftsent; + int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV); + int ret; + + if (argc != 3) { + printf("Usage: %s add|del \n", argv[0]); + return -EINVAL; + } + + paths[0] = argv[2]; + + fts = fts_open(paths, fts_flags, NULL); + if (!fts) + return -EACCES; + + while ((ftsent = fts_read(fts)) != NULL) { + switch (ftsent->fts_info) { + case FTS_F: + ret = add_format_parser(ftsent->fts_path); + if (ret < 0) + printf("Cannot upload %s\n", ftsent->fts_path); + + break; + default: + break; + } + } + + fts_close(fts); + fts = NULL; + + ret = mount_filesystems(); + if (ret < 0) + goto out; + + ret = readlink("/proc/self/exe", parser_path, sizeof(parser_path)); + if (ret < 0) + goto out; + + sep = strrchr(parser_path, '/'); + if (!sep++) { + ret = -ENOENT; + goto out; + } + + cur = head; + + while (cur) { + if (fork() == 0) { + snprintf(sep, sizeof(parser_path) - (sep - parser_path), + "%s_parser", cur->format); + return execlp(parser_path, parser_path, argv[1], + argv[2], NULL); + } + + wait(NULL); + cur = cur->next; + } + +out: + free_list(); + umount_filesystems(); + return ret; +} From patchwork Wed Sep 15 16:31:44 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496749 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 21A71C4321E for ; Wed, 15 Sep 2021 16:34:21 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 0D9B761246 for ; Wed, 15 Sep 2021 16:34:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230438AbhIOQfi (ORCPT ); Wed, 15 Sep 2021 12:35:38 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3831 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229992AbhIOQfc (ORCPT ); Wed, 15 Sep 2021 12:35:32 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m296JqLz67tWW; Thu, 16 Sep 2021 00:31:45 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:34:10 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 8/9] diglim: RPM parser Date: Wed, 15 Sep 2021 18:31:44 +0200 Message-ID: <20210915163145.1046505-9-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce a parser of the RPM digest list, which converts the digest lists to the compact format and uploads the converted digest lists to the kernel. It takes as input the type of operation to perform, add or delete, and the file or directory with the files to process. Also the RPM parser is intentionally compiled as a static binary to avoid to generate a compact digest list also for its dependencies. Signed-off-by: Roberto Sassu --- MAINTAINERS | 1 + tools/diglim/Makefile | 5 +- tools/diglim/rpm_parser.c | 483 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 488 insertions(+), 1 deletion(-) create mode 100644 tools/diglim/rpm_parser.c diff --git a/MAINTAINERS b/MAINTAINERS index 148a2a7957b7..1efc1724376e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5527,6 +5527,7 @@ F: tools/diglim/common.c F: tools/diglim/common.h F: tools/diglim/compact_gen.c F: tools/diglim/rpm_gen.c +F: tools/diglim/rpm_parser.c F: tools/diglim/upload_digest_lists.c F: tools/testing/selftests/diglim/ diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile index a22125ad0281..7019c5b9fad9 100644 --- a/tools/diglim/Makefile +++ b/tools/diglim/Makefile @@ -3,7 +3,7 @@ CC := $(CROSS_COMPILE)gcc CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb -PROGS := compact_gen rpm_gen upload_digest_lists +PROGS := compact_gen rpm_gen upload_digest_lists rpm_parser PROGS_EXTENDED := common.o all: $(PROGS) @@ -22,3 +22,6 @@ rpm_gen: rpm_gen.c $(PROGS_EXTENDED) upload_digest_lists: upload_digest_lists.c $(CC) $(CFLAGS) -static $< -o $@ $(LDFLAGS) + +rpm_parser: rpm_parser.c $(PROGS_EXTENDED) + $(CC) $(CFLAGS) -static $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) diff --git a/tools/diglim/rpm_parser.c b/tools/diglim/rpm_parser.c new file mode 100644 index 000000000000..f8a4b63b2fa8 --- /dev/null +++ b/tools/diglim/rpm_parser.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Parse RPM header and upload digest list to the kernel. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#if __BYTE_ORDER == __BIG_ENDIAN +#include +#else +#include +#endif + +#define SYSFS_MNTPOINT "/sys" +#define SECURITYFS_MNTPOINT SYSFS_MNTPOINT "/kernel/security" +#define DIGLIM_DIR SECURITYFS_MNTPOINT "/integrity/diglim" +#define DIGEST_LIST_ADD DIGLIM_DIR "/digest_list_add" +#define DIGEST_LIST_DEL DIGLIM_DIR "/digest_list_del" +#define DIGEST_LIST_LABEL DIGLIM_DIR "/digest_list_label" +#define DIGEST_LIST_DIR "/etc/digest_lists" + +#define MOUNT_FLAGS (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME) + +enum hash_algo pgp_algo_mapping[PGPHASHALGO_SHA224 + 1] = { + [PGPHASHALGO_MD5] = HASH_ALGO_MD5, + [PGPHASHALGO_SHA1] = HASH_ALGO_SHA1, + [PGPHASHALGO_SHA224] = HASH_ALGO_SHA224, + [PGPHASHALGO_SHA256] = HASH_ALGO_SHA256, + [PGPHASHALGO_SHA384] = HASH_ALGO_SHA384, + [PGPHASHALGO_SHA512] = HASH_ALGO_SHA512, +}; + +struct rpm_hdr { + int32_t magic; + int32_t reserved; + int32_t tags; + int32_t datasize; +} __attribute__((packed)); + +struct rpm_entryinfo { + int32_t tag; + int32_t type; + int32_t offset; + int32_t count; +} __attribute__((packed)); + +/* from lib/hexdump.c (Linux kernel) */ +static int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +int _hex2bin(unsigned char *dst, const char *src, size_t count) +{ + while (count--) { + int hi = hex_to_bin(*src++); + int lo = hex_to_bin(*src++); + + if ((hi < 0) || (lo < 0)) + return -1; + + *dst++ = (hi << 4) | lo; + } + return 0; +} + +static u8 *new_digest_list(enum hash_algo algo, enum compact_types type, + u16 modifiers, u32 count) +{ + u8 *digest_list; + struct compact_list_hdr *hdr; + + digest_list = calloc(1, sizeof(struct compact_list_hdr) + + count * hash_digest_size[algo]); + if (!digest_list) + return NULL; + + hdr = (struct compact_list_hdr *)digest_list; + hdr->version = 1; + hdr->type = __cpu_to_le16(type); + hdr->modifiers = __cpu_to_le16(modifiers); + hdr->algo = __cpu_to_le16(algo); + return digest_list; +} + +static int upload_digest_list(int add_del_fd, int label_fd, u8 *digest_list, + char *label) +{ + struct compact_list_hdr *hdr; + u32 datalen; + ssize_t ret; + + hdr = (struct compact_list_hdr *)digest_list; + if (!hdr->count) + return 0; + + datalen = hdr->datalen; + hdr->count = __cpu_to_le32(hdr->count); + hdr->datalen = __cpu_to_le32(hdr->datalen); + + ret = write(label_fd, label, strlen(label) + 1); + if (ret < 0 || ret != strlen(label) + 1) + return -EIO; + + ret = write(add_del_fd, digest_list, sizeof(*hdr) + datalen); + if (ret < 0) { + if (errno == EEXIST || errno == ENOENT) + return 0; + + return ret; + } else if (ret != sizeof(*hdr) + datalen) { + return -EIO; + } + + return ret; +} + +static int parse_rpm(int add_del_fd, int label_fd, char *path, + struct stat *st, bool only_immutable) +{ + void *buf, *bufp, *bufendp, *datap; + struct rpm_hdr *hdr; + int32_t tags; + struct rpm_entryinfo *entry; + void *digests = NULL, *algo_buf = NULL, *modes = NULL, *sizes = NULL; + void *dirnames = NULL, *basenames = NULL, *dirindexes = NULL; + char **dirnames_ptr = NULL; + u8 *digest_list = NULL, *digest_list_immutable = NULL; + u32 digests_count = 0, dirnames_count = 0; + u16 algo = HASH_ALGO_MD5; + char *label = strrchr(path, '/') + 1; + int ret = 0, fd_rpm, i, dirname_len; + + const unsigned char rpm_header_magic[8] = { + 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00 + }; + + if (st->st_size < sizeof(*hdr)) { + printf("Missing RPM header\n"); + return -EINVAL; + } + + fd_rpm = open(path, O_RDONLY); + if (fd_rpm < 0) { + printf("Cannot access %s (%d)\n", path, -errno); + return -EACCES; + } + + buf = bufp = mmap(NULL, st->st_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd_rpm, 0); + + close(fd_rpm); + + if (bufp == MAP_FAILED) + return -ENOMEM; + + if (memcmp(bufp, rpm_header_magic, sizeof(rpm_header_magic))) { + printf("Invalid RPM header\n"); + ret = -EINVAL; + goto out; + } + + hdr = (struct rpm_hdr *)bufp; + tags = __be32_to_cpu(hdr->tags); + datap = bufp + sizeof(*hdr) + tags * sizeof(struct rpm_entryinfo); + bufendp = bufp + st->st_size; + bufp += sizeof(*hdr); + + for (i = 0; i < tags && (bufp + sizeof(*entry)) <= bufendp; + i++, bufp += sizeof(*entry)) { + entry = bufp; + + switch (__be32_to_cpu(entry->tag)) { + case RPMTAG_FILEDIGESTS: + digests = datap + __be32_to_cpu(entry->offset); + digests_count = __be32_to_cpu(entry->count); + break; + case RPMTAG_FILEDIGESTALGO: + algo_buf = datap + __be32_to_cpu(entry->offset); + break; + case RPMTAG_DIRNAMES: + dirnames = datap + __be32_to_cpu(entry->offset); + dirnames_count = __be32_to_cpu(entry->count); + break; + case RPMTAG_BASENAMES: + basenames = datap + __be32_to_cpu(entry->offset); + break; + case RPMTAG_DIRINDEXES: + dirindexes = datap + __be32_to_cpu(entry->offset); + break; + case RPMTAG_FILEMODES: + modes = datap + __be32_to_cpu(entry->offset); + break; + case RPMTAG_FILESIZES: + sizes = datap + __be32_to_cpu(entry->offset); + break; + + if (digests && algo_buf && modes && dirnames && basenames && + dirindexes) + break; + } + } + + if (!digests || !modes || !sizes || !dirnames || !basenames || + !dirindexes) { + ret = 0; + goto out; + } + + if (algo_buf && algo_buf + sizeof(u32) <= bufendp) + algo = pgp_algo_mapping[__be32_to_cpu(*(u32 *)algo_buf)]; + + digest_list = new_digest_list(algo, COMPACT_FILE, 0, digests_count); + if (!digest_list) { + ret = -ENOMEM; + goto out; + } + + digest_list_immutable = new_digest_list(algo, COMPACT_FILE, + (1 << COMPACT_MOD_IMMUTABLE), + digests_count); + if (!digest_list_immutable) { + ret = -ENOMEM; + goto out; + } + + dirnames_ptr = calloc(dirnames_count, sizeof(*dirnames_ptr)); + if (!dirnames_ptr) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < dirnames_count; i++) { + dirname_len = strlen(dirnames) + 1; + + if (dirnames + dirname_len > bufendp) { + ret = -EINVAL; + goto out; + } + + dirnames_ptr[i] = dirnames; + dirnames += dirname_len; + } + + for (i = 0; i < digests_count; i++) { + int digest_str_len = strlen(digests); + int basename_str_len = strlen(basenames); + u8 *digest_list_ptr = digest_list; + struct compact_list_hdr *cur_hdr; + u32 dirindex; + u32 size; + u16 mode; + + if (digests + digest_str_len * 2 + 1 > bufendp) { + printf("RPM header read at invalid offset\n"); + ret = -EINVAL; + goto out; + } + + if (basenames + basename_str_len + 1 > bufendp) { + printf("RPM header read at invalid offset\n"); + ret = -EINVAL; + goto out; + } + + if (dirindexes + sizeof(u32) > bufendp) { + printf("RPM header read at invalid offset\n"); + ret = -EINVAL; + goto out; + } + + if (sizes + sizeof(u32) > bufendp) { + ret = -EINVAL; + goto out; + } + + if (modes + sizeof(u16) > bufendp) { + ret = -EINVAL; + goto out; + } + + if (!digest_str_len) { + digests += digest_str_len + 1; + basenames += basename_str_len + 1; + dirindexes += sizeof(u32); + sizes += sizeof(u32); + modes += sizeof(u16); + continue; + } + + dirindex = __be32_to_cpu(*(u32 *)dirindexes); + size = __be32_to_cpu(*(u32 *)sizes); + mode = __be16_to_cpu(*(u16 *)modes); + + if (((mode & 0111) || !(mode & 0222)) && size) + digest_list_ptr = digest_list_immutable; + + if ((strstr(dirnames_ptr[dirindex], "/lib/modules") && + strncmp(basenames, "modules.", 8)) || + strstr(dirnames_ptr[dirindex], "/lib/firmware") || + strstr(dirnames_ptr[dirindex], "/usr/libexec/sudo")) + digest_list_ptr = digest_list_immutable; + + if (only_immutable && digest_list_ptr != digest_list_immutable) + continue; + + cur_hdr = (struct compact_list_hdr *)digest_list_ptr; + + ret = _hex2bin(digest_list_ptr + sizeof(*cur_hdr) + + cur_hdr->count * hash_digest_size[algo], + digests, digest_str_len / 2); + if (ret < 0) + goto out; + + digests += digest_str_len + 1; + basenames += basename_str_len + 1; + dirindexes += sizeof(u32); + sizes += sizeof(u32); + modes += sizeof(u16); + + cur_hdr->count++; + cur_hdr->datalen += hash_digest_size[algo]; + } + + ret = upload_digest_list(add_del_fd, label_fd, digest_list, label); + if (ret < 0) { + printf("Failed to upload digest list\n"); + goto out; + } + + ret = upload_digest_list(add_del_fd, label_fd, digest_list_immutable, + label); + if (ret < 0) + printf("Failed to upload digest list\n"); +out: + munmap(buf, st->st_size); + free(digest_list); + free(digest_list_immutable); + free(dirnames_ptr); + return ret; +} + +int parse_digest_list(char *name) +{ + char *type_start, *format_start, *format_end; + + type_start = strchr(name, '-'); + if (!type_start++) + return 0; + + format_start = strchr(type_start, '-'); + if (!format_start++) + return 0; + + format_end = strchr(format_start, '-'); + if (!format_end) + return 0; + + if (format_end - format_start != 3 || + strncmp(format_start, "rpm", 3)) + return 0; + + return 1; +} + +int main(int argc, char *argv[]) +{ + FTS *fts = NULL; + FTSENT *ftsent; + int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV); + char *paths[2] = { NULL, NULL }; + char *add_del_path = DIGEST_LIST_ADD; + char path[PATH_MAX], *path_ptr = path; + bool only_immutable = false; + struct stat st; + int ret, add_del_fd = -1, label_fd = -1; + + if (argc < 3) { + printf("Usage: %s add|del \n", + argv[0]); + return -EINVAL; + } + + if (stat(argv[2], &st) == -1) + return -ENOENT; + + strncpy(path, argv[2], sizeof(path) - 1); + + if (!S_ISDIR(st.st_mode)) { + path_ptr = strrchr(path, '/'); + if (!path_ptr) + path_ptr = path; + else + path_ptr++; + } else { + path_ptr = path + strlen(path); + } + + strncpy(path_ptr, "/.immutable", sizeof(path) - (path_ptr - path)); + if (!stat(path, &st)) + only_immutable = true; + + paths[0] = argv[2]; + + if (!strcmp(argv[1], "del")) + add_del_path = DIGEST_LIST_DEL; + + add_del_fd = open(add_del_path, O_WRONLY); + if (add_del_fd < 0) { + printf("Unable to open %s\n", add_del_path); + return -EACCES; + } + + label_fd = open(DIGEST_LIST_LABEL, O_WRONLY); + if (label_fd < 0) { + printf("Unable to open %s\n", DIGEST_LIST_LABEL); + ret = -EACCES; + goto out; + } + + fts = fts_open(paths, fts_flags, NULL); + if (!fts) { + printf("Unable to open %s\n", argv[2]); + ret = -EACCES; + goto out; + } + + while ((ftsent = fts_read(fts)) != NULL) { + switch (ftsent->fts_info) { + case FTS_F: + if (!parse_digest_list(ftsent->fts_name)) + break; + + ret = parse_rpm(add_del_fd, label_fd, + ftsent->fts_path, ftsent->fts_statp, + only_immutable); + if (ret < 0) + printf("Cannot parse %s\n", ftsent->fts_path); + break; + default: + break; + } + } + +out: + if (add_del_fd >= 0) + close(add_del_fd); + if (label_fd >= 0) + close(label_fd); + if (fts) + fts_close(fts); + + return 0; +} From patchwork Wed Sep 15 16:31:45 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12496751 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-21.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,MENTIONS_GIT_HOSTING,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9A218C28B5E for ; Wed, 15 Sep 2021 16:34:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7AE4A61029 for ; Wed, 15 Sep 2021 16:34:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230481AbhIOQfj (ORCPT ); Wed, 15 Sep 2021 12:35:39 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3832 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230226AbhIOQfd (ORCPT ); Wed, 15 Sep 2021 12:35:33 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8m2B67JVz67vP9; Thu, 16 Sep 2021 00:31:46 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Wed, 15 Sep 2021 18:34:10 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 9/9] diglim: Admin guide Date: Wed, 15 Sep 2021 18:31:45 +0200 Message-ID: <20210915163145.1046505-10-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210915163145.1046505-1-roberto.sassu@huawei.com> References: <20210915163145.1046505-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml753-chm.china.huawei.com (10.201.108.203) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org Introduce a DIGLIM administration guide. Its main purpose is to help users to configure a system to load to the kernel all the digests of executable and firmware from the RPM DB, and kernel modules of a custom kernel and a temporary file mapped as executable as custom digest lists. With further patch sets, it will be possible to load an execution policy in IMA and create a measurement list only with digest lists and unknown files, and to perform secure boot at application level. Signed-off-by: Roberto Sassu --- Documentation/admin-guide/diglim.rst | 136 +++++++++++++++++++++++++++ Documentation/admin-guide/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 138 insertions(+) create mode 100644 Documentation/admin-guide/diglim.rst diff --git a/Documentation/admin-guide/diglim.rst b/Documentation/admin-guide/diglim.rst new file mode 100644 index 000000000000..886100cf5a62 --- /dev/null +++ b/Documentation/admin-guide/diglim.rst @@ -0,0 +1,136 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====== +DIGLIM +====== + +Digest Lists Integrity Module (DIGLIM) is an integrity extension aiming to +facilitate the deployment of remote attestation and secure boot solutions +based on Integrity Measurement Architecture (IMA). + +DIGLIM documentation can be retrieved at ``Documentation/security/diglim``. + +Kernel Configuration Options +============================ + +DIGLIM can be enabled by setting ``CONFIG_DIGLIM=y`` in the kernel +configuration. Optionally, it is possible to set +``CONFIG_DIGLIM_DIGEST_LISTS_DIR`` with the directory digest lists are +taken from by a kernel loader executed at kernel initialization time. +Finally, with ``CONFIG_DIGLIM_UPLOADER_PATH`` it is possible to specify the +path of the digest list uploader, which will execute user space parsers to +process the digest lists in ``CONFIG_DIGLIM_DIGEST_LISTS_DIR`` that are not +in the format recognized by the kernel. + + +LSM +=== + +DIGLIM includes an LSM to protect user space parsers from other processes, +when the parsers convert a digest list and uploads it to the kernel. As for +other LSMs, ``diglim`` should be added to the list of enabled LSMs, +provided with the ``lsm=`` kernel option. If DIGLIM LSM is not enabled, +digest lists uploaded by the parser will not be marked as processed by IMA +and will not be suitable for use. + + +Setup +===== + +Digest lists must be loaded as soon as possible, before files are accessed, +so that IMA finds the digest of those files with a query. More details on +the benefits of DIGLIM for IMA can be found in +``Documentation/security/diglim/introduction.rst``. + + +Digest List Generation +---------------------- + +Digest lists can be generated with the tools provided in ``tools/diglim`` +in the kernel sources. In order to compile the tools, it is necessary to +install the ``glibc-static`` and ``rpm-devel`` packages. + +``compact_gen`` can be used to generate digest lists in the compact format, +which can be directly uploaded to the kernel. + +In order to upload digests from the RPM database, it is necessary to +generate three digest lists: one for ``upload_digest_lists``, which is +responsible to execute the parsers for digest lists not in the compact +format; two for ``rpm_parser``, which actually loads the RPM digest lists. + +``rpm_parser`` requires two digest lists, one for identification by DIGLIM +LSM, and the other for measurement and appraisal with IMA. The commands +are:: + + # tools/diglim/compact_gen -d /etc/digest_lists -i /usr/libexec/diglim/rpm_parser -t parser + # tools/diglim/compact_gen -d /etc/digest_lists -i /usr/libexec/diglim/rpm_parser -t file + # tools/diglim/compact_gen -d /etc/digest_lists -i /usr/libexec/diglim/upload_digest_lists -t file + +Optionally, an appended signature can be added to the generated digest +lists, with the sign-file tool included in the kernel sources:: + + # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-parser_list-compact-rpm_parser + # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-rpm_parser + # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-upload_digest_lists + +With an appropriate policy, appended signatures can be seen in the +measurement, by selecting the ``ima-modsig`` template. + +Afterwards, digest lists can be generated from the RPM database with the +command:: + + # tools/diglim/rpm_gen -d /etc/digest_lists + +If a custom kernel is used, an additional digest list should be generated +for kernel modules:: + + # tools/diglim/compact_gen -d /etc/digest_lists -i /lib/modules/`uname -r` -t file + # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-`uname -r` + +Finally, in Fedora there is an mmap with execution permission on a file +with 4K of zeros. A digest list can be generated by executing:: + + # dd if=/dev/zero of=/tmp/mmap bs=4096 count=1 + # tools/diglim/compact_gen -d /etc/digest_lists -i /tmp/mmap -f + # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-mmap + + +Initial Ram Disk +---------------- + +Generated digest lists should be copied to the initial ram disk in the +``CONFIG_DIGLIM_DIGEST_LISTS_DIR`` directory. This can be accomplished, +with dracut, by adding in /etc/dracut.conf:: + + install_optional_items+=" /etc/digest_lists/* " + +if ``CONFIG_DIGLIM_DIGEST_LISTS_DIR=/etc/digest_lists``. + +``upload_digest_lists`` and ``rpm_parser`` can be also copied to the +initial ram disk by adding the following lines in /etc/dracut.conf:: + + install_optional_items+=" /usr/libexec/diglim/upload_digest_lists " + install_optional_items+=" /usr/libexec/diglim/rpm_parser " + +assuming that the binaries are installed in /usr/libexec/diglim. + +Another important option is:: + + do_strip="no" + +This prevents dracut from stripping the symbols from binaries. If binaries +are altered, their digest will be different from the reference value and +will not be found in the DIGLIM hash table. + + +Boot and Digest List Upload +--------------------------- + +After generating the initial ram disk and rebooting, digest lists should +have been added to the DIGLIM hash table. This can be checked by executing:: + + # cat /sys/kernel/security/integrity/diglim/digests_count + Parser digests: 1 + File digests: 104273 + Metadata digests: 0 + Digest list digests: 2430 diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst index dc00afcabb95..1cc7d3b3e79c 100644 --- a/Documentation/admin-guide/index.rst +++ b/Documentation/admin-guide/index.rst @@ -79,6 +79,7 @@ configure specific aspects of kernel behavior to your liking. cputopology dell_rbu device-mapper/index + diglim edid efi-stub ext4 diff --git a/MAINTAINERS b/MAINTAINERS index 1efc1724376e..953c86915c49 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5507,6 +5507,7 @@ M: Roberto Sassu L: linux-integrity@vger.kernel.org S: Supported T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git +F: Documentation/admin-guide/diglim.rst F: Documentation/security/diglim/architecture.rst F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst