From patchwork Fri Jun 25 16:56:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345617 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.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,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 5EB54C49ECB for ; Fri, 25 Jun 2021 16:56:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3E17B6193F for ; Fri, 25 Jun 2021 16:56:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230127AbhFYQ7F (ORCPT ); Fri, 25 Jun 2021 12:59:05 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3308 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229630AbhFYQ67 (ORCPT ); Fri, 25 Jun 2021 12:58:59 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNDy142vz6G8m4; Sat, 26 Jun 2021 00:46:26 +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.2176.2; Fri, 25 Jun 2021 18:56:35 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu , Paul Moore , Stephen Smalley , , Prakhar Srivastava , Tushar Sugandhi , Lakshmi Ramasubramanian Subject: [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data() Date: Fri, 25 Jun 2021 18:56:03 +0200 Message-ID: <20210625165614.2284243-2-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: ima_measure_critical_data() allows any caller in the kernel to provide a buffer, so that is measured by IMA if an appropriate policy is set. Some information that could be useful to the callers are the digest of the buffer included in the new measurement entry, the digest algorithm and whether the buffer was measured. This patch modifies the definition of ima_measure_critical_data() to include three new parameters: digest, algo and measured. If they are NULL, the function behaves as before and just measures the buffer, if requested with the IMA policy. Otherwise, it also writes the digest, algorithm and whether the buffer is measured to the provided pointers. If the pointers are not NULL, the digest is calculated also if there is no matching rule in the IMA policy. Signed-off-by: Roberto Sassu Cc: Paul Moore Cc: Stephen Smalley Cc: selinux@vger.kernel.org Cc: Prakhar Srivastava Cc: Tushar Sugandhi Cc: Lakshmi Ramasubramanian --- include/linux/ima.h | 8 +++-- security/integrity/ima/ima.h | 3 +- security/integrity/ima/ima_appraise.c | 3 +- security/integrity/ima/ima_asymmetric_keys.c | 3 +- security/integrity/ima/ima_init.c | 3 +- security/integrity/ima/ima_main.c | 32 ++++++++++++++++---- security/integrity/ima/ima_queue_keys.c | 2 +- security/selinux/ima.c | 5 +-- 8 files changed, 44 insertions(+), 15 deletions(-) diff --git a/include/linux/ima.h b/include/linux/ima.h index 61d5723ec303..f7fd931456c7 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -11,6 +11,7 @@ #include #include #include +#include struct linux_binprm; #ifdef CONFIG_IMA @@ -36,7 +37,8 @@ extern void ima_kexec_cmdline(int kernel_fd, const void *buf, int size); extern void ima_measure_critical_data(const char *event_label, const char *event_name, const void *buf, size_t buf_len, - bool hash); + bool hash, u8 *digest, + enum hash_algo *algo, bool *measured); #ifdef CONFIG_IMA_APPRAISE_BOOTPARAM extern void ima_appraise_parse_cmdline(void); @@ -140,7 +142,9 @@ static inline void ima_kexec_cmdline(int kernel_fd, const void *buf, int size) { static inline void ima_measure_critical_data(const char *event_label, const char *event_name, const void *buf, size_t buf_len, - bool hash) {} + bool hash, u8 *digest, + enum hash_algo *algo, + bool *measured) {} #endif /* CONFIG_IMA */ diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index f0e448ed1f9f..fff31065d600 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -268,7 +268,8 @@ void process_buffer_measurement(struct user_namespace *mnt_userns, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *func_data, - bool buf_hash); + bool buf_hash, u8 *digest, enum hash_algo *algo, + bool *measured); void ima_audit_measurement(struct integrity_iint_cache *iint, const unsigned char *filename); int ima_alloc_init_template(struct ima_event_data *event_data, diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ef9dcfce45d4..3fcbf1bfa449 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -357,7 +357,8 @@ int ima_check_blacklist(struct integrity_iint_cache *iint, if ((rc == -EPERM) && (iint->flags & IMA_MEASURE)) process_buffer_measurement(&init_user_ns, NULL, digest, digestsize, "blacklisted-hash", NONE, - pcr, NULL, false); + pcr, NULL, false, NULL, NULL, + NULL); } return rc; diff --git a/security/integrity/ima/ima_asymmetric_keys.c b/security/integrity/ima/ima_asymmetric_keys.c index c985418698a4..4370bf7b8866 100644 --- a/security/integrity/ima/ima_asymmetric_keys.c +++ b/security/integrity/ima/ima_asymmetric_keys.c @@ -62,5 +62,6 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key, */ process_buffer_measurement(&init_user_ns, NULL, payload, payload_len, keyring->description, KEY_CHECK, 0, - keyring->description, false); + keyring->description, false, NULL, NULL, + NULL); } diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 5076a7d9d23e..a4dcd15187a8 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -154,7 +154,8 @@ int __init ima_init(void) ima_init_key_queue(); ima_measure_critical_data("kernel_info", "kernel_version", - UTS_RELEASE, strlen(UTS_RELEASE), false); + UTS_RELEASE, strlen(UTS_RELEASE), false, NULL, + NULL, NULL); return rc; } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 287b90509006..04d1aed5adae 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -833,6 +833,9 @@ int ima_post_load_data(char *buf, loff_t size, * @pcr: pcr to extend the measurement * @func_data: func specific data, may be NULL * @buf_hash: measure buffer data hash + * @digest: buffer digest will be written to + * @algo: digest algorithm + * @measured: whether the buffer has been measured by IMA * * Based on policy, either the buffer data or buffer data hash is measured */ @@ -840,7 +843,8 @@ void process_buffer_measurement(struct user_namespace *mnt_userns, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *func_data, - bool buf_hash) + bool buf_hash, u8 *digest, enum hash_algo *algo, + bool *measured) { int ret = 0; const char *audit_cause = "ENOMEM"; @@ -861,7 +865,7 @@ void process_buffer_measurement(struct user_namespace *mnt_userns, int action = 0; u32 secid; - if (!ima_policy_flag) + if (!ima_policy_flag && (!digest || !algo || !measured)) return; template = ima_template_desc_buf(); @@ -883,7 +887,7 @@ void process_buffer_measurement(struct user_namespace *mnt_userns, action = ima_get_action(mnt_userns, inode, current_cred(), secid, 0, func, &pcr, &template, func_data); - if (!(action & IMA_MEASURE)) + if (!(action & IMA_MEASURE) && (!digest || !algo || !measured)) return; } @@ -914,6 +918,15 @@ void process_buffer_measurement(struct user_namespace *mnt_userns, event_data.buf_len = digest_hash_len; } + if (digest && algo) { + memcpy(digest, iint.ima_hash->digest, + hash_digest_size[ima_hash_algo]); + *algo = ima_hash_algo; + } + + if (!(action & IMA_MEASURE)) + return; + ret = ima_alloc_init_template(&event_data, &entry, template); if (ret < 0) { audit_cause = "alloc_entry"; @@ -924,8 +937,11 @@ void process_buffer_measurement(struct user_namespace *mnt_userns, if (ret < 0) { audit_cause = "store_entry"; ima_free_template_entry(entry); + goto out; } + if (measured) + *measured = true; out: if (ret < 0) integrity_audit_message(AUDIT_INTEGRITY_PCR, NULL, eventname, @@ -956,7 +972,7 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size) process_buffer_measurement(file_mnt_user_ns(f.file), file_inode(f.file), buf, size, "kexec-cmdline", KEXEC_CMDLINE, 0, - NULL, false); + NULL, false, NULL, NULL, NULL); fdput(f); } @@ -967,6 +983,9 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size) * @buf: pointer to buffer data * @buf_len: length of buffer data (in bytes) * @hash: measure buffer data hash + * @digest: buffer digest will be written to + * @algo: digest algorithm + * @measured: whether the buffer has been measured by IMA * * Measure data critical to the integrity of the kernel into the IMA log * and extend the pcr. Examples of critical data could be various data @@ -976,14 +995,15 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size) void ima_measure_critical_data(const char *event_label, const char *event_name, const void *buf, size_t buf_len, - bool hash) + bool hash, u8 *digest, enum hash_algo *algo, + bool *measured) { if (!event_name || !event_label || !buf || !buf_len) return; process_buffer_measurement(&init_user_ns, NULL, buf, buf_len, event_name, CRITICAL_DATA, 0, event_label, - hash); + hash, digest, algo, measured); } static int __init init_ima(void) diff --git a/security/integrity/ima/ima_queue_keys.c b/security/integrity/ima/ima_queue_keys.c index 979ef6c71f3d..97ed974651fd 100644 --- a/security/integrity/ima/ima_queue_keys.c +++ b/security/integrity/ima/ima_queue_keys.c @@ -165,7 +165,7 @@ void ima_process_queued_keys(void) entry->keyring_name, KEY_CHECK, 0, entry->keyring_name, - false); + false, NULL, NULL, NULL); list_del(&entry->list); ima_free_key_entry(entry); } diff --git a/security/selinux/ima.c b/security/selinux/ima.c index 34d421861bfc..af1016dbb5aa 100644 --- a/security/selinux/ima.c +++ b/security/selinux/ima.c @@ -86,7 +86,8 @@ void selinux_ima_measure_state_locked(struct selinux_state *state) } ima_measure_critical_data("selinux", "selinux-state", - state_str, strlen(state_str), false); + state_str, strlen(state_str), false, NULL, + NULL, NULL); kfree(state_str); @@ -103,7 +104,7 @@ void selinux_ima_measure_state_locked(struct selinux_state *state) } ima_measure_critical_data("selinux", "selinux-policy-hash", - policy, policy_len, true); + policy, policy_len, true, NULL, NULL, NULL); vfree(policy); } From patchwork Fri Jun 25 16:56:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345623 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.8 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,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 03ADDC49EAF for ; Fri, 25 Jun 2021 16:56:48 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D900661990 for ; Fri, 25 Jun 2021 16:56:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230137AbhFYQ7G (ORCPT ); Fri, 25 Jun 2021 12:59:06 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3309 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229697AbhFYQ7A (ORCPT ); Fri, 25 Jun 2021 12:59:00 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNJ13yfbz6G7xF; Sat, 26 Jun 2021 00:49:05 +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.2176.2; Fri, 25 Jun 2021 18:56:36 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 02/12] digest_lists: Overview Date: Fri, 25 Jun 2021 18:56:04 +0200 Message-ID: <20210625165614.2284243-3-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch adds an overview of Huawei Digest Lists to Documentation/security/digest_lists.rst. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 228 ++++++++++++++++++++++++ Documentation/security/index.rst | 1 + MAINTAINERS | 7 + 3 files changed, 236 insertions(+) create mode 100644 Documentation/security/digest_lists.rst diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst new file mode 100644 index 000000000000..8980be7836f8 --- /dev/null +++ b/Documentation/security/digest_lists.rst @@ -0,0 +1,228 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=================== +Huawei Digest Lists +=================== + +Introduction +============ + +Huawei Digest Lists is a simple in kernel database for storing file and +metadata digests and for reporting to its users (e.g. Integrity Measurement +Architecture or IMA) whether the digests are in the database or not. The +choice of placing it in the kernel and not in a user space process is +explained later in the Security Assumptions section. + +The database is populated by directly uploading the so called digest lists, +a set of digests concatenated together and preceded by a header including +information about them (e.g. whether the file or metadata with a given +digest is immutable or not). Digest lists are stored in the kernel memory +as they are, the kernel just builds indexes to easily lookup digests. + +The kernel supports only one digest list format called ``compact``. However, +alternative formats (e.g. the RPM header) can be supported through a user +space parser that, by invoking the appropriate library (more can be added), +converts the original digest list to the compact format and uploads it to +the kernel. + +The database keeps track of whether digest lists have been processed in +some way (e.g. measured or appraised by IMA). This is important for example +for remote attestation, so that remote verifiers understand what has been +loaded in the database. + +It is a transactional database, i.e. it has the ability to roll back to the +beginning of the transaction if an error occurred during the addition of a +digest list (the deletion operation always succeeds). This capability has +been tested with an ad-hoc fault injection mechanism capable of simulating +failures during the operations. + +Finally, the database exposes to user space, through securityfs, the digest +lists currently loaded, the number of digests added, a query interface and +an interface to set digest list labels. + + +Binary Integrity +---------------- + +Integrity is a fundamental security property in information systems. +Integrity could be described as the condition in which a generic +component is just after it has been released by the entity that created it. + +One way to check whether a component is in this condition (called binary +integrity) is to calculate its digest and to compare it with a reference +value (i.e. the digest calculated in controlled conditions, when the +component is released). + +IMA, a software part of the integrity subsystem, can perform such +evaluation and execute different actions: + +- store the digest in an integrity-protected measurement list, so that it + can be sent to a remote verifier for analysis; +- compare the calculated digest with a reference value (usually protected + with a signature) and deny operations if the file is found corrupted; +- store the digest in the system log. + + +Contribution +------------ + +Huawei Digest Lists facilitates the provisioning of reference values for +system and application files from software vendors, to the kernel. + +Possible sources for digest lists are: + +- RPM headers; +- Debian repository metadata. + +These sources are already usable without any modification required for +Linux vendors. + +If digest list sources are signed (usually they are, like the ones above), +remote verifiers could identify their provenance, or Linux vendors could +prevent the loading of unsigned ones or those signed with an untrusted key. + + +Possible Usages +--------------- + +Provisioned reference values can be used (e.g. by IMA) to make +integrity-related decisions (allow list or deny list). + +Possible usages for IMA are: + +- avoid recording measurement of files whose digest is found in the + pre-provisioned reference values: + + - reduces the number of TPM operations (PCR extend); + - could make a TPM PCR predictable, as the PCR would not be affected by + the temporal sequence of executions if binaries are known + (no measurement); + +- exclusively grant access to files whose digest is found in the + pre-provisioned reference values: + + - faster verification time (fewer signature verifications); + - no need to generate a signature for every file. + + +Security Assumptions +-------------------- + +Since digest lists are stored in the kernel memory, they are no susceptible +to attacks by user space processes. + +A locked-down kernel, that accepts only verified kernel modules, will allow +digest lists to be added or deleted only though a well-defined and +monitored interface. In this situation, the root user is assumed to be +untrusted, i.e. it cannot subvert without being detected the mandatory +policy stating which files are accessible by the system. + +Adoption +-------- + +A former version of Huawei Digest Lists is used in the following OSes: + +- openEuler 20.09 + https://github.com/openeuler-mirror/kernel/tree/openEuler-20.09 + +- openEuler 21.03 + https://github.com/openeuler-mirror/kernel/tree/openEuler-21.03 + +Originally, Huawei Digest Lists was part of IMA. In this version, +it has been redesigned as a standalone module with an API that makes +its functionality accessible by IMA and, eventually, other subsystems. + +User Space Support +------------------ + +Digest lists can be generated and managed with ``digest-list-tools``: + +https://github.com/openeuler-mirror/digest-list-tools + +It includes two main applications: + +- ``gen_digest_lists``: generates digest lists from files in the + filesystem or from the RPM database (more digest list sources can be + supported); +- ``manage_digest_lists``: converts and uploads digest lists to the + kernel. + + +Simple Usage Example +-------------------- + +1. Digest list generation (RPM headers and their signature are copied to + the specified directory): + +.. code-block:: bash + + # mkdir /etc/digest_lists + # gen_digest_lists -t file -f rpm+db -d /etc/digest_lists -o add + + +2. Digest list upload with the user space parser: + +.. code-block:: bash + + # manage_digest_lists -p add-digest -d /etc/digest_lists + +3. First digest list query: + +.. code-block:: bash + + # echo sha256-$(sha256sum /bin/cat) > /sys/kernel/security/integrity/digest_lists/digest_query + # cat /sys/kernel/security/integrity/digest_lists/digest_query + sha256-[...]-0-file_list-rpm-coreutils-8.32-18.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 106, datalen: 3392 + +4. Second digest list query: + +.. code-block:: bash + + # echo sha256-$(sha256sum /bin/zip) > /sys/kernel/security/integrity/digest_lists/digest_query + # cat /sys/kernel/security/integrity/digest_lists/digest_query + sha256-[...]-0-file_list-rpm-zip-3.0-27.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 4, datalen: 128 + + +Architecture +============ + +This section introduces the high level architecture. + +:: + + 5. add/delete from hash table and add refs to digest list + +---------------------------------------------+ + | +-----+ +-------------+ +--+ + | | key |-->| digest refs |-->...-->| | + V +-----+ +-------------+ +--+ + +-------------+ +-----+ +-------------+ + | digest list | | key |-->| digest refs | + | (compact) | +-----+ +-------------+ + +-------------+ +-----+ +-------------+ + ^ 4. copy to | key |-->| digest refs | + | kernel memory +-----+ +-------------+ kernel space + -------------------------------------------------------------------------- + ^ ^ user space + |<----------------+ 3b. upload | + +-------------+ +------------+ | 6. query digest + | digest list | | user space | 2b. convert + | (compact) | | parser | + +-------------+ +------------+ + 1a. upload ^ 1b. read + | + +------------+ + | RPM header | + +------------+ + + +As mentioned before, digest lists can be uploaded directly if they are in +the compact format (step 1a) or can be uploaded indirectly by the user +space parser if they are in an alternative format (steps 1b-3b). + +During upload, the kernel makes a copy of the digest list to the kernel +memory (step 4), and creates the necessary structures to index the digests +(hash table and an array of digest list references to locate the digests in +the digest list) (step 5). + +Finally, digests can be searched from user space through a securityfs file +(step 6) or by the kernel itself. diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 16335de04e8c..80877b520403 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -17,3 +17,4 @@ Security Documentation tpm/index digsig landlock + digest_lists diff --git a/MAINTAINERS b/MAINTAINERS index 8c5ee008301a..cba3d82fee43 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8381,6 +8381,13 @@ W: http://www.st.com/ F: Documentation/devicetree/bindings/iio/humidity/st,hts221.yaml F: drivers/iio/humidity/hts221* +HUAWEI DIGEST LISTS +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/security/digest_lists.rst + HUAWEI ETHERNET DRIVER M: Bin Luo L: netdev@vger.kernel.org From patchwork Fri Jun 25 16:56:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345621 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.8 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,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 33A32C49EAB for ; Fri, 25 Jun 2021 16:56:47 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 128A36194F for ; Fri, 25 Jun 2021 16:56:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230116AbhFYQ7G (ORCPT ); Fri, 25 Jun 2021 12:59:06 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3310 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229723AbhFYQ7A (ORCPT ); Fri, 25 Jun 2021 12:59:00 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBN935PWCz6L52F; Sat, 26 Jun 2021 00:43:03 +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.2176.2; Fri, 25 Jun 2021 18:56:36 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 03/12] digest_lists: Basic definitions Date: Fri, 25 Jun 2021 18:56:05 +0200 Message-ID: <20210625165614.2284243-4-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces the basic definitions, exported to user space, to use digest lists. The definitions, added to include/uapi/linux/digest_lists.h, are documented in Documentation/security/digest_lists.rst. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 119 ++++++++++++++++++++++++ MAINTAINERS | 1 + include/uapi/linux/digest_lists.h | 43 +++++++++ 3 files changed, 163 insertions(+) create mode 100644 include/uapi/linux/digest_lists.h diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 8980be7836f8..995260294783 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -226,3 +226,122 @@ the digest list) (step 5). Finally, digests can be searched from user space through a securityfs file (step 6) or by the kernel itself. + + +Implementation +============== + +This section describes the implementation of Huawei Digest Lists. + + +Basic Definitions +----------------- + +This section introduces the basic definitions required to use digest lists. + + +Compact Digest List Format +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Compact digest lists consist of one or multiple headers defined as: + +:: + + struct compact_list_hdr { + __u8 version; + __u8 _reserved; + __le16 type; + __le16 modifiers; + __le16 algo; + __le32 count; + __le32 datalen; + } __packed; + +which characterize the subsequent block of concatenated digests. + +The ``algo`` field specifies the algorithm used to calculate the digest. + +The ``count`` field specifies how many digests are stored in the subsequent +block of digests. + +The ``datalen`` field specifies the length of the subsequent block of +digests (it is redundant, it is the same as +``hash_digest_size[algo] * count``). + + +Compact Types +............. + +Digests can be of different types: + +- ``COMPACT_PARSER``: digests of executables which are given the ability to + parse digest lists not in the compact format and to upload to the kernel + the digest list converted to the compact format; +- ``COMPACT_FILE``: digests of regular files; +- ``COMPACT_METADATA``: digests of file metadata (e.g. the digest + calculated by EVM to verify a portable signature); +- ``COMPACT_DIGEST_LIST``: digests of digest lists (only used internally by + the kernel). + +Different users of Huawei Digest Lists might query digests with different +compact types. For example, IMA would be interested in COMPACT_FILE, as it +deals with regular files, while EVM would be interested in +COMPACT_METADATA, as it verifies file metadata. + + +Compact Modifiers +................. + +Digests can also have specific attributes called modifiers (bit position): + +- ``COMPACT_MOD_IMMUTABLE``: file content or metadata should not be + modifiable. + +IMA might use this information to deny open for writing, or EVM to deny +setxattr operations. + + +Actions +....... + +This section defines a set of possible actions that have been executed on +the digest lists (bit position): + +- ``COMPACT_ACTION_IMA_MEASURED``: the digest list has been measured by + IMA; +- ``COMPACT_ACTION_IMA_APPRAISED``: the digest list has been successfully + appraised by IMA; +- ``COMPACT_ACTION_IMA_APPRAISED_DIGSIG``: the digest list has been + successfully appraised by IMA by verifying a digital signature. + +This information might help users of Huawei Digest Lists to decide whether +to use the result of a queried digest. + +For example, if a digest belongs to a digest list that was not measured +before, IMA should ignore the result of the query as the measurement list +sent to remote verifiers would lack how the database was populated. + + +Compact Digest List Example +........................... + +:: + + version: 1, type: 2, modifiers: 0 algo: 4, count: 3, datalen: 96 + + version: 1, type: 3, modifiers: 1 algo: 6, count: 2, datalen: 128 + + +This digest list consists of two blocks. The first block contains three +SHA256 digests of regular files. The second block contains two SHA512 +digests of immutable metadata. + + +Compact Digest List Operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Finally, this section defines the possible operations that can be performed +with digest lists: + +- ``DIGEST_LIST_ADD``: the digest list is being added; +- ``DIGEST_LIST_DEL``: the digest list is being deleted. diff --git a/MAINTAINERS b/MAINTAINERS index cba3d82fee43..ccf555862673 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8387,6 +8387,7 @@ L: linux-integrity@vger.kernel.org S: Supported T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/digest_lists.rst +F: uapi/linux/digest_lists.h HUAWEI ETHERNET DRIVER M: Bin Luo diff --git a/include/uapi/linux/digest_lists.h b/include/uapi/linux/digest_lists.h new file mode 100644 index 000000000000..9545a8aaa231 --- /dev/null +++ b/include/uapi/linux/digest_lists.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: digest_lists.h + * Digest list definitions exported to user space. + */ + +#ifndef _UAPI__LINUX_DIGEST_LISTS_H +#define _UAPI__LINUX_DIGEST_LISTS_H + +#include +#include + +enum compact_types { COMPACT_KEY, COMPACT_PARSER, COMPACT_FILE, + COMPACT_METADATA, COMPACT_DIGEST_LIST, COMPACT__LAST }; + +enum compact_modifiers { COMPACT_MOD_IMMUTABLE, COMPACT_MOD__LAST }; + +enum compact_actions { COMPACT_ACTION_IMA_MEASURED, + COMPACT_ACTION_IMA_APPRAISED, + COMPACT_ACTION_IMA_APPRAISED_DIGSIG, + COMPACT_ACTION__LAST }; + +enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST }; + +struct compact_list_hdr { + __u8 version; + __u8 _reserved; + __le16 type; + __le16 modifiers; + __le16 algo; + __le32 count; + __le32 datalen; +} __packed; +#endif From patchwork Fri Jun 25 16:56:06 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345619 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.8 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,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 800E3C49EB9 for ; Fri, 25 Jun 2021 16:56:48 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 67A4D6193F for ; Fri, 25 Jun 2021 16:56:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230151AbhFYQ7H (ORCPT ); Fri, 25 Jun 2021 12:59:07 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3311 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229738AbhFYQ7A (ORCPT ); Fri, 25 Jun 2021 12:59:00 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNJ25g9kz6G83c; Sat, 26 Jun 2021 00:49:06 +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.2176.2; Fri, 25 Jun 2021 18:56:37 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 04/12] digest_lists: Objects Date: Fri, 25 Jun 2021 18:56:06 +0200 Message-ID: <20210625165614.2284243-5-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch defines the objects to manage digest lists: - digest_list_item: represents a digest list; - digest_list_item_ref: represents a reference to a digest list, i.e. the location at which a digest within a digest list can be accessed; - digest_item: represents a unique digest. It also defines some helpers for the objects. More information can be found in Documentation/security/digest_lists.rst. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 156 ++++++++++++++++++ MAINTAINERS | 1 + .../integrity/digest_lists/digest_lists.h | 117 +++++++++++++ 3 files changed, 274 insertions(+) create mode 100644 security/integrity/digest_lists/digest_lists.h diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 995260294783..1031667324c9 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -345,3 +345,159 @@ with digest lists: - ``DIGEST_LIST_ADD``: the digest list is being added; - ``DIGEST_LIST_DEL``: the digest list is being deleted. + + +Objects +------- + +This section defines the objects to manage digest lists: + +- ``digest_list_item``: represents a digest list; +- ``digest_list_item_ref``: represents a reference to a digest list, + i.e. the location at which a digest within a digest list can be accessed; +- ``digest_item``: represents a unique digest. + +They are represented in the following class diagram: + +:: + + digest_offset,-----------+ + hdr_offset | + | + +------------------+ | +----------------------+ + | digest_list_item |--- N:1 ---| digest_list_item_ref | + +------------------+ +----------------------+ + | + 1:N + | + +-------------+ + | digest_item | + +-------------+ + +A ``digest_list_item`` is associated to one or multiple +``digest_list_item_ref``, one for each digest it contains. However, +a ``digest_list_item_ref`` is associated to only one ``digest_list_item``, +as it represents a single location within a specific digest list. + +Given that a ``digest_list_item_ref`` represents a single location, it is +associated to only one ``digest_item``. However, a ``digest_item`` can have +multiple references (as it might appears multiple times within the same +digest list or in different digest lists, if it is duplicated). + + +A ``digest_list_item`` is defined as: + +:: + + struct digest_list_item { + loff_t size; + u8 *buf; + u8 actions; + u8 digest[64]; + enum hash_algo algo; + const char *label; + }; + +- ``size``: size of the digest list buffer; +- ``buf``: digest list buffer; +- ``actions``: actions performed on the digest list; +- ``digest``: digest of the digest list; +- ``algo``: digest algorithm; +- ``label``: label used to identify the digest list (e.g. file name). + +A ``digest_list_item_ref`` is defined as: + +:: + + struct digest_list_item_ref { + struct digest_list_item *digest_list; + loff_t digest_offset; + loff_t hdr_offset; + }; + +- ``digest_list``: pointer to a ``digest_list_item`` structure; +- ``digest_offset``: offset of the digest related to the digest list + buffer; +- ``hdr_offset``: offset of the header of the digest block containing the + digest. + +A ``digest_item`` is defined as: + +:: + + struct digest_item { + struct hlist_node hnext; + struct digest_list_item_ref *refs; + }; + +- ``hnext``: pointers of the hash table; +- ``refs``: array of ``digest_list_item_ref`` structures including a + terminator (protected by RCU). + +All digest list references are stored for a given digest, so that a query +result can include the OR of the modifiers and actions of each referenced +digest list. + +The relationship between the described objects can be graphically +represented as: + +:: + + Hash table +-------------+ +-------------+ + PARSER +-----+ | digest_item | | digest_item | + FILE | key |-->| |-->...-->| | + METADATA +-----+ |ref0|...|refN| |ref0|...|refN| + +-------------+ +-------------+ + ref0: | | refN: + digest_offset | +-----------------------------+ digest_offset + hdr_offset | | hdr_offset + V V + +--------------------+ + | digest_list_item | + | | + | size, buf, actions | + +--------------------+ + ^ + | + Hash table +-------------+ +-------------+ + DIGEST_LIST +-----+ |ref0 | |ref0 | + | key |-->| |-->...-->| | + +-----+ | digest_item | | digest_item | + +-------------+ +-------------+ + +The reference for the digest of the digest list differs from the references +for the other digest types. ``digest_offset`` and ``hdr_offset`` are set to +zero, so that the digest of the digest list is retrieved from the +``digest_list_item`` structure directly (see ``get_digest()`` below). + +Finally, this section defines useful helpers to access a digest or the +header the digest belongs to. For example: + +:: + + static inline struct compact_list_hdr *get_hdr( + struct digest_list_item *digest_list, + loff_t hdr_offset) + { + return (struct compact_list_hdr *)(digest_list->buf + hdr_offset); + } + +the header can be obtained by summing the address of the digest list buffer +in the ``digest_list_item`` structure with ``hdr_offset``. + +Similarly: + +:: + + static inline u8 *get_digest(struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) + { + /* Digest list digest is stored in a different place. */ + if (!digest_offset) + return digest_list->digest; + return digest_list->buf + digest_offset; + } + +the digest can be obtained by summing the address of the digest list buffer +with ``digest_offset`` (except for the digest lists, where the digest is +stored in the ``digest`` field of the ``digest_list_item`` structure). diff --git a/MAINTAINERS b/MAINTAINERS index ccf555862673..9a7e9f16eee8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8387,6 +8387,7 @@ L: linux-integrity@vger.kernel.org S: Supported T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/digest_lists.rst +F: security/integrity/digest_lists/digest_list.h F: uapi/linux/digest_lists.h HUAWEI ETHERNET DRIVER diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h new file mode 100644 index 000000000000..81b6cb10f4f1 --- /dev/null +++ b/security/integrity/digest_lists/digest_lists.h @@ -0,0 +1,117 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: digest_lists.h + * Unexported definitions for digest lists. + */ + +#ifndef __DIGEST_LISTS_INTERNAL_H +#define __DIGEST_LISTS_INTERNAL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DIGEST_SIZE 64 +#define HASH_BITS 10 +#define MEASURE_HTABLE_SIZE (1 << HASH_BITS) + +struct digest_list_item { + loff_t size; + u8 *buf; + u8 actions; + u8 digest[64]; + enum hash_algo algo; + const char *label; +}; + +struct digest_list_item_ref { + struct digest_list_item *digest_list; + loff_t digest_offset; + loff_t hdr_offset; +}; + +struct digest_item { + /* hash table pointers */ + struct hlist_node hnext; + /* digest list references (protected by RCU) */ + struct digest_list_item_ref *refs; +}; + +struct h_table { + atomic_long_t len; + struct hlist_head queue[MEASURE_HTABLE_SIZE]; +}; + +static inline unsigned int hash_key(u8 *digest) +{ + return (digest[0] | digest[1] << 8) % MEASURE_HTABLE_SIZE; +} + +static inline struct compact_list_hdr *get_hdr( + struct digest_list_item *digest_list, + loff_t hdr_offset) +{ + return (struct compact_list_hdr *)(digest_list->buf + hdr_offset); +} + +static inline enum hash_algo get_algo(struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + /* Digest list digest algorithm is stored in a different place. */ + if (!digest_offset) + return digest_list->algo; + + return get_hdr(digest_list, hdr_offset)->algo; +} + +static inline u8 *get_digest(struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + /* Digest list digest is stored in a different place. */ + if (!digest_offset) + return digest_list->digest; + + return digest_list->buf + digest_offset; +} + +static inline struct compact_list_hdr *get_hdr_ref( + struct digest_list_item_ref *ref) +{ + return get_hdr(ref->digest_list, ref->hdr_offset); +} + +static inline enum hash_algo get_algo_ref(struct digest_list_item_ref *ref) +{ + /* Digest list digest algorithm is stored in a different place. */ + if (!ref->digest_offset) + return ref->digest_list->algo; + + return get_hdr_ref(ref)->algo; +} + +static inline u8 *get_digest_ref(struct digest_list_item_ref *ref) +{ + /* Digest list digest is stored in a different place. */ + if (!ref->digest_offset) + return ref->digest_list->digest; + + return ref->digest_list->buf + ref->digest_offset; +} +#endif /*__DIGEST_LISTS_INTERNAL_H*/ From patchwork Fri Jun 25 16:56:07 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345659 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.8 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,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 61E18C49EBE for ; Fri, 25 Jun 2021 16:58:03 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4C32A61981 for ; Fri, 25 Jun 2021 16:58:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230136AbhFYRAW (ORCPT ); Fri, 25 Jun 2021 13:00:22 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3312 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229958AbhFYRAU (ORCPT ); Fri, 25 Jun 2021 13:00:20 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNKY4mcGz6G8HX; Sat, 26 Jun 2021 00:50:25 +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.2176.2; Fri, 25 Jun 2021 18:57:56 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 05/12] digest_lists: Methods Date: Fri, 25 Jun 2021 18:56:07 +0200 Message-ID: <20210625165614.2284243-6-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces the methods requires to manage the three objects defined. - digest_item methods: - digest_add() - digest_del() - digest_lookup() - digest_get_info() - digest_list_item_ref methods: - digest_list_ref_add() - digest_list_ref_del() - digest_list_item methods: - digest_list_add() - digest_list_del() More information about these functions can be found in Documentation/security/digest_lists.rst. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 110 ++++ MAINTAINERS | 2 + include/linux/digest_lists.h | 24 + security/integrity/Kconfig | 1 + security/integrity/Makefile | 1 + security/integrity/digest_lists/Kconfig | 11 + security/integrity/digest_lists/Makefile | 8 + .../integrity/digest_lists/digest_lists.h | 34 ++ security/integrity/digest_lists/methods.c | 548 ++++++++++++++++++ 9 files changed, 739 insertions(+) create mode 100644 include/linux/digest_lists.h create mode 100644 security/integrity/digest_lists/Kconfig create mode 100644 security/integrity/digest_lists/Makefile create mode 100644 security/integrity/digest_lists/methods.c diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 1031667324c9..8f1d15a37dbd 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -501,3 +501,113 @@ Similarly: the digest can be obtained by summing the address of the digest list buffer with ``digest_offset`` (except for the digest lists, where the digest is stored in the ``digest`` field of the ``digest_list_item`` structure). + + +Methods +------- + +This section introduces the methods requires to manage the three objects +defined. + + +``digest_item`` Methods +~~~~~~~~~~~~~~~~~~~~~~~ + + +``digest_add()`` +................ + +``digest_add()`` first checks in the hash table for the passed type if a +``digest_item`` for the same digest already exists. If not, it creates a +new one. Then, ``digest_add()`` calls ``digest_list_ref_add()`` to add a +new reference of the digest list being added to the found or new +``digest_item``. + + +``digest_del()`` +................ + +``digest_del()`` also searches the ``digest_item`` in the hash table. It +should be always found, as digest lists can be deleted only if they were +added before. Then, ``digest_del()`` calls ``digest_list_ref_del()`` to +delete a reference of the digest list being deleted from the found +``digest_item``. + + +``digest_lookup()`` +................... + +``digest_lookup()`` searches the passed digest in the hash table. Then, it +returns immediately a ``digest_item`` (or NULL if the digest is not found) +if the modifiers and actions information are not requested by the caller, +or iterates over all the valid references of the digest and calculates the +OR for both of them. Iteration in the array of references ends when the +digest list pointer in a reference is set to NULL. Access to the ``refs`` +array is protected by RCU to avoid access to digest lists being added or +deleted (update is serialized by the securityfs interfaces). + +``digest_lookup()`` is not exposed to the rest of the kernel, because +access to the returned ``digest_item`` outside RCU would be illegal. + + +``digest_get_info()`` +..................... + +``digest_get_info()`` is the public version of ``digest_lookup()``, which +does not return a ``digest_item`` but just the resulting modifiers and +actions from the OR of the modifiers and actions from the referenced +digest lists. + + +``digest_list_item_ref`` Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +``digest_list_ref_add()`` +......................... + +``digest_list_ref_add()`` adds a new reference at the end of the ``refs`` +array (also keeps a terminator as the last element). It does not search for +duplicates, as a duplicate reference simply means that the digest appears +multiple times in the digest list. ``digest_list_ref_add()`` does not add +the new element in place, but first creates a copy of the current ``refs`` +array and uses RCU to replace it with the new one. + + +``digest_list_ref_del()`` +......................... + +``digest_list_ref_del()`` first searches in the ``refs`` array a reference +to a given digest list. Then, it invalidates the found reference so that it +is skipped by the reader. Afterwards, it tries to allocate a smaller +``refs`` array (with enough slots to store the valid references, except the +one being deleted). If memory allocation succeeds, +``digest_list_ref_del()`` copies the valid references to the copy of +``refs`` and uses RCU to replace the original ``refs``. Otherwise, it keeps +the original ``refs`` with the invalidated reference. + + +``digest_list_item`` Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +``digest_list_add()`` +..................... + +``digest_list_add()`` first searches the digest of the digest list in the +hash table for the ``COMPACT_DIGEST_LIST`` type. Addition can be done if +the digest list is not found (it is pointless to load the same digest list +again). ``digest_list_add()`` then creates a new ``digest_item``, +representing the digest of the digest list, a special +``digest_list_item_ref`` with ``digest_offset`` and ``hdr_offset`` set to +zero, and a new ``digest_list_item``. + + +``digest_list_del()`` +..................... + +``digest_list_del()`` also searches the digest of the digest list in the +hash table for the ``COMPACT_DIGEST_LIST`` type. Deletion can be done only +if the digest list is found. ``digest_list_del()`` then deletes the +``digest_list_item``, the special ``digest_list_item_ref`` and the +``digest_item``. diff --git a/MAINTAINERS b/MAINTAINERS index 9a7e9f16eee8..a9eb52e65b12 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8387,7 +8387,9 @@ L: linux-integrity@vger.kernel.org S: Supported T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/digest_lists.rst +F: include/linux/digest_list.h F: security/integrity/digest_lists/digest_list.h +F: security/integrity/digest_lists/methods.c F: uapi/linux/digest_lists.h HUAWEI ETHERNET DRIVER diff --git a/include/linux/digest_lists.h b/include/linux/digest_lists.h new file mode 100644 index 000000000000..f59a2bc5224f --- /dev/null +++ b/include/linux/digest_lists.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: digest_lists.h + * Exported functions for digest list management. + */ + +#ifndef __DIGEST_LISTS_H +#define __DIGEST_LISTS_H + +#include +#include + +int digest_get_info(u8 *digest, enum hash_algo algo, enum compact_types type, + u16 *modifiers, u8 *actions); +#endif diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 71f0177e8716..a6ae57a7453a 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -98,5 +98,6 @@ config INTEGRITY_AUDIT source "security/integrity/ima/Kconfig" source "security/integrity/evm/Kconfig" +source "security/integrity/digest_lists/Kconfig" endif # if INTEGRITY diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 7ee39d66cf16..a1e4acb4d2ae 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -19,3 +19,4 @@ integrity-$(CONFIG_LOAD_PPC_KEYS) += platform_certs/efi_parser.o \ platform_certs/keyring_handler.o obj-$(CONFIG_IMA) += ima/ obj-$(CONFIG_EVM) += evm/ +obj-$(CONFIG_DIGEST_LISTS) += digest_lists/ diff --git a/security/integrity/digest_lists/Kconfig b/security/integrity/digest_lists/Kconfig new file mode 100644 index 000000000000..2d8290bd2d4d --- /dev/null +++ b/security/integrity/digest_lists/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Huawei Digest Lists +# +config DIGEST_LISTS + bool "Digest Lists" + select SECURITYFS + select CRYPTO + select CRYPTO_HASH_INFO + help + Huawei Digest Lists provides reference values for file content and + metadata, that can be used for measurement and appraisal with IMA. diff --git a/security/integrity/digest_lists/Makefile b/security/integrity/digest_lists/Makefile new file mode 100644 index 000000000000..0ba66ab2e260 --- /dev/null +++ b/security/integrity/digest_lists/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for building Huawei Digest Lists. +# + +obj-$(CONFIG_DIGEST_LISTS) += digest_lists.o + +digest_lists-y := methods.o diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h index 81b6cb10f4f1..aadce9ca8f5f 100644 --- a/security/integrity/digest_lists/digest_lists.h +++ b/security/integrity/digest_lists/digest_lists.h @@ -64,6 +64,8 @@ static inline unsigned int hash_key(u8 *digest) return (digest[0] | digest[1] << 8) % MEASURE_HTABLE_SIZE; } +extern struct h_table htable[COMPACT__LAST]; + static inline struct compact_list_hdr *get_hdr( struct digest_list_item *digest_list, loff_t hdr_offset) @@ -114,4 +116,36 @@ static inline u8 *get_digest_ref(struct digest_list_item_ref *ref) return ref->digest_list->buf + ref->digest_offset; } + +static inline bool digest_list_ref_invalidated(struct digest_list_item_ref *ref) +{ + return (ref->digest_list == ZERO_SIZE_PTR); +} + +static inline void digest_list_ref_invalidate(struct digest_list_item_ref *ref) +{ + ref->digest_list = ZERO_SIZE_PTR; +} + +static inline bool digest_list_ref_is_last(struct digest_list_item_ref *ref) +{ + return (ref->digest_list == NULL); +} + +struct digest_item *digest_lookup(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 *modifiers, + u8 *actions); +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, + enum compact_types type, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset); +struct digest_item *digest_del(u8 *digest, enum hash_algo algo, + enum compact_types type, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset); +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, + loff_t size, u8 *buf, u8 actions, + const char *label); +struct digest_item *digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, + struct digest_list_item *digest_list); #endif /*__DIGEST_LISTS_INTERNAL_H*/ diff --git a/security/integrity/digest_lists/methods.c b/security/integrity/digest_lists/methods.c new file mode 100644 index 000000000000..c4655fb3897a --- /dev/null +++ b/security/integrity/digest_lists/methods.c @@ -0,0 +1,548 @@ +// 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: methods.c + * Functions to manage digest lists. + */ + +#include +#include +#include +#include + +#include "digest_lists.h" +#include "../integrity.h" + +/* Define a hash table for each digest type. */ +struct h_table htable[COMPACT__LAST] = {{ + .len = ATOMIC_LONG_INIT(0), + .queue[0 ... MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +}}; + +#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS +static DECLARE_FAULT_ATTR(fail_digest_lists); + +static int __init fail_digest_lists_debugfs(void) +{ + struct dentry *dir = fault_create_debugfs_attr("fail_digest_lists", + NULL, &fail_digest_lists); + + return PTR_ERR_OR_ZERO(dir); +} + +static inline bool should_fail_digest_lists(void) +{ + return should_fail(&fail_digest_lists, 1); +} + +late_initcall(fail_digest_lists_debugfs); +#else +static inline bool should_fail_digest_lists(void) +{ + return false; +} +#endif + +/** + * digest_lookup - lookup digest and return associated modifiers and actions + * @digest: digest to lookup + * @algo: digest algorithm + * @type: type of digest to lookup (e.g. file, metadata) + * @modifiers: modifiers (attributes) associated to the found digest + * @actions: actions performed by IMA on the digest list containing the digest + * + * This function searches the given digest in the hash table depending on the + * passed type and sets the modifiers and actions associated to the digest, if + * the pointers are not NULL. + * + * This function is not intended for external use, as the returned digest item + * could be freed at any time after it has been returned. digest_get_info() + * should be used instead by external callers, as it only returns the modifiers + * and the actions associated to the digest at the time the digest is searched. + * + * RCU protects both the hash table and the digest_lists array, which contains + * references to the digest lists containing the found digest. + * + * Returns a digest_item structure if the digest is found, NULL otherwise. + */ +struct digest_item *digest_lookup(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 *modifiers, + u8 *actions) +{ + struct digest_item *d = NULL; + struct digest_list_item_ref *ref; + int digest_len = hash_digest_size[algo]; + unsigned int key = hash_key(digest); + bool found = false; + + rcu_read_lock(); + hlist_for_each_entry_rcu(d, &htable[type].queue[key], hnext) { + ref = rcu_dereference(d->refs); + + for (; ref != NULL && !digest_list_ref_is_last(ref); ref++) { + if (digest_list_ref_invalidated(ref)) + continue; + + if (get_algo_ref(ref) != algo || + memcmp(get_digest_ref(ref), digest, digest_len)) + break; + + found = true; + + /* There is no need to scan all digest list refs. */ + if (!modifiers || !actions) + break; + + /* + * The resulting modifiers and actions are the OR of the + * modifiers and actions for each digest list. + */ + *modifiers |= get_hdr_ref(ref)->modifiers; + *actions |= ref->digest_list->actions; + } + + if (found) + break; + } + + rcu_read_unlock(); + return d; +} + +/** + * digest_get_info - lookup digest and return associated modifiers and actions + * @digest: digest to lookup + * @algo: digest algorithm + * @type: type of digest to lookup (e.g. file, metadata) + * @modifiers: modifiers (attributes) associated to the found digest + * @actions: actions performed by IMA on the digest list containing the digest + * + * This function searches the given digest in the hash table depending on the + * passed type and sets the modifiers and actions associated to the digest, if + * the pointers are not NULL. + * + * This function is safe for external use, as it does not return pointers of + * objects that can be freed without the caller notices it. + * + * Returns 0 if the digest is found, -ENOENT otherwise. + */ +int digest_get_info(u8 *digest, enum hash_algo algo, enum compact_types type, + u16 *modifiers, u8 *actions) +{ + struct digest_item *d; + + d = digest_lookup(digest, algo, type, modifiers, actions); + if (!d) + return -ENOENT; + + return 0; +} + +/** + * digest_list_ref_add - add reference to a digest list + * @d: digest a new reference is added to + * @digest_list: digest list whose reference is being added + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function adds a new reference to an existing digest list for a given + * digest. The reference is described by the digest_list_item_ref structure and + * consists of a pointer of the digest list, the offset of the digest to the + * beginning of the digest list buffer and the offset of the header the digest + * refers to (each digest list might be composed of several digest blocks, each + * prefixed by a header describing the attributes of those digests). + * + * This function carefully updates the array of digest list references by + * creating a copy of the existing references, adding the new one and using + * RCU to replace the old array. An additional empty reference is allocated so + * that the reader can stop the iteration. + * + * Returns 0 if a new digest list reference was successfully added, a negative + * value otherwise. + */ +static int digest_list_ref_add(struct digest_item *d, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + struct digest_list_item_ref *new_refs = NULL, *old_refs = d->refs, *ref; + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); + int digest_len = hash_digest_size[algo]; + int all_refs; + + for (ref = old_refs, all_refs = 0; + ref != NULL && !digest_list_ref_is_last(ref); ref++, all_refs++) + ; + + /* + * Allocate a new array of references with + 1 element for the new + * reference and + 1 element for the terminator. + */ + if (!should_fail_digest_lists()) + new_refs = kmalloc_array(all_refs + 2, sizeof(*new_refs), + GFP_KERNEL); + if (!new_refs) { + print_hex_dump(KERN_ERR, "digest list ref allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return -ENOMEM; + } + + memcpy(new_refs, old_refs, all_refs * sizeof(*new_refs)); + + /* Set the new reference. */ + new_refs[all_refs].digest_list = digest_list; + new_refs[all_refs].digest_offset = digest_offset; + new_refs[all_refs].hdr_offset = hdr_offset; + /* Set the terminator. */ + new_refs[all_refs + 1].digest_list = NULL; + + /* Replace the old digest list references with the new ones with RCU. */ + rcu_assign_pointer(d->refs, new_refs); + kfree_rcu(old_refs); + + print_hex_dump_debug("add digest list ref: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); + return 0; +} + +/** + * digest_list_ref_del - del reference to a digest list + * @d: digest a reference is deleted from + * @digest_list: digest list whose reference is being deleted + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function searches the reference to an already loaded digest list in the + * array of references stored for each digest item. If the reference is found + * (if not, it is a bug), the function allocates a smaller array from which the + * found reference is removed and uses RCU to replace the existing array. + * + * Returns 0 if a reference of the passed digest list was successfully removed, + * a negative value otherwise. + */ +static int digest_list_ref_del(struct digest_item *d, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + struct digest_list_item_ref *new_refs = NULL, *old_refs = d->refs; + struct digest_list_item_ref *ref, *found_ref = NULL; + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); + int digest_len = hash_digest_size[algo]; + int i, valid_refs = 0; + + /* Search for a digest list reference. */ + for (ref = d->refs, valid_refs = 0; !digest_list_ref_is_last(ref); + ref++) { + if (digest_list_ref_invalidated(ref)) + continue; + + valid_refs++; + + if (!found_ref && ref->digest_list == digest_list) + found_ref = ref; + } + + if (!found_ref) { + print_hex_dump(KERN_ERR, "digest list ref not found: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return 0; + } + + digest_list_ref_invalidate(found_ref); + + if (valid_refs > 1) { + /* Allocate a smaller array of digest list references. */ + if (!should_fail_digest_lists()) + new_refs = kcalloc(valid_refs, sizeof(*new_refs), + GFP_KERNEL); + if (new_refs) { + for (ref = d->refs, i = 0; + !digest_list_ref_is_last(ref); ref++) { + /* Skip the reference to delete. */ + if (ref == found_ref) + continue; + + /* Skip invalid references. */ + if (digest_list_ref_invalidated(ref)) + continue; + + /* Copy the remaining references. */ + memcpy(&new_refs[i++], ref, sizeof(*new_refs)); + } + } else { + new_refs = old_refs; + } + } + + /* Replace the array of digest list references with RCU. */ + rcu_assign_pointer(d->refs, new_refs); + if (old_refs != new_refs) + kfree_rcu(old_refs); + + print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); + return 0; +} + +/** + * digest_add - add a new digest + * @digest: digest in binary form + * @algo: digest algorithm + * @type: digest type + * @digest_list: digest list the new digest belongs to + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function first searches if the digest is already in the hash table for + * the given type. The digest is searched by comparing the passed digest and + * algorithm with the digest obtained from the first valid digest list reference + * (buffer + digest offset). + * + * If the digest exists, only a new reference is added (there might be multiple + * references to the same digest list). + * + * If the digest is not found, a new digest item is allocated and a reference to + * the passed digest list is added to that item. The digest item is finally + * added to the hash table for the given type. + * + * Returns a new or the found digest item on success, an error pointer + * otherwise. + */ +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, + enum compact_types type, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + int digest_len = hash_digest_size[algo]; + struct digest_item *d; + int ret; + + /* Search the digest. */ + d = digest_lookup(digest, algo, type, NULL, NULL); + if (d) { + /* + * Add a new digest list reference to the existing digest item. + */ + ret = digest_list_ref_add(d, digest_list, digest_offset, + hdr_offset); + if (ret < 0) + return ERR_PTR(ret); + + print_hex_dump_debug("digest add duplicate: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); + return d; + } + + /* Allocate a new digest item. */ + if (!should_fail_digest_lists()) + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) { + print_hex_dump_debug("digest allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-ENOMEM); + } + + /* Add a new digest list reference to the new digest item. */ + ret = digest_list_ref_add(d, digest_list, digest_offset, hdr_offset); + if (ret < 0) { + kfree(d); + return ERR_PTR(ret); + } + + /* Add the new digest item to the hash table for the given type. */ + hlist_add_head_rcu(&d->hnext, &htable[type].queue[hash_key(digest)]); + atomic_long_inc(&htable[type].len); + + print_hex_dump_debug("digest add: ", DUMP_PREFIX_NONE, digest_len, 1, + digest, digest_len, true); + return d; +} + +/** + * digest_del - delete a digest with one reference, or just a reference + * @digest: digest in binary form + * @algo: digest algorithm + * @type: digest type + * @digest_list: digest list the digest belongs to + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function is called when a digest list is being removed. The digest is + * first searched in the hash table for the given type. If it is found (if not, + * it is a bug, because digest lists can be deleted only if they were added + * previously), a reference of the passed digest list is deleted from the array + * of references of the digest item. + * + * If the last reference was deleted, the digest item is also deleted and + * removed from the hash table. + * + * Returns the found digest item if it still has digest list references, NULL + * if all references were deleted, an error pointer otherwise. + */ +struct digest_item *digest_del(u8 *digest, enum hash_algo algo, + enum compact_types type, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + struct digest_item *d; + int digest_len = hash_digest_size[algo]; + int ret; + + /* Search the digest. */ + d = digest_lookup(digest, algo, type, NULL, NULL); + if (!d) { + print_hex_dump(KERN_ERR, "digest not found: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); + return ERR_PTR(-ENOENT); + } + + /* Delete a reference of the passed digest list. */ + ret = digest_list_ref_del(d, digest_list, digest_offset, hdr_offset); + if (ret < 0) + return ERR_PTR(ret); + + print_hex_dump_debug(d->refs != NULL ? + "digest del duplicate: " : "digest del: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + + /* Return if there are still references. */ + if (d->refs != NULL) + return d; + + /* + * Remove the digest item from the hash table and free it if there are + * no more references left. + */ + hlist_del_rcu(&d->hnext); + atomic_long_dec(&htable[type].len); + kfree(d); + return NULL; +} + +/** + * digest_list_add - add a new digest list + * @digest: digest of the digest list in binary form + * @algo: digest algorithm + * @size: digest list size + * @buf: digest list buffer + * @actions: actions (measure/appraise) performed by IMA on the digest list + * @label: label to be used to identify the digest list + * + * This function allocates a new digest list item, which contains the buffer, + * size, actions performed by IMA and a label. Each digest list item is + * associated to a digest item representing the digest of the digest list. + * + * This function prevents the same digest list to be added multiple times by + * searching its digest in the hash table for the COMPACT_DIGEST_LIST type. + * + * The passed buffer is copied in a new memory area, to avoid to reference + * memory that could be freed by the caller. + * + * If allocation of a new digest list and the associated buffer was successful, + * its digest is added to the hash table for the COMPACT_DIGEST_LIST type. + * + * Returns the digest item associated to the digest list item on success, an + * error pointer otherwise. + */ +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, + loff_t size, u8 *buf, u8 actions, + const char *label) +{ + struct digest_item *d; + struct digest_list_item *digest_list = NULL; + int digest_len = hash_digest_size[algo]; + + /* Search the digest of the digest list. */ + d = digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, NULL); + if (d) { + print_hex_dump(KERN_ERR, "digest list already uploaded: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-EEXIST); + } + + /* Allocate a new digest list. */ + if (!should_fail_digest_lists()) + digest_list = kzalloc(sizeof(*digest_list), GFP_KERNEL); + if (!digest_list) { + print_hex_dump(KERN_ERR, "digest list allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-ENOMEM); + } + + digest_list->size = size; + if (!should_fail_digest_lists()) + digest_list->buf = kmemdup(buf, size, GFP_KERNEL); + if (!digest_list->buf) { + print_hex_dump(KERN_ERR, "digest list allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + kfree(digest_list); + return ERR_PTR(-ENOMEM); + } + + digest_list->actions = actions; + memcpy(digest_list->digest, digest, hash_digest_size[algo]); + digest_list->algo = algo; + digest_list->label = label; + + /* Add the digest of the digest list to the hash table. */ + d = digest_add(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); + if (IS_ERR(d)) { + kfree(digest_list->buf); + kfree(digest_list); + } + + return d; +} + +/** + * digest_list_del - delete an existing digest list + * @digest: digest of the digest list in binary form + * @algo: digest algorithm + * @actions: actions (measure/appraise) performed by IMA on the digest list + * @digest_list: digest list to delete + * + * This function searches the digest of the digest list in the hash table for + * the COMPACT_DIGEST_LIST type. If it is found, this function frees the + * buffer and the digest list item allocated in digest_list_add(). + * + * This function prevents the imbalance of digests (references left after + * delete) by ensuring that only digest lists that were previously added can be + * deleted. + * + * Returns NULL on successful deletion, an error pointer otherwise. + */ +struct digest_item *digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, + struct digest_list_item *digest_list) +{ + struct digest_item *d; + + /* Delete the digest item associated to the digest list. */ + d = digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); + if (IS_ERR(d)) + return d; + + /* + * Free the buffer and the digest list item allocated when the digest + * list was added. + */ + kfree(digest_list->buf); + kfree(digest_list); + return NULL; +} From patchwork Fri Jun 25 16:56:08 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345653 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.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,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 EFDA4C49EAF for ; Fri, 25 Jun 2021 16:58:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D31636193F for ; Fri, 25 Jun 2021 16:58:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230092AbhFYRAV (ORCPT ); Fri, 25 Jun 2021 13:00:21 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3313 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229653AbhFYRAU (ORCPT ); Fri, 25 Jun 2021 13:00:20 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNGW44gnz6G8kB; Sat, 26 Jun 2021 00:47:47 +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.2176.2; Fri, 25 Jun 2021 18:57:56 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 06/12] digest_lists: Parser Date: Fri, 25 Jun 2021 18:56:08 +0200 Message-ID: <20210625165614.2284243-7-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces the necessary functions to parse a digest list and to execute the requested operation. The main function is digest_list_parse(), which coordinates the various steps required to add or delete a digest list, and has the logic to roll back when one of the steps fails. A more detailed description about the steps can be found in Documentation/security/digest_lists.rst Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 33 +++ MAINTAINERS | 1 + security/integrity/digest_lists/Makefile | 2 +- .../integrity/digest_lists/digest_lists.h | 3 + security/integrity/digest_lists/parser.c | 270 ++++++++++++++++++ 5 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 security/integrity/digest_lists/parser.c diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 8f1d15a37dbd..04ea4b3790e0 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -611,3 +611,36 @@ hash table for the ``COMPACT_DIGEST_LIST`` type. Deletion can be done only if the digest list is found. ``digest_list_del()`` then deletes the ``digest_list_item``, the special ``digest_list_item_ref`` and the ``digest_item``. + + +Parser +------ + +This section introduces the necessary functions to parse a digest list and +to execute the requested operation. + +The main function is ``digest_list_parse()``, which coordinates the +various steps required to add or delete a digest list, and has the logic +to roll back when one of the steps fails. + +#. Calls ``digest_list_validate()`` to validate the passed buffer + containing the digest list to ensure that the format is correct. + +#. Calls ``get_digest_list()`` to create a new ``digest_list_item`` for the + add operation, or to retrieve the existing one for the delete operation. + ``get_digest_list()`` refuses to add digest lists that were previously + added and to delete digest lists that weren't previously added. Also, + ``get_digest_list()`` refuses to delete digest lists that are not + processed in the same way as when they were added (it would guarantee + that also deletion is notified to remote verifiers). + +#. Calls ``_digest_list_parse()`` which takes the created/retrieved + ``digest_list_item`` and adds or delete the digests included in the + digest list. + +#. If an error occurred, performs a rollback to the previous state, by + calling ``_digest_list_parse()`` with the opposite operation and the + buffer size at the time the error occurred. + +#. ``digest_list_parse()`` deletes the ``digest_list_item`` on unsuccessful + add or successful delete. diff --git a/MAINTAINERS b/MAINTAINERS index a9eb52e65b12..31d280acf5fb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8390,6 +8390,7 @@ F: Documentation/security/digest_lists.rst F: include/linux/digest_list.h F: security/integrity/digest_lists/digest_list.h F: security/integrity/digest_lists/methods.c +F: security/integrity/digest_lists/parser.c F: uapi/linux/digest_lists.h HUAWEI ETHERNET DRIVER diff --git a/security/integrity/digest_lists/Makefile b/security/integrity/digest_lists/Makefile index 0ba66ab2e260..86cca5bb7824 100644 --- a/security/integrity/digest_lists/Makefile +++ b/security/integrity/digest_lists/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGEST_LISTS) += digest_lists.o -digest_lists-y := methods.o +digest_lists-y := methods.o parser.o diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h index aadce9ca8f5f..442ab116a6a5 100644 --- a/security/integrity/digest_lists/digest_lists.h +++ b/security/integrity/digest_lists/digest_lists.h @@ -148,4 +148,7 @@ struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, const char *label); struct digest_item *digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, struct digest_list_item *digest_list); + +int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, const char *label); #endif /*__DIGEST_LISTS_INTERNAL_H*/ diff --git a/security/integrity/digest_lists/parser.c b/security/integrity/digest_lists/parser.c new file mode 100644 index 000000000000..7ed6765a9dc4 --- /dev/null +++ b/security/integrity/digest_lists/parser.c @@ -0,0 +1,270 @@ +// 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: parser.c + * Functions to parse digest lists. + */ + +#include +#include + +#include "digest_lists.h" +#include "../integrity.h" + +/** + * digest_list_validate - validate format of digest list + * @size: buffer size + * @buf: buffer containing the digest list + * + * This function validates the format of the passed digest list. + * + * Returns 0 if the digest list was successfully validated, -EINVAL otherwise. + */ +static int digest_list_validate(loff_t size, void *buf) +{ + void *bufp = buf, *bufendp = buf + size; + struct compact_list_hdr *hdr; + size_t digest_len; + + while (bufp < bufendp) { + if (bufp + sizeof(*hdr) > bufendp) { + pr_err("invalid data\n"); + return -EINVAL; + } + + hdr = bufp; + + if (hdr->version != 1) { + pr_err("unsupported version\n"); + return -EINVAL; + } + + hdr->type = le16_to_cpu(hdr->type); + hdr->modifiers = le16_to_cpu(hdr->modifiers); + hdr->algo = le16_to_cpu(hdr->algo); + hdr->count = le32_to_cpu(hdr->count); + hdr->datalen = le32_to_cpu(hdr->datalen); + + if (hdr->algo >= HASH_ALGO__LAST) { + pr_err("invalid hash algorithm\n"); + return -EINVAL; + } + + digest_len = hash_digest_size[hdr->algo]; + + if (hdr->type >= COMPACT__LAST || + hdr->type == COMPACT_DIGEST_LIST) { + pr_err("invalid type %d\n", hdr->type); + return -EINVAL; + } + + bufp += sizeof(*hdr); + + if (hdr->datalen != hdr->count * digest_len || + bufp + hdr->datalen > bufendp) { + pr_err("invalid data\n"); + return -EINVAL; + } + + bufp += hdr->count * digest_len; + } + + return 0; +} + +/** + * _digest_list_parse - parse digest list and add/delete digests + * @size: buffer size + * @buf: buffer containing the digest list + * @op: operation to be performed + * @digest_list: digest list digests being added/deleted belong to + * + * This function parses the digest list and adds or delete the digests in the + * found digest blocks. + * + * Returns the buffer size if all digests were successfully added or deleted, + * the size of the already parsed buffer on error. + */ +static int _digest_list_parse(loff_t size, void *buf, enum ops op, + struct digest_list_item *digest_list) +{ + void *bufp = buf, *bufendp = buf + size; + struct compact_list_hdr *hdr; + struct digest_item *d; + size_t digest_len; + int i; + + while (bufp < bufendp) { + if (bufp + sizeof(*hdr) > bufendp) + break; + + hdr = bufp; + bufp += sizeof(*hdr); + + digest_len = hash_digest_size[hdr->algo]; + + for (i = 0; i < hdr->count && bufp + digest_len <= bufendp; + i++, bufp += digest_len) { + switch (op) { + case DIGEST_LIST_ADD: + d = digest_add(bufp, hdr->algo, hdr->type, + digest_list, bufp - buf, + (void *)hdr - buf); + break; + case DIGEST_LIST_DEL: + d = digest_del(bufp, hdr->algo, hdr->type, + digest_list, bufp - buf, + (void *)hdr - buf); + break; + default: + break; + } + + if (IS_ERR(d)) { + pr_err("failed to %s a digest from %s\n", + (op == DIGEST_LIST_ADD) ? + "add" : "delete", digest_list->label); + goto out; + } + } + } +out: + return bufp - buf; +} + +/** + * get_digest_list - get the digest list extracted digests will be associated to + * @size: buffer size + * @buf: buffer containing the digest list + * @op: digest list operation + * @actions: actions performed on the digest list being processed + * @digest: digest of the digest list + * @algo: digest algorithm + * @label: label to identify the digest list (e.g. file name) + * + * This function retrieves the digest list item for the passed digest and + * algorithm. If it is not found at addition time, this function creates a new + * one. + * + * This function also ensures that the actions done at the time of deletion + * match the actions done at the time of addition (it would guarantee that also + * deletion is notified to remote verifiers). + * + * Returns the retrieved/created digest list item on success, an error pointer + * otherwise. + */ +static struct digest_list_item *get_digest_list(loff_t size, void *buf, + enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, + const char *label) +{ + struct digest_item *d; + struct digest_list_item *digest_list; + int digest_len = hash_digest_size[algo]; + + switch (op) { + case DIGEST_LIST_ADD: + /* Add digest list to be associated to each digest. */ + d = digest_list_add(digest, algo, size, buf, actions, label); + if (IS_ERR(d)) + return (void *)d; + + digest_list = d->refs[0].digest_list; + break; + case DIGEST_LIST_DEL: + /* Lookup digest list to delete the references. */ + d = digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, + NULL); + if (!d) { + print_hex_dump(KERN_ERR, + "digest list digest not found: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-ENOENT); + } + + digest_list = d->refs[0].digest_list; + + /* Reject deletion if actions on delete differ from add. */ + if (digest_list->actions != actions) { + pr_err("actions mismatch, add: %d, del: %d\n", + digest_list->actions, actions); + return ERR_PTR(-EPERM); + } + + break; + default: + return ERR_PTR(-EINVAL); + } + + return digest_list; +} + +/** + * digest_list_parse - parse a digest list + * @size: buffer size + * @buf: buffer containing the digest list + * @op: digest list operation + * @actions: actions performed on the digest list being processed + * @digest: digest of the digest list + * @algo: digest algorithm + * @label: label to identify the digest list (e.g. file name) + * + * This function parses the passed digest list and executed the requested + * operation. If the operation cannot be successfully executed, this function + * performs a rollback to the previous state. + * + * Returns the buffer size on success, a negative value otherwise. + */ +int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, const char *label) +{ + struct digest_item *d; + struct digest_list_item *digest_list; + enum ops rollback_op = (op == DIGEST_LIST_ADD) ? + DIGEST_LIST_DEL : DIGEST_LIST_ADD; + int ret, rollback_size; + + ret = digest_list_validate(size, buf); + if (ret < 0) + return ret; + + digest_list = get_digest_list(size, buf, op, actions, digest, algo, + label); + if (IS_ERR(digest_list)) + return PTR_ERR(digest_list); + + ret = _digest_list_parse(size, buf, op, digest_list); + if (ret < 0) + goto out; + + if (ret != size) { + rollback_size = ret; + + ret = _digest_list_parse(rollback_size, buf, rollback_op, + digest_list); + if (ret != rollback_size) + pr_err("rollback failed\n"); + + ret = -EINVAL; + } +out: + /* Delete digest list on unsuccessful add or successful delete. */ + if ((op == DIGEST_LIST_ADD && ret < 0) || + (op == DIGEST_LIST_DEL && ret == size)) { + d = digest_list_del(digest, algo, actions, digest_list); + if (IS_ERR(d)) + return PTR_ERR(d); + } + + return ret; +} From patchwork Fri Jun 25 16:56:09 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345655 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.8 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,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 C43C4C49EBA for ; Fri, 25 Jun 2021 16:58:02 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id AE50861981 for ; Fri, 25 Jun 2021 16:58:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230112AbhFYRAV (ORCPT ); Fri, 25 Jun 2021 13:00:21 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3314 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230018AbhFYRAU (ORCPT ); Fri, 25 Jun 2021 13:00:20 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNBc3mcYz6L4vj; Sat, 26 Jun 2021 00:44: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.2176.2; Fri, 25 Jun 2021 18:57:57 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 07/12] digest_lists: Interfaces - digest_list_add, digest_list_del Date: Fri, 25 Jun 2021 18:56:09 +0200 Message-ID: <20210625165614.2284243-8-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces /integrity/digest_lists/digest_list_add, which can be used to upload a digest list and add the digests to the hash table; passed data are interpreted as file path if the first byte is / or as the digest list itself otherwise. ima_measure_critical_data() is called to calculate the digest of the digest list and eventually, if an appropriate rule is set in the IMA policy, to measure it. It also introduces /integrity/digest_lists/digest_list_del, which can be used to upload a digest list and delete the digests from the hash table; data are interpreted in the same way as described for digest_list_add. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 28 +++ MAINTAINERS | 1 + include/linux/kernel_read_file.h | 1 + security/integrity/digest_lists/Makefile | 2 +- .../integrity/digest_lists/digest_lists.h | 3 +- security/integrity/digest_lists/fs.c | 210 ++++++++++++++++++ security/integrity/integrity.h | 4 + 7 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 security/integrity/digest_lists/fs.c diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 04ea4b3790e0..85a34a5ad7ce 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -644,3 +644,31 @@ to roll back when one of the steps fails. #. ``digest_list_parse()`` deletes the ``digest_list_item`` on unsuccessful add or successful delete. + + +Interfaces +---------- + +This section introduces the interfaces in +``/integrity/digest_lists`` necessary to interact with Huawei +Digest Lists. + + +``digest_list_add`` +~~~~~~~~~~~~~~~~~~~ + +``digest_list_add`` can be used to upload a digest list and add the digests +to the hash table; passed data are interpreted as file path if the first +byte is ``/`` or as the digest list itself otherwise. + +``ima_measure_critical_data()`` is called to calculate the digest of the +digest list and eventually, if an appropriate rule is set in the IMA +policy, to measure it. + + +``digest_list_del`` +~~~~~~~~~~~~~~~~~~~ + +``digest_list_del`` can be used to upload a digest list and delete the +digests from the hash table; data are interpreted in the same way as +described for ``digest_list_add``. diff --git a/MAINTAINERS b/MAINTAINERS index 31d280acf5fb..c86b410f2c2c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8389,6 +8389,7 @@ T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/digest_lists.rst F: include/linux/digest_list.h F: security/integrity/digest_lists/digest_list.h +F: security/integrity/digest_lists/fs.c F: security/integrity/digest_lists/methods.c F: security/integrity/digest_lists/parser.c F: uapi/linux/digest_lists.h diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 575ffa1031d3..636ecdfdc616 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -14,6 +14,7 @@ id(KEXEC_INITRAMFS, kexec-initramfs) \ id(POLICY, security-policy) \ id(X509_CERTIFICATE, x509-certificate) \ + id(DIGEST_LIST, digest-list) \ id(MAX_ID, ) #define __fid_enumify(ENUM, dummy) READING_ ## ENUM, diff --git a/security/integrity/digest_lists/Makefile b/security/integrity/digest_lists/Makefile index 86cca5bb7824..00ca06d4bdfd 100644 --- a/security/integrity/digest_lists/Makefile +++ b/security/integrity/digest_lists/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGEST_LISTS) += digest_lists.o -digest_lists-y := methods.o parser.o +digest_lists-y := methods.o parser.o fs.o diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h index 442ab116a6a5..4b74995848dc 100644 --- a/security/integrity/digest_lists/digest_lists.h +++ b/security/integrity/digest_lists/digest_lists.h @@ -28,7 +28,8 @@ #include #include -#define MAX_DIGEST_SIZE 64 +#include "../integrity.h" + #define HASH_BITS 10 #define MEASURE_HTABLE_SIZE (1 << HASH_BITS) diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c new file mode 100644 index 000000000000..b407c5f7b659 --- /dev/null +++ b/security/integrity/digest_lists/fs.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: fs.c + * Functions for the interfaces exposed in securityfs. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "digest_lists.h" + +static struct dentry *digest_lists_dir; +static struct dentry *digest_list_add_dentry; +static struct dentry *digest_list_del_dentry; + +static ssize_t digest_list_read(char *path, enum ops op) +{ + void *data = NULL; + char *datap; + size_t size; + u8 actions = 0; + bool measured = false; + struct file *file; + u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 }; + enum hash_algo algo = HASH_ALGO__LAST; + int rc, pathlen = strlen(path); + + /* remove \n */ + datap = path; + strsep(&datap, "\n"); + + file = filp_open(path, O_RDONLY, 0); + if (IS_ERR(file)) { + pr_err("unable to open file: %s (%ld)", path, PTR_ERR(file)); + return PTR_ERR(file); + } + + rc = kernel_read_file(file, 0, &data, INT_MAX, NULL, + READING_DIGEST_LIST); + if (rc < 0) { + pr_err("unable to read file: %s (%d)", path, rc); + goto out; + } + + size = rc; + + ima_measure_critical_data("digest_lists", "file_upload", data, size, + false, digest, &algo, &measured); + if (algo == HASH_ALGO__LAST) { + rc = -EINVAL; + goto out_vfree; + } + + if (measured) + actions |= COMPACT_ACTION_IMA_MEASURED; + + rc = digest_list_parse(size, data, op, actions, digest, algo, ""); + if (rc < 0) + pr_err("unable to upload digest list %s (%d)\n", path, rc); +out_vfree: + vfree(data); +out: + fput(file); + + if (rc < 0) + return rc; + + return pathlen; +} + +static ssize_t digest_list_write(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + char *data; + ssize_t result; + enum ops op = DIGEST_LIST_ADD; + struct dentry *dentry = file_dentry(file); + u8 digest[IMA_MAX_DIGEST_SIZE]; + enum hash_algo algo = HASH_ALGO__LAST; + u8 actions = 0; + bool measured = false; + + /* No partial writes. */ + result = -EINVAL; + if (*ppos != 0) + goto out; + + result = -EFBIG; + if (datalen > 64 * 1024 * 1024 - 1) + goto out; + + data = memdup_user_nul(buf, datalen); + if (IS_ERR(data)) { + result = PTR_ERR(data); + goto out; + } + + if (dentry == digest_list_del_dentry) + op = DIGEST_LIST_DEL; + + result = -EPERM; + + if (data[0] == '/') { + result = digest_list_read(data, op); + } else { + ima_measure_critical_data("digest_lists", "buffer_upload", data, + datalen, false, digest, &algo, + &measured); + if (algo == HASH_ALGO__LAST) { + pr_err("failed to calculate buffer digest\n"); + result = -EINVAL; + goto out_kfree; + } + + if (measured) + actions |= COMPACT_ACTION_IMA_MEASURED; + + result = digest_list_parse(datalen, data, op, actions, digest, + algo, ""); + if (result != datalen) { + pr_err("unable to upload generated digest list\n"); + result = -EINVAL; + } + } +out_kfree: + kfree(data); +out: + return result; +} + +static unsigned long flags; + +/* + * digest_list_open: sequentialize access to the add/del files + */ +static int digest_list_open(struct inode *inode, struct file *filp) +{ + if ((filp->f_flags & O_ACCMODE) != O_WRONLY) + return -EACCES; + + if (test_and_set_bit(0, &flags)) + return -EBUSY; + + return 0; +} + +/* + * digest_list_release - release the add/del files + */ +static int digest_list_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &flags); + return 0; +} + +static const struct file_operations digest_list_upload_ops = { + .open = digest_list_open, + .write = digest_list_write, + .read = seq_read, + .release = digest_list_release, + .llseek = generic_file_llseek, +}; + +int __init digest_lists_fs_init(void) +{ + digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir); + if (IS_ERR(digest_lists_dir)) + return -1; + + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, + digest_lists_dir, NULL, + &digest_list_upload_ops); + if (IS_ERR(digest_list_add_dentry)) + goto out; + + digest_list_del_dentry = securityfs_create_file("digest_list_del", 0200, + digest_lists_dir, NULL, + &digest_list_upload_ops); + if (IS_ERR(digest_list_del_dentry)) + goto out; + + return 0; +out: + securityfs_remove(digest_list_del_dentry); + securityfs_remove(digest_list_add_dentry); + securityfs_remove(digest_lists_dir); + return -1; +} + +late_initcall(digest_lists_fs_init); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 547425c20e11..ac45e1599c2d 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -6,6 +6,9 @@ * Mimi Zohar */ +#ifndef __INTEGRITY_H +#define __INTEGRITY_H + #ifdef pr_fmt #undef pr_fmt #endif @@ -283,3 +286,4 @@ static inline void __init add_to_platform_keyring(const char *source, { } #endif +#endif /*__INTEGRITY_H*/ From patchwork Fri Jun 25 16:56:10 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345657 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.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,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 3FA73C49EC8 for ; Fri, 25 Jun 2021 16:58:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 0B66F6194C for ; Fri, 25 Jun 2021 16:58:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230231AbhFYRAY (ORCPT ); Fri, 25 Jun 2021 13:00:24 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3315 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230031AbhFYRAV (ORCPT ); Fri, 25 Jun 2021 13:00:21 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNGX5wmGz6G8gh; Sat, 26 Jun 2021 00:47:48 +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.2176.2; Fri, 25 Jun 2021 18:57:58 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 08/12] digest_lists: Interfaces - digest_lists_loaded Date: Fri, 25 Jun 2021 18:56:10 +0200 Message-ID: <20210625165614.2284243-9-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces the digest_lists_loaded directory in /integrity/digest_lists. It contains two files for each loaded digest list: one shows the digest list in binary format, and the other (with .ascii prefix) shows the digest list in ASCII format. Files are added and removed at the same time digest lists are added and removed. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 11 + security/integrity/digest_lists/fs.c | 298 +++++++++++++++++++++++- 2 files changed, 305 insertions(+), 4 deletions(-) diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 85a34a5ad7ce..8f245fae6825 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -672,3 +672,14 @@ policy, to measure it. ``digest_list_del`` can be used to upload a digest list and delete the digests from the hash table; data are interpreted in the same way as described for ``digest_list_add``. + + +``digest_lists_loaded`` +~~~~~~~~~~~~~~~~~~~~~~~ + +``digest_lists_loaded`` is a directory containing two files for each +loaded digest list: one shows the digest list in binary format, and the +other (with .ascii prefix) shows the digest list in ASCII format. + +Files are added and removed at the same time digest lists are added and +removed. diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c index b407c5f7b659..f665ef063df7 100644 --- a/security/integrity/digest_lists/fs.c +++ b/security/integrity/digest_lists/fs.c @@ -29,9 +29,250 @@ #include "digest_lists.h" +#define HDR_ASCII_FMT \ + "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n" + static struct dentry *digest_lists_dir; +static struct dentry *digest_lists_loaded_dir; static struct dentry *digest_list_add_dentry; static struct dentry *digest_list_del_dentry; +char digest_label[NAME_MAX + 1]; + +static int parse_digest_list_filename(const char *digest_list_filename, + u8 *digest, enum hash_algo *algo) +{ + int i; + + for (i = 0; i < HASH_ALGO__LAST; i++) + if (!strncmp(digest_list_filename, hash_algo_name[i], + strlen(hash_algo_name[i]))) + break; + + if (i == HASH_ALGO__LAST) + return -ENOENT; + + *algo = i; + return hex2bin(digest, strchr(digest_list_filename, '-') + 1, + hash_digest_size[*algo]); +} + +/* returns pointer to hlist_node */ +static void *digest_list_start(struct seq_file *m, loff_t *pos) +{ + struct digest_item *d; + u8 digest[IMA_MAX_DIGEST_SIZE]; + enum hash_algo algo; + struct compact_list_hdr *hdr; + u32 count = 0; + void *buf, *bufp, *bufendp; + int ret; + + ret = parse_digest_list_filename(file_dentry(m->file)->d_name.name, + digest, &algo); + if (ret < 0) + return NULL; + + d = digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, NULL); + if (!d) + return NULL; + + bufp = buf = d->refs[0].digest_list->buf; + bufendp = bufp + d->refs[0].digest_list->size; + + while (bufp < bufendp) { + hdr = (struct compact_list_hdr *)bufp; + count += hdr->count; + bufp += sizeof(*hdr) + hdr->datalen; + } + + return *pos <= count ? d : NULL; +} + +static void *digest_list_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct digest_item *d = (struct digest_item *)v; + struct compact_list_hdr *hdr; + u32 count = 0; + void *buf = d->refs[0].digest_list->buf; + void *bufp = buf; + void *bufendp = bufp + d->refs[0].digest_list->size; + + (*pos)++; + + while (bufp < bufendp) { + hdr = (struct compact_list_hdr *)bufp; + count += hdr->count; + bufp += sizeof(*hdr) + hdr->datalen; + } + + return *pos <= count ? d : NULL; +} + +static void digest_list_stop(struct seq_file *m, void *v) +{ +} + +static void print_digest(struct seq_file *m, u8 *digest, u32 size) +{ + u32 i; + + for (i = 0; i < size; i++) + seq_printf(m, "%02x", *(digest + i)); +} + +static void digest_list_putc(struct seq_file *m, void *data, int datalen) +{ + while (datalen--) + seq_putc(m, *(char *)data++); +} + +static int digest_list_show_common(struct seq_file *m, void *v, bool binary) +{ + struct digest_item *d = (struct digest_item *)v; + struct compact_list_hdr *hdr; + u32 count = 0; + void *buf = d->refs[0].digest_list->buf; + void *bufp = buf; + void *bufendp = bufp + d->refs[0].digest_list->size; + + while (bufp < bufendp) { + hdr = (struct compact_list_hdr *)bufp; + + if (m->index >= count + hdr->count) { + bufp += sizeof(*hdr) + hdr->datalen; + count += hdr->count; + continue; + } + + if (count == m->index) { + if (binary) + digest_list_putc(m, (void *)hdr, sizeof(*hdr)); + else + seq_printf(m, HDR_ASCII_FMT, + d->refs[0].digest_list->actions, + hdr->version, + hash_algo_name[hdr->algo], hdr->type, + hdr->modifiers, hdr->count, + hdr->datalen); + } + + if (binary) { + digest_list_putc(m, bufp + sizeof(*hdr) + + (m->index - count) * + hash_digest_size[hdr->algo], + hash_digest_size[hdr->algo]); + } else { + print_digest(m, bufp + sizeof(*hdr) + + (m->index - count) * + hash_digest_size[hdr->algo], + hash_digest_size[hdr->algo]); + seq_puts(m, "\n"); + } + + break; + } + + return 0; +} + +static int digest_list_show(struct seq_file *m, void *v) +{ + return digest_list_show_common(m, v, true); +} + +static int digest_list_ascii_show(struct seq_file *m, void *v) +{ + return digest_list_show_common(m, v, false); +} + +static const struct seq_operations digest_list_seqops = { + .start = digest_list_start, + .next = digest_list_next, + .stop = digest_list_stop, + .show = digest_list_show +}; + +static int digest_list_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &digest_list_seqops); +} + +static const struct file_operations digest_list_ops = { + .open = digest_list_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static const struct seq_operations digest_list_ascii_seqops = { + .start = digest_list_start, + .next = digest_list_next, + .stop = digest_list_stop, + .show = digest_list_ascii_show +}; + +static int digest_list_ascii_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &digest_list_ascii_seqops); +} + +static const struct file_operations digest_list_ascii_ops = { + .open = digest_list_ascii_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int digest_list_get_secfs_files(char *label, u8 *digest, + enum hash_algo algo, enum ops op, + struct dentry **dentry, + struct dentry **dentry_ascii) +{ + char digest_list_filename[NAME_MAX + 1] = { 0 }; + u8 digest_str[IMA_MAX_DIGEST_SIZE * 2 + 1] = { 0 }; + char *dot, *label_ptr; + + label_ptr = strrchr(label, '/'); + if (label_ptr) + label = label_ptr + 1; + + bin2hex(digest_str, digest, hash_digest_size[algo]); + + snprintf(digest_list_filename, sizeof(digest_list_filename), + "%s-%s-%s.ascii", hash_algo_name[algo], digest_str, label); + + dot = strrchr(digest_list_filename, '.'); + + *dot = '\0'; + if (op == DIGEST_LIST_ADD) + *dentry = securityfs_create_file(digest_list_filename, 0440, + digest_lists_loaded_dir, NULL, + &digest_list_ops); + else + *dentry = lookup_positive_unlocked(digest_list_filename, + digest_lists_loaded_dir, + strlen(digest_list_filename)); + *dot = '.'; + if (IS_ERR(*dentry)) + return PTR_ERR(*dentry); + + if (op == DIGEST_LIST_ADD) + *dentry_ascii = securityfs_create_file(digest_list_filename, + 0440, digest_lists_loaded_dir, + NULL, &digest_list_ascii_ops); + else + *dentry_ascii = lookup_positive_unlocked(digest_list_filename, + digest_lists_loaded_dir, + strlen(digest_list_filename)); + if (IS_ERR(*dentry_ascii)) { + if (op == DIGEST_LIST_ADD) + securityfs_remove(*dentry); + + return PTR_ERR(*dentry_ascii); + } + + return 0; +} static ssize_t digest_list_read(char *path, enum ops op) { @@ -43,6 +284,7 @@ static ssize_t digest_list_read(char *path, enum ops op) struct file *file; u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 }; enum hash_algo algo = HASH_ALGO__LAST; + struct dentry *dentry, *dentry_ascii; int rc, pathlen = strlen(path); /* remove \n */ @@ -74,9 +316,27 @@ static ssize_t digest_list_read(char *path, enum ops op) if (measured) actions |= COMPACT_ACTION_IMA_MEASURED; - rc = digest_list_parse(size, data, op, actions, digest, algo, ""); + rc = digest_list_get_secfs_files(path, digest, algo, op, &dentry, + &dentry_ascii); + if (rc < 0) + goto out_vfree; + + rc = digest_list_parse(size, data, op, actions, digest, algo, + dentry->d_name.name); if (rc < 0) pr_err("unable to upload digest list %s (%d)\n", path, rc); + + if ((rc < 0 && op == DIGEST_LIST_ADD) || + (rc == size && op == DIGEST_LIST_DEL)) { + /* Release reference taken in digest_list_get_secfs_files(). */ + if (op == DIGEST_LIST_DEL) { + dput(dentry); + dput(dentry_ascii); + } + + securityfs_remove(dentry); + securityfs_remove(dentry_ascii); + } out_vfree: vfree(data); out: @@ -94,7 +354,7 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf, char *data; ssize_t result; enum ops op = DIGEST_LIST_ADD; - struct dentry *dentry = file_dentry(file); + struct dentry *dentry = file_dentry(file), *dentry_ascii; u8 digest[IMA_MAX_DIGEST_SIZE]; enum hash_algo algo = HASH_ALGO__LAST; u8 actions = 0; @@ -135,12 +395,36 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf, if (measured) actions |= COMPACT_ACTION_IMA_MEASURED; + result = digest_list_get_secfs_files(digest_label[0] != '\0' ? + digest_label : "parser", + digest, algo, op, + &dentry, &dentry_ascii); + if (result < 0) + goto out_kfree; + + memset(digest_label, 0, sizeof(digest_label)); + result = digest_list_parse(datalen, data, op, actions, digest, - algo, ""); + algo, dentry->d_name.name); if (result != datalen) { pr_err("unable to upload generated digest list\n"); result = -EINVAL; } + + if ((result < 0 && op == DIGEST_LIST_ADD) || + (result == datalen && op == DIGEST_LIST_DEL)) { + /* + * Release reference taken in + * digest_list_get_secfs_files(). + */ + if (op == DIGEST_LIST_DEL) { + dput(dentry); + dput(dentry_ascii); + } + + securityfs_remove(dentry); + securityfs_remove(dentry_ascii); + } } out_kfree: kfree(data); @@ -181,12 +465,17 @@ static const struct file_operations digest_list_upload_ops = { .llseek = generic_file_llseek, }; -int __init digest_lists_fs_init(void) +static int __init digest_lists_fs_init(void) { digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir); if (IS_ERR(digest_lists_dir)) return -1; + digest_lists_loaded_dir = securityfs_create_dir("digest_lists_loaded", + digest_lists_dir); + if (IS_ERR(digest_lists_loaded_dir)) + goto out; + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, digest_lists_dir, NULL, &digest_list_upload_ops); @@ -203,6 +492,7 @@ int __init digest_lists_fs_init(void) out: securityfs_remove(digest_list_del_dentry); securityfs_remove(digest_list_add_dentry); + securityfs_remove(digest_lists_loaded_dir); securityfs_remove(digest_lists_dir); return -1; } From patchwork Fri Jun 25 16:56:11 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345661 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.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,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 C8B4BC48BC2 for ; Fri, 25 Jun 2021 16:58:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B9EC261965 for ; Fri, 25 Jun 2021 16:58:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230193AbhFYRAX (ORCPT ); Fri, 25 Jun 2021 13:00:23 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3316 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230044AbhFYRAV (ORCPT ); Fri, 25 Jun 2021 13:00:21 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNGY20pzz6G8m4; Sat, 26 Jun 2021 00:47:49 +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.2176.2; Fri, 25 Jun 2021 18:57:58 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 09/12] digest_lists: Interfaces - digest_label Date: Fri, 25 Jun 2021 18:56:11 +0200 Message-ID: <20210625165614.2284243-10-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces the digest_label interface. It can be used to set a label to be applied to the next digest list (buffer) loaded through digest_list_add. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 7 +++++ security/integrity/digest_lists/fs.c | 34 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 8f245fae6825..d83279046a55 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -683,3 +683,10 @@ other (with .ascii prefix) shows the digest list in ASCII format. Files are added and removed at the same time digest lists are added and removed. + + +``digest_label`` +~~~~~~~~~~~~~~~~ + +``digest_label`` can be used to set a label to be applied to the next +digest list (buffer) loaded ``through digest_list_add``. diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c index f665ef063df7..f6e88fac27bc 100644 --- a/security/integrity/digest_lists/fs.c +++ b/security/integrity/digest_lists/fs.c @@ -34,6 +34,7 @@ static struct dentry *digest_lists_dir; static struct dentry *digest_lists_loaded_dir; +static struct dentry *digest_label_dentry; static struct dentry *digest_list_add_dentry; static struct dentry *digest_list_del_dentry; char digest_label[NAME_MAX + 1]; @@ -465,6 +466,32 @@ static const struct file_operations digest_list_upload_ops = { .llseek = generic_file_llseek, }; +/* + * digest_label_write: write label for next uploaded digest list. + */ +static ssize_t digest_label_write(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + int rc; + + if (datalen >= sizeof(digest_label)) + return -EINVAL; + + rc = copy_from_user(digest_label, buf, datalen); + if (rc < 0) + return rc; + + digest_label[datalen] = '\0'; + return datalen; +} + +static const struct file_operations digest_label_ops = { + .open = generic_file_open, + .write = digest_label_write, + .read = seq_read, + .llseek = generic_file_llseek, +}; + static int __init digest_lists_fs_init(void) { digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir); @@ -488,8 +515,15 @@ static int __init digest_lists_fs_init(void) if (IS_ERR(digest_list_del_dentry)) goto out; + digest_label_dentry = securityfs_create_file("digest_label", 0600, + digest_lists_dir, NULL, + &digest_label_ops); + if (IS_ERR(digest_label_dentry)) + goto out; + return 0; out: + securityfs_remove(digest_label_dentry); securityfs_remove(digest_list_del_dentry); securityfs_remove(digest_list_add_dentry); securityfs_remove(digest_lists_loaded_dir); From patchwork Fri Jun 25 16:56:12 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345663 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.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,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 3CC68C49EA7 for ; Fri, 25 Jun 2021 16:59:21 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 218FE6193F for ; Fri, 25 Jun 2021 16:59:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230170AbhFYRBk (ORCPT ); Fri, 25 Jun 2021 13:01:40 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3317 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230011AbhFYRBk (ORCPT ); Fri, 25 Jun 2021 13:01:40 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBND84LGRz6L4sy; Sat, 26 Jun 2021 00:45:44 +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.2176.2; Fri, 25 Jun 2021 18:59:17 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 10/12] digest_lists: Interfaces - digest_query Date: Fri, 25 Jun 2021 18:56:12 +0200 Message-ID: <20210625165614.2284243-11-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces the digest_query interface, which allows to write a query in the format ``-`` and to obtain all digest lists that include that digest. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 7 ++ security/integrity/digest_lists/fs.c | 150 ++++++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index d83279046a55..f3900a6e92f6 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -690,3 +690,10 @@ removed. ``digest_label`` can be used to set a label to be applied to the next digest list (buffer) loaded ``through digest_list_add``. + + +``digest_query`` +~~~~~~~~~~~~~~~~ + +``digest_query``: allows to write a query in the format ``-`` +and to obtain all digest lists that include that digest. diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c index f6e88fac27bc..bdfeb8797760 100644 --- a/security/integrity/digest_lists/fs.c +++ b/security/integrity/digest_lists/fs.c @@ -31,12 +31,17 @@ #define HDR_ASCII_FMT \ "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n" +#define QUERY_RESULT_FMT \ + "%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n" +#define QUERY_RESULT_DIGEST_LIST_FMT "%s (actions: %d): type: %d, size: %lld\n" static struct dentry *digest_lists_dir; static struct dentry *digest_lists_loaded_dir; static struct dentry *digest_label_dentry; +static struct dentry *digest_query_dentry; static struct dentry *digest_list_add_dentry; static struct dentry *digest_list_del_dentry; +char digest_query[CRYPTO_MAX_ALG_NAME + 1 + IMA_MAX_DIGEST_SIZE * 2 + 1]; char digest_label[NAME_MAX + 1]; static int parse_digest_list_filename(const char *digest_list_filename, @@ -224,6 +229,83 @@ static const struct file_operations digest_list_ascii_ops = { .release = seq_release, }; +static void *digest_query_start(struct seq_file *m, loff_t *pos) +{ + struct digest_item *d; + u8 digest[IMA_MAX_DIGEST_SIZE]; + enum hash_algo algo; + loff_t count = 0; + enum compact_types type = 0; + struct digest_list_item_ref *ref; + int ret, refs; + + ret = parse_digest_list_filename(digest_query, digest, &algo); + if (ret < 0) + return NULL; + + for (type = 0; type < COMPACT__LAST; type++) { + d = digest_lookup(digest, algo, type, NULL, NULL); + if (!d) + continue; + + rcu_read_lock(); + for (ref = rcu_dereference(d->refs), refs = 0; + ref != NULL && !digest_list_ref_is_last(ref); + ref++, refs++) + ; + rcu_read_unlock(); + + count += refs; + + if (count > *pos) + break; + } + + if (type == COMPACT__LAST) + return NULL; + + return d->refs ? d->refs + (*pos - (count - refs)) : NULL; +} + +static void *digest_query_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct digest_list_item_ref *refs = (struct digest_list_item_ref *)v; + + refs++; + (*pos)++; + + return (refs->digest_list) ? refs : NULL; +} + +static void digest_query_stop(struct seq_file *m, void *v) +{ +} + +static int digest_query_show(struct seq_file *m, void *v) +{ + struct digest_list_item_ref *refs = (struct digest_list_item_ref *)v; + struct digest_list_item *digest_list = refs->digest_list; + struct compact_list_hdr *hdr; + + if (digest_list_ref_invalidated(refs)) + return 0; + + hdr = get_hdr_ref(refs); + + if (!refs->digest_offset) { + seq_printf(m, QUERY_RESULT_DIGEST_LIST_FMT, digest_list->label, + digest_list->actions, COMPACT_DIGEST_LIST, + digest_list->size); + return 0; + } + + seq_printf(m, QUERY_RESULT_FMT, digest_list->label, + digest_list->actions, hdr->version, + hash_algo_name[hdr->algo], hdr->type, hdr->modifiers, + hdr->count, hdr->datalen); + return 0; +} + static int digest_list_get_secfs_files(char *label, u8 *digest, enum hash_algo algo, enum ops op, struct dentry **dentry, @@ -492,6 +574,67 @@ static const struct file_operations digest_label_ops = { .llseek = generic_file_llseek, }; +static const struct seq_operations digest_query_seqops = { + .start = digest_query_start, + .next = digest_query_next, + .stop = digest_query_stop, + .show = digest_query_show, +}; + +/* + * digest_query_open: open to write a query or read the result. + */ +static int digest_query_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &flags)) + return -EBUSY; + + if (file->f_flags & O_WRONLY) + return 0; + + return seq_open(file, &digest_query_seqops); +} + +/* + * digest_query_open: write digest query (-). + */ +static ssize_t digest_query_write(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + int rc; + + if (datalen >= sizeof(digest_query)) + return -EINVAL; + + rc = copy_from_user(digest_query, buf, datalen); + if (rc < 0) + return rc; + + digest_query[datalen] = '\0'; + return datalen; +} + +/* + * digest_query_release - release the digest_query file + */ +static int digest_query_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &flags); + + if (file->f_flags & O_WRONLY) + return 0; + + return seq_release(inode, file); +} + +static const struct file_operations digest_query_ops = { + .open = digest_query_open, + .write = digest_query_write, + .read = seq_read, + .release = digest_query_release, + .llseek = generic_file_llseek, +}; + static int __init digest_lists_fs_init(void) { digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir); @@ -521,8 +664,15 @@ static int __init digest_lists_fs_init(void) if (IS_ERR(digest_label_dentry)) goto out; + digest_query_dentry = securityfs_create_file("digest_query", 0600, + digest_lists_dir, NULL, + &digest_query_ops); + if (IS_ERR(digest_query_dentry)) + goto out; + return 0; out: + securityfs_remove(digest_query_dentry); securityfs_remove(digest_label_dentry); securityfs_remove(digest_list_del_dentry); securityfs_remove(digest_list_add_dentry); From patchwork Fri Jun 25 16:56:13 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345665 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.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,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 1C9EEC49EB7 for ; Fri, 25 Jun 2021 16:59:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 02E556193F for ; Fri, 25 Jun 2021 16:59:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230252AbhFYRBm (ORCPT ); Fri, 25 Jun 2021 13:01:42 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3318 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230094AbhFYRBl (ORCPT ); Fri, 25 Jun 2021 13:01:41 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBND91GHlz6L52F; Sat, 26 Jun 2021 00:45: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.2176.2; Fri, 25 Jun 2021 18:59:18 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 11/12] digest_lists: Interfaces - digests_count Date: Fri, 25 Jun 2021 18:56:13 +0200 Message-ID: <20210625165614.2284243-12-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces the digests_count interface, which shows the current number of digests stored in the hash table by type. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 7 +++++ security/integrity/digest_lists/fs.c | 35 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index f3900a6e92f6..25b5665bbeaa 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -697,3 +697,10 @@ digest list (buffer) loaded ``through digest_list_add``. ``digest_query``: allows to write a query in the format ``-`` and to obtain all digest lists that include that digest. + + +``digests_count`` +~~~~~~~~~~~~~~~~~ + +``digests_count`` shows the current number of digests stored in the hash +table by type. diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c index bdfeb8797760..37091db50df5 100644 --- a/security/integrity/digest_lists/fs.c +++ b/security/integrity/digest_lists/fs.c @@ -37,6 +37,7 @@ static struct dentry *digest_lists_dir; static struct dentry *digest_lists_loaded_dir; +static struct dentry *digests_count; static struct dentry *digest_label_dentry; static struct dentry *digest_query_dentry; static struct dentry *digest_list_add_dentry; @@ -44,6 +45,33 @@ static struct dentry *digest_list_del_dentry; char digest_query[CRYPTO_MAX_ALG_NAME + 1 + IMA_MAX_DIGEST_SIZE * 2 + 1]; char digest_label[NAME_MAX + 1]; +static char *types_str[COMPACT__LAST] = { + [COMPACT_PARSER] = "Parser", + [COMPACT_FILE] = "File", + [COMPACT_METADATA] = "Metadata", + [COMPACT_DIGEST_LIST] = "Digest list", +}; + +static ssize_t digest_lists_show_htable_len(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[1024]; + ssize_t len = 0; + int i; + + for (i = COMPACT_PARSER; i < COMPACT__LAST; i++) + len += scnprintf(tmpbuf + len, sizeof(tmpbuf) - len, + "%s digests: %li\n", types_str[i], + atomic_long_read(&htable[i].len)); + + return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); +} + +static const struct file_operations htable_len_ops = { + .read = digest_lists_show_htable_len, + .llseek = generic_file_llseek, +}; + static int parse_digest_list_filename(const char *digest_list_filename, u8 *digest, enum hash_algo *algo) { @@ -646,6 +674,12 @@ static int __init digest_lists_fs_init(void) if (IS_ERR(digest_lists_loaded_dir)) goto out; + digests_count = securityfs_create_file("digests_count", 0440, + digest_lists_dir, NULL, + &htable_len_ops); + if (IS_ERR(digests_count)) + goto out; + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, digest_lists_dir, NULL, &digest_list_upload_ops); @@ -676,6 +710,7 @@ static int __init digest_lists_fs_init(void) securityfs_remove(digest_label_dentry); securityfs_remove(digest_list_del_dentry); securityfs_remove(digest_list_add_dentry); + securityfs_remove(digests_count); securityfs_remove(digest_lists_loaded_dir); securityfs_remove(digest_lists_dir); return -1; From patchwork Fri Jun 25 16:56:14 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345667 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.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,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 51EECC49EB7 for ; Fri, 25 Jun 2021 16:59:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 357AC61945 for ; Fri, 25 Jun 2021 16:59:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230418AbhFYRBq (ORCPT ); Fri, 25 Jun 2021 13:01:46 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3319 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230121AbhFYRBm (ORCPT ); Fri, 25 Jun 2021 13:01:42 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNJ53l8Fz6G8gh; Sat, 26 Jun 2021 00:49:09 +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.2176.2; Fri, 25 Jun 2021 18:59:18 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 12/12] digest_lists: Tests Date: Fri, 25 Jun 2021 18:56:14 +0200 Message-ID: <20210625165614.2284243-13-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces a number of tests to ensure that the digest lists feature works as expected: - ``digest_list_add_del_test_file_upload``; - ``digest_list_add_del_test_file_upload_fault``; - ``digest_list_add_del_test_buffer_upload``; - ``digest_list_add_del_test_buffer_upload_fault``; - ``digest_list_fuzzing_test``. The tests are in ``tools/testing/selftests/digest_lists/selftest.c``. A description of the tests can be found in Documentation/security/digest_lists.rst. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 50 + MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/digest_lists/Makefile | 6 + tools/testing/selftests/digest_lists/common.c | 109 ++ tools/testing/selftests/digest_lists/common.h | 37 + tools/testing/selftests/digest_lists/config | 3 + .../testing/selftests/digest_lists/selftest.c | 1169 +++++++++++++++++ 8 files changed, 1376 insertions(+) create mode 100644 tools/testing/selftests/digest_lists/Makefile create mode 100644 tools/testing/selftests/digest_lists/common.c create mode 100644 tools/testing/selftests/digest_lists/common.h create mode 100644 tools/testing/selftests/digest_lists/config create mode 100644 tools/testing/selftests/digest_lists/selftest.c diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 25b5665bbeaa..fe1313f52e2d 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -704,3 +704,53 @@ and to obtain all digest lists that include that digest. ``digests_count`` shows the current number of digests stored in the hash table by type. + + +Testing +======= + +This section introduces a number of tests to ensure that Huawei Digest +Lists works as expected: + +- ``digest_list_add_del_test_file_upload``; +- ``digest_list_add_del_test_file_upload_fault``; +- ``digest_list_add_del_test_buffer_upload``; +- ``digest_list_add_del_test_buffer_upload_fault``; +- ``digest_list_fuzzing_test``. + +The tests are in ``tools/testing/selftests/digest_lists/selftest.c``. + +The first four tests randomly perform add, delete and query of digest +lists. They internally keep track at any time of the digest lists that are +currently uploaded to the kernel. + +Also, digest lists are generated randomly by selecting an arbitrary digest +algorithm and an arbitrary the number of digests. To ensure a good number +of collisions, digests are a sequence of zeros, except for the first four +bytes that are set with a random number within a defined range. + +When a query operation is selected, a digest is chosen by getting another +random number within the same range. Then, the tests count how many times +the digest is found in the internally stored digest lists and in the query +result obtained from the kernel. The tests are successful if the obtained +numbers are the same. + +The ``file_upload`` variant creates a temporary file from a generated +digest list and sends its path to the kernel, so that the file is uploaded. +The ``digest_upload`` variant directly sends the digest list buffer to the +kernel (it will be done by the user space parser after it converts a digest +list not in the compact format). + +The ``fault`` variant performs the test by enabling the ad-hoc fault +injection mechanism in the kernel (accessible through +``/fail_digest_lists``). The fault injection mechanism randomly +injects errors during the addition and deletion of digest lists. When an +error occurs, the rollback mechanism performs the reverse operation until +the point the error occurred, so that the kernel is left in the same state +as when the requested operation began. Since the kernel returns the error +to user space, the tests also know that the operation didn't succeed and +behave accordingly (they also revert the internal state). + +Lastly, the fuzzing test simply sends randomly generated digest lists to +the kernel, to ensure that the parser is robust enough to handle malformed +data. diff --git a/MAINTAINERS b/MAINTAINERS index c86b410f2c2c..359c8ce912e4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8392,6 +8392,7 @@ F: security/integrity/digest_lists/digest_list.h F: security/integrity/digest_lists/fs.c F: security/integrity/digest_lists/methods.c F: security/integrity/digest_lists/parser.c +F: tools/testing/selftests/digest_lists/ F: uapi/linux/digest_lists.h HUAWEI ETHERNET DRIVER diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index bc3299a20338..f5938fe9f3a7 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -8,6 +8,7 @@ TARGETS += clone3 TARGETS += core TARGETS += cpufreq TARGETS += cpu-hotplug +TARGETS += digest_lists TARGETS += drivers/dma-buf TARGETS += efivarfs TARGETS += exec diff --git a/tools/testing/selftests/digest_lists/Makefile b/tools/testing/selftests/digest_lists/Makefile new file mode 100644 index 000000000000..4e88b5677cc6 --- /dev/null +++ b/tools/testing/selftests/digest_lists/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -Wl,-no-as-needed -Wall -ggdb common.c +LDFLAGS += -lcrypto + +TEST_GEN_PROGS := selftest +include ../lib.mk diff --git a/tools/testing/selftests/digest_lists/common.c b/tools/testing/selftests/digest_lists/common.c new file mode 100644 index 000000000000..a1d0706f8c5f --- /dev/null +++ b/tools/testing/selftests/digest_lists/common.c @@ -0,0 +1,109 @@ +// 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: common.c + * Common functions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +int write_buffer(char *path, char *buffer, size_t buffer_len) +{ + ssize_t to_write = buffer_len, written = 0; + int ret = 0, fd; + + fd = open(path, O_WRONLY); + if (fd < 0) + return -errno; + + while (to_write) { + written = write(fd, buffer + buffer_len - to_write, to_write); + if (written <= 0) { + ret = -errno; + break; + } + + to_write -= written; + } + + close(fd); + return ret; +} + +int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, + bool is_char) +{ + ssize_t len = 0, read_len; + int ret = 0, fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + if (alloc) { + *buffer = NULL; + *buffer_len = 0; + } + + while (1) { + if (alloc) { + if (*buffer_len == len) { + *buffer_len += BUFFER_SIZE; + *buffer = realloc(*buffer, *buffer_len + 1); + if (!*buffer) { + ret = -ENOMEM; + goto out; + } + } + } + + read_len = read(fd, *buffer + len, *buffer_len - len); + if (read_len < 0) { + ret = -errno; + goto out; + } + + if (!read_len) + break; + + len += read_len; + } + + *buffer_len = len; + if (is_char) + (*buffer)[(*buffer_len)++] = '\0'; +out: + close(fd); + if (ret < 0) { + if (alloc) { + free(*buffer); + *buffer = NULL; + } + } + + return ret; +} diff --git a/tools/testing/selftests/digest_lists/common.h b/tools/testing/selftests/digest_lists/common.h new file mode 100644 index 000000000000..60c275f42009 --- /dev/null +++ b/tools/testing/selftests/digest_lists/common.h @@ -0,0 +1,37 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: common.h + * Header of common.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 1024 + +int write_buffer(char *path, char *buffer, size_t buffer_len); +int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, + bool is_char); diff --git a/tools/testing/selftests/digest_lists/config b/tools/testing/selftests/digest_lists/config new file mode 100644 index 000000000000..faafc742974c --- /dev/null +++ b/tools/testing/selftests/digest_lists/config @@ -0,0 +1,3 @@ +CONFIG_DIGEST_LISTS=y +CONFIG_FAULT_INJECTION=y +CONFIG_FAULT_INJECTION_DEBUG_FS=y diff --git a/tools/testing/selftests/digest_lists/selftest.c b/tools/testing/selftests/digest_lists/selftest.c new file mode 100644 index 000000000000..6727e1f3f8fd --- /dev/null +++ b/tools/testing/selftests/digest_lists/selftest.c @@ -0,0 +1,1169 @@ +// 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: selftest.c + * Functions to test Huawei Digest Lists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "../kselftest_harness.h" + +#define HDR_ASCII_FMT \ + "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n" +#define QUERY_RESULT_FMT \ + "%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n" +#define QUERY_RESULT_DIGEST_LIST_FMT "%s (actions: %d): type: %d, size: %lld\n" + +enum compact_types { COMPACT_KEY, COMPACT_PARSER, COMPACT_FILE, + COMPACT_METADATA, COMPACT_DIGEST_LIST, COMPACT__LAST }; + +enum compact_modifiers { COMPACT_MOD_IMMUTABLE, COMPACT_MOD__LAST }; + +enum compact_actions { COMPACT_ACTION_IMA_MEASURED, + COMPACT_ACTION_IMA_APPRAISED, + COMPACT_ACTION_IMA_APPRAISED_DIGSIG, + COMPACT_ACTION__LAST }; + +enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST }; + +struct compact_list_hdr { + __u8 version; + __u8 _reserved; + __le16 type; + __le16 modifiers; + __le16 algo; + __le32 count; + __le32 datalen; +} __packed; + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +#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 DIGEST_LIST_PATH_TEMPLATE "/tmp/digest_list.XXXXXX" +#define PARSER_BASENAME "manage_digest_lists" + +#define INTEGRITY_DIR "/sys/kernel/security/integrity" + +#define DIGEST_LIST_DIR INTEGRITY_DIR "/digest_lists" +#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query" +#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_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" + +#define DIGEST_LIST_DEBUGFS_DIR "/sys/kernel/debug/fail_digest_lists" +#define DIGEST_LIST_DEBUGFS_TASK_FILTER DIGEST_LIST_DEBUGFS_DIR "/task-filter" +#define DIGEST_LIST_DEBUGFS_PROBABILITY DIGEST_LIST_DEBUGFS_DIR "/probability" +#define DIGEST_LIST_DEBUGFS_TIMES DIGEST_LIST_DEBUGFS_DIR "/times" +#define DIGEST_LIST_DEBUGFS_VERBOSE DIGEST_LIST_DEBUGFS_DIR "/verbose" +#define PROCFS_SELF_FAULT "/proc/self/make-it-fail" + +#define MAX_LINE_LENGTH 512 +#define LABEL_LEN 32 +#define MAX_DIGEST_COUNT 100 +#define MAX_DIGEST_LISTS 100 +#define MAX_DIGEST_BLOCKS 10 +#define MAX_DIGEST_VALUE 10 +#define MAX_SEARCH_ATTEMPTS 10 +#define NUM_QUERIES 1000 +#define MAX_DIGEST_LIST_SIZE 10000 +#define NUM_ITERATIONS 100000 + +enum upload_types { UPLOAD_FILE, UPLOAD_BUFFER }; + +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, +}; + +struct digest_list_item { + loff_t size; + u8 *buf; + u8 actions; + char digest_str[64 * 2 + 1]; + enum hash_algo algo; + char filename_suffix[6 + 1]; +}; + +static const char hex_asc[] = "0123456789abcdef"; + +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +static inline char *hex_byte_pack(char *buf, unsigned char byte) +{ + *buf++ = hex_asc_hi(byte); + *buf++ = hex_asc_lo(byte); + return buf; +} + +/* 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; +} + +char *_bin2hex(char *dst, const void *src, size_t count) +{ + const unsigned char *_src = src; + + while (count--) + dst = hex_byte_pack(dst, *_src++); + return dst; +} + +u32 num_max_digest_lists = MAX_DIGEST_LISTS; +u32 digest_lists_pos; +struct digest_list_item *digest_lists[MAX_DIGEST_LISTS]; + +enum hash_algo ima_hash_algo = HASH_ALGO__LAST; + +static enum hash_algo get_ima_hash_algo(void) +{ + char *measurement_list, *measurement_list_ptr; + size_t measurement_list_len; + int ret, i = 0; + + if (ima_hash_algo != HASH_ALGO__LAST) + return ima_hash_algo; + + ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list, + &measurement_list_len, true, true); + if (ret < 0) + return ret; + + measurement_list_ptr = measurement_list; + while ((strsep(&measurement_list_ptr, " ")) && i++ < 2) + ; + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (!strncmp(hash_algo_name[i], measurement_list_ptr, + strlen(hash_algo_name[i]))) { + ima_hash_algo = i; + break; + } + } + + free(measurement_list); + return ima_hash_algo; +} + +int calc_digest(u8 *digest, void *data, u64 len, enum hash_algo algo) +{ + EVP_MD_CTX *mdctx; + const EVP_MD *md; + int ret = -EINVAL; + + OpenSSL_add_all_algorithms(); + + md = EVP_get_digestbyname(hash_algo_name[algo]); + if (!md) + goto out; + + mdctx = EVP_MD_CTX_create(); + if (!mdctx) + goto out; + + if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + goto out_mdctx; + + if (EVP_DigestUpdate(mdctx, data, len) != 1) + goto out_mdctx; + + if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1) + goto out_mdctx; + + ret = 0; +out_mdctx: + EVP_MD_CTX_destroy(mdctx); +out: + EVP_cleanup(); + return ret; +} + +int calc_file_digest(u8 *digest, 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 -errno; + + 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 struct digest_list_item *digest_list_generate(void) +{ + struct digest_list_item *digest_list; + struct compact_list_hdr *hdr_array; + u8 *buf_ptr; + u32 num_digest_blocks = 0; + u8 digest[64]; + int ret, i, j; + + digest_list = calloc(1, sizeof(*digest_list)); + if (!digest_list) + return NULL; + + while (!num_digest_blocks) { + ret = getrandom(&num_digest_blocks, + sizeof(num_digest_blocks), 0); + if (ret < 0) + goto out; + + num_digest_blocks = num_digest_blocks % MAX_DIGEST_BLOCKS; + } + + hdr_array = calloc(num_digest_blocks, sizeof(*hdr_array)); + if (!hdr_array) + goto out; + + for (i = 0; i < num_digest_blocks; i++) { + ret = getrandom(&hdr_array[i], sizeof(hdr_array[i]), 0); + if (ret < 0) + goto out; + + hdr_array[i].version = 1; + /* COMPACT_DIGEST_LIST type is not allowed. */ + hdr_array[i].type = hdr_array[i].type % (COMPACT__LAST - 1); + hdr_array[i].modifiers = + hdr_array[i].modifiers % (1 << COMPACT_MOD_IMMUTABLE) + 1; + hdr_array[i].algo = hdr_array[i].algo % HASH_ALGO_RIPE_MD_128; + hdr_array[i].count = hdr_array[i].count % MAX_DIGEST_COUNT; + + while (!hdr_array[i].count) { + ret = getrandom(&hdr_array[i].count, + sizeof(hdr_array[i].count), 0); + if (ret < 0) + goto out; + + hdr_array[i].count = + hdr_array[i].count % MAX_DIGEST_COUNT; + } + + hdr_array[i].datalen = + hdr_array[i].count * hash_digest_size[hdr_array[i].algo]; + + digest_list->size += sizeof(*hdr_array) + hdr_array[i].datalen; + } + + digest_list->buf = calloc(digest_list->size, sizeof(unsigned char)); + if (!digest_list->buf) { + free(digest_list); + ret = -ENOMEM; + goto out; + } + + buf_ptr = digest_list->buf; + + for (i = 0; i < num_digest_blocks; i++) { + memcpy(buf_ptr, &hdr_array[i], sizeof(*hdr_array)); + buf_ptr += sizeof(*hdr_array); + + for (j = 0; j < hdr_array[i].count; j++) { + ret = getrandom(buf_ptr, sizeof(u32), 0); + if (ret < 0) + goto out; + + *(u32 *)buf_ptr = *(u32 *)buf_ptr % MAX_DIGEST_VALUE; + buf_ptr += hash_digest_size[hdr_array[i].algo]; + } + } + + 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]); + + ret = 0; +out: + if (ret < 0) { + free(digest_list->buf); + free(digest_list); + } + + free(hdr_array); + return !ret ? digest_list : NULL; +} + +static struct digest_list_item *digest_list_generate_random(void) +{ + struct digest_list_item *digest_list; + struct compact_list_hdr *hdr; + u32 size = 0; + u8 digest[64]; + int ret; + + digest_list = calloc(1, sizeof(*digest_list)); + if (!digest_list) + return NULL; + + while (!size) { + ret = getrandom(&size, sizeof(size), 0); + if (ret < 0) + goto out; + + size = size % MAX_DIGEST_LIST_SIZE; + } + + digest_list->size = size; + digest_list->buf = calloc(digest_list->size, sizeof(unsigned char)); + if (!digest_list->buf) { + free(digest_list); + ret = -ENOMEM; + goto out; + } + + ret = getrandom(digest_list->buf, digest_list->size, 0); + if (ret < 0) + goto out; + + hdr = (struct compact_list_hdr *)digest_list->buf; + hdr->version = 1; + hdr->type = hdr->type % (COMPACT__LAST - 1); + hdr->algo = hdr->algo % HASH_ALGO__LAST; + + 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]); + + ret = 0; +out: + if (ret < 0) { + free(digest_list->buf); + free(digest_list); + } + + return !ret ? digest_list : NULL; +} + +static int digest_list_upload(struct digest_list_item *digest_list, enum ops op, + enum upload_types upload_type, char *parser_path, + char *parser_mode) +{ + char path_template[] = DIGEST_LIST_PATH_TEMPLATE; + char *path_upload = DIGEST_LIST_ADD_PATH, *basename; + unsigned char *buffer = digest_list->buf; + size_t buffer_len = digest_list->size; + unsigned char rnd[3]; + int ret = 0, fd; + + if (op == DIGEST_LIST_ADD) { + if (upload_type == UPLOAD_FILE) { + fd = mkstemp(path_template); + if (fd < 0) + return -EPERM; + + close(fd); + ret = write_buffer(path_template, + (char *)digest_list->buf, + digest_list->size); + if (ret < 0) + goto out; + + buffer = (unsigned char *)path_template; + buffer_len = strlen(path_template); + } else { + ret = getrandom(rnd, sizeof(rnd), 0); + if (ret < 0) + goto out; + + _bin2hex(path_template + + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, rnd, + sizeof(rnd)); + } + + memcpy(digest_list->filename_suffix, + path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + 6); + } else { + memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + digest_list->filename_suffix, 6); + path_upload = DIGEST_LIST_DEL_PATH; + if (upload_type == UPLOAD_FILE) { + buffer = (unsigned char *)path_template; + buffer_len = strlen(path_template); + } + } + + if (upload_type == UPLOAD_BUFFER) { + basename = strrchr(path_template, '/'); + ret = write_buffer(DIGEST_LABEL_PATH, basename, + strlen(basename)); + if (ret < 0) + goto out; + } + + ret = write_buffer(path_upload, (char *)buffer, buffer_len); +out: + if (ret < 0 || op == DIGEST_LIST_DEL) + unlink(path_template); + + return ret; +} + +static int digest_list_check(struct digest_list_item *digest_list, enum ops op) +{ + char path[PATH_MAX]; + u8 digest_list_buf[MAX_LINE_LENGTH]; + char digest_list_info[MAX_LINE_LENGTH]; + ssize_t size = digest_list->size; + struct compact_list_hdr *hdr; + struct stat st; + int ret = 0, i, fd, path_len, len, read_len; + + path_len = snprintf(path, sizeof(path), "%s/%s-%s-digest_list.%s.ascii", + DIGEST_LISTS_LOADED_PATH, + hash_algo_name[digest_list->algo], + digest_list->digest_str, + digest_list->filename_suffix); + + path[path_len - 6] = '\0'; + + if (op == DIGEST_LIST_DEL) { + if (stat(path, &st) != -1) + return -EEXIST; + + path[path_len - 6] = '.'; + + if (stat(path, &st) != -1) + return -EEXIST; + + return 0; + } + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + while (size) { + len = read(fd, digest_list_buf, sizeof(digest_list_buf)); + if (len <= 0) { + ret = -errno; + goto out; + } + + if (memcmp(digest_list_buf, + digest_list->buf + digest_list->size - size, len)) { + ret = -EINVAL; + goto out; + } + + size -= len; + } + + close(fd); + + path[path_len - 6] = '.'; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + size = digest_list->size; + while (size) { + hdr = (struct compact_list_hdr *)(digest_list->buf + + digest_list->size - size); + + len = snprintf(digest_list_info, sizeof(digest_list_info), + HDR_ASCII_FMT, digest_list->actions, + hdr->version, hash_algo_name[hdr->algo], + hdr->type, hdr->modifiers, hdr->count, + hdr->datalen); + + read_len = read(fd, digest_list_buf, len); + + if (read_len != len || + memcmp(digest_list_info, digest_list_buf, len)) { + ret = -EIO; + goto out; + } + + size -= sizeof(*hdr); + + for (i = 0; i < hdr->count; i++) { + _bin2hex(digest_list_info, + digest_list->buf + digest_list->size - size, + hash_digest_size[hdr->algo]); + + read_len = read(fd, digest_list_buf, + hash_digest_size[hdr->algo] * 2 + 1); + + if (read_len != hash_digest_size[hdr->algo] * 2 + 1 || + memcmp(digest_list_info, digest_list_buf, + read_len - 1) || + digest_list_buf[read_len - 1] != '\n') { + ret = -EIO; + goto out; + } + + size -= hash_digest_size[hdr->algo]; + } + } +out: + close(fd); + return ret; +} + +static int digest_list_query(u8 *digest, enum hash_algo algo, + char **query_result) +{ + ssize_t len, to_write, written; + char query[256] = { 0 }; + size_t query_result_len; + int ret = 0, fd; + + len = snprintf(query, sizeof(query), "%s-", hash_algo_name[algo]); + + _bin2hex(query + len, digest, hash_digest_size[algo]); + len += hash_digest_size[algo] * 2 + 1; + + fd = open(DIGEST_QUERY_PATH, O_WRONLY); + if (fd < 0) + return -errno; + + to_write = len; + + while (to_write) { + written = write(fd, query + len - to_write, to_write); + if (written <= 0) { + ret = -errno; + break; + } + + to_write -= written; + } + + close(fd); + if (ret < 0) + return ret; + + return read_buffer(DIGEST_QUERY_PATH, query_result, &query_result_len, + true, true); +} + +static int *get_count_gen_lists(u8 *digest, enum hash_algo algo) +{ + struct compact_list_hdr *hdr; + u8 *buf_ptr; + loff_t size; + int i, j, *count; + + count = calloc(num_max_digest_lists, sizeof(*count)); + if (!count) + return count; + + for (i = 0; i < num_max_digest_lists; i++) { + if (!digest_lists[i]) + continue; + + size = digest_lists[i]->size; + buf_ptr = digest_lists[i]->buf; + + while (size) { + hdr = (struct compact_list_hdr *)buf_ptr; + if (hdr->algo != algo) { + buf_ptr += sizeof(*hdr) + hdr->datalen; + size -= sizeof(*hdr) + hdr->datalen; + continue; + } + + buf_ptr += sizeof(*hdr); + size -= sizeof(*hdr); + + for (j = 0; j < hdr->count; j++) { + if (!memcmp(digest, buf_ptr, + hash_digest_size[algo])) + count[i]++; + buf_ptr += hash_digest_size[algo]; + size -= hash_digest_size[algo]; + } + } + } + + return count; +} + +static int *get_count_kernel_query(u8 *digest, enum hash_algo algo) +{ + char *query_result = NULL, *query_result_ptr, *line; + char digest_list_info[MAX_LINE_LENGTH]; + char label[256]; + struct compact_list_hdr *hdr; + struct digest_list_item *digest_list; + size_t size, size_info; + int ret, i, *count = NULL; + + count = calloc(num_max_digest_lists, sizeof(*count)); + if (!count) + return count; + + ret = digest_list_query(digest, algo, &query_result); + if (ret < 0) + goto out; + + query_result_ptr = query_result; + + while ((line = strsep(&query_result_ptr, "\n"))) { + if (!strlen(line)) + continue; + + for (i = 0; i < num_max_digest_lists; i++) { + if (!digest_lists[i]) + continue; + + digest_list = digest_lists[i]; + size = digest_list->size; + + while (size) { + hdr = + (struct compact_list_hdr *)(digest_list->buf + + digest_list->size - size); + size -= sizeof(*hdr) + hdr->datalen; + + snprintf(label, sizeof(label), + "%s-%s-digest_list.%s", + hash_algo_name[digest_list->algo], + digest_list->digest_str, + digest_list->filename_suffix); + + /* From digest_query_show(). */ + size_info = snprintf(digest_list_info, + sizeof(digest_list_info), + QUERY_RESULT_FMT, label, + digest_list->actions, hdr->version, + hash_algo_name[hdr->algo], hdr->type, + hdr->modifiers, hdr->count, + hdr->datalen); + + /* strsep() replaced '\n' with '\0' in line. */ + digest_list_info[size_info - 1] = '\0'; + + if (!strcmp(digest_list_info, line)) { + count[i]++; + break; + } + } + } + } +out: + free(query_result); + if (ret < 0) + free(count); + + return (!ret) ? count : NULL; +} + +static int compare_count(u32 value, enum hash_algo algo, + struct __test_metadata *_metadata) +{ + int *count_gen_list_array, *count_kernel_query_array; + int count_gen_list = 0, count_kernel_query = 0; + u8 digest[64] = { 0 }; + int i; + + *(u32 *)digest = value; + + count_gen_list_array = get_count_gen_lists(digest, algo); + if (!count_gen_list_array) + return -EINVAL; + + count_kernel_query_array = get_count_kernel_query(digest, algo); + if (!count_kernel_query_array) { + free(count_gen_list_array); + return -EINVAL; + } + + for (i = 0; i < num_max_digest_lists; i++) { + count_gen_list += count_gen_list_array[i]; + count_kernel_query += count_kernel_query_array[i]; + } + + TH_LOG("value: %d, algo: %s, gen list digests: %d, kernel digests: %d", + value, hash_algo_name[algo], count_gen_list, count_kernel_query); + free(count_gen_list_array); + free(count_kernel_query_array); + return (count_gen_list == count_kernel_query) ? 0 : -EINVAL; +} + +static void digest_list_delete_all(struct __test_metadata *_metadata, + enum upload_types upload_type, + char *parser_path) +{ + int ret, i; + + for (i = 0; i < MAX_DIGEST_LISTS; i++) { + if (!digest_lists[i]) + continue; + + ret = digest_list_upload(digest_lists[i], DIGEST_LIST_DEL, + upload_type, parser_path, "normal"); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed\n"); + } + + free(digest_lists[i]->buf); + free(digest_lists[i]); + digest_lists[i] = NULL; + } +} + +FIXTURE(test) +{ + enum upload_types upload_type; +}; + +FIXTURE_SETUP(test) +{ +} + +FIXTURE_TEARDOWN(test) +{ + digest_list_delete_all(_metadata, self->upload_type, NULL); +} + +static int enable_fault_injection(void) +{ + int ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TASK_FILTER, "Y", 1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_PROBABILITY, "1", 1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TIMES, "10000", 5); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_VERBOSE, "1", 1); + if (ret < 0) + return ret; + + ret = write_buffer(PROCFS_SELF_FAULT, "1", 1); + if (ret < 0) + return ret; + + return 0; +} + +static void digest_list_add_del_test(struct __test_metadata *_metadata, + int fault_injection, + enum upload_types upload_type) +{ + u32 value; + enum ops op; + enum hash_algo algo; + int ret, i, cur_queries = 1; + + while (cur_queries <= NUM_QUERIES) { + ret = getrandom(&op, 1, 0); + ASSERT_EQ(1, ret) { + TH_LOG("getrandom() failed\n"); + } + + op = op % 2; + + switch (op) { + case DIGEST_LIST_ADD: + TH_LOG("add digest list..."); + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (!digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + + digest_lists[digest_lists_pos] = digest_list_generate(); + ASSERT_NE(NULL, digest_lists[digest_lists_pos]) { + TH_LOG("digest_list_generate() failed"); + } + + ret = digest_list_upload(digest_lists[digest_lists_pos], + op, upload_type, NULL, NULL); + /* Handle failures from fault injection. */ + if (fault_injection && ret < 0) { + TH_LOG("handle failure..."); + ret = digest_list_check( + digest_lists[digest_lists_pos], + DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_lists[digest_lists_pos]->buf); + free(digest_lists[digest_lists_pos]); + digest_lists[digest_lists_pos] = NULL; + break; + } + + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[digest_lists_pos], + op); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + break; + case DIGEST_LIST_DEL: + TH_LOG("delete digest list..."); + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + + for (i = 0; i < MAX_SEARCH_ATTEMPTS; i++) { + ret = getrandom(&digest_lists_pos, + sizeof(digest_lists_pos), 0); + ASSERT_EQ(sizeof(digest_lists_pos), ret) { + TH_LOG("getrandom() failed"); + } + + digest_lists_pos = + digest_lists_pos % num_max_digest_lists; + + if (digest_lists[digest_lists_pos]) + break; + } + + if (i == MAX_SEARCH_ATTEMPTS) { + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + } + + ret = digest_list_upload(digest_lists[digest_lists_pos], + op, upload_type, NULL, NULL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[digest_lists_pos], + op); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_lists[digest_lists_pos]->buf); + free(digest_lists[digest_lists_pos]); + digest_lists[digest_lists_pos] = NULL; + break; + default: + break; + } + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % 10; + + if (value != 1) + continue; + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % MAX_DIGEST_VALUE; + + ret = getrandom(&algo, sizeof(algo), 0); + ASSERT_EQ(sizeof(algo), ret) { + TH_LOG("getrandom() failed"); + } + + algo = algo % HASH_ALGO_RIPE_MD_128; + + ret = compare_count(value, algo, _metadata); + ASSERT_EQ(0, ret) { + TH_LOG("count mismatch"); + } + + TH_LOG("query digest lists (%d/%d)...", cur_queries, + NUM_QUERIES); + + cur_queries++; + } +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload, UINT_MAX) +{ + self->upload_type = UPLOAD_FILE; + digest_list_add_del_test(_metadata, 0, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload_fault, UINT_MAX) +{ + int ret; + + self->upload_type = UPLOAD_FILE; + + ret = enable_fault_injection(); + ASSERT_EQ(0, ret) { + TH_LOG("enable_fault_injection() failed"); + } + + digest_list_add_del_test(_metadata, 1, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload, UINT_MAX) +{ + self->upload_type = UPLOAD_BUFFER; + digest_list_add_del_test(_metadata, 0, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload_fault, UINT_MAX) +{ + int ret; + + self->upload_type = UPLOAD_BUFFER; + + ret = enable_fault_injection(); + ASSERT_EQ(0, ret) { + TH_LOG("enable_fault_injection() failed"); + } + + digest_list_add_del_test(_metadata, 1, self->upload_type); +} + +FIXTURE(test_fuzzing) +{ +}; + +FIXTURE_SETUP(test_fuzzing) +{ +} + +FIXTURE_TEARDOWN(test_fuzzing) +{ +} + +TEST_F_TIMEOUT(test_fuzzing, digest_list_fuzzing_test, UINT_MAX) +{ + char digests_count_before[256] = { 0 }; + char *digests_count_before_ptr = digests_count_before; + char digests_count_after[256] = { 0 }; + char *digests_count_after_ptr = digests_count_after; + size_t len = sizeof(digests_count_before) - 1; + int ret, i; + + ret = read_buffer(DIGESTS_COUNT, &digests_count_before_ptr, &len, + false, true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + for (i = 1; i <= NUM_ITERATIONS; i++) { + TH_LOG("add digest list (%d/%d)...", i, NUM_ITERATIONS); + + digest_lists[0] = digest_list_generate_random(); + ASSERT_NE(NULL, digest_lists[0]) { + TH_LOG("digest_list_generate() failed"); + } + + ret = digest_list_upload(digest_lists[0], DIGEST_LIST_ADD, + UPLOAD_FILE, NULL, NULL); + if (!ret) { + ret = digest_list_check(digest_lists[0], + DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = digest_list_upload(digest_lists[0], + DIGEST_LIST_DEL, UPLOAD_FILE, + NULL, NULL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[0], + DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + } + + free(digest_lists[0]->buf); + free(digest_lists[0]); + digest_lists[0] = NULL; + } + + ret = read_buffer(DIGESTS_COUNT, &digests_count_after_ptr, &len, false, + true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + ASSERT_STREQ(digests_count_before, digests_count_after); +} + +TEST_HARNESS_MAIN