From patchwork Tue Sep 14 16:33:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 511254 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 934A9C43217 for ; Tue, 14 Sep 2021 16:34:26 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 778A361165 for ; Tue, 14 Sep 2021 16:34:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229825AbhINQfm (ORCPT ); Tue, 14 Sep 2021 12:35:42 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3795 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229492AbhINQfm (ORCPT ); Tue, 14 Sep 2021 12:35:42 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H884w3zWLz67xjd; Wed, 15 Sep 2021 00:32:00 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Tue, 14 Sep 2021 18:34:21 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [PATCH v3 01/13] diglim: Overview Date: Tue, 14 Sep 2021 18:33:49 +0200 Message-ID: <20210914163401.864635-2-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210914163401.864635-1-roberto.sassu@huawei.com> References: <20210914163401.864635-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml754-chm.china.huawei.com (10.201.108.204) 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 Add an overview of DIGLIM to Documentation/security/diglim/introduction.rst and the architecture to Documentation/security/diglim/architecture.rst Signed-off-by: Roberto Sassu --- .../security/diglim/architecture.rst | 46 ++ Documentation/security/diglim/index.rst | 11 + .../security/diglim/introduction.rst | 599 ++++++++++++++++++ Documentation/security/index.rst | 1 + MAINTAINERS | 9 + 5 files changed, 666 insertions(+) create mode 100644 Documentation/security/diglim/architecture.rst create mode 100644 Documentation/security/diglim/index.rst create mode 100644 Documentation/security/diglim/introduction.rst diff --git a/Documentation/security/diglim/architecture.rst b/Documentation/security/diglim/architecture.rst new file mode 100644 index 000000000000..bb22e25c92a1 --- /dev/null +++ b/Documentation/security/diglim/architecture.rst @@ -0,0 +1,46 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Architecture +============ + +This section introduces the high level architecture of DIGLIM. + +:: + + 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 at Documentation/security/diglim/introductions.rst, 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 a linked list 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/diglim/index.rst b/Documentation/security/diglim/index.rst new file mode 100644 index 000000000000..0fc5ab019bc0 --- /dev/null +++ b/Documentation/security/diglim/index.rst @@ -0,0 +1,11 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +Digest Lists Integrity Module (DIGLIM) +====================================== + +.. toctree:: + :maxdepth: 1 + + introduction + architecture diff --git a/Documentation/security/diglim/introduction.rst b/Documentation/security/diglim/introduction.rst new file mode 100644 index 000000000000..c73fe70c0f44 --- /dev/null +++ b/Documentation/security/diglim/introduction.rst @@ -0,0 +1,599 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Introduction +============ + +Digest Lists Integrity Module (DIGLIM) is a component of the integrity +subsystem in the kernel, primarily aiming to aid Integrity Measurement +Architecture (IMA) in the process of checking the integrity of file content +and metadata. It accomplishes this task by storing reference values coming +from software vendors and by reporting whether or not the digest of file +content or metadata calculated by IMA (or EVM) is found among those values. +In this way, IMA can decide, depending on the result of a query, if a +measurement should be taken or access to the file should be granted. The +`Security Assumptions`_ section explains more in detail why this component +has been placed in the kernel. + +The main benefits of using IMA in conjunction with DIGLIM are the ability +to implement advanced remote attestation schemes based on the usage of a +TPM key for establishing a TLS secure channel [1]_ [2]_, and to reduce the +burden on Linux distribution vendors to extend secure boot at OS level to +applications. + +DIGLIM does not have the complexity of feature-rich databases. In fact, its +main functionality comes from the hash table primitives already in the +kernel. It does not have an ad-hoc storage module, it just indexes data in +a fixed format (digest lists, a set of concatenated digests preceded by a +header), copied to kernel memory as they are. Lastly, it does not support +database-oriented languages such as SQL, but only accepts a digest and its +algorithm as a query. + +The only digest list format supported by DIGLIM is called ``compact``. +However, Linux distribution vendors don't have to generate new digest lists +in this format for the packages they release, as already available +information, such as RPM headers and DEB package metadata, can be used as a +source for reference values (they include file digests), with a user space +parser taking care of the conversion to the compact format. + +Although one might perceive that storing file or metadata digests for a +Linux distribution would significantly increase the memory usage, this does +not seem to be the case. As an anticipation of the evaluation done in the +`Preliminary Performance Evaluation`_ section, protecting binaries and +shared libraries of a minimal Fedora 33 installation requires 208K of +memory for the digest lists plus 556K for indexing. + +In exchange for a slightly increased memory usage, DIGLIM improves the +performance of the integrity subsystem. In the considered scenario, IMA +measurement and appraisal of 5896 files with digest lists requires +respectively less than one quarter and less than half the time, compared to +the current solution. + +DIGLIM also 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 +uploaded to the kernel. + +Operations in DIGLIM are atomic: if an error occurs during the addition of +a digest list, DIGLIM rolls back the entire insert operation; deletions +instead always succeed. This capability has been tested with an ad-hoc +fault injection mechanism capable of simulating failures during the +operations. + +Finally, DIGLIM 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. + +.. [1] LSS EU 2019 + `slides `__ + and `video `__ + +.. [2] FutureTPM EU project, final review meeting demo + `slides `__ + and `video `__ + + +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. + + +Benefits +-------- + +DIGLIM further enhances the capabilities offered by IMA-based solutions +and, at the same time, makes them more practical to adopt by reusing +existing sources as reference values for integrity decisions. + +Possible sources for digest lists are: + +- RPM headers; +- Debian repository metadata. + + +Benefits for IMA Measurement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One of the issues that arises when files are measured by the OS is that, +due to parallel execution, the order in which file accesses happen cannot +be predicted. Since the TPM Platform Configuration Register (PCR) extend +operation, executed after each file measurement, cryptographically binds +the current measurement to the previous ones, the PCR value at the end of a +workload cannot be predicted too. + +Thus, even if the usage of a TPM key, bound to a PCR value, should be +allowed when only good files were accessed, the TPM could unexpectedly deny +an operation on that key if files accesses did not happen as stated by the +key policy (which allows only one of the possible sequences). + +DIGLIM solves this issue by making the PCR value stable over the time and +not dependent on file accesses. The following figure depicts the current +and the new approaches:: + + IMA measurement list (current) + + entry# 1st boot 2nd boot 3rd boot + +----+---------------+ +----+---------------+ +----+---------------+ + 1: | 10 | file1 measur. | | 10 | file3 measur. | | 10 | file2 measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + 2: | 10 | file2 measur. | | 10 | file2 measur. | | 10 | file3 measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + 3: | 10 | file3 measur. | | 10 | file1 measur. | | 10 | file4 measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + + PCR: Extend != Extend != Extend + file1, file2, file3 file3, file2, file1 file2, file3, file4 + + + PCR Extend definition: + + PCR(new value) = Hash(Hash(meas. entry), PCR(previous value)) + +A new entry in the measurement list is created by IMA for each file access. +Assuming that ``file1``, ``file2`` and ``file3`` are files provided by the +software vendor, ``file4`` is an unknown file, the first two PCR values +above represent a good system state, the third a bad system state. The PCR +values are the result of the PCR extend operation performed for each +measurement entry with the digest of the measurement entry as an input. + +:: + + IMA measurement list (with DIGLIM) + + dlist + +--------------+ + | header | + +--------------+ + | file1 digest | + | file2 digest | + | file3 digest | + +--------------+ + +``dlist`` is a digest list containing the digest of ``file1``, ``file2`` +and ``file3``. In the intended scenario, it is generated by a software +vendor at the end of the building process, and retrieved by the +administrator of the system where the digest list is loaded. + +:: + + entry# 1st boot 2nd boot 3rd boot + +----+---------------+ +----+---------------+ +----+---------------+ + 0: | 11 | dlist measur. | | 11 | dlist measur. | | 11 | dlist measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + 1: < file1 measur. skip > < file3 measur. skip > < file2 measur. skip > + + 2: < file2 measur. skip > < file2 measur. skip > < file3 measur. skip > + +----+---------------+ + 3: < file3 measur. skip > < file1 measur. skip > | 11 | file4 measur. | + +----+---------------+ + + PCR: Extend = Extend != Extend + dlist dlist dlist, file4 + + +The first entry in the measurement list contains the digest of the digest +list uploaded to the kernel at kernel initialization time. + +When a file is accessed, IMA queries DIGLIM with the calculated file digest +and, if it is found, IMA skips the measurement. + +Thus, the only information sent to remote verifiers are: the list of +files that could possibly be accessed (from the digest list), but not if +they were accessed and when; the measurement of unknown files. + +Despite providing less information, this solution has the advantage that +the good system state (i.e. when only ``file1``, ``file2`` and ``file3`` +are accessed) now can be represented with a deterministic PCR value (the +PCR is extended only with the measurement of the digest list). Also, the +bad system state can still be distinguished from the good state (the PCR is +extended also with the measurement of ``file4``). + +If a TPM key is bound to the good PCR value, the TPM would allow the key to +be used if ``file1``, ``file2`` or ``file3`` are accessed, regardless of +the sequence in which they are accessed (the PCR value does not change), +and would revoke the permission when the unknown ``file4`` is accessed (the +PCR value changes). If a system is able to establish a TLS connection with +a peer, this implicitly means that the system was in a good state (i.e. +``file4`` was not accessed, otherwise the TPM would have denied the usage +of the TPM key due to the key policy). + + +Benefits for IMA Appraisal +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Extending secure boot to applications means being able to verify the +provenance of files accessed. IMA does it by verifying file signatures with +a key that it trusts, which requires Linux distribution vendors to +additionally include in the package header a signature for each file that +must be verified (there is the dedicated ``RPMTAG_FILESIGNATURES`` section +in the RPM header). + +The proposed approach would be instead to verify data provenance from +already available metadata (file digests) in existing packages. IMA would +verify the signature of package metadata and search file digests extracted +from package metadata and added to the hash table in the kernel. + +For RPMs, file digests can be found in the ``RPMTAG_FILEDIGESTS`` section +of ``RPMTAG_IMMUTABLE``, whose signature is in ``RPMTAG_RSAHEADER``. For +DEBs, file digests (unsafe to use due to a weak digest algorithm) can be +found in the ``md5sum`` file, which can be indirectly verified from +``Release.gpg``. + +The following figure highlights the differences between the current and the +proposed approach. + +:: + + IMA appraisal (current solution, with file signatures): + + appraise + +-----------+ + V | + +-------------------------+-----+ +-------+-----+ | + | RPM header | | ima rpm | file1 | sig | | + | ... | | plugin +-------+-----+ +-----+ + | file1 sig [to be added] | sig |--------> ... | IMA | + | ... | | +-------+-----+ +-----+ + | fileN sig [to be added] | | | fileN | sig | + +-------------------------+-----+ +-------+-----+ + +In this case, file signatures must be added to the RPM header, so that the +``ima`` rpm plugin can extract them together with the file content. The RPM +header signature is not used. + +:: + + IMA appraisal (with DIGLIM): + + kernel hash table + with RPM header content + +---+ +--------------+ + | |--->| file1 digest | + +---+ +--------------+ + ... + +---+ appraise (file1) + | | <--------------+ + +----------------+-----+ +---+ | + | RPM header | | ^ | + | ... | | digest_list | | + | file1 digest | sig | rpm plugin | +-------+ +-----+ + | ... | |-------------+--->| file1 | | IMA | + | fileN digest | | +-------+ +-----+ + +----------------+-----+ | + ^ | + +------------------------------------+ + appraise (RPM header) + +In this case, the RPM header is used as it is, and its signature is used +for IMA appraisal. Then, the ``digest_list`` rpm plugin executes the user +space parser to parse the RPM header and add the extracted digests to an +hash table in the kernel. IMA appraisal of the files in the RPM package +consists in searching their digest in the hash table. + +Other than reusing available information as digest list, another advantage +is the lower computational overhead compared to the solution with file +signatures (only one signature verification for many files and digest +lookup, instead of per file signature verification, see `Preliminary +Performance Evaluation`_ for more details). + + +Lifecycle +--------- + +The lifecycle of DIGLIM is represented in the following figure:: + + Vendor premises (release process with modifications): + + +------------+ +-----------------------+ +------------------------+ + | 1. build a | | 2. generate and sign | | 3. publish the package | + | package |-->| a digest list from |-->| and digest list in | + | | | packaged files | | a repository | + +------------+ +-----------------------+ +------------------------+ + | + | + User premises: | + V + +---------------------+ +------------------------+ +-----------------+ + | 6. use digest lists | | 5. download the digest | | 4. download and | + | for measurement |<--| list and upload to |<--| install the | + | and/or appraisal | | the kernel | | package | + +---------------------+ +------------------------+ +-----------------+ + +The figure above represents all the steps when a digest list is generated +separately. However, as mentioned in `Benefits`_, in most cases existing +packages can be already used as a source for digest lists, limiting the +effort for software vendors. + +If, for example, RPMs are used as a source for digest lists, the figure +above becomes:: + + Vendor premises (release process without modifications): + + +------------+ +------------------------+ + | 1. build a | | 2. publish the package | + | package |-->| in a repository |---------------------+ + | | | | | + +------------+ +------------------------+ | + | + | + User premises: | + V + +---------------------+ +------------------------+ +-----------------+ + | 5. use digest lists | | 4. extract digest list | | 3. download and | + | for measurement |<--| from the package |<--| install the | + | and/or appraisal | | and upload to the | | package | + | | | kernel | | | + +---------------------+ +------------------------+ +-----------------+ + +Step 4 can be performed with the ``digest_list`` rpm plugin and the user +space parser, without changes to rpm itself. + + +Security Assumptions +-------------------- + +As mentioned in the `Introduction`_, DIGLIM will be primarily used in +conjunction with IMA to enforce a mandatory policy on all user space +processes, including those owned by root. Even root, in a system with a +locked-down kernel, cannot affect the enforcement of the mandatory policy +or, if changes are permitted, it cannot do so without being detected. + +Given that the target of the enforcement are user space processes, DIGLIM +cannot be placed in the target, as a Mandatory Access Control (MAC) design +is required to have the components responsible to enforce the mandatory +policy separated from the target. + +While locking-down a system and limiting actions with a mandatory policy is +generally perceived by users as an obstacle, it has noteworthy benefits for +the users themselves. + +First, it would timely block attempts by malicious software to steal or +misuse user assets. Although users could query the package managers to +detect them, detection would happen after the fact, or it wouldn't happen +at all if the malicious software tampered with package managers. With a +mandatory policy enforced by the kernel, users would still be able to +decide which software they want to be executed except that, unlike package +managers, the kernel is not affected by user space processes or root. + +Second, it might make systems more easily verifiable from outside, due to +the limited actions the system allows. When users connect to a server, not +only they would be able to verify the server identity, which is already +possible with communication protocols like TLS, but also if the software +running on that server can be trusted to handle their sensitive data. + + +Adoption +-------- + +A former version of DIGLIM 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, DIGLIM was part of IMA (known as IMA Digest Lists). 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. + +Integration with rpm is done with the ``digest_list`` plugin: + +https://gitee.com/src-openeuler/rpm/blob/master/Add-digest-list-plugin.patch + +This plugin writes the RPM header and its signature to a file, so that the +file is ready to be appraised by IMA, and calls the user space parser to +convert and upload the digest list to the kernel. + + +Simple Usage Example (Tested with Fedora 33) +-------------------------------------------- + +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/diglim/digest_query + # cat /sys/kernel/security/integrity/diglim/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/diglim/digest_query + # cat /sys/kernel/security/integrity/diglim/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 + + +Preliminary Performance Evaluation +---------------------------------- + +This section provides an initial estimation of the overhead introduced by +DIGLIM. The estimation has been performed on a Fedora 33 virtual machine +with 1447 packages installed. The virtual machine has 16 vCPU (host CPU: +AMD Ryzen Threadripper PRO 3955WX 16-Cores) and 2G of RAM (host memory: +64G). The virtual machine also has a vTPM with libtpms and swtpm as +backend. + +After writing the RPM headers to files, the size of the directory +containing them is 36M. + +After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 3.6M. + +The time to load the entire RPM database is 0.628s. + +After loading the digest lists to the kernel, the slab usage due to +indexing is (obtained with slab_nomerge in the kernel command line):: + + OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME + 118144 118144 100% 0,03K 923 128 3692K digest_list_item_ref_cache + 102400 102400 100% 0,03K 800 128 3200K digest_item_cache + 2646 2646 100% 0,09K 63 42 252K digest_list_item_cache + +The stats, obtained from the ``digests_count`` interface, introduced later, +are:: + + Parser digests: 0 + File digests: 99100 + Metadata digests: 0 + Digest list digests: 1423 + +On this installation, this would be the worst case in which all files are +measured and/or appraised, which is currently not recommended without +enforcing an integrity policy protecting mutable files. Infoflow LSM is a +component to accomplish this task: + +https://patchwork.kernel.org/project/linux-integrity/cover/20190818235745.1417-1-roberto.sassu@huawei.com/ + +The first manageable goal of IMA with DIGLIM is to use an execution policy, +with measurement and/or appraisal of files executed or mapped in memory as +executable (in addition to kernel modules and firmware). In this +case, the digest list contains the digest only for those files. The numbers +above change as follows. + +After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 208K. + +The time to load the digest of binaries and shared libraries is 0.062s. + +After loading the digest lists to the kernel, the slab usage due to +indexing is:: + + OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME + 7168 7168 100% 0,03K 56 128 224K digest_list_item_ref_cache + 7168 7168 100% 0,03K 56 128 224K digest_item_cache + 1134 1134 100% 0,09K 27 42 108K digest_list_item_cache + + +The stats, obtained from the ``digests_count`` interface, are:: + + Parser digests: 0 + File digests: 5986 + Metadata digests: 0 + Digest list digests: 1104 + + +Comparison with IMA +~~~~~~~~~~~~~~~~~~~ + +This section compares the performance between the current solution for IMA +measurement and appraisal, and IMA with DIGLIM. + + +Workload A (without DIGLIM): + +#. cat file[0-5985] > /dev/null + + +Workload B (with DIGLIM): + +#. echo $PWD/0-file_list-compact-file[0-1103] > /integrity/diglim/digest_list_add +#. cat file[0-5985] > /dev/null + + +Workload A execution time without IMA policy:: + + real 0m0,155s + user 0m0,008s + sys 0m0,066s + + +Measurement +........... + +IMA policy:: + + measure fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow pcr=11 ima_template=ima-sig + +``use_diglim`` is a policy keyword not yet supported by IMA. + + +Workload A execution time with IMA and 5986 files with signature measured:: + + real 0m8,273s + user 0m0,008s + sys 0m2,537s + + +Workload B execution time with IMA, 1104 digest lists with signature +measured and uploaded to the kernel, and 5986 files with signature accessed +but not measured (due to the file digest being found in the hash table):: + + real 0m1,837s + user 0m0,036s + sys 0m0,583s + + +Appraisal +......... + +IMA policy:: + + appraise fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow + +``use_diglim`` is a policy keyword not yet supported by IMA. + + +Workload A execution time with IMA and 5986 files with file signature +appraised:: + + real 0m2,197s + user 0m0,011s + sys 0m2,022s + + +Workload B execution time with IMA, 1104 digest lists with signature +appraised and uploaded to the kernel, and with 5986 files with signature +not verified (due to the file digest being found in the hash table):: + + real 0m0,982s + user 0m0,020s + sys 0m0,865s diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 16335de04e8c..6c3aea41c55b 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -17,3 +17,4 @@ Security Documentation tpm/index digsig landlock + diglim/index diff --git a/MAINTAINERS b/MAINTAINERS index eeb4c70b3d5b..a3e8a6f2e34d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5502,6 +5502,15 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-gpio-mm.c +DIGLIM +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/diglim/architecture.rst +F: Documentation/security/diglim/index.rst +F: Documentation/security/diglim/introduction.rst + DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck L: linux-i2c@vger.kernel.org From patchwork Tue Sep 14 16:33:51 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 511253 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 E1EDAC41535 for ; Tue, 14 Sep 2021 16:34:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CF7E961163 for ; Tue, 14 Sep 2021 16:34:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229968AbhINQfo (ORCPT ); Tue, 14 Sep 2021 12:35:44 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3797 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229522AbhINQfn (ORCPT ); Tue, 14 Sep 2021 12:35:43 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H884x6TV0z6H6mX; Wed, 15 Sep 2021 00:32:01 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Tue, 14 Sep 2021 18:34:23 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [PATCH v3 03/13] diglim: Objects Date: Tue, 14 Sep 2021 18:33:51 +0200 Message-ID: <20210914163401.864635-4-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210914163401.864635-1-roberto.sassu@huawei.com> References: <20210914163401.864635-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml754-chm.china.huawei.com (10.201.108.204) 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 Define the objects to manage digest lists: - digest_list_item: a digest list loaded into the kernel; - digest_list_item_ref: a reference to a digest list; - digest_item: a digest of a digest list. Also define some helpers for the objects. More information can be found in Documentation/security/diglim/implementation.rst. Signed-off-by: Roberto Sassu --- .../security/diglim/implementation.rst | 78 +++++++ MAINTAINERS | 1 + security/integrity/diglim/diglim.h | 203 ++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 security/integrity/diglim/diglim.h diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 59a180b3bb3f..2089b854e5a5 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -95,3 +95,81 @@ 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. + +.. kernel-doc:: security/integrity/diglim/diglim.h + +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). + +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: + +.. kernel-doc:: security/integrity/diglim/diglim.h + :identifiers: get_hdr + +.. kernel-doc:: security/integrity/diglim/diglim.h + :identifiers: get_digest diff --git a/MAINTAINERS b/MAINTAINERS index b02a8616362b..49e3989677a1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5512,6 +5512,7 @@ F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst F: include/uapi/linux/diglim.h +F: security/integrity/diglim/diglim.h DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h new file mode 100644 index 000000000000..1c1f513060c7 --- /dev/null +++ b/security/integrity/diglim/diglim.h @@ -0,0 +1,203 @@ +/* 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 + * + * Definitions only used inside DIGLIM. + */ + +#ifndef __DIGLIM_INTERNAL_H +#define __DIGLIM_INTERNAL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DIGEST_SIZE 64 +#define HASH_BITS 10 +#define DIGLIM_HTABLE_SIZE (1 << HASH_BITS) + +/** + * struct digest_list_item - a digest list loaded into the kernel + * + * @size: size of the digest list buffer + * @buf: digest list buffer + * @digest: digest of the digest list + * @label: label used to identify the digest list (e.g. file name) + * @actions: actions performed on the digest list + * @algo: digest algorithm + */ +struct digest_list_item { + loff_t size; + u8 *buf; + u8 digest[64]; + const char *label; + u8 actions; + enum hash_algo algo; +}; + +/** + * struct digest_list_item_ref - a reference to a digest list + * + * @list: linked list pointers + * @digest_list: pointer to struct digest_list_item + * @digest_offset: offset of the digest in the referenced digest list + * @hdr_offset: offset of the header the digest refers to in the digest list + */ +struct digest_list_item_ref { + struct list_head list; + struct digest_list_item *digest_list; + u32 digest_offset; + u32 hdr_offset; +}; + +/** + * struct digest_item - a digest of a digest list + * + * @hnext: pointers of the hash table + * @refs: linked list of struct digest_list_item_ref + */ +struct digest_item { + struct hlist_node hnext; + struct list_head refs; +}; + +/** + * struct h_table - hash table of struct digest_item + * + * @len: number of stored struct digest_item + * @queue: heads of the hash table + */ +struct h_table { + unsigned long len; + struct hlist_head queue[DIGLIM_HTABLE_SIZE]; +}; + +static inline unsigned int hash_key(u8 *digest) +{ + return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE; +} + +/** + * get_hdr - get a compact header from a digest list + * @digest_list: digest list the header is obtained from + * @hdr_offset: header offset relative to the digest list buffer + * + * This function obtains a header from a digest list buffer and a header offset. + * + * Return: a compact list header + */ +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); +} + +/** + * get_algo - get a digest algorithm from a digest list + * @digest_list: digest list the digest algorithm is obtained from + * @digest_offset: offset of the digest relative to the digest list buffer + * @hdr_offset: offset of the header relative to the digest list buffer + * + * This function returns the algorithm from struct digest_list_item if the + * passed digest offset is zero, or from the header the digest refers to if the + * digest offset is not zero. + * + * Return: the algorithm of the digest list digest or a digest inside the digest + * list + */ +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; +} + +/** + * get_digest - get a digest from a digest list + * @digest_list: digest list the digest is obtained from + * @digest_offset: offset of the digest relative to the digest list buffer + * @hdr_offset: offset of the header relative to the digest list buffer + * + * This function returns the digest from struct digest_list_item if the + * passed digest offset is zero, or from the digest list buffer if the + * digest offset is not zero. + * + * Return: the digest list digest or a digest inside the digest list + */ +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; +} + +/** + * get_hdr_ref - get a compact header from a digest list reference + * @ref: digest list reference the header is obtained from + * + * This function obtains a header from a digest list reference, which contains + * the pointer to the digest list buffer and the digest and header offsets. + * + * Return: a compact list header + */ +static inline struct compact_list_hdr * +get_hdr_ref(struct digest_list_item_ref *ref) +{ + return get_hdr(ref->digest_list, ref->hdr_offset); +} + +/** + * get_algo_ref - get a digest algorithm from a digest list reference + * @ref: digest list reference the digest algorithm is obtained from + * + * This function returns the algorithm from struct digest_list_item_ref, which + * contains the pointer to the digest list buffer and the digest and header + * offsets. + * + * Return: the algorithm of the digest list digest or a digest inside the digest + * list + */ +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; +} + +/** + * get_digest_ref - get a digest from a digest list reference + * @ref: digest list reference the digest is obtained from + * + * This function returns the digest from struct digest_list_item_ref, which + * contains the pointer to the digest list buffer and the digest and header + * offsets. + * + * Return: the digest list digest or a digest inside the digest list + */ +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 /*__DIGLIM_INTERNAL_H*/ From patchwork Tue Sep 14 16:33:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 511252 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 2CC26C433F5 for ; Tue, 14 Sep 2021 16:35:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 112F260FD7 for ; Tue, 14 Sep 2021 16:35:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229825AbhINQhC (ORCPT ); Tue, 14 Sep 2021 12:37:02 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3799 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229675AbhINQg4 (ORCPT ); Tue, 14 Sep 2021 12:36:56 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H886c14mFz67stq; Wed, 15 Sep 2021 00:33:28 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Tue, 14 Sep 2021 18:35:36 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [PATCH v3 05/13] diglim: Parser Date: Tue, 14 Sep 2021 18:33:53 +0200 Message-ID: <20210914163401.864635-6-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210914163401.864635-1-roberto.sassu@huawei.com> References: <20210914163401.864635-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml754-chm.china.huawei.com (10.201.108.204) 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 Introduce the necessary functions to parse a digest list and to execute the requested operation. The main function is digest_list_parse(), which coordinates the various steps required to add or delete a digest list, and has the logic to roll back when one of the steps fails. A more detailed description about the steps can be found in Documentation/security/diglim/implementation.rst Signed-off-by: Roberto Sassu --- .../security/diglim/implementation.rst | 35 +++ MAINTAINERS | 1 + security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/diglim.h | 3 + security/integrity/diglim/parser.c | 274 ++++++++++++++++++ 5 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 security/integrity/diglim/parser.c diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 83342ec12f74..626af0d245ef 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -182,3 +182,38 @@ This section introduces the methods requires to manage the three objects defined. .. kernel-doc:: security/integrity/diglim/methods.c + + +Parser +------ + +This section introduces the necessary functions to parse a digest list and +to execute the requested operation. + +.. kernel-doc:: security/integrity/diglim/parser.c + +The main function is digest_list_parse(), which coordinates the various +steps required to add or delete a digest list, and has the logic to roll +back when one of the steps fails. + +#. Calls digest_list_validate() to validate the passed buffer containing + the digest list to ensure that the format is correct. + +#. Calls get_digest_list() to create a new digest_list_item for the add + operation, or to retrieve the existing one for the delete operation. + get_digest_list() refuses to add digest lists that were previously + added and to delete digest lists that weren't previously added. Also, + get_digest_list() refuses to delete digest lists if there are actions + done at addition time that are not currently being performed (it would + guarantee that also deletion is notified to remote verifiers). + +#. Calls _digest_list_parse() which takes the created/retrieved + struct digest_list_item and adds or delete the digests included in the + digest list. + +#. If an error occurred, performs a rollback to the previous state, by + calling _digest_list_parse() with the opposite operation and the buffer + size at the time the error occurred. + +#. digest_list_parse() deletes the struct digest_list_item on unsuccessful + add or successful delete. diff --git a/MAINTAINERS b/MAINTAINERS index 06c6ba0b3f25..f5959936d490 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5515,6 +5515,7 @@ F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h F: security/integrity/diglim/methods.c +F: security/integrity/diglim/parser.c DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile index b761ed8cfb3e..34e4e154fff3 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGLIM) += diglim.o -diglim-y := methods.o +diglim-y := methods.o parser.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 75359f9cd3dd..afdb0affdc5e 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -218,4 +218,7 @@ struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, const char *label); void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, struct digest_list_item *digest_list); + +int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, const char *label); #endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/parser.c b/security/integrity/diglim/parser.c new file mode 100644 index 000000000000..435d231028c7 --- /dev/null +++ b/security/integrity/diglim/parser.c @@ -0,0 +1,274 @@ +// 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 + * + * Functions to parse digest lists. + */ + +#include +#include + +#include "diglim.h" +#include "../integrity.h" + +/** + * digest_list_validate - validate format of digest list + * @size: buffer size + * @buf: buffer containing the digest list + * + * This function validates the format of the passed digest list. + * + * Return: 0 if the digest list was successfully validated, -EINVAL otherwise. + */ +static int digest_list_validate(loff_t size, void *buf) +{ + void *bufp = buf, *bufendp = buf + size; + struct compact_list_hdr *hdr; + size_t digest_len; + + while (bufp < bufendp) { + if (bufp + sizeof(*hdr) > bufendp) { + pr_err("insufficient data\n"); + return -EINVAL; + } + + hdr = bufp; + + if (hdr->version != 1) { + pr_err("unsupported version\n"); + return -EINVAL; + } + + if (hdr->_reserved != 0) { + pr_err("unexpected value for _reserved field\n"); + return -EINVAL; + } + + hdr->type = le16_to_cpu(hdr->type); + hdr->modifiers = le16_to_cpu(hdr->modifiers); + hdr->algo = le16_to_cpu(hdr->algo); + hdr->count = le32_to_cpu(hdr->count); + hdr->datalen = le32_to_cpu(hdr->datalen); + + if (hdr->algo >= HASH_ALGO__LAST) { + pr_err("invalid hash algorithm\n"); + return -EINVAL; + } + + digest_len = hash_digest_size[hdr->algo]; + + if (hdr->type >= COMPACT__LAST || + hdr->type == COMPACT_DIGEST_LIST) { + pr_err("invalid type %d\n", hdr->type); + return -EINVAL; + } + + bufp += sizeof(*hdr); + + if (hdr->datalen != hdr->count * digest_len || + bufp + hdr->datalen > bufendp) { + pr_err("invalid data\n"); + return -EINVAL; + } + + bufp += hdr->count * digest_len; + } + + return 0; +} + +/** + * _digest_list_parse - parse digest list and add/delete digests + * @size: buffer size + * @buf: buffer containing the digest list + * @op: operation to be performed + * @digest_list: digest list digests being added/deleted belong to + * + * This function parses the digest list and adds or delete the digests in the + * found digest blocks. + * + * Return: the buffer size if all digests were successfully added or deleted, + * the size of the already parsed buffer on error. + */ +static int _digest_list_parse(loff_t size, void *buf, enum ops op, + struct digest_list_item *digest_list) +{ + void *bufp = buf, *bufendp = buf + size; + struct compact_list_hdr *hdr; + struct digest_item *d = ERR_PTR(-EINVAL); + size_t digest_len; + int i; + + while (bufp < bufendp) { + if (bufp + sizeof(*hdr) > bufendp) + break; + + hdr = bufp; + bufp += sizeof(*hdr); + + digest_len = hash_digest_size[hdr->algo]; + + for (i = 0; i < hdr->count && bufp + digest_len <= bufendp; + i++, bufp += digest_len) { + switch (op) { + case DIGEST_LIST_ADD: + d = digest_add(bufp, hdr->algo, hdr->type, + digest_list, bufp - buf, + (void *)hdr - buf); + if (IS_ERR(d)) { + pr_err( + "failed to add a digest from %s\n", + digest_list->label); + goto out; + } + + break; + case DIGEST_LIST_DEL: + digest_del(bufp, hdr->algo, hdr->type, + digest_list, bufp - buf, + (void *)hdr - buf); + break; + default: + break; + } + } + } +out: + return bufp - buf; +} + +/** + * get_digest_list - get the digest list extracted digests will be associated to + * @size: buffer size + * @buf: buffer containing the digest list + * @op: digest list operation + * @actions: actions performed on the digest list being processed + * @digest: digest of the digest list + * @algo: digest algorithm + * @label: label to identify the digest list (e.g. file name) + * + * This function retrieves the digest list item for the passed digest and + * algorithm. If it is not found at addition time, this function creates a new + * one. + * + * This function prevents the imbalance of digests (references left after + * delete) by ensuring that only digest lists that were previously added can be + * deleted. + * + * This function also ensures that the actions done at the time of addition are + * also performed at the time of deletion (it would guarantee that also deletion + * is notified to remote verifiers). + * + * Return: the retrieved/created digest list item on success, an error pointer + * otherwise. + */ +static struct digest_list_item *get_digest_list(loff_t size, void *buf, + enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, + const char *label) +{ + struct digest_item *d; + struct digest_list_item *digest_list; + int digest_len = hash_digest_size[algo]; + + switch (op) { + case DIGEST_LIST_ADD: + /* Add digest list to be associated to each digest. */ + d = digest_list_add(digest, algo, size, buf, actions, label); + if (IS_ERR(d)) + return (void *)d; + + digest_list = list_first_entry(&d->refs, + struct digest_list_item_ref, list)->digest_list; + break; + case DIGEST_LIST_DEL: + /* Lookup digest list to delete the references. */ + d = __digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, + NULL); + if (!d) { + print_hex_dump(KERN_ERR, + "digest list digest not found: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-ENOENT); + } + + digest_list = list_first_entry(&d->refs, + struct digest_list_item_ref, list)->digest_list; + + /* + * Reject deletion if there are actions done at addition time + * that are currently not being performed. + */ + if ((digest_list->actions & actions) != digest_list->actions) { + pr_err("missing actions, add: %d, del: %d\n", + digest_list->actions, actions); + return ERR_PTR(-EPERM); + } + + break; + default: + return ERR_PTR(-EINVAL); + } + + return digest_list; +} + +/** + * digest_list_parse - parse a digest list + * @size: buffer size + * @buf: buffer containing the digest list + * @op: digest list operation + * @actions: actions performed on the digest list being processed + * @digest: digest of the digest list + * @algo: digest algorithm + * @label: label to identify the digest list (e.g. file name) + * + * This function parses the passed digest list and executed the requested + * operation. If the operation cannot be successfully executed, this function + * performs a rollback to the previous state. + * + * Return: the buffer size on success, a negative value otherwise. + */ +int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, const char *label) +{ + struct digest_list_item *digest_list; + enum ops rollback_op = (op == DIGEST_LIST_ADD) ? + DIGEST_LIST_DEL : DIGEST_LIST_ADD; + int ret, rollback_size; + + ret = digest_list_validate(size, buf); + if (ret < 0) + return ret; + + digest_list = get_digest_list(size, buf, op, actions, digest, algo, + label); + if (IS_ERR(digest_list)) + return PTR_ERR(digest_list); + + ret = _digest_list_parse(size, buf, op, digest_list); + if (ret < 0) + goto out; + + if (ret != size) { + rollback_size = ret; + + ret = _digest_list_parse(rollback_size, buf, rollback_op, + digest_list); + if (ret != rollback_size) + pr_err("rollback failed\n"); + + ret = -EINVAL; + } +out: + /* Delete digest list on unsuccessful add or successful delete. */ + if ((op == DIGEST_LIST_ADD && ret < 0) || + (op == DIGEST_LIST_DEL && ret == size)) + digest_list_del(digest, algo, actions, digest_list); + + return ret; +} From patchwork Tue Sep 14 16:33:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 511250 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 D8F4DC433FE for ; Tue, 14 Sep 2021 16:36:00 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C3D1761165 for ; Tue, 14 Sep 2021 16:36:00 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229968AbhINQhE (ORCPT ); Tue, 14 Sep 2021 12:37:04 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3800 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229870AbhINQg5 (ORCPT ); Tue, 14 Sep 2021 12:36:57 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H886W5Xzmz67Pmr; Wed, 15 Sep 2021 00:33:23 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Tue, 14 Sep 2021 18:35:37 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [PATCH v3 06/13] diglim: IMA info Date: Tue, 14 Sep 2021 18:33:54 +0200 Message-ID: <20210914163401.864635-7-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210914163401.864635-1-roberto.sassu@huawei.com> References: <20210914163401.864635-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml754-chm.china.huawei.com (10.201.108.204) 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 Introduce diglim_ima_get_info() to retrieve the digest and the actions performed by IMA on the passed digest list file or buffer. diglim_ima_get_info() requires the caller to write-lock the file to ensure that the file content the integrity status is retrieved from didn't change since the time the buffer passed as argument was filled. Signed-off-by: Roberto Sassu --- MAINTAINERS | 1 + security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/diglim.h | 6 ++ security/integrity/diglim/ima.c | 122 +++++++++++++++++++++++++++++ security/integrity/integrity.h | 4 + 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 security/integrity/diglim/ima.c diff --git a/MAINTAINERS b/MAINTAINERS index f5959936d490..f10690dda734 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5514,6 +5514,7 @@ F: Documentation/security/diglim/introduction.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h +F: security/integrity/diglim/ima.c F: security/integrity/diglim/methods.c F: security/integrity/diglim/parser.c diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile index 34e4e154fff3..880dc5300792 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGLIM) += diglim.o -diglim-y := methods.o parser.o +diglim-y := methods.o parser.o ima.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index afdb0affdc5e..ebe8936520b5 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -22,6 +22,8 @@ #include #include +#include "../integrity.h" + #define MAX_DIGEST_SIZE 64 #define HASH_BITS 10 #define DIGLIM_HTABLE_SIZE (1 << HASH_BITS) @@ -221,4 +223,8 @@ void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions, u8 *digest, enum hash_algo algo, const char *label); + +int diglim_ima_get_info(struct file *file, u8 *buffer, size_t buffer_len, + char *event_name, u8 *digest, size_t digest_len, + enum hash_algo *algo, u8 *actions); #endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/ima.c b/security/integrity/diglim/ima.c new file mode 100644 index 000000000000..2cc1ec1299f8 --- /dev/null +++ b/security/integrity/diglim/ima.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Functions to retrieve the integrity status from IMA. + */ + +#include +#include +#include + +#include "diglim.h" + +static int diglim_ima_get_info_file(struct file *file, u8 *digest, + size_t digest_len, enum hash_algo *algo, + u8 *actions) +{ + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + int ret = -ENOENT; + + iint = integrity_iint_find(inode); + if (!iint) + return ret; + + mutex_lock(&iint->mutex); + /* File digest has not been calculated. */ + if (!(iint->flags & IMA_COLLECTED)) + goto out; + + ret = 0; + + if (iint->flags & IMA_MEASURED) + *actions |= 1 << COMPACT_ACTION_IMA_MEASURED; + + if (iint->flags & IMA_APPRAISED) + *actions |= 1 << COMPACT_ACTION_IMA_APPRAISED; + + if (test_bit(IMA_DIGSIG, &iint->atomic_flags)) + *actions |= 1 << COMPACT_ACTION_IMA_APPRAISED_DIGSIG; + + if (algo) + *algo = iint->ima_hash->algo; + if (digest) + memcpy(digest, iint->ima_hash->digest, hash_digest_size[*algo]); +out: + mutex_unlock(&iint->mutex); + return ret; +} + +static int diglim_ima_get_info_buffer(u8 *buffer, size_t buffer_len, + char *event_name, u8 *digest, + size_t digest_len, enum hash_algo *algo, + u8 *actions) +{ + int ret; + + ret = ima_measure_critical_data("diglim", event_name, buffer, + buffer_len, false, digest, digest_len); + if (ret < 0 && ret != -EEXIST) + return -ENOENT; + + *algo = ima_get_current_hash_algo(); + + if (!ret || ret == -EEXIST) + *actions |= 1 << COMPACT_ACTION_IMA_MEASURED; + + return 0; +} + +/** + * diglim_ima_get_info - retrieve the integrity status of digest list from IMA + * @file: file to retrieve the integrity status from + * @buffer: buffer to retrieve the integrity status from (alternative to file) + * @buffer_len: buffer length + * @event_name: name of the event to be generated by IMA for buffer measurement + * @digest: digest of the file or the buffer + * @digest_len: digest length + * @algo: digest algorithm + * @actions: actions performed on the file or the buffer + * + * This function attempts to retrieve some information from the passed digest + * list file or buffer: the digest, its algorithm, and the actions performed by + * IMA. + * + * This function first attempts to retrieve the information from the file, and + * if unsuccessful, attempts with the buffer. + * + * The caller must prevent writes to the file with deny_write_access() to ensure + * that the file content the integrity status is retrieved from didn't change + * since the time the buffer passed as argument was filled. + * + * Return: 0 if the information has been successfully retrieved, -ENOENT + * otherwise. + */ +int diglim_ima_get_info(struct file *file, u8 *buffer, size_t buffer_len, + char *event_name, u8 *digest, size_t digest_len, + enum hash_algo *algo, u8 *actions) +{ + int ret = -ENOENT; + + /* Ensure that the file is write-locked. */ + if (file && atomic_read(&file_inode(file)->i_writecount) >= 0) + return -EINVAL; + + if (file) { + ret = diglim_ima_get_info_file(file, digest, digest_len, algo, + actions); + if (!ret && (*actions & (1 << COMPACT_ACTION_IMA_MEASURED))) + return ret; + } + + if (buffer) { + ret = diglim_ima_get_info_buffer(buffer, buffer_len, event_name, + digest, digest_len, algo, + actions); + } + + return ret; +} diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 74919b638f52..de5dde382f11 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 @@ -285,3 +288,4 @@ static inline void __init add_to_platform_keyring(const char *source, { } #endif +#endif /*__INTEGRITY_H*/ From patchwork Tue Sep 14 16:33:57 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 511251 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.9 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,UNWANTED_LANGUAGE_BODY, 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 766C4C4167B for ; Tue, 14 Sep 2021 16:35:52 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5BAEC61107 for ; Tue, 14 Sep 2021 16:35:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231185AbhINQhI (ORCPT ); Tue, 14 Sep 2021 12:37:08 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3803 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229931AbhINQg6 (ORCPT ); Tue, 14 Sep 2021 12:36:58 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H886f5F28z67x8s; Wed, 15 Sep 2021 00:33:30 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Tue, 14 Sep 2021 18:35:39 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [PATCH v3 09/13] diglim: Interfaces - digest_list_label Date: Tue, 14 Sep 2021 18:33:57 +0200 Message-ID: <20210914163401.864635-10-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210914163401.864635-1-roberto.sassu@huawei.com> References: <20210914163401.864635-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml754-chm.china.huawei.com (10.201.108.204) 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 Introduce the digest_list_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 --- security/integrity/diglim/fs.c | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index 4913c1df2918..deeb04f3c42c 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -37,6 +37,13 @@ static struct dentry *diglim_dir; * removed. */ static struct dentry *digest_lists_loaded_dir; +/** + * DOC: digest_list_label + * + * digest_list_label can be used to set a label to be applied to the next digest + * list (buffer) loaded through digest_list_add. + */ +static struct dentry *digest_list_label_dentry; /** * DOC: digest_list_add * @@ -553,6 +560,40 @@ static const struct file_operations digest_list_upload_ops = { .llseek = generic_file_llseek, }; +/* + * digest_list_label_write: write label for next uploaded digest list. + */ +static ssize_t digest_list_label_write(struct file *file, + const char __user *buf, size_t datalen, + loff_t *ppos) +{ + int rc, i; + + if (datalen >= sizeof(digest_list_label)) + return -EINVAL; + + rc = copy_from_user(digest_list_label, buf, datalen); + if (rc) + return -EFAULT; + + for (i = 0; i < datalen; i++) { + if (!isgraph(digest_list_label[i]) && + digest_list_label[i] != '\0') { + memset(digest_list_label, 0, sizeof(digest_list_label)); + return -EINVAL; + } + } + + return datalen; +} + +static const struct file_operations digest_list_label_ops = { + .open = generic_file_open, + .write = digest_list_label_write, + .read = seq_read, + .llseek = generic_file_llseek, +}; + static int __init diglim_fs_init(void) { diglim_dir = securityfs_create_dir("diglim", integrity_dir); @@ -576,8 +617,15 @@ static int __init diglim_fs_init(void) if (IS_ERR(digest_list_del_dentry)) goto out; + digest_list_label_dentry = securityfs_create_file("digest_list_label", + 0600, diglim_dir, NULL, + &digest_list_label_ops); + if (IS_ERR(digest_list_label_dentry)) + goto out; + return 0; out: + securityfs_remove(digest_list_label_dentry); securityfs_remove(digest_list_del_dentry); securityfs_remove(digest_list_add_dentry); securityfs_remove(digest_lists_loaded_dir); From patchwork Tue Sep 14 16:33:59 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 511249 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.9 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,UNWANTED_LANGUAGE_BODY, 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 2ED90C43219 for ; Tue, 14 Sep 2021 16:36:56 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1519B60FD7 for ; Tue, 14 Sep 2021 16:36:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229707AbhINQiM (ORCPT ); Tue, 14 Sep 2021 12:38:12 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3805 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229464AbhINQiL (ORCPT ); Tue, 14 Sep 2021 12:38:11 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H8883086vz67vy0; Wed, 15 Sep 2021 00:34:43 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Tue, 14 Sep 2021 18:36:51 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu , kernel test robot Subject: [PATCH v3 11/13] diglim: Interfaces - digests_count Date: Tue, 14 Sep 2021 18:33:59 +0200 Message-ID: <20210914163401.864635-12-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210914163401.864635-1-roberto.sassu@huawei.com> References: <20210914163401.864635-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml754-chm.china.huawei.com (10.201.108.204) 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 Introduce the digests_count interface, which shows the current number of items stored in the hash table by type. Reported-by: kernel test robot (frame size warning) Signed-off-by: Roberto Sassu --- security/integrity/diglim/fs.c | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index e383254c72a4..467ff4f7c0ce 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -24,6 +24,7 @@ #include "diglim.h" #define MAX_DIGEST_LIST_SIZE (64 * 1024 * 1024 - 1) +#define TMPBUF_SIZE 512 static struct dentry *diglim_dir; /** @@ -37,6 +38,13 @@ static struct dentry *diglim_dir; * removed. */ static struct dentry *digest_lists_loaded_dir; +/** + * DOC: digests_count + * + * digests_count shows the current number of digests stored in the hash + * table by type. + */ +static struct dentry *digests_count; /** * DOC: digest_list_label * @@ -74,6 +82,39 @@ static struct dentry *digest_list_del_dentry; char digest_query[CRYPTO_MAX_ALG_NAME + 1 + IMA_MAX_DIGEST_SIZE * 2 + 1]; char digest_list_label[NAME_MAX + 1]; +static char *types_str[COMPACT__LAST] = { + [COMPACT_PARSER] = "Parser", + [COMPACT_FILE] = "File", + [COMPACT_METADATA] = "Metadata", + [COMPACT_DIGEST_LIST] = "Digest list", +}; + +static ssize_t diglim_show_htable_len(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char *tmpbuf; + ssize_t ret, len = 0; + int i; + + tmpbuf = kmalloc(TMPBUF_SIZE, GFP_KERNEL); + if (!tmpbuf) + return -ENOMEM; + + for (i = 0; i < COMPACT__LAST; i++) + len += scnprintf(tmpbuf + len, TMPBUF_SIZE - len, + "%s digests: %lu\n", types_str[i], + diglim_htable[i].len); + + ret = simple_read_from_buffer(buf, count, ppos, tmpbuf, len); + kfree(tmpbuf); + return ret; +} + +static const struct file_operations htable_len_ops = { + .read = diglim_show_htable_len, + .llseek = generic_file_llseek, +}; + static int parse_digest_list_filename(const char *digest_list_filename, u8 *digest, enum hash_algo *algo) { @@ -779,6 +820,12 @@ static int __init diglim_fs_init(void) if (IS_ERR(digest_lists_loaded_dir)) goto out; + digests_count = securityfs_create_file("digests_count", 0440, + diglim_dir, NULL, + &htable_len_ops); + if (IS_ERR(digests_count)) + goto out; + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, diglim_dir, NULL, &digest_list_upload_ops); @@ -809,6 +856,7 @@ static int __init diglim_fs_init(void) securityfs_remove(digest_list_label_dentry); securityfs_remove(digest_list_del_dentry); securityfs_remove(digest_list_add_dentry); + securityfs_remove(digests_count); securityfs_remove(digest_lists_loaded_dir); securityfs_remove(diglim_dir); return -1; From patchwork Tue Sep 14 16:34:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 511248 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 3665EC43217 for ; Tue, 14 Sep 2021 16:37:02 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 11C4D60F12 for ; Tue, 14 Sep 2021 16:37:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230221AbhINQiQ (ORCPT ); Tue, 14 Sep 2021 12:38:16 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3807 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229617AbhINQiN (ORCPT ); Tue, 14 Sep 2021 12:38:13 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4H887q4VX9z6H6kl; Wed, 15 Sep 2021 00:34:31 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2308.8; Tue, 14 Sep 2021 18:36:52 +0200 From: Roberto Sassu To: , , CC: , , , , , Roberto Sassu Subject: [PATCH v3 13/13] diglim: Tests Date: Tue, 14 Sep 2021 18:34:01 +0200 Message-ID: <20210914163401.864635-14-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210914163401.864635-1-roberto.sassu@huawei.com> References: <20210914163401.864635-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml754-chm.china.huawei.com (10.201.108.204) 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 Introduce a number of tests to ensure that DIGLIM 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; - digest_list_add_del_test_file_upload_measured; - digest_list_add_del_test_file_upload_measured_chown; - digest_list_check_measurement_list_test_file_upload; - digest_list_check_measurement_list_test_buffer_upload. The tests are in tools/testing/selftests/diglim/selftest.c. A description of the tests can be found in Documentation/security/diglim/tests.rst. Signed-off-by: Roberto Sassu --- Documentation/security/diglim/index.rst | 1 + Documentation/security/diglim/tests.rst | 70 + MAINTAINERS | 2 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/diglim/Makefile | 19 + tools/testing/selftests/diglim/common.c | 135 ++ tools/testing/selftests/diglim/common.h | 32 + tools/testing/selftests/diglim/config | 3 + tools/testing/selftests/diglim/selftest.c | 1442 +++++++++++++++++++++ 9 files changed, 1705 insertions(+) create mode 100644 Documentation/security/diglim/tests.rst create mode 100644 tools/testing/selftests/diglim/Makefile create mode 100644 tools/testing/selftests/diglim/common.c create mode 100644 tools/testing/selftests/diglim/common.h create mode 100644 tools/testing/selftests/diglim/config create mode 100644 tools/testing/selftests/diglim/selftest.c diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst index 0f28c5ad71c0..d4ba4ce50a59 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -11,3 +11,4 @@ Digest Lists Integrity Module (DIGLIM) architecture implementation remote_attestation + tests diff --git a/Documentation/security/diglim/tests.rst b/Documentation/security/diglim/tests.rst new file mode 100644 index 000000000000..899e7d6683cf --- /dev/null +++ b/Documentation/security/diglim/tests.rst @@ -0,0 +1,70 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Testing +======= + +This section introduces a number of tests to ensure that DIGLIM 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``; +- ``digest_list_add_del_test_file_upload_measured``; +- ``digest_list_add_del_test_file_upload_measured_chown``; +- ``digest_list_check_measurement_list_test_file_upload``; +- ``digest_list_check_measurement_list_test_buffer_upload``. + +The tests are in ``tools/testing/selftests/diglim/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 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 ``buffer_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_diglim``). 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). + +The fuzzing test simply sends randomly generated digest lists to the +kernel, to ensure that the parser is robust enough to handle malformed +data. + +The ``measured`` and ``measured_chown`` variants of the +``digest_list_add_del_test`` series check whether the digest lists actions +are properly set after adding IMA rules to measure the digest lists. The +``measured`` variant is expected to match the IMA rule for critical data, +while the ``measured_chown`` variant is expected to match the IMA rule for +files with UID 3000. + +The ``digest_list_check_measurement_list_test`` tests verify the remote +attestation functionality. They verify whether IMA creates a measurement +entry for each addition and deletion of a digest list, and that the +deletion is forbidden if IMA created a measurement entry only for the +addition. + +The ``file_upload`` variant uploads a file, while the ``buffer_upload`` +variant uploads a buffer. diff --git a/MAINTAINERS b/MAINTAINERS index eac82f151d18..033c70014568 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5512,6 +5512,7 @@ F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst F: Documentation/security/diglim/remote_attestation.rst +F: Documentation/security/diglim/tests.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h @@ -5519,6 +5520,7 @@ F: security/integrity/diglim/fs.c F: security/integrity/diglim/ima.c F: security/integrity/diglim/methods.c F: security/integrity/diglim/parser.c +F: tools/testing/selftests/diglim/ DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index c852eb40c4f7..667cd738327b 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 += diglim TARGETS += drivers/dma-buf TARGETS += efivarfs TARGETS += exec diff --git a/tools/testing/selftests/diglim/Makefile b/tools/testing/selftests/diglim/Makefile new file mode 100644 index 000000000000..100c219955d7 --- /dev/null +++ b/tools/testing/selftests/diglim/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +LDFLAGS += -lcrypto + +CFLAGS += -O2 -Wall -Wl,-no-as-needed -g -I./ -I../../../../usr/include/ \ + -L$(OUTPUT) -Wl,-rpath=./ -ggdb +LDLIBS += -lpthread + +OVERRIDE_TARGETS = 1 + +TEST_GEN_PROGS = selftest +TEST_GEN_PROGS_EXTENDED = libcommon.so + +include ../lib.mk + +$(OUTPUT)/libcommon.so: common.c + $(CC) $(CFLAGS) -shared -fPIC $< $(LDLIBS) -o $@ + +$(OUTPUT)/selftest: selftest.c $(TEST_GEN_PROGS_EXTENDED) + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) -lcommon diff --git a/tools/testing/selftests/diglim/common.c b/tools/testing/selftests/diglim/common.c new file mode 100644 index 000000000000..20d693a4fc26 --- /dev/null +++ b/tools/testing/selftests/diglim/common.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Common functions. + */ + +#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, int uid) +{ + ssize_t to_write = buffer_len, written = 0; + int ret = 0, ret_seteuid, fd, cur_uid = geteuid(); + int open_flags = O_WRONLY; + struct stat st; + + if (stat(path, &st) == -1) + open_flags |= O_CREAT; + + fd = open(path, open_flags, 0644); + if (fd < 0) + return -errno; + + if (uid >= 0) { + ret_seteuid = seteuid(uid); + if (ret_seteuid < 0) + return ret_seteuid; + } + + while (to_write) { + written = write(fd, buffer + buffer_len - to_write, to_write); + if (written <= 0) { + ret = -errno; + break; + } + + to_write -= written; + } + + if (uid >= 0) { + ret_seteuid = seteuid(cur_uid); + if (ret_seteuid < 0) + return ret_seteuid; + } + + 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; +} + +int copy_file(char *src_path, char *dst_path) +{ + char *buffer; + size_t buffer_len; + int ret; + + ret = read_buffer(src_path, &buffer, &buffer_len, true, false); + if (!ret) { + ret = write_buffer(dst_path, buffer, buffer_len, -1); + free(buffer); + } + + return ret; +} diff --git a/tools/testing/selftests/diglim/common.h b/tools/testing/selftests/diglim/common.h new file mode 100644 index 000000000000..6c7979f4182e --- /dev/null +++ b/tools/testing/selftests/diglim/common.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header of common.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 1024 + +int write_buffer(char *path, char *buffer, size_t buffer_len, int uid); +int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, + bool is_char); +int copy_file(char *src_path, char *dst_path); diff --git a/tools/testing/selftests/diglim/config b/tools/testing/selftests/diglim/config new file mode 100644 index 000000000000..faafc742974c --- /dev/null +++ b/tools/testing/selftests/diglim/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/diglim/selftest.c b/tools/testing/selftests/diglim/selftest.c new file mode 100644 index 000000000000..273ba80c43fd --- /dev/null +++ b/tools/testing/selftests/diglim/selftest.c @@ -0,0 +1,1442 @@ +// 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 + * + * Functions to test DIGLIM. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __BYTE_ORDER == __BIG_ENDIAN +#include +#else +#include +#endif + +#include + +#include "common.h" +#include "../kselftest_harness.h" + +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 INTEGRITY_DIR "/sys/kernel/security/integrity" + +#define DIGEST_LIST_DIR INTEGRITY_DIR "/diglim" +#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query" +#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_list_label" +#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add" +#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del" +#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded" +#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count" + +#define IMA_POLICY_PATH INTEGRITY_DIR "/ima/policy" +#define IMA_MEASUREMENTS_PATH INTEGRITY_DIR "/ima/ascii_runtime_measurements" + +#define DIGEST_LIST_DEBUGFS_DIR "/sys/kernel/debug/fail_diglim" +#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_FILE_CHOWN, 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 { + unsigned long long 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; +} + +static void set_hdr(u8 *buf, struct compact_list_hdr *hdr) +{ + memcpy(hdr, buf, sizeof(*hdr)); + hdr->type = __le16_to_cpu(hdr->type); + hdr->modifiers = __le16_to_cpu(hdr->modifiers); + hdr->algo = __le16_to_cpu(hdr->algo); + hdr->count = __le32_to_cpu(hdr->count); + hdr->datalen = __le32_to_cpu(hdr->datalen); +} + +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 HASH_ALGO_SHA256; + + 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 = NULL, *hdr; + 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; + + digest_list->buf = 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; + hdr_array[i]._reserved = 0; + /* 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) { + 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)); + hdr = (struct compact_list_hdr *)buf_ptr; + hdr->type = __cpu_to_le16(hdr->type); + hdr->modifiers = __cpu_to_le16(hdr->modifiers); + hdr->algo = __cpu_to_le16(hdr->algo); + hdr->count = __cpu_to_le32(hdr->count); + hdr->datalen = __cpu_to_le32(hdr->datalen); + + 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->_reserved = 0; + hdr->type = hdr->type % (COMPACT__LAST - 1); + hdr->algo = hdr->algo % HASH_ALGO__LAST; + + hdr->type = __cpu_to_le16(hdr->type); + hdr->modifiers = __cpu_to_le16(hdr->modifiers); + hdr->algo = __cpu_to_le16(hdr->algo); + hdr->count = __cpu_to_le32(hdr->count); + hdr->datalen = __cpu_to_le32(hdr->datalen); + + 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, int uid) +{ + 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 || + upload_type == UPLOAD_FILE_CHOWN) { + fd = mkstemp(path_template); + if (fd < 0) + return -EPERM; + + if (upload_type == UPLOAD_FILE_CHOWN) + ret = fchown(fd, 3000, -1); + + fchmod(fd, 0644); + close(fd); + + if (ret < 0) + goto out; + + ret = write_buffer(path_template, + (char *)digest_list->buf, + digest_list->size, -1); + 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 || + upload_type == UPLOAD_FILE_CHOWN) { + buffer = (unsigned char *)path_template; + buffer_len = strlen(path_template); + } + } + + if (upload_type == UPLOAD_BUFFER) { + basename = strrchr(path_template, '/') + 1; + ret = write_buffer(DIGEST_LABEL_PATH, basename, + strlen(basename), -1); + if (ret < 0) + goto out; + } + + ret = write_buffer(path_upload, (char *)buffer, buffer_len, uid); +out: + if ((op == DIGEST_LIST_ADD && ret < 0) || + (op == DIGEST_LIST_DEL && !ret)) + 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 = -EIO; + 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) { + set_hdr(digest_list->buf + digest_list->size - size, &hdr); + + /* From digest_list_show_common(). */ + len = snprintf(digest_list_info, sizeof(digest_list_info), + "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n", + 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, + bool is_digest_list) +{ + struct compact_list_hdr hdr; + u8 *buf_ptr; + unsigned long long size; + struct digest_list_item *digest_list; + u8 digest_list_digest[64]; + 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; + + digest_list = digest_lists[i]; + size = digest_lists[i]->size; + buf_ptr = digest_lists[i]->buf; + + if (is_digest_list) { + _hex2bin(digest_list_digest, digest_list->digest_str, + hash_digest_size[digest_list->algo]); + if (!memcmp(digest_list_digest, digest, + hash_digest_size[digest_list->algo])) + count[i]++; + + continue; + } + + while (size) { + set_hdr(buf_ptr, &hdr); + + 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, + bool is_digest_list) +{ + 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; + unsigned long long 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; + + if (is_digest_list) { + 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), + "%s (actions: %d): type: %d, size: %lld\n", + label, digest_list->actions, + COMPACT_DIGEST_LIST, size); + + /* strsep() replaced '\n' with '\0' in line. */ + digest_list_info[size_info - 1] = '\0'; + + if (!strcmp(digest_list_info, line)) + count[i]++; + + continue; + } + + while (size) { + set_hdr(digest_list->buf + digest_list->size - + size, &hdr); + 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), + "%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n", + 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(u8 *digest, enum hash_algo algo, + bool is_digest_list, struct __test_metadata *_metadata) +{ + int *count_gen_list_array, *count_kernel_query_array; + int count_gen_list = 0, count_kernel_query = 0; + char digest_str[64 * 2 + 1] = { 0 }; + int i; + + count_gen_list_array = get_count_gen_lists(digest, algo, + is_digest_list); + if (!count_gen_list_array) + return -EINVAL; + + count_kernel_query_array = get_count_kernel_query(digest, algo, + is_digest_list); + 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]; + } + + _bin2hex(digest_str, digest, hash_digest_size[algo]); + + TH_LOG("digest: %s, algo: %s, gen list digests: %d, kernel digests: %d", + digest_str, 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) +{ + 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, -1); + 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); +} + +static int enable_fault_injection(void) +{ + int ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TASK_FILTER, "Y", 1, -1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_PROBABILITY, "1", 1, -1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TIMES, "10000", 5, -1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_VERBOSE, "1", 1, -1); + if (ret < 0) + return ret; + + ret = write_buffer(PROCFS_SELF_FAULT, "1", 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; + u8 digest[64]; + int ret, i, cur_queries = 1; + + while (cur_queries <= NUM_QUERIES) { + ret = getrandom(&op, sizeof(op), 0); + ASSERT_EQ(sizeof(op), 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, -1); + /* 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, -1); + 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; + + memset(digest, 0, sizeof(digest)); + *(u32 *)digest = value; + + ret = compare_count(digest, algo, false, _metadata); + ASSERT_EQ(0, ret) { + TH_LOG("count mismatch"); + } + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % MAX_DIGEST_LISTS; + + if (digest_lists[value] != NULL) { + _hex2bin(digest, digest_lists[value]->digest_str, + hash_digest_size[digest_lists[value]->algo]); + + ret = compare_count(digest, digest_lists[value]->algo, + true, _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; + struct digest_list_item *digest_list; + 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_list = digest_list_generate_random(); + ASSERT_NE(NULL, digest_list) { + TH_LOG("digest_list_generate() failed"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, + UPLOAD_FILE, -1); + if (!ret) { + ret = digest_list_check(digest_list, DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = digest_list_upload(digest_list, + DIGEST_LIST_DEL, UPLOAD_FILE, + -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + } + + free(digest_list->buf); + free(digest_list); + } + + 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); +} + +#define IMA_MEASURE_RULES "measure func=CRITICAL_DATA label=diglim euid=1000 \nmeasure func=FILE_CHECK fowner=3000 \n" + +static int load_ima_policy(char *policy) +{ + char *cur_ima_policy = NULL; + size_t cur_ima_policy_len = 0; + bool rule_found = false; + int ret; + + ret = read_buffer(IMA_POLICY_PATH, &cur_ima_policy, &cur_ima_policy_len, + true, true); + if (ret < 0) + return ret; + + rule_found = (strstr(cur_ima_policy, policy) != NULL); + free(cur_ima_policy); + + if (!rule_found) { + ret = write_buffer(IMA_POLICY_PATH, policy, strlen(policy), -1); + if (ret < 0) + return ret; + } + + return 0; +} + +FIXTURE(test_measure) +{ +}; + +FIXTURE_SETUP(test_measure) +{ + int ret; + + ret = load_ima_policy(IMA_MEASURE_RULES); + ASSERT_EQ(0, ret) { + TH_LOG("load_ima_policy() failed"); + } +} + +FIXTURE_TEARDOWN(test_measure) +{ +} + +static void digest_list_add_del_test_file_upload_measured_common( + struct __test_metadata *_metadata, + enum upload_types upload_type, uid_t uid) +{ + struct digest_list_item *digest_list; + int ret; + + digest_list = digest_list_generate(); + ASSERT_NE(NULL, digest_list) { + TH_LOG("digest_list_generate() failed"); + } + + digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED); + + ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, upload_type, + uid); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_DEL, + upload_type, uid); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_list->buf); + free(digest_list); +} + +TEST_F_TIMEOUT(test_measure, digest_list_add_del_test_file_upload_measured, + UINT_MAX) +{ + digest_list_add_del_test_file_upload_measured_common(_metadata, + UPLOAD_FILE, 1000); +} + +TEST_F_TIMEOUT(test_measure, + digest_list_add_del_test_file_upload_measured_chown, UINT_MAX) +{ + digest_list_add_del_test_file_upload_measured_common(_metadata, + UPLOAD_FILE_CHOWN, + -1); +} + +void digest_list_check_measurement_list_test_common( + struct __test_metadata *_metadata, + enum upload_types upload_type) +{ + struct digest_list_item *digest_list; + char *measurement_list = NULL; + size_t measurement_list_len; + char event_digest_name[512]; + bool entry_found; + int ret; + + digest_list = digest_list_generate(); + ASSERT_NE(NULL, digest_list) { + TH_LOG("digest_list_generate() failed"); + } + + digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED); + + ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, upload_type, + 1000); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list, + &measurement_list_len, true, true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + snprintf(event_digest_name, sizeof(event_digest_name), + "%s:%s add_%s_digest_list.%s", + hash_algo_name[digest_list->algo], + digest_list->digest_str, + upload_type == UPLOAD_FILE ? "file" : "buffer", + digest_list->filename_suffix); + + entry_found = (strstr(measurement_list, event_digest_name) != NULL); + free(measurement_list); + + ASSERT_EQ(true, entry_found) { + TH_LOG("digest list not found in measurement list"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_DEL, upload_type, -1); + ASSERT_NE(0, ret) { + TH_LOG("digest_list_upload() success unexpected"); + } + + ret = digest_list_upload(digest_list, DIGEST_LIST_DEL, upload_type, + 1000); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_list, DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + measurement_list = NULL; + + ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list, + &measurement_list_len, true, true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + snprintf(event_digest_name, sizeof(event_digest_name), + "%s:%s del_%s_digest_list.%s", + hash_algo_name[digest_list->algo], + digest_list->digest_str, + upload_type == UPLOAD_FILE ? "file" : "buffer", + digest_list->filename_suffix); + + entry_found = (strstr(measurement_list, event_digest_name) != NULL); + free(measurement_list); + + ASSERT_EQ(true, entry_found) { + TH_LOG("digest list not found in measurement list"); + } + + free(digest_list->buf); + free(digest_list); +} + +TEST_F_TIMEOUT(test_measure, + digest_list_check_measurement_list_test_file_upload, UINT_MAX) +{ + digest_list_check_measurement_list_test_common(_metadata, UPLOAD_FILE); +} + +TEST_F_TIMEOUT(test_measure, + digest_list_check_measurement_list_test_buffer_upload, UINT_MAX) +{ + digest_list_check_measurement_list_test_common(_metadata, + UPLOAD_BUFFER); +} + +TEST_HARNESS_MAIN