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: 467112 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=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 09FCDC49EBE for ; Fri, 25 Jun 2021 16:56:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D73786194C for ; Fri, 25 Jun 2021 16:56:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230108AbhFYQ7C (ORCPT ); Fri, 25 Jun 2021 12:59:02 -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: X-Mailing-List: linux-kselftest@vger.kernel.org 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: 467113 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 BDCE2C2B9F4 for ; Fri, 25 Jun 2021 16:56:44 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A4CFC6194C for ; Fri, 25 Jun 2021 16:56:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230097AbhFYQ7B (ORCPT ); Fri, 25 Jun 2021 12:59:01 -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: X-Mailing-List: linux-kselftest@vger.kernel.org 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: 467111 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=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C7D2FC49EC8 for ; Fri, 25 Jun 2021 16:56:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id AFE416197C for ; Fri, 25 Jun 2021 16:56:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230118AbhFYQ7E (ORCPT ); Fri, 25 Jun 2021 12:59:04 -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: X-Mailing-List: linux-kselftest@vger.kernel.org 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: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: 467110 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=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id CE2E8C49EA7 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 AFF7061969 for ; Fri, 25 Jun 2021 16:58:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230101AbhFYRAV (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: X-Mailing-List: linux-kselftest@vger.kernel.org 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: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: 467109 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 D7E8EC49ECB 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 C4E2761982 for ; Fri, 25 Jun 2021 16:58:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230234AbhFYRAY (ORCPT ); Fri, 25 Jun 2021 13:00:24 -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: X-Mailing-List: linux-kselftest@vger.kernel.org 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: 467108 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=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D48BDC2B9F4 for ; Fri, 25 Jun 2021 16:59:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B80126194C for ; Fri, 25 Jun 2021 16:59:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230178AbhFYRBl (ORCPT ); Fri, 25 Jun 2021 13:01:41 -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: X-Mailing-List: linux-kselftest@vger.kernel.org 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: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: 467107 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 1B60BC49EAB 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 F3C8461945 for ; Fri, 25 Jun 2021 16:59:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230363AbhFYRBq (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: X-Mailing-List: linux-kselftest@vger.kernel.org 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