linux-integrity.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC][PATCH 00/12] Huawei Digest Lists
@ 2021-06-25 16:56 Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data() Roberto Sassu
                   ` (11 more replies)
  0 siblings, 12 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

Introduction

Huawei Digest Lists is a simple in kernel database for storing file and
metadata digests and for reporting to its users (e.g. Integrity
Measurement Architecture or IMA) whether the digests are in the database
or not. The choice of placing it in the kernel and not in a user space
process is explained later in the Security Assumptions section.

The database is populated by directly uploading the so called digest
lists, a set of digests concatenated together and preceded by a header
including information about them (e.g. whether the file or metadata with
a given digest is immutable or not). Digest lists are stored in the
kernel memory as they are, the kernel just builds indexes to easily
lookup digests.

The kernel supports only one digest list format called compact. However,
alternative formats (e.g. the RPM header) can be supported through a
user space parser that, by invoking the appropriate library (more can be
added), converts the original digest list to the compact format and
uploads it to the kernel.

The database keeps track of whether digest lists have been processed in
some way (e.g. measured or appraised by IMA). This is important for
example for remote attestation, so that remote verifiers understand what
has been loaded in the database.

It is a transactional database, i.e. it has the ability to roll back to
the beginning of the transaction if an error occurred during the
addition of a digest list (the deletion operation always succeeds). This
capability has been tested with an ad-hoc fault injection mechanism
capable of simulating failures during the operations.

Finally, the database exposes to user space, through securityfs, the
digest lists currently loaded, the number of digests added, a query
interface and an interface to set digest list labels.

Binary Integrity

Integrity is a fundamental security property in information systems.
Integrity could be described as the condition in which a generic
component is just after it has been released by the entity that created
it.

One way to check whether a component is in this condition (called binary
integrity) is to calculate its digest and to compare it with a reference
value (i.e. the digest calculated in controlled conditions, when the
component is released).

IMA, a software part of the integrity subsystem, can perform such
evaluation and execute different actions:

-   store the digest in an integrity-protected measurement list, so that
    it can be sent to a remote verifier for analysis;
-   compare the calculated digest with a reference value (usually
    protected with a signature) and deny operations if the file is found
    corrupted;
-   store the digest in the system log.

Contribution

Huawei Digest Lists facilitates the provisioning of reference values for
system and application files from software vendors, to the kernel.

Possible sources for digest lists are:

-   RPM headers;
-   Debian repository metadata.

These sources are already usable without any modification required for
Linux vendors.

If digest list sources are signed (usually they are, like the ones
above), remote verifiers could identify their provenance, or Linux
vendors could prevent the loading of unsigned ones or those signed with
an untrusted key.

Possible Usages

Provisioned reference values can be used (e.g. by IMA) to make
integrity-related decisions (allow list or deny list).

Possible usages for IMA are:

-   avoid recording measurement of files whose digest is found in the
    pre-provisioned reference values:
    -   reduces the number of TPM operations (PCR extend);
    -   could make a TPM PCR predictable, as the PCR would not be
        affected by the temporal sequence of executions if binaries are
        known (no measurement);
-   exclusively grant access to files whose digest is found in the
    pre-provisioned reference values:
    -   faster verification time (fewer signature verifications);
    -   no need to generate a signature for every file.

Security Assumptions

Since digest lists are stored in the kernel memory, they are no
susceptible to attacks by user space processes.

A locked-down kernel, that accepts only verified kernel modules, will
allow digest lists to be added or deleted only though a well-defined and
monitored interface. In this situation, the root user is assumed to be
untrusted, i.e. it cannot subvert without being detected the mandatory
policy stating which files are accessible by the system.

Adoption

A former version of Huawei Digest Lists is used in the following OSes:

-   openEuler 20.09
    https://github.com/openeuler-mirror/kernel/tree/openEuler-20.09
-   openEuler 21.03
    https://github.com/openeuler-mirror/kernel/tree/openEuler-21.03

Originally, Huawei Digest Lists was part of IMA. In this version, it has
been redesigned as a standalone module with an API that makes its
functionality accessible by IMA and, eventually, other subsystems.

User Space Support

Digest lists can be generated and managed with digest-list-tools:

https://github.com/openeuler-mirror/digest-list-tools

It includes two main applications:

-   gen_digest_lists: generates digest lists from files in the
    filesystem or from the RPM database (more digest list sources can be
    supported);
-   manage_digest_lists: converts and uploads digest lists to the
    kernel.

Simple Usage Example

1.  Digest list generation (RPM headers and their signature are copied
    to the specified directory):

    # 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:

    # manage_digest_lists -p add-digest -d /etc/digest_lists

3.  First digest list query:

    # echo sha256-$(sha256sum /bin/cat) > \
      /sys/kernel/security/integrity/digest_lists/digest_query
    # cat /sys/kernel/security/integrity/digest_lists/digest_query
      sha256-[...]-0-file_list-rpm-coreutils-8.32-18.fc33.x86_64
       (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1,
       count: 106, datalen: 3392

4.  Second digest list query:

    # echo sha256-$(sha256sum /bin/zip) > \
      /sys/kernel/security/integrity/digest_lists/digest_query
    # cat /sys/kernel/security/integrity/digest_lists/digest_query
      sha256-[...]-0-file_list-rpm-zip-3.0-27.fc33.x86_64 (actions: 0):
       version: 1, algo: sha256, type: 2, modifiers: 1, count: 4,
       datalen: 128


Architecture

This section introduces the high level architecture.

 5. add/delete from hash table and add refs to digest list
        +---------------------------------------------+
        |                            +-----+   +-------------+         +--+
        |                            | key |-->| digest refs |-->...-->|  |
        V                            +-----+   +-------------+         +--+
 +-------------+                     +-----+   +-------------+
 | digest list |                     | key |-->| digest refs |
 |  (compact)  |                     +-----+   +-------------+
 +-------------+                     +-----+   +-------------+
        ^ 4. copy to                 | key |-->| digest refs |
        |    kernel memory           +-----+   +-------------+ kernel space
 --------------------------------------------------------------------------
        ^                                          ^             user space
        |<----------------+       3b. upload       |
 +-------------+   +------------+                  | 6. query digest
 | digest list |   | user space | 2b. convert
 |  (compact)  |   |   parser   |
 +-------------+   +------------+
 1a. upload               ^       1b. read
                          |
                   +------------+
                   | RPM header |
                   +------------+

As mentioned before, digest lists can be uploaded directly if they are
in the compact format (step 1a) or can be uploaded indirectly by the
user space parser if they are in an alternative format (steps 1b-3b).

During upload, the kernel makes a copy of the digest list to the kernel
memory (step 4), and creates the necessary structures to index the
digests (hash table and an array of digest list references to locate the
digests in the digest list) (step 5).

Finally, digests can be searched from user space through a securityfs
file (step 6) or by the kernel itself.


Implementation

This section describes the implementation of Huawei Digest Lists.

Basic Definitions

This section introduces the basic definitions required to use digest
lists.

Compact Digest List Format

Compact digest lists consist of one or multiple headers defined as:

    struct compact_list_hdr {
        __u8 version;
        __u8 _reserved;
        __le16 type;
        __le16 modifiers;
        __le16 algo;
        __le32 count;
        __le32 datalen;
    } __packed;

which characterize the subsequent block of concatenated digests.

The algo field specifies the algorithm used to calculate the digest.

The count field specifies how many digests are stored in the subsequent
block of digests.

The datalen field specifies the length of the subsequent block of
digests (it is redundant, it is the same as
hash_digest_size[algo] * count).

Compact Types

Digests can be of different types:

-   COMPACT_PARSER: digests of executables which are given the ability
    to parse digest lists not in the compact format and to upload to the
    kernel the digest list converted to the compact format;
-   COMPACT_FILE: digests of regular files;
-   COMPACT_METADATA: digests of file metadata (e.g. the digest
    calculated by EVM to verify a portable signature);
-   COMPACT_DIGEST_LIST: digests of digest lists (only used internally
    by the kernel).

Different users of Huawei Digest Lists might query digests with
different compact types. For example, IMA would be interested in
COMPACT_FILE, as it deals with regular files, while EVM would be
interested in COMPACT_METADATA, as it verifies file metadata.

Compact Modifiers

Digests can also have specific attributes called modifiers (bit
position):

-   COMPACT_MOD_IMMUTABLE: file content or metadata should not be
    modifiable.

IMA might use this information to deny open for writing, or EVM to deny
setxattr operations.

Actions

This section defines a set of possible actions that have been executed
on the digest lists (bit position):

-   COMPACT_ACTION_IMA_MEASURED: the digest list has been measured by
    IMA;
-   COMPACT_ACTION_IMA_APPRAISED: the digest list has been successfully
    appraised by IMA;
-   COMPACT_ACTION_IMA_APPRAISED_DIGSIG: the digest list has been
    successfully appraised by IMA by verifying a digital signature.

This information might help users of Huawei Digest Lists to decide
whether to use the result of a queried digest.

For example, if a digest belongs to a digest list that was not measured
before, IMA should ignore the result of the query as the measurement
list sent to remote verifiers would lack how the database was populated.

Compact Digest List Example

    version: 1, type: 2, modifiers: 0 algo: 4, count: 3, datalen: 96
    <SHA256 digest1><SHA256 digest2><SHA256 digest3>
    version: 1, type: 3, modifiers: 1 algo: 6, count: 2, datalen: 128
    <SHA512 digest1><SHA512 digest2>

This digest list consists of two blocks. The first block contains three
SHA256 digests of regular files. The second block contains two SHA512
digests of immutable metadata.

Compact Digest List Operations

Finally, this section defines the possible operations that can be
performed with digest lists:

-   DIGEST_LIST_ADD: the digest list is being added;
-   DIGEST_LIST_DEL: the digest list is being deleted.

Objects

This section defines the objects to manage digest lists:

-   digest_list_item: represents a digest list;
-   digest_list_item_ref: represents a reference to a digest list, i.e.
    the location at which a digest within a digest list can be accessed;
-   digest_item: represents a unique digest.

They are represented in the following class diagram:

 digest_offset,-----------+
 hdr_offset               |
                          |
 +------------------+     |     +----------------------+
 | digest_list_item |--- N:1 ---| digest_list_item_ref |
 +------------------+           +----------------------+
                                           |
                                          1:N
                                           |
                                    +-------------+
                                    | digest_item |
                                    +-------------+

A digest_list_item is associated to one or multiple
digest_list_item_ref, one for each digest it contains. However, a
digest_list_item_ref is associated to only one digest_list_item, as it
represents a single location within a specific digest list.

Given that a digest_list_item_ref represents a single location, it is
associated to only one digest_item. However, a digest_item can have
multiple references (as it might appears multiple times within the same
digest list or in different digest lists, if it is duplicated).

A digest_list_item is defined as:

    struct digest_list_item {
        loff_t size;
        u8 *buf;
        u8 actions;
        u8 digest[64];
        enum hash_algo algo;
        const char *label;
    };

-   size: size of the digest list buffer;
-   buf: digest list buffer;
-   actions: actions performed on the digest list;
-   digest: digest of the digest list;
-   algo: digest algorithm;
-   label: label used to identify the digest list (e.g. file name).

A digest_list_item_ref is defined as:

    struct digest_list_item_ref {
        struct digest_list_item *digest_list;
        loff_t digest_offset;
        loff_t hdr_offset;
    };

-   digest_list: pointer to a digest_list_item structure;
-   digest_offset: offset of the digest related to the digest list
    buffer;
-   hdr_offset: offset of the header of the digest block containing the
    digest.

A digest_item is defined as:

    struct digest_item {
        struct hlist_node hnext;
        struct digest_list_item_ref *refs;
    };

-   hnext: pointers of the hash table;
-   refs: array of digest_list_item_ref structures including a
    terminator (protected by RCU).

All digest list references are stored for a given digest, so that a
query result can include the OR of the modifiers and actions of each
referenced digest list.

The relationship between the described objects can be graphically
represented as:

 Hash table            +-------------+         +-------------+
 PARSER      +-----+   | digest_item |         | digest_item |
 FILE        | key |-->|             |-->...-->|             |
 METADATA    +-----+   |ref0|...|refN|         |ref0|...|refN|
                       +-------------+         +-------------+
            ref0:         |                               | refN:
            digest_offset | +-----------------------------+ digest_offset
            hdr_offset    | |                               hdr_offset
                          V V
                     +--------------------+
                     |  digest_list_item  |
                     |                    |
                     | size, buf, actions |
                     +--------------------+
                          ^
                          |
 Hash table            +-------------+         +-------------+
 DIGEST_LIST +-----+   |ref0         |         |ref0         |
             | key |-->|             |-->...-->|             |
             +-----+   | digest_item |         | digest_item |
                       +-------------+         +-------------+

The reference for the digest of the digest list differs from the
references for the other digest types. digest_offset and hdr_offset are
set to zero, so that the digest of the digest list is retrieved from the
digest_list_item structure directly (see get_digest() below).

Finally, this section defines useful helpers to access a digest or the
header the digest belongs to. For example:

 static inline struct compact_list_hdr *get_hdr(
                                      struct digest_list_item *digest_list,
                                      loff_t hdr_offset)
 {
         return (struct compact_list_hdr *)(digest_list->buf + hdr_offset);
 }

the header can be obtained by summing the address of the digest list
buffer in the digest_list_item structure with hdr_offset.

Similarly:

 static inline u8 *get_digest(struct digest_list_item *digest_list,
                              loff_t digest_offset, loff_t hdr_offset)
 {
         /* Digest list digest is stored in a different place. */
         if (!digest_offset)
                 return digest_list->digest;
         return digest_list->buf + digest_offset;
 }

the digest can be obtained by summing the address of the digest list
buffer with digest_offset (except for the digest lists, where the digest
is stored in the digest field of the digest_list_item structure).

Methods

This section introduces the methods requires to manage the three objects
defined.

digest_item Methods

digest_add()

digest_add() first checks in the hash table for the passed type if a
digest_item for the same digest already exists. If not, it creates a new
one. Then, digest_add() calls digest_list_ref_add() to add a new
reference of the digest list being added to the found or new
digest_item.

digest_del()

digest_del() also searches the digest_item in the hash table. It should
be always found, as digest lists can be deleted only if they were added
before. Then, digest_del() calls digest_list_ref_del() to delete a
reference of the digest list being deleted from the found digest_item.

digest_lookup()

digest_lookup() searches the passed digest in the hash table. Then, it
returns immediately a digest_item (or NULL if the digest is not found)
if the modifiers and actions information are not requested by the
caller, or iterates over all the valid references of the digest and
calculates the OR for both of them. Iteration in the array of references
ends when the digest list pointer in a reference is set to NULL. Access
to the refs array is protected by RCU to avoid access to digest lists
being added or deleted (update is serialized by the securityfs
interfaces).

digest_lookup() is not exposed to the rest of the kernel, because access
to the returned digest_item outside RCU would be illegal.

digest_get_info()

digest_get_info() is the public version of digest_lookup(), which does
not return a digest_item but just the resulting modifiers and actions
from the OR of the modifiers and actions from the referenced digest
lists.

digest_list_item_ref Methods

digest_list_ref_add()

digest_list_ref_add() adds a new reference at the end of the refs array
(also keeps a terminator as the last element). It does not search for
duplicates, as a duplicate reference simply means that the digest
appears multiple times in the digest list. digest_list_ref_add() does
not add the new element in place, but first creates a copy of the
current refs array and uses RCU to replace it with the new one.

digest_list_ref_del()

digest_list_ref_del() first searches in the refs array a reference to a
given digest list. Then, it invalidates the found reference so that it
is skipped by the reader. Afterwards, it tries to allocate a smaller
refs array (with enough slots to store the valid references, except the
one being deleted). If memory allocation succeeds, digest_list_ref_del()
copies the valid references to the copy of refs and uses RCU to replace
the original refs. Otherwise, it keeps the original refs with the
invalidated reference.

digest_list_item Methods

digest_list_add()

digest_list_add() first searches the digest of the digest list in the
hash table for the COMPACT_DIGEST_LIST type. Addition can be done if the
digest list is not found (it is pointless to load the same digest list
again). digest_list_add() then creates a new digest_item, representing
the digest of the digest list, a special digest_list_item_ref with
digest_offset and hdr_offset set to zero, and a new digest_list_item.

digest_list_del()

digest_list_del() also searches the digest of the digest list in the
hash table for the COMPACT_DIGEST_LIST type. Deletion can be done only
if the digest list is found. digest_list_del() then deletes the
digest_list_item, the special digest_list_item_ref and the digest_item.

Parser

This section introduces the necessary functions to parse a digest list
and to execute the requested operation.

The main function is digest_list_parse(), which coordinates the various
steps required to add or delete a digest list, and has the logic to roll
back when one of the steps fails.

1.  Calls digest_list_validate() to validate the passed buffer
    containing the digest list to ensure that the format is correct.
2.  Calls get_digest_list() to create a new digest_list_item for the add
    operation, or to retrieve the existing one for the delete operation.
    get_digest_list() refuses to add digest lists that were previously
    added and to delete digest lists that weren't previously added.
    Also, get_digest_list() refuses to delete digest lists that are not
    processed in the same way as when they were added (it would
    guarantee that also deletion is notified to remote verifiers).
3.  Calls _digest_list_parse() which takes the created/retrieved
    digest_list_item and adds or delete the digests included in the
    digest list.
4.  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.
5.  digest_list_parse() deletes the digest_list_item on unsuccessful add
    or successful delete.

Interfaces

This section introduces the interfaces in
<securityfs>/integrity/digest_lists necessary to interact with Huawei
Digest Lists.

digest_list_add

digest_list_add can be used to upload a digest list and add the digests
to the hash table; passed data are interpreted as file path if the first
byte is / or as the digest list itself otherwise.

ima_measure_critical_data() is called to calculate the digest of the
digest list and eventually, if an appropriate rule is set in the IMA
policy, to measure it.

digest_list_del

digest_list_del can be used to upload a digest list and delete the
digests from the hash table; data are interpreted in the same way as
described for digest_list_add.

digest_lists_loaded

digest_lists_loaded is a directory containing two files for each loaded
digest list: one shows the digest list in binary format, and the other
(with .ascii prefix) shows the digest list in ASCII format.

Files are added and removed at the same time digest lists are added and
removed.

digest_label

digest_label can be used to set a label to be applied to the next digest
list (buffer) loaded through digest_list_add.

digest_query

digest_query: allows to write a query in the format <algo>-<digest> and
to obtain all digest lists that include that digest.

digests_count

digests_count shows the current number of digests stored in the hash
table by type.


Testing

This section introduces a number of tests to ensure that Huawei Digest
Lists works as expected:

-   digest_list_add_del_test_file_upload;
-   digest_list_add_del_test_file_upload_fault;
-   digest_list_add_del_test_buffer_upload;
-   digest_list_add_del_test_buffer_upload_fault;
-   digest_list_fuzzing_test.

The tests are in tools/testing/selftests/digest_lists/selftest.c.

The first four tests randomly perform add, delete and query of digest
lists. They internally keep track at any time of the digest lists that
are currently uploaded to the kernel.

Also, digest lists are generated randomly by selecting an arbitrary
digest algorithm and an arbitrary the number of digests. To ensure a
good number of collisions, digests are a sequence of zeros, except for
the first four bytes that are set with a random number within a defined
range.

When a query operation is selected, a digest is chosen by getting
another random number within the same range. Then, the tests count how
many times the digest is found in the internally stored digest lists and
in the query result obtained from the kernel. The tests are successful
if the obtained numbers are the same.

The file_upload variant creates a temporary file from a generated digest
list and sends its path to the kernel, so that the file is uploaded. The
digest_upload variant directly sends the digest list buffer to the
kernel (it will be done by the user space parser after it converts a
digest list not in the compact format).

The fault variant performs the test by enabling the ad-hoc fault
injection mechanism in the kernel (accessible through
<debugfs>/fail_digest_lists). The fault injection mechanism randomly
injects errors during the addition and deletion of digest lists. When an
error occurs, the rollback mechanism performs the reverse operation
until the point the error occurred, so that the kernel is left in the
same state as when the requested operation began. Since the kernel
returns the error to user space, the tests also know that the operation
didn't succeed and behave accordingly (they also revert the internal
state).

Lastly, the fuzzing test simply sends randomly generated digest lists to
the kernel, to ensure that the parser is robust enough to handle
malformed data.

Roberto Sassu (12):
  ima: Add digest, algo, measured parameters to
    ima_measure_critical_data()
  digest_lists: Overview
  digest_lists: Basic definitions
  digest_lists: Objects
  digest_lists: Methods
  digest_lists: Parser
  digest_lists: Interfaces - digest_list_add, digest_list_del
  digest_lists: Interfaces - digest_lists_loaded
  digest_lists: Interfaces - digest_label
  digest_lists: Interfaces - digest_query
  digest_lists: Interfaces - digests_count
  digest_lists: Tests

 Documentation/security/digest_lists.rst       |  756 +++++++++++
 Documentation/security/index.rst              |    1 +
 MAINTAINERS                                   |   14 +
 include/linux/digest_lists.h                  |   24 +
 include/linux/ima.h                           |    8 +-
 include/linux/kernel_read_file.h              |    1 +
 include/uapi/linux/digest_lists.h             |   43 +
 security/integrity/Kconfig                    |    1 +
 security/integrity/Makefile                   |    1 +
 security/integrity/digest_lists/Kconfig       |   11 +
 security/integrity/digest_lists/Makefile      |    8 +
 .../integrity/digest_lists/digest_lists.h     |  155 +++
 security/integrity/digest_lists/fs.c          |  719 ++++++++++
 security/integrity/digest_lists/methods.c     |  548 ++++++++
 security/integrity/digest_lists/parser.c      |  270 ++++
 security/integrity/ima/ima.h                  |    3 +-
 security/integrity/ima/ima_appraise.c         |    3 +-
 security/integrity/ima/ima_asymmetric_keys.c  |    3 +-
 security/integrity/ima/ima_init.c             |    3 +-
 security/integrity/ima/ima_main.c             |   32 +-
 security/integrity/ima/ima_queue_keys.c       |    2 +-
 security/integrity/integrity.h                |    4 +
 security/selinux/ima.c                        |    5 +-
 tools/testing/selftests/Makefile              |    1 +
 tools/testing/selftests/digest_lists/Makefile |    6 +
 tools/testing/selftests/digest_lists/common.c |  109 ++
 tools/testing/selftests/digest_lists/common.h |   37 +
 tools/testing/selftests/digest_lists/config   |    3 +
 .../testing/selftests/digest_lists/selftest.c | 1169 +++++++++++++++++
 29 files changed, 3925 insertions(+), 15 deletions(-)
 create mode 100644 Documentation/security/digest_lists.rst
 create mode 100644 include/linux/digest_lists.h
 create mode 100644 include/uapi/linux/digest_lists.h
 create mode 100644 security/integrity/digest_lists/Kconfig
 create mode 100644 security/integrity/digest_lists/Makefile
 create mode 100644 security/integrity/digest_lists/digest_lists.h
 create mode 100644 security/integrity/digest_lists/fs.c
 create mode 100644 security/integrity/digest_lists/methods.c
 create mode 100644 security/integrity/digest_lists/parser.c
 create mode 100644 tools/testing/selftests/digest_lists/Makefile
 create mode 100644 tools/testing/selftests/digest_lists/common.c
 create mode 100644 tools/testing/selftests/digest_lists/common.h
 create mode 100644 tools/testing/selftests/digest_lists/config
 create mode 100644 tools/testing/selftests/digest_lists/selftest.c

-- 
2.25.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data()
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 18:26   ` Mimi Zohar
  2021-06-25 16:56 ` [RFC][PATCH 02/12] digest_lists: Overview Roberto Sassu
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu, Paul Moore,
	Stephen Smalley, selinux, Prakhar Srivastava, Tushar Sugandhi,
	Lakshmi Ramasubramanian

ima_measure_critical_data() allows any caller in the kernel to provide a
buffer, so that is measured by IMA if an appropriate policy is set. Some
information that could be useful to the callers are the digest of the
buffer included in the new measurement entry, the digest algorithm and
whether the buffer was measured.

This patch modifies the definition of ima_measure_critical_data() to
include three new parameters: digest, algo and measured. If they are NULL,
the function behaves as before and just measures the buffer, if requested
with the IMA policy. Otherwise, it also writes the digest, algorithm and
whether the buffer is measured to the provided pointers.

If the pointers are not NULL, the digest is calculated also if there is no
matching rule in the IMA policy.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Stephen Smalley <stephen.smalley.work@gmail.com>
Cc: selinux@vger.kernel.org
Cc: Prakhar Srivastava <prsriva02@gmail.com>
Cc: Tushar Sugandhi <tusharsu@linux.microsoft.com>
Cc: Lakshmi Ramasubramanian <nramas@linux.microsoft.com>
---
 include/linux/ima.h                          |  8 +++--
 security/integrity/ima/ima.h                 |  3 +-
 security/integrity/ima/ima_appraise.c        |  3 +-
 security/integrity/ima/ima_asymmetric_keys.c |  3 +-
 security/integrity/ima/ima_init.c            |  3 +-
 security/integrity/ima/ima_main.c            | 32 ++++++++++++++++----
 security/integrity/ima/ima_queue_keys.c      |  2 +-
 security/selinux/ima.c                       |  5 +--
 8 files changed, 44 insertions(+), 15 deletions(-)

diff --git a/include/linux/ima.h b/include/linux/ima.h
index 61d5723ec303..f7fd931456c7 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -11,6 +11,7 @@
 #include <linux/fs.h>
 #include <linux/security.h>
 #include <linux/kexec.h>
+#include <crypto/hash_info.h>
 struct linux_binprm;
 
 #ifdef CONFIG_IMA
@@ -36,7 +37,8 @@ extern void ima_kexec_cmdline(int kernel_fd, const void *buf, int size);
 extern void ima_measure_critical_data(const char *event_label,
 				      const char *event_name,
 				      const void *buf, size_t buf_len,
-				      bool hash);
+				      bool hash, u8 *digest,
+				      enum hash_algo *algo, bool *measured);
 
 #ifdef CONFIG_IMA_APPRAISE_BOOTPARAM
 extern void ima_appraise_parse_cmdline(void);
@@ -140,7 +142,9 @@ static inline void ima_kexec_cmdline(int kernel_fd, const void *buf, int size) {
 static inline void ima_measure_critical_data(const char *event_label,
 					     const char *event_name,
 					     const void *buf, size_t buf_len,
-					     bool hash) {}
+					     bool hash, u8 *digest,
+					     enum hash_algo *algo,
+					     bool *measured) {}
 
 #endif /* CONFIG_IMA */
 
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index f0e448ed1f9f..fff31065d600 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -268,7 +268,8 @@ void process_buffer_measurement(struct user_namespace *mnt_userns,
 				struct inode *inode, const void *buf, int size,
 				const char *eventname, enum ima_hooks func,
 				int pcr, const char *func_data,
-				bool buf_hash);
+				bool buf_hash, u8 *digest, enum hash_algo *algo,
+				bool *measured);
 void ima_audit_measurement(struct integrity_iint_cache *iint,
 			   const unsigned char *filename);
 int ima_alloc_init_template(struct ima_event_data *event_data,
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index ef9dcfce45d4..3fcbf1bfa449 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -357,7 +357,8 @@ int ima_check_blacklist(struct integrity_iint_cache *iint,
 		if ((rc == -EPERM) && (iint->flags & IMA_MEASURE))
 			process_buffer_measurement(&init_user_ns, NULL, digest, digestsize,
 						   "blacklisted-hash", NONE,
-						   pcr, NULL, false);
+						   pcr, NULL, false, NULL, NULL,
+						   NULL);
 	}
 
 	return rc;
diff --git a/security/integrity/ima/ima_asymmetric_keys.c b/security/integrity/ima/ima_asymmetric_keys.c
index c985418698a4..4370bf7b8866 100644
--- a/security/integrity/ima/ima_asymmetric_keys.c
+++ b/security/integrity/ima/ima_asymmetric_keys.c
@@ -62,5 +62,6 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
 	 */
 	process_buffer_measurement(&init_user_ns, NULL, payload, payload_len,
 				   keyring->description, KEY_CHECK, 0,
-				   keyring->description, false);
+				   keyring->description, false, NULL, NULL,
+				   NULL);
 }
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index 5076a7d9d23e..a4dcd15187a8 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -154,7 +154,8 @@ int __init ima_init(void)
 	ima_init_key_queue();
 
 	ima_measure_critical_data("kernel_info", "kernel_version",
-				  UTS_RELEASE, strlen(UTS_RELEASE), false);
+				  UTS_RELEASE, strlen(UTS_RELEASE), false, NULL,
+				  NULL, NULL);
 
 	return rc;
 }
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 287b90509006..04d1aed5adae 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -833,6 +833,9 @@ int ima_post_load_data(char *buf, loff_t size,
  * @pcr: pcr to extend the measurement
  * @func_data: func specific data, may be NULL
  * @buf_hash: measure buffer data hash
+ * @digest: buffer digest will be written to
+ * @algo: digest algorithm
+ * @measured: whether the buffer has been measured by IMA
  *
  * Based on policy, either the buffer data or buffer data hash is measured
  */
@@ -840,7 +843,8 @@ void process_buffer_measurement(struct user_namespace *mnt_userns,
 				struct inode *inode, const void *buf, int size,
 				const char *eventname, enum ima_hooks func,
 				int pcr, const char *func_data,
-				bool buf_hash)
+				bool buf_hash, u8 *digest, enum hash_algo *algo,
+				bool *measured)
 {
 	int ret = 0;
 	const char *audit_cause = "ENOMEM";
@@ -861,7 +865,7 @@ void process_buffer_measurement(struct user_namespace *mnt_userns,
 	int action = 0;
 	u32 secid;
 
-	if (!ima_policy_flag)
+	if (!ima_policy_flag && (!digest || !algo || !measured))
 		return;
 
 	template = ima_template_desc_buf();
@@ -883,7 +887,7 @@ void process_buffer_measurement(struct user_namespace *mnt_userns,
 		action = ima_get_action(mnt_userns, inode, current_cred(),
 					secid, 0, func, &pcr, &template,
 					func_data);
-		if (!(action & IMA_MEASURE))
+		if (!(action & IMA_MEASURE) && (!digest || !algo || !measured))
 			return;
 	}
 
@@ -914,6 +918,15 @@ void process_buffer_measurement(struct user_namespace *mnt_userns,
 		event_data.buf_len = digest_hash_len;
 	}
 
+	if (digest && algo) {
+		memcpy(digest, iint.ima_hash->digest,
+		       hash_digest_size[ima_hash_algo]);
+		*algo = ima_hash_algo;
+	}
+
+	if (!(action & IMA_MEASURE))
+		return;
+
 	ret = ima_alloc_init_template(&event_data, &entry, template);
 	if (ret < 0) {
 		audit_cause = "alloc_entry";
@@ -924,8 +937,11 @@ void process_buffer_measurement(struct user_namespace *mnt_userns,
 	if (ret < 0) {
 		audit_cause = "store_entry";
 		ima_free_template_entry(entry);
+		goto out;
 	}
 
+	if (measured)
+		*measured = true;
 out:
 	if (ret < 0)
 		integrity_audit_message(AUDIT_INTEGRITY_PCR, NULL, eventname,
@@ -956,7 +972,7 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size)
 
 	process_buffer_measurement(file_mnt_user_ns(f.file), file_inode(f.file),
 				   buf, size, "kexec-cmdline", KEXEC_CMDLINE, 0,
-				   NULL, false);
+				   NULL, false, NULL, NULL, NULL);
 	fdput(f);
 }
 
@@ -967,6 +983,9 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size)
  * @buf: pointer to buffer data
  * @buf_len: length of buffer data (in bytes)
  * @hash: measure buffer data hash
+ * @digest: buffer digest will be written to
+ * @algo: digest algorithm
+ * @measured: whether the buffer has been measured by IMA
  *
  * Measure data critical to the integrity of the kernel into the IMA log
  * and extend the pcr.  Examples of critical data could be various data
@@ -976,14 +995,15 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size)
 void ima_measure_critical_data(const char *event_label,
 			       const char *event_name,
 			       const void *buf, size_t buf_len,
-			       bool hash)
+			       bool hash, u8 *digest, enum hash_algo *algo,
+			       bool *measured)
 {
 	if (!event_name || !event_label || !buf || !buf_len)
 		return;
 
 	process_buffer_measurement(&init_user_ns, NULL, buf, buf_len, event_name,
 				   CRITICAL_DATA, 0, event_label,
-				   hash);
+				   hash, digest, algo, measured);
 }
 
 static int __init init_ima(void)
diff --git a/security/integrity/ima/ima_queue_keys.c b/security/integrity/ima/ima_queue_keys.c
index 979ef6c71f3d..97ed974651fd 100644
--- a/security/integrity/ima/ima_queue_keys.c
+++ b/security/integrity/ima/ima_queue_keys.c
@@ -165,7 +165,7 @@ void ima_process_queued_keys(void)
 						   entry->keyring_name,
 						   KEY_CHECK, 0,
 						   entry->keyring_name,
-						   false);
+						   false, NULL, NULL, NULL);
 		list_del(&entry->list);
 		ima_free_key_entry(entry);
 	}
diff --git a/security/selinux/ima.c b/security/selinux/ima.c
index 34d421861bfc..af1016dbb5aa 100644
--- a/security/selinux/ima.c
+++ b/security/selinux/ima.c
@@ -86,7 +86,8 @@ void selinux_ima_measure_state_locked(struct selinux_state *state)
 	}
 
 	ima_measure_critical_data("selinux", "selinux-state",
-				  state_str, strlen(state_str), false);
+				  state_str, strlen(state_str), false, NULL,
+				  NULL, NULL);
 
 	kfree(state_str);
 
@@ -103,7 +104,7 @@ void selinux_ima_measure_state_locked(struct selinux_state *state)
 	}
 
 	ima_measure_critical_data("selinux", "selinux-policy-hash",
-				  policy, policy_len, true);
+				  policy, policy_len, true, NULL, NULL, NULL);
 
 	vfree(policy);
 }
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 02/12] digest_lists: Overview
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data() Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 03/12] digest_lists: Basic definitions Roberto Sassu
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch adds an overview of Huawei Digest Lists to
Documentation/security/digest_lists.rst.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst | 228 ++++++++++++++++++++++++
 Documentation/security/index.rst        |   1 +
 MAINTAINERS                             |   7 +
 3 files changed, 236 insertions(+)
 create mode 100644 Documentation/security/digest_lists.rst

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
new file mode 100644
index 000000000000..8980be7836f8
--- /dev/null
+++ b/Documentation/security/digest_lists.rst
@@ -0,0 +1,228 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===================
+Huawei Digest Lists
+===================
+
+Introduction
+============
+
+Huawei Digest Lists is a simple in kernel database for storing file and
+metadata digests and for reporting to its users (e.g. Integrity Measurement
+Architecture or IMA) whether the digests are in the database or not. The
+choice of placing it in the kernel and not in a user space process is
+explained later in the Security Assumptions section.
+
+The database is populated by directly uploading the so called digest lists,
+a set of digests concatenated together and preceded by a header including
+information about them (e.g. whether the file or metadata with a given
+digest is immutable or not). Digest lists are stored in the kernel memory
+as they are, the kernel just builds indexes to easily lookup digests.
+
+The kernel supports only one digest list format called ``compact``. However,
+alternative formats (e.g. the RPM header) can be supported through a user
+space parser that, by invoking the appropriate library (more can be added),
+converts the original digest list to the compact format and uploads it to
+the kernel.
+
+The database keeps track of whether digest lists have been processed in
+some way (e.g. measured or appraised by IMA). This is important for example
+for remote attestation, so that remote verifiers understand what has been
+loaded in the database.
+
+It is a transactional database, i.e. it has the ability to roll back to the
+beginning of the transaction if an error occurred during the addition of a
+digest list (the deletion operation always succeeds). This capability has
+been tested with an ad-hoc fault injection mechanism capable of simulating
+failures during the operations.
+
+Finally, the database exposes to user space, through securityfs, the digest
+lists currently loaded, the number of digests added, a query interface and
+an interface to set digest list labels.
+
+
+Binary Integrity
+----------------
+
+Integrity is a fundamental security property in information systems.
+Integrity could be described as the condition in which a generic
+component is just after it has been released by the entity that created it.
+
+One way to check whether a component is in this condition (called binary
+integrity) is to calculate its digest and to compare it with a reference
+value (i.e. the digest calculated in controlled conditions, when the
+component is released).
+
+IMA, a software part of the integrity subsystem, can perform such
+evaluation and execute different actions:
+
+- store the digest in an integrity-protected measurement list, so that it
+  can be sent to a remote verifier for analysis;
+- compare the calculated digest with a reference value (usually protected
+  with a signature) and deny operations if the file is found corrupted;
+- store the digest in the system log.
+
+
+Contribution
+------------
+
+Huawei Digest Lists facilitates the provisioning of reference values for
+system and application files from software vendors, to the kernel.
+
+Possible sources for digest lists are:
+
+- RPM headers;
+- Debian repository metadata.
+
+These sources are already usable without any modification required for
+Linux vendors.
+
+If digest list sources are signed (usually they are, like the ones above),
+remote verifiers could identify their provenance, or Linux vendors could
+prevent the loading of unsigned ones or those signed with an untrusted key.
+
+
+Possible Usages
+---------------
+
+Provisioned reference values can be used (e.g. by IMA) to make
+integrity-related decisions (allow list or deny list).
+
+Possible usages for IMA are:
+
+- avoid recording measurement of files whose digest is found in the
+  pre-provisioned reference values:
+
+  - reduces the number of TPM operations (PCR extend);
+  - could make a TPM PCR predictable, as the PCR would not be affected by
+    the temporal sequence of executions if binaries are known
+    (no measurement);
+
+- exclusively grant access to files whose digest is found in the
+  pre-provisioned reference values:
+
+  - faster verification time (fewer signature verifications);
+  - no need to generate a signature for every file.
+
+
+Security Assumptions
+--------------------
+
+Since digest lists are stored in the kernel memory, they are no susceptible
+to attacks by user space processes.
+
+A locked-down kernel, that accepts only verified kernel modules, will allow
+digest lists to be added or deleted only though a well-defined and
+monitored interface. In this situation, the root user is assumed to be
+untrusted, i.e. it cannot subvert without being detected the mandatory
+policy stating which files are accessible by the system.
+
+Adoption
+--------
+
+A former version of Huawei Digest Lists is used in the following OSes:
+
+- openEuler 20.09
+  https://github.com/openeuler-mirror/kernel/tree/openEuler-20.09
+
+- openEuler 21.03
+  https://github.com/openeuler-mirror/kernel/tree/openEuler-21.03
+
+Originally, Huawei Digest Lists was part of IMA. In this version,
+it has been redesigned as a standalone module with an API that makes
+its functionality accessible by IMA and, eventually, other subsystems.
+
+User Space Support
+------------------
+
+Digest lists can be generated and managed with ``digest-list-tools``:
+
+https://github.com/openeuler-mirror/digest-list-tools
+
+It includes two main applications:
+
+- ``gen_digest_lists``: generates digest lists from files in the
+  filesystem or from the RPM database (more digest list sources can be
+  supported);
+- ``manage_digest_lists``: converts and uploads digest lists to the
+  kernel.
+
+
+Simple Usage Example
+--------------------
+
+1. Digest list generation (RPM headers and their signature are copied to
+   the specified directory):
+
+.. code-block:: bash
+
+ # mkdir /etc/digest_lists
+ # gen_digest_lists -t file -f rpm+db -d /etc/digest_lists -o add
+
+
+2. Digest list upload with the user space parser:
+
+.. code-block:: bash
+
+ # manage_digest_lists -p add-digest -d /etc/digest_lists
+
+3. First digest list query:
+
+.. code-block:: bash
+
+ # echo sha256-$(sha256sum /bin/cat) > /sys/kernel/security/integrity/digest_lists/digest_query
+ # cat /sys/kernel/security/integrity/digest_lists/digest_query
+   sha256-[...]-0-file_list-rpm-coreutils-8.32-18.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 106, datalen: 3392
+
+4. Second digest list query:
+
+.. code-block:: bash
+
+ # echo sha256-$(sha256sum /bin/zip) > /sys/kernel/security/integrity/digest_lists/digest_query
+ # cat /sys/kernel/security/integrity/digest_lists/digest_query
+   sha256-[...]-0-file_list-rpm-zip-3.0-27.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 4, datalen: 128
+
+
+Architecture
+============
+
+This section introduces the high level architecture.
+
+::
+
+ 5. add/delete from hash table and add refs to digest list
+        +---------------------------------------------+
+        |                            +-----+   +-------------+         +--+
+        |                            | key |-->| digest refs |-->...-->|  |
+        V                            +-----+   +-------------+         +--+
+ +-------------+                     +-----+   +-------------+
+ | digest list |                     | key |-->| digest refs |
+ |  (compact)  |                     +-----+   +-------------+
+ +-------------+                     +-----+   +-------------+
+        ^ 4. copy to                 | key |-->| digest refs |
+        |    kernel memory           +-----+   +-------------+ kernel space
+ --------------------------------------------------------------------------
+        ^                                          ^             user space
+        |<----------------+       3b. upload       |
+ +-------------+   +------------+                  | 6. query digest
+ | digest list |   | user space | 2b. convert
+ |  (compact)  |   |   parser   |
+ +-------------+   +------------+
+ 1a. upload               ^       1b. read
+                          |
+                   +------------+
+                   | RPM header |
+                   +------------+
+
+
+As mentioned before, digest lists can be uploaded directly if they are in
+the compact format (step 1a) or can be uploaded indirectly by the user
+space parser if they are in an alternative format (steps 1b-3b).
+
+During upload, the kernel makes a copy of the digest list to the kernel
+memory (step 4), and creates the necessary structures to index the digests
+(hash table and an array of digest list references to locate the digests in
+the digest list) (step 5).
+
+Finally, digests can be searched from user space through a securityfs file
+(step 6) or by the kernel itself.
diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst
index 16335de04e8c..80877b520403 100644
--- a/Documentation/security/index.rst
+++ b/Documentation/security/index.rst
@@ -17,3 +17,4 @@ Security Documentation
    tpm/index
    digsig
    landlock
+   digest_lists
diff --git a/MAINTAINERS b/MAINTAINERS
index 8c5ee008301a..cba3d82fee43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8381,6 +8381,13 @@ W:	http://www.st.com/
 F:	Documentation/devicetree/bindings/iio/humidity/st,hts221.yaml
 F:	drivers/iio/humidity/hts221*
 
+HUAWEI DIGEST LISTS
+M:	Roberto Sassu <roberto.sassu@huawei.com>
+L:	linux-integrity@vger.kernel.org
+S:	Supported
+T:	git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
+F:	Documentation/security/digest_lists.rst
+
 HUAWEI ETHERNET DRIVER
 M:	Bin Luo <luobin9@huawei.com>
 L:	netdev@vger.kernel.org
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data() Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 02/12] digest_lists: Overview Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-27 10:53   ` Greg KH
  2021-06-25 16:56 ` [RFC][PATCH 04/12] digest_lists: Objects Roberto Sassu
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces the basic definitions, exported to user space, to use
digest lists. The definitions, added to include/uapi/linux/digest_lists.h,
are documented in Documentation/security/digest_lists.rst.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst | 119 ++++++++++++++++++++++++
 MAINTAINERS                             |   1 +
 include/uapi/linux/digest_lists.h       |  43 +++++++++
 3 files changed, 163 insertions(+)
 create mode 100644 include/uapi/linux/digest_lists.h

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 8980be7836f8..995260294783 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -226,3 +226,122 @@ the digest list) (step 5).
 
 Finally, digests can be searched from user space through a securityfs file
 (step 6) or by the kernel itself.
+
+
+Implementation
+==============
+
+This section describes the implementation of Huawei Digest Lists.
+
+
+Basic Definitions
+-----------------
+
+This section introduces the basic definitions required to use digest lists.
+
+
+Compact Digest List Format
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Compact digest lists consist of one or multiple headers defined as:
+
+::
+
+	struct compact_list_hdr {
+		__u8 version;
+		__u8 _reserved;
+		__le16 type;
+		__le16 modifiers;
+		__le16 algo;
+		__le32 count;
+		__le32 datalen;
+	} __packed;
+
+which characterize the subsequent block of concatenated digests.
+
+The ``algo`` field specifies the algorithm used to calculate the digest.
+
+The ``count`` field specifies how many digests are stored in the subsequent
+block of digests.
+
+The ``datalen`` field specifies the length of the subsequent block of
+digests (it is redundant, it is the same as
+``hash_digest_size[algo] * count``).
+
+
+Compact Types
+.............
+
+Digests can be of different types:
+
+- ``COMPACT_PARSER``: digests of executables which are given the ability to
+  parse digest lists not in the compact format and to upload to the kernel
+  the digest list converted to the compact format;
+- ``COMPACT_FILE``: digests of regular files;
+- ``COMPACT_METADATA``: digests of file metadata (e.g. the digest
+  calculated by EVM to verify a portable signature);
+- ``COMPACT_DIGEST_LIST``: digests of digest lists (only used internally by
+  the kernel).
+
+Different users of Huawei Digest Lists might query digests with different
+compact types. For example, IMA would be interested in COMPACT_FILE, as it
+deals with regular files, while EVM would be interested in
+COMPACT_METADATA, as it verifies file metadata.
+
+
+Compact Modifiers
+.................
+
+Digests can also have specific attributes called modifiers (bit position):
+
+- ``COMPACT_MOD_IMMUTABLE``: file content or metadata should not be
+  modifiable.
+
+IMA might use this information to deny open for writing, or EVM to deny
+setxattr operations.
+
+
+Actions
+.......
+
+This section defines a set of possible actions that have been executed on
+the digest lists (bit position):
+
+- ``COMPACT_ACTION_IMA_MEASURED``: the digest list has been measured by
+  IMA;
+- ``COMPACT_ACTION_IMA_APPRAISED``: the digest list has been successfully
+  appraised by IMA;
+- ``COMPACT_ACTION_IMA_APPRAISED_DIGSIG``: the digest list has been
+  successfully appraised by IMA by verifying a digital signature.
+
+This information might help users of Huawei Digest Lists to decide whether
+to use the result of a queried digest.
+
+For example, if a digest belongs to a digest list that was not measured
+before, IMA should ignore the result of the query as the measurement list
+sent to remote verifiers would lack how the database was populated.
+
+
+Compact Digest List Example
+...........................
+
+::
+
+ version: 1, type: 2, modifiers: 0 algo: 4, count: 3, datalen: 96
+ <SHA256 digest1><SHA256 digest2><SHA256 digest3>
+ version: 1, type: 3, modifiers: 1 algo: 6, count: 2, datalen: 128
+ <SHA512 digest1><SHA512 digest2>
+
+This digest list consists of two blocks. The first block contains three
+SHA256 digests of regular files. The second block contains two SHA512
+digests of immutable metadata.
+
+
+Compact Digest List Operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Finally, this section defines the possible operations that can be performed
+with digest lists:
+
+- ``DIGEST_LIST_ADD``: the digest list is being added;
+- ``DIGEST_LIST_DEL``: the digest list is being deleted.
diff --git a/MAINTAINERS b/MAINTAINERS
index cba3d82fee43..ccf555862673 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8387,6 +8387,7 @@ L:	linux-integrity@vger.kernel.org
 S:	Supported
 T:	git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
 F:	Documentation/security/digest_lists.rst
+F:	uapi/linux/digest_lists.h
 
 HUAWEI ETHERNET DRIVER
 M:	Bin Luo <luobin9@huawei.com>
diff --git a/include/uapi/linux/digest_lists.h b/include/uapi/linux/digest_lists.h
new file mode 100644
index 000000000000..9545a8aaa231
--- /dev/null
+++ b/include/uapi/linux/digest_lists.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: digest_lists.h
+ *      Digest list definitions exported to user space.
+ */
+
+#ifndef _UAPI__LINUX_DIGEST_LISTS_H
+#define _UAPI__LINUX_DIGEST_LISTS_H
+
+#include <linux/types.h>
+#include <linux/hash_info.h>
+
+enum compact_types { COMPACT_KEY, COMPACT_PARSER, COMPACT_FILE,
+		     COMPACT_METADATA, COMPACT_DIGEST_LIST, COMPACT__LAST };
+
+enum compact_modifiers { COMPACT_MOD_IMMUTABLE, COMPACT_MOD__LAST };
+
+enum compact_actions { COMPACT_ACTION_IMA_MEASURED,
+		       COMPACT_ACTION_IMA_APPRAISED,
+		       COMPACT_ACTION_IMA_APPRAISED_DIGSIG,
+		       COMPACT_ACTION__LAST };
+
+enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST };
+
+struct compact_list_hdr {
+	__u8 version;
+	__u8 _reserved;
+	__le16 type;
+	__le16 modifiers;
+	__le16 algo;
+	__le32 count;
+	__le32 datalen;
+} __packed;
+#endif
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 04/12] digest_lists: Objects
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (2 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 03/12] digest_lists: Basic definitions Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-27 10:56   ` Greg KH
  2021-06-25 16:56 ` [RFC][PATCH 05/12] digest_lists: Methods Roberto Sassu
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch defines the objects to manage digest lists:

- digest_list_item: represents a digest list;
- digest_list_item_ref: represents a reference to a digest list, i.e. the
  location at which a digest within a digest list can be accessed;
- digest_item: represents a unique digest.

It also defines some helpers for the objects. More information can be found
in Documentation/security/digest_lists.rst.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst       | 156 ++++++++++++++++++
 MAINTAINERS                                   |   1 +
 .../integrity/digest_lists/digest_lists.h     | 117 +++++++++++++
 3 files changed, 274 insertions(+)
 create mode 100644 security/integrity/digest_lists/digest_lists.h

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 995260294783..1031667324c9 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -345,3 +345,159 @@ with digest lists:
 
 - ``DIGEST_LIST_ADD``: the digest list is being added;
 - ``DIGEST_LIST_DEL``: the digest list is being deleted.
+
+
+Objects
+-------
+
+This section defines the objects to manage digest lists:
+
+- ``digest_list_item``: represents a digest list;
+- ``digest_list_item_ref``: represents a reference to a digest list,
+  i.e. the location at which a digest within a digest list can be accessed;
+- ``digest_item``: represents a unique digest.
+
+They are represented in the following class diagram:
+
+::
+
+ digest_offset,-----------+
+ hdr_offset               |
+                          |
+ +------------------+     |     +----------------------+
+ | digest_list_item |--- N:1 ---| digest_list_item_ref |
+ +------------------+           +----------------------+
+                                           |
+                                          1:N
+                                           |
+                                    +-------------+
+                                    | digest_item |
+                                    +-------------+
+
+A ``digest_list_item`` is associated to one or multiple
+``digest_list_item_ref``, one for each digest it contains. However,
+a ``digest_list_item_ref`` is associated to only one ``digest_list_item``,
+as it represents a single location within a specific digest list.
+
+Given that a ``digest_list_item_ref`` represents a single location, it is
+associated to only one ``digest_item``. However, a ``digest_item`` can have
+multiple references (as it might appears multiple times within the same
+digest list or in different digest lists, if it is duplicated).
+
+
+A ``digest_list_item`` is defined as:
+
+::
+
+	struct digest_list_item {
+		loff_t size;
+		u8 *buf;
+		u8 actions;
+		u8 digest[64];
+		enum hash_algo algo;
+		const char *label;
+	};
+
+- ``size``: size of the digest list buffer;
+- ``buf``: digest list buffer;
+- ``actions``: actions performed on the digest list;
+- ``digest``: digest of the digest list;
+- ``algo``: digest algorithm;
+- ``label``: label used to identify the digest list (e.g. file name).
+
+A ``digest_list_item_ref`` is defined as:
+
+::
+
+	struct digest_list_item_ref {
+		struct digest_list_item *digest_list;
+		loff_t digest_offset;
+		loff_t hdr_offset;
+	};
+
+- ``digest_list``: pointer to a ``digest_list_item`` structure;
+- ``digest_offset``: offset of the digest related to the digest list
+  buffer;
+- ``hdr_offset``: offset of the header of the digest block containing the
+  digest.
+
+A ``digest_item`` is defined as:
+
+::
+
+	struct digest_item {
+		struct hlist_node hnext;
+		struct digest_list_item_ref *refs;
+	};
+
+- ``hnext``: pointers of the hash table;
+- ``refs``: array of ``digest_list_item_ref`` structures including a
+  terminator (protected by RCU).
+
+All digest list references are stored for a given digest, so that a query
+result can include the OR of the modifiers and actions of each referenced
+digest list.
+
+The relationship between the described objects can be graphically
+represented as:
+
+::
+
+ Hash table            +-------------+         +-------------+
+ PARSER      +-----+   | digest_item |         | digest_item |
+ FILE        | key |-->|             |-->...-->|             |
+ METADATA    +-----+   |ref0|...|refN|         |ref0|...|refN|
+                       +-------------+         +-------------+
+            ref0:         |                               | refN:
+            digest_offset | +-----------------------------+ digest_offset
+            hdr_offset    | |                               hdr_offset
+                          V V
+                     +--------------------+
+                     |  digest_list_item  |
+                     |                    |
+                     | size, buf, actions |
+                     +--------------------+
+                          ^
+                          |
+ Hash table            +-------------+         +-------------+
+ DIGEST_LIST +-----+   |ref0         |         |ref0         |
+             | key |-->|             |-->...-->|             |
+             +-----+   | digest_item |         | digest_item |
+                       +-------------+         +-------------+
+
+The reference for the digest of the digest list differs from the references
+for the other digest types. ``digest_offset`` and ``hdr_offset`` are set to
+zero, so that the digest of the digest list is retrieved from the
+``digest_list_item`` structure directly (see ``get_digest()`` below).
+
+Finally, this section defines useful helpers to access a digest or the
+header the digest belongs to. For example:
+
+::
+
+ static inline struct compact_list_hdr *get_hdr(
+                                      struct digest_list_item *digest_list,
+                                      loff_t hdr_offset)
+ {
+         return (struct compact_list_hdr *)(digest_list->buf + hdr_offset);
+ }
+
+the header can be obtained by summing the address of the digest list buffer
+in the ``digest_list_item`` structure with ``hdr_offset``.
+
+Similarly:
+
+::
+
+ static inline u8 *get_digest(struct digest_list_item *digest_list,
+                              loff_t digest_offset, loff_t hdr_offset)
+ {
+         /* Digest list digest is stored in a different place. */
+         if (!digest_offset)
+                 return digest_list->digest;
+         return digest_list->buf + digest_offset;
+ }
+
+the digest can be obtained by summing the address of the digest list buffer
+with ``digest_offset`` (except for the digest lists, where the digest is
+stored in the ``digest`` field of the ``digest_list_item`` structure).
diff --git a/MAINTAINERS b/MAINTAINERS
index ccf555862673..9a7e9f16eee8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8387,6 +8387,7 @@ L:	linux-integrity@vger.kernel.org
 S:	Supported
 T:	git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
 F:	Documentation/security/digest_lists.rst
+F:	security/integrity/digest_lists/digest_list.h
 F:	uapi/linux/digest_lists.h
 
 HUAWEI ETHERNET DRIVER
diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h
new file mode 100644
index 000000000000..81b6cb10f4f1
--- /dev/null
+++ b/security/integrity/digest_lists/digest_lists.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: digest_lists.h
+ *      Unexported definitions for digest lists.
+ */
+
+#ifndef __DIGEST_LISTS_INTERNAL_H
+#define __DIGEST_LISTS_INTERNAL_H
+
+#include <linux/types.h>
+#include <linux/crypto.h>
+#include <linux/fs.h>
+#include <linux/security.h>
+#include <linux/hash.h>
+#include <linux/tpm.h>
+#include <linux/audit.h>
+#include <crypto/hash_info.h>
+#include <linux/hash_info.h>
+#include <uapi/linux/digest_lists.h>
+
+#define MAX_DIGEST_SIZE	64
+#define HASH_BITS 10
+#define MEASURE_HTABLE_SIZE (1 << HASH_BITS)
+
+struct digest_list_item {
+	loff_t size;
+	u8 *buf;
+	u8 actions;
+	u8 digest[64];
+	enum hash_algo algo;
+	const char *label;
+};
+
+struct digest_list_item_ref {
+	struct digest_list_item *digest_list;
+	loff_t digest_offset;
+	loff_t hdr_offset;
+};
+
+struct digest_item {
+	/* hash table pointers */
+	struct hlist_node hnext;
+	/* digest list references (protected by RCU) */
+	struct digest_list_item_ref *refs;
+};
+
+struct h_table {
+	atomic_long_t len;
+	struct hlist_head queue[MEASURE_HTABLE_SIZE];
+};
+
+static inline unsigned int hash_key(u8 *digest)
+{
+	return (digest[0] | digest[1] << 8) % MEASURE_HTABLE_SIZE;
+}
+
+static inline struct compact_list_hdr *get_hdr(
+					struct digest_list_item *digest_list,
+					loff_t hdr_offset)
+{
+	return (struct compact_list_hdr *)(digest_list->buf + hdr_offset);
+}
+
+static inline enum hash_algo get_algo(struct digest_list_item *digest_list,
+				      loff_t digest_offset, loff_t hdr_offset)
+{
+	/* Digest list digest algorithm is stored in a different place. */
+	if (!digest_offset)
+		return digest_list->algo;
+
+	return get_hdr(digest_list, hdr_offset)->algo;
+}
+
+static inline u8 *get_digest(struct digest_list_item *digest_list,
+			     loff_t digest_offset, loff_t hdr_offset)
+{
+	/* Digest list digest is stored in a different place. */
+	if (!digest_offset)
+		return digest_list->digest;
+
+	return digest_list->buf + digest_offset;
+}
+
+static inline struct compact_list_hdr *get_hdr_ref(
+					struct digest_list_item_ref *ref)
+{
+	return get_hdr(ref->digest_list, ref->hdr_offset);
+}
+
+static inline enum hash_algo get_algo_ref(struct digest_list_item_ref *ref)
+{
+	/* Digest list digest algorithm is stored in a different place. */
+	if (!ref->digest_offset)
+		return ref->digest_list->algo;
+
+	return get_hdr_ref(ref)->algo;
+}
+
+static inline u8 *get_digest_ref(struct digest_list_item_ref *ref)
+{
+	/* Digest list digest is stored in a different place. */
+	if (!ref->digest_offset)
+		return ref->digest_list->digest;
+
+	return ref->digest_list->buf + ref->digest_offset;
+}
+#endif /*__DIGEST_LISTS_INTERNAL_H*/
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 05/12] digest_lists: Methods
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (3 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 04/12] digest_lists: Objects Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 06/12] digest_lists: Parser Roberto Sassu
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces the methods requires to manage the three objects
defined.

- digest_item methods:
  - digest_add()
  - digest_del()
  - digest_lookup()
  - digest_get_info()

- digest_list_item_ref methods:
  - digest_list_ref_add()
  - digest_list_ref_del()

- digest_list_item methods:
  - digest_list_add()
  - digest_list_del()

More information about these functions can be found in
Documentation/security/digest_lists.rst.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst       | 110 ++++
 MAINTAINERS                                   |   2 +
 include/linux/digest_lists.h                  |  24 +
 security/integrity/Kconfig                    |   1 +
 security/integrity/Makefile                   |   1 +
 security/integrity/digest_lists/Kconfig       |  11 +
 security/integrity/digest_lists/Makefile      |   8 +
 .../integrity/digest_lists/digest_lists.h     |  34 ++
 security/integrity/digest_lists/methods.c     | 548 ++++++++++++++++++
 9 files changed, 739 insertions(+)
 create mode 100644 include/linux/digest_lists.h
 create mode 100644 security/integrity/digest_lists/Kconfig
 create mode 100644 security/integrity/digest_lists/Makefile
 create mode 100644 security/integrity/digest_lists/methods.c

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 1031667324c9..8f1d15a37dbd 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -501,3 +501,113 @@ Similarly:
 the digest can be obtained by summing the address of the digest list buffer
 with ``digest_offset`` (except for the digest lists, where the digest is
 stored in the ``digest`` field of the ``digest_list_item`` structure).
+
+
+Methods
+-------
+
+This section introduces the methods requires to manage the three objects
+defined.
+
+
+``digest_item`` Methods
+~~~~~~~~~~~~~~~~~~~~~~~
+
+
+``digest_add()``
+................
+
+``digest_add()`` first checks in the hash table for the passed type if a
+``digest_item`` for the same digest already exists. If not, it creates a
+new one. Then, ``digest_add()`` calls ``digest_list_ref_add()`` to add a
+new reference of the digest list being added to the found or new
+``digest_item``.
+
+
+``digest_del()``
+................
+
+``digest_del()`` also searches the ``digest_item`` in the hash table. It
+should be always found, as digest lists can be deleted only if they were
+added before. Then, ``digest_del()`` calls ``digest_list_ref_del()`` to
+delete a reference of the digest list being deleted from the found
+``digest_item``.
+
+
+``digest_lookup()``
+...................
+
+``digest_lookup()`` searches the passed digest in the hash table. Then, it
+returns immediately a ``digest_item`` (or NULL if the digest is not found)
+if the modifiers and actions information are not requested by the caller,
+or iterates over all the valid references of the digest and calculates the
+OR for both of them. Iteration in the array of references ends when the
+digest list pointer in a reference is set to NULL. Access to the ``refs``
+array is protected by RCU to avoid access to digest lists being added or
+deleted (update is serialized by the securityfs interfaces).
+
+``digest_lookup()`` is not exposed to the rest of the kernel, because
+access to the returned ``digest_item`` outside RCU would be illegal.
+
+
+``digest_get_info()``
+.....................
+
+``digest_get_info()`` is the public version of ``digest_lookup()``, which
+does not return a ``digest_item`` but just the resulting modifiers and
+actions from the OR of the modifiers and actions from the referenced
+digest lists.
+
+
+``digest_list_item_ref`` Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+``digest_list_ref_add()``
+.........................
+
+``digest_list_ref_add()`` adds a new reference at the end of the ``refs``
+array (also keeps a terminator as the last element). It does not search for
+duplicates, as a duplicate reference simply means that the digest appears
+multiple times in the digest list. ``digest_list_ref_add()`` does not add
+the new element in place, but first creates a copy of the current ``refs``
+array and uses RCU to replace it with the new one.
+
+
+``digest_list_ref_del()``
+.........................
+
+``digest_list_ref_del()`` first searches in the ``refs`` array a reference
+to a given digest list. Then, it invalidates the found reference so that it
+is skipped by the reader. Afterwards, it tries to allocate a smaller
+``refs`` array (with enough slots to store the valid references, except the
+one being deleted). If memory allocation succeeds,
+``digest_list_ref_del()`` copies the valid references to the copy of
+``refs`` and uses RCU to replace the original ``refs``. Otherwise, it keeps
+the original ``refs`` with the invalidated reference.
+
+
+``digest_list_item`` Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+``digest_list_add()``
+.....................
+
+``digest_list_add()`` first searches the digest of the digest list in the
+hash table for the ``COMPACT_DIGEST_LIST`` type. Addition can be done if
+the digest list is not found (it is pointless to load the same digest list
+again). ``digest_list_add()`` then creates a new ``digest_item``,
+representing the digest of the digest list, a special
+``digest_list_item_ref`` with ``digest_offset`` and ``hdr_offset`` set to
+zero, and a new ``digest_list_item``.
+
+
+``digest_list_del()``
+.....................
+
+``digest_list_del()`` also searches the digest of the digest list in the
+hash table for the ``COMPACT_DIGEST_LIST`` type. Deletion can be done only
+if the digest list is found. ``digest_list_del()`` then deletes the
+``digest_list_item``, the special ``digest_list_item_ref`` and the
+``digest_item``.
diff --git a/MAINTAINERS b/MAINTAINERS
index 9a7e9f16eee8..a9eb52e65b12 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8387,7 +8387,9 @@ L:	linux-integrity@vger.kernel.org
 S:	Supported
 T:	git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
 F:	Documentation/security/digest_lists.rst
+F:	include/linux/digest_list.h
 F:	security/integrity/digest_lists/digest_list.h
+F:	security/integrity/digest_lists/methods.c
 F:	uapi/linux/digest_lists.h
 
 HUAWEI ETHERNET DRIVER
diff --git a/include/linux/digest_lists.h b/include/linux/digest_lists.h
new file mode 100644
index 000000000000..f59a2bc5224f
--- /dev/null
+++ b/include/linux/digest_lists.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: digest_lists.h
+ *      Exported functions for digest list management.
+ */
+
+#ifndef __DIGEST_LISTS_H
+#define __DIGEST_LISTS_H
+
+#include <crypto/hash_info.h>
+#include <uapi/linux/digest_lists.h>
+
+int digest_get_info(u8 *digest, enum hash_algo algo, enum compact_types type,
+		    u16 *modifiers, u8 *actions);
+#endif
diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig
index 71f0177e8716..a6ae57a7453a 100644
--- a/security/integrity/Kconfig
+++ b/security/integrity/Kconfig
@@ -98,5 +98,6 @@ config INTEGRITY_AUDIT
 
 source "security/integrity/ima/Kconfig"
 source "security/integrity/evm/Kconfig"
+source "security/integrity/digest_lists/Kconfig"
 
 endif   # if INTEGRITY
diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index 7ee39d66cf16..a1e4acb4d2ae 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -19,3 +19,4 @@ integrity-$(CONFIG_LOAD_PPC_KEYS) += platform_certs/efi_parser.o \
                                      platform_certs/keyring_handler.o
 obj-$(CONFIG_IMA)			+= ima/
 obj-$(CONFIG_EVM)			+= evm/
+obj-$(CONFIG_DIGEST_LISTS)		+= digest_lists/
diff --git a/security/integrity/digest_lists/Kconfig b/security/integrity/digest_lists/Kconfig
new file mode 100644
index 000000000000..2d8290bd2d4d
--- /dev/null
+++ b/security/integrity/digest_lists/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Huawei Digest Lists
+#
+config DIGEST_LISTS
+	bool "Digest Lists"
+	select SECURITYFS
+	select CRYPTO
+	select CRYPTO_HASH_INFO
+	help
+	  Huawei Digest Lists provides reference values for file content and
+	  metadata, that can be used for measurement and appraisal with IMA.
diff --git a/security/integrity/digest_lists/Makefile b/security/integrity/digest_lists/Makefile
new file mode 100644
index 000000000000..0ba66ab2e260
--- /dev/null
+++ b/security/integrity/digest_lists/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for building Huawei Digest Lists.
+#
+
+obj-$(CONFIG_DIGEST_LISTS) += digest_lists.o
+
+digest_lists-y := methods.o
diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h
index 81b6cb10f4f1..aadce9ca8f5f 100644
--- a/security/integrity/digest_lists/digest_lists.h
+++ b/security/integrity/digest_lists/digest_lists.h
@@ -64,6 +64,8 @@ static inline unsigned int hash_key(u8 *digest)
 	return (digest[0] | digest[1] << 8) % MEASURE_HTABLE_SIZE;
 }
 
+extern struct h_table htable[COMPACT__LAST];
+
 static inline struct compact_list_hdr *get_hdr(
 					struct digest_list_item *digest_list,
 					loff_t hdr_offset)
@@ -114,4 +116,36 @@ static inline u8 *get_digest_ref(struct digest_list_item_ref *ref)
 
 	return ref->digest_list->buf + ref->digest_offset;
 }
+
+static inline bool digest_list_ref_invalidated(struct digest_list_item_ref *ref)
+{
+	return (ref->digest_list == ZERO_SIZE_PTR);
+}
+
+static inline void digest_list_ref_invalidate(struct digest_list_item_ref *ref)
+{
+	ref->digest_list = ZERO_SIZE_PTR;
+}
+
+static inline bool digest_list_ref_is_last(struct digest_list_item_ref *ref)
+{
+	return (ref->digest_list == NULL);
+}
+
+struct digest_item *digest_lookup(u8 *digest, enum hash_algo algo,
+				  enum compact_types type, u16 *modifiers,
+				  u8 *actions);
+struct digest_item *digest_add(u8 *digest, enum hash_algo algo,
+			       enum compact_types type,
+			       struct digest_list_item *digest_list,
+			       loff_t digest_offset, loff_t hdr_offset);
+struct digest_item *digest_del(u8 *digest, enum hash_algo algo,
+			       enum compact_types type,
+			       struct digest_list_item *digest_list,
+			       loff_t digest_offset, loff_t hdr_offset);
+struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo,
+				    loff_t size, u8 *buf, u8 actions,
+				    const char *label);
+struct digest_item *digest_list_del(u8 *digest, enum hash_algo algo, u8 actions,
+				    struct digest_list_item *digest_list);
 #endif /*__DIGEST_LISTS_INTERNAL_H*/
diff --git a/security/integrity/digest_lists/methods.c b/security/integrity/digest_lists/methods.c
new file mode 100644
index 000000000000..c4655fb3897a
--- /dev/null
+++ b/security/integrity/digest_lists/methods.c
@@ -0,0 +1,548 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: methods.c
+ *      Functions to manage digest lists.
+ */
+
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/fault-inject.h>
+#include <linux/digest_lists.h>
+
+#include "digest_lists.h"
+#include "../integrity.h"
+
+/* Define a hash table for each digest type. */
+struct h_table htable[COMPACT__LAST] = {{
+	.len = ATOMIC_LONG_INIT(0),
+	.queue[0 ... MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
+}};
+
+#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS
+static DECLARE_FAULT_ATTR(fail_digest_lists);
+
+static int __init fail_digest_lists_debugfs(void)
+{
+	struct dentry *dir = fault_create_debugfs_attr("fail_digest_lists",
+						NULL, &fail_digest_lists);
+
+	return PTR_ERR_OR_ZERO(dir);
+}
+
+static inline bool should_fail_digest_lists(void)
+{
+	return should_fail(&fail_digest_lists, 1);
+}
+
+late_initcall(fail_digest_lists_debugfs);
+#else
+static inline bool should_fail_digest_lists(void)
+{
+	return false;
+}
+#endif
+
+/**
+ * digest_lookup - lookup digest and return associated modifiers and actions
+ * @digest: digest to lookup
+ * @algo: digest algorithm
+ * @type: type of digest to lookup (e.g. file, metadata)
+ * @modifiers: modifiers (attributes) associated to the found digest
+ * @actions: actions performed by IMA on the digest list containing the digest
+ *
+ * This function searches the given digest in the hash table depending on the
+ * passed type and sets the modifiers and actions associated to the digest, if
+ * the pointers are not NULL.
+ *
+ * This function is not intended for external use, as the returned digest item
+ * could be freed at any time after it has been returned. digest_get_info()
+ * should be used instead by external callers, as it only returns the modifiers
+ * and the actions associated to the digest at the time the digest is searched.
+ *
+ * RCU protects both the hash table and the digest_lists array, which contains
+ * references to the digest lists containing the found digest.
+ *
+ * Returns a digest_item structure if the digest is found, NULL otherwise.
+ */
+struct digest_item *digest_lookup(u8 *digest, enum hash_algo algo,
+				  enum compact_types type, u16 *modifiers,
+				  u8 *actions)
+{
+	struct digest_item *d = NULL;
+	struct digest_list_item_ref *ref;
+	int digest_len = hash_digest_size[algo];
+	unsigned int key = hash_key(digest);
+	bool found = false;
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(d, &htable[type].queue[key], hnext) {
+		ref = rcu_dereference(d->refs);
+
+		for (; ref != NULL && !digest_list_ref_is_last(ref); ref++) {
+			if (digest_list_ref_invalidated(ref))
+				continue;
+
+			if (get_algo_ref(ref) != algo ||
+			    memcmp(get_digest_ref(ref), digest, digest_len))
+				break;
+
+			found = true;
+
+			/* There is no need to scan all digest list refs. */
+			if (!modifiers || !actions)
+				break;
+
+			/*
+			 * The resulting modifiers and actions are the OR of the
+			 * modifiers and actions for each digest list.
+			 */
+			*modifiers |= get_hdr_ref(ref)->modifiers;
+			*actions |= ref->digest_list->actions;
+		}
+
+		if (found)
+			break;
+	}
+
+	rcu_read_unlock();
+	return d;
+}
+
+/**
+ * digest_get_info - lookup digest and return associated modifiers and actions
+ * @digest: digest to lookup
+ * @algo: digest algorithm
+ * @type: type of digest to lookup (e.g. file, metadata)
+ * @modifiers: modifiers (attributes) associated to the found digest
+ * @actions: actions performed by IMA on the digest list containing the digest
+ *
+ * This function searches the given digest in the hash table depending on the
+ * passed type and sets the modifiers and actions associated to the digest, if
+ * the pointers are not NULL.
+ *
+ * This function is safe for external use, as it does not return pointers of
+ * objects that can be freed without the caller notices it.
+ *
+ * Returns 0 if the digest is found, -ENOENT otherwise.
+ */
+int digest_get_info(u8 *digest, enum hash_algo algo, enum compact_types type,
+		    u16 *modifiers, u8 *actions)
+{
+	struct digest_item *d;
+
+	d = digest_lookup(digest, algo, type, modifiers, actions);
+	if (!d)
+		return -ENOENT;
+
+	return 0;
+}
+
+/**
+ * digest_list_ref_add - add reference to a digest list
+ * @d: digest a new reference is added to
+ * @digest_list: digest list whose reference is being added
+ * @digest_offset: offset of the digest in the buffer of the digest list
+ * @hdr_offset: offset of the header within the digest list the digest refers to
+ *
+ * This function adds a new reference to an existing digest list for a given
+ * digest. The reference is described by the digest_list_item_ref structure and
+ * consists of a pointer of the digest list, the offset of the digest to the
+ * beginning of the digest list buffer and the offset of the header the digest
+ * refers to (each digest list might be composed of several digest blocks, each
+ * prefixed by a header describing the attributes of those digests).
+ *
+ * This function carefully updates the array of digest list references by
+ * creating a copy of the existing references, adding the new one and using
+ * RCU to replace the old array. An additional empty reference is allocated so
+ * that the reader can stop the iteration.
+ *
+ * Returns 0 if a new digest list reference was successfully added, a negative
+ * value otherwise.
+ */
+static int digest_list_ref_add(struct digest_item *d,
+			       struct digest_list_item *digest_list,
+			       loff_t digest_offset, loff_t hdr_offset)
+{
+	struct digest_list_item_ref *new_refs = NULL, *old_refs = d->refs, *ref;
+	u8 *digest = get_digest(digest_list, digest_offset, hdr_offset);
+	enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset);
+	int digest_len = hash_digest_size[algo];
+	int all_refs;
+
+	for (ref = old_refs, all_refs = 0;
+	     ref != NULL && !digest_list_ref_is_last(ref); ref++, all_refs++)
+		;
+
+	/*
+	 * Allocate a new array of references with + 1 element for the new
+	 * reference and + 1 element for the terminator.
+	 */
+	if (!should_fail_digest_lists())
+		new_refs = kmalloc_array(all_refs + 2, sizeof(*new_refs),
+					 GFP_KERNEL);
+	if (!new_refs) {
+		print_hex_dump(KERN_ERR, "digest list ref allocation failed: ",
+			       DUMP_PREFIX_NONE, digest_len, 1, digest,
+			       digest_len, true);
+		return -ENOMEM;
+	}
+
+	memcpy(new_refs, old_refs, all_refs * sizeof(*new_refs));
+
+	/* Set the new reference. */
+	new_refs[all_refs].digest_list = digest_list;
+	new_refs[all_refs].digest_offset = digest_offset;
+	new_refs[all_refs].hdr_offset = hdr_offset;
+	/* Set the terminator. */
+	new_refs[all_refs + 1].digest_list = NULL;
+
+	/* Replace the old digest list references with the new ones with RCU. */
+	rcu_assign_pointer(d->refs, new_refs);
+	kfree_rcu(old_refs);
+
+	print_hex_dump_debug("add digest list ref: ", DUMP_PREFIX_NONE,
+			     digest_len, 1, digest, digest_len, true);
+	return 0;
+}
+
+/**
+ * digest_list_ref_del - del reference to a digest list
+ * @d: digest a reference is deleted from
+ * @digest_list: digest list whose reference is being deleted
+ * @digest_offset: offset of the digest in the buffer of the digest list
+ * @hdr_offset: offset of the header within the digest list the digest refers to
+ *
+ * This function searches the reference to an already loaded digest list in the
+ * array of references stored for each digest item. If the reference is found
+ * (if not, it is a bug), the function allocates a smaller array from which the
+ * found reference is removed and uses RCU to replace the existing array.
+ *
+ * Returns 0 if a reference of the passed digest list was successfully removed,
+ * a negative value otherwise.
+ */
+static int digest_list_ref_del(struct digest_item *d,
+			       struct digest_list_item *digest_list,
+			       loff_t digest_offset, loff_t hdr_offset)
+{
+	struct digest_list_item_ref *new_refs = NULL, *old_refs = d->refs;
+	struct digest_list_item_ref *ref, *found_ref = NULL;
+	u8 *digest = get_digest(digest_list, digest_offset, hdr_offset);
+	enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset);
+	int digest_len = hash_digest_size[algo];
+	int i, valid_refs = 0;
+
+	/* Search for a digest list reference. */
+	for (ref = d->refs, valid_refs = 0; !digest_list_ref_is_last(ref);
+	     ref++) {
+		if (digest_list_ref_invalidated(ref))
+			continue;
+
+		valid_refs++;
+
+		if (!found_ref && ref->digest_list == digest_list)
+			found_ref = ref;
+	}
+
+	if (!found_ref) {
+		print_hex_dump(KERN_ERR, "digest list ref not found: ",
+			       DUMP_PREFIX_NONE, digest_len, 1, digest,
+			       digest_len, true);
+		return 0;
+	}
+
+	digest_list_ref_invalidate(found_ref);
+
+	if (valid_refs > 1) {
+		/* Allocate a smaller array of digest list references. */
+		if (!should_fail_digest_lists())
+			new_refs = kcalloc(valid_refs, sizeof(*new_refs),
+					   GFP_KERNEL);
+		if (new_refs) {
+			for (ref = d->refs, i = 0;
+			     !digest_list_ref_is_last(ref); ref++) {
+				/* Skip the reference to delete. */
+				if (ref == found_ref)
+					continue;
+
+				/* Skip invalid references. */
+				if (digest_list_ref_invalidated(ref))
+					continue;
+
+				/* Copy the remaining references. */
+				memcpy(&new_refs[i++], ref, sizeof(*new_refs));
+			}
+		} else {
+			new_refs = old_refs;
+		}
+	}
+
+	/* Replace the array of digest list references with RCU. */
+	rcu_assign_pointer(d->refs, new_refs);
+	if (old_refs != new_refs)
+		kfree_rcu(old_refs);
+
+	print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE,
+			     digest_len, 1, digest, digest_len, true);
+	return 0;
+}
+
+/**
+ * digest_add - add a new digest
+ * @digest: digest in binary form
+ * @algo: digest algorithm
+ * @type: digest type
+ * @digest_list: digest list the new digest belongs to
+ * @digest_offset: offset of the digest in the buffer of the digest list
+ * @hdr_offset: offset of the header within the digest list the digest refers to
+ *
+ * This function first searches if the digest is already in the hash table for
+ * the given type. The digest is searched by comparing the passed digest and
+ * algorithm with the digest obtained from the first valid digest list reference
+ * (buffer + digest offset).
+ *
+ * If the digest exists, only a new reference is added (there might be multiple
+ * references to the same digest list).
+ *
+ * If the digest is not found, a new digest item is allocated and a reference to
+ * the passed digest list is added to that item. The digest item is finally
+ * added to the hash table for the given type.
+ *
+ * Returns a new or the found digest item on success, an error pointer
+ * otherwise.
+ */
+struct digest_item *digest_add(u8 *digest, enum hash_algo algo,
+			       enum compact_types type,
+			       struct digest_list_item *digest_list,
+			       loff_t digest_offset, loff_t hdr_offset)
+{
+	int digest_len = hash_digest_size[algo];
+	struct digest_item *d;
+	int ret;
+
+	/* Search the digest. */
+	d = digest_lookup(digest, algo, type, NULL, NULL);
+	if (d) {
+		/*
+		 * Add a new digest list reference to the existing digest item.
+		 */
+		ret = digest_list_ref_add(d, digest_list, digest_offset,
+					  hdr_offset);
+		if (ret < 0)
+			return ERR_PTR(ret);
+
+		print_hex_dump_debug("digest add duplicate: ", DUMP_PREFIX_NONE,
+				     digest_len, 1, digest, digest_len, true);
+		return d;
+	}
+
+	/* Allocate a new digest item. */
+	if (!should_fail_digest_lists())
+		d = kzalloc(sizeof(*d), GFP_KERNEL);
+	if (!d) {
+		print_hex_dump_debug("digest allocation failed: ",
+				     DUMP_PREFIX_NONE, digest_len, 1, digest,
+				     digest_len, true);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	/* Add a new digest list reference to the new digest item. */
+	ret = digest_list_ref_add(d, digest_list, digest_offset, hdr_offset);
+	if (ret < 0) {
+		kfree(d);
+		return ERR_PTR(ret);
+	}
+
+	/* Add the new digest item to the hash table for the given type. */
+	hlist_add_head_rcu(&d->hnext, &htable[type].queue[hash_key(digest)]);
+	atomic_long_inc(&htable[type].len);
+
+	print_hex_dump_debug("digest add: ", DUMP_PREFIX_NONE, digest_len, 1,
+			     digest, digest_len, true);
+	return d;
+}
+
+/**
+ * digest_del - delete a digest with one reference, or just a reference
+ * @digest: digest in binary form
+ * @algo: digest algorithm
+ * @type: digest type
+ * @digest_list: digest list the digest belongs to
+ * @digest_offset: offset of the digest in the buffer of the digest list
+ * @hdr_offset: offset of the header within the digest list the digest refers to
+ *
+ * This function is called when a digest list is being removed. The digest is
+ * first searched in the hash table for the given type. If it is found (if not,
+ * it is a bug, because digest lists can be deleted only if they were added
+ * previously), a reference of the passed digest list is deleted from the array
+ * of references of the digest item.
+ *
+ * If the last reference was deleted, the digest item is also deleted and
+ * removed from the hash table.
+ *
+ * Returns the found digest item if it still has digest list references, NULL
+ * if all references were deleted, an error pointer otherwise.
+ */
+struct digest_item *digest_del(u8 *digest, enum hash_algo algo,
+			       enum compact_types type,
+			       struct digest_list_item *digest_list,
+			       loff_t digest_offset, loff_t hdr_offset)
+{
+	struct digest_item *d;
+	int digest_len = hash_digest_size[algo];
+	int ret;
+
+	/* Search the digest. */
+	d = digest_lookup(digest, algo, type, NULL, NULL);
+	if (!d) {
+		print_hex_dump(KERN_ERR, "digest not found: ", DUMP_PREFIX_NONE,
+			       digest_len, 1, digest, digest_len, true);
+		return ERR_PTR(-ENOENT);
+	}
+
+	/* Delete a reference of the passed digest list. */
+	ret = digest_list_ref_del(d, digest_list, digest_offset, hdr_offset);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	print_hex_dump_debug(d->refs != NULL ?
+			     "digest del duplicate: " : "digest del: ",
+			     DUMP_PREFIX_NONE, digest_len, 1, digest,
+			     digest_len, true);
+
+	/* Return if there are still references. */
+	if (d->refs != NULL)
+		return d;
+
+	/*
+	 * Remove the digest item from the hash table and free it if there are
+	 * no more references left.
+	 */
+	hlist_del_rcu(&d->hnext);
+	atomic_long_dec(&htable[type].len);
+	kfree(d);
+	return NULL;
+}
+
+/**
+ * digest_list_add - add a new digest list
+ * @digest: digest of the digest list in binary form
+ * @algo: digest algorithm
+ * @size: digest list size
+ * @buf: digest list buffer
+ * @actions: actions (measure/appraise) performed by IMA on the digest list
+ * @label: label to be used to identify the digest list
+ *
+ * This function allocates a new digest list item, which contains the buffer,
+ * size, actions performed by IMA and a label. Each digest list item is
+ * associated to a digest item representing the digest of the digest list.
+ *
+ * This function prevents the same digest list to be added multiple times by
+ * searching its digest in the hash table for the COMPACT_DIGEST_LIST type.
+ *
+ * The passed buffer is copied in a new memory area, to avoid to reference
+ * memory that could be freed by the caller.
+ *
+ * If allocation of a new digest list and the associated buffer was successful,
+ * its digest is added to the hash table for the COMPACT_DIGEST_LIST type.
+ *
+ * Returns the digest item associated to the digest list item on success, an
+ * error pointer otherwise.
+ */
+struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo,
+				    loff_t size, u8 *buf, u8 actions,
+				    const char *label)
+{
+	struct digest_item *d;
+	struct digest_list_item *digest_list = NULL;
+	int digest_len = hash_digest_size[algo];
+
+	/* Search the digest of the digest list. */
+	d = digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, NULL);
+	if (d) {
+		print_hex_dump(KERN_ERR, "digest list already uploaded: ",
+			       DUMP_PREFIX_NONE, digest_len, 1, digest,
+			       digest_len, true);
+		return ERR_PTR(-EEXIST);
+	}
+
+	/* Allocate a new digest list. */
+	if (!should_fail_digest_lists())
+		digest_list = kzalloc(sizeof(*digest_list), GFP_KERNEL);
+	if (!digest_list) {
+		print_hex_dump(KERN_ERR, "digest list allocation failed: ",
+			       DUMP_PREFIX_NONE, digest_len, 1, digest,
+			       digest_len, true);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	digest_list->size = size;
+	if (!should_fail_digest_lists())
+		digest_list->buf = kmemdup(buf, size, GFP_KERNEL);
+	if (!digest_list->buf) {
+		print_hex_dump(KERN_ERR, "digest list allocation failed: ",
+			       DUMP_PREFIX_NONE, digest_len, 1, digest,
+			       digest_len, true);
+		kfree(digest_list);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	digest_list->actions = actions;
+	memcpy(digest_list->digest, digest, hash_digest_size[algo]);
+	digest_list->algo = algo;
+	digest_list->label = label;
+
+	/* Add the digest of the digest list to the hash table. */
+	d = digest_add(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0);
+	if (IS_ERR(d)) {
+		kfree(digest_list->buf);
+		kfree(digest_list);
+	}
+
+	return d;
+}
+
+/**
+ * digest_list_del - delete an existing digest list
+ * @digest: digest of the digest list in binary form
+ * @algo: digest algorithm
+ * @actions: actions (measure/appraise) performed by IMA on the digest list
+ * @digest_list: digest list to delete
+ *
+ * This function searches the digest of the digest list in the hash table for
+ * the COMPACT_DIGEST_LIST type. If it is found, this function frees the
+ * buffer and the digest list item allocated in digest_list_add().
+ *
+ * This function prevents the imbalance of digests (references left after
+ * delete) by ensuring that only digest lists that were previously added can be
+ * deleted.
+ *
+ * Returns NULL on successful deletion, an error pointer otherwise.
+ */
+struct digest_item *digest_list_del(u8 *digest, enum hash_algo algo, u8 actions,
+				    struct digest_list_item *digest_list)
+{
+	struct digest_item *d;
+
+	/* Delete the digest item associated to the digest list. */
+	d = digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0);
+	if (IS_ERR(d))
+		return d;
+
+	/*
+	 * Free the buffer and the digest list item allocated when the digest
+	 * list was added.
+	 */
+	kfree(digest_list->buf);
+	kfree(digest_list);
+	return NULL;
+}
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 06/12] digest_lists: Parser
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (4 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 05/12] digest_lists: Methods Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 07/12] digest_lists: Interfaces - digest_list_add, digest_list_del Roberto Sassu
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces the necessary functions to parse a digest list and
to execute the requested operation.

The main function is digest_list_parse(), which coordinates the various
steps required to add or delete a digest list, and has the logic to roll
back when one of the steps fails.

A more detailed description about the steps can be found in
Documentation/security/digest_lists.rst

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst       |  33 +++
 MAINTAINERS                                   |   1 +
 security/integrity/digest_lists/Makefile      |   2 +-
 .../integrity/digest_lists/digest_lists.h     |   3 +
 security/integrity/digest_lists/parser.c      | 270 ++++++++++++++++++
 5 files changed, 308 insertions(+), 1 deletion(-)
 create mode 100644 security/integrity/digest_lists/parser.c

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 8f1d15a37dbd..04ea4b3790e0 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -611,3 +611,36 @@ hash table for the ``COMPACT_DIGEST_LIST`` type. Deletion can be done only
 if the digest list is found. ``digest_list_del()`` then deletes the
 ``digest_list_item``, the special ``digest_list_item_ref`` and the
 ``digest_item``.
+
+
+Parser
+------
+
+This section introduces the necessary functions to parse a digest list and
+to execute the requested operation.
+
+The main function is ``digest_list_parse()``, which coordinates the
+various steps required to add or delete a digest list, and has the logic
+to roll back when one of the steps fails.
+
+#. Calls ``digest_list_validate()`` to validate the passed buffer
+   containing the digest list to ensure that the format is correct.
+
+#. Calls ``get_digest_list()`` to create a new ``digest_list_item`` for the
+   add operation, or to retrieve the existing one for the delete operation.
+   ``get_digest_list()`` refuses to add digest lists that were previously
+   added and to delete digest lists that weren't previously added. Also,
+   ``get_digest_list()`` refuses to delete digest lists that are not
+   processed in the same way as when they were added (it would guarantee
+   that also deletion is notified to remote verifiers).
+
+#. Calls ``_digest_list_parse()`` which takes the created/retrieved
+   ``digest_list_item`` and adds or delete the digests included in the
+   digest list.
+
+#. If an error occurred, performs a rollback to the previous state, by
+   calling ``_digest_list_parse()`` with the opposite operation and the
+   buffer size at the time the error occurred.
+
+#. ``digest_list_parse()`` deletes the ``digest_list_item`` on unsuccessful
+   add or successful delete.
diff --git a/MAINTAINERS b/MAINTAINERS
index a9eb52e65b12..31d280acf5fb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8390,6 +8390,7 @@ F:	Documentation/security/digest_lists.rst
 F:	include/linux/digest_list.h
 F:	security/integrity/digest_lists/digest_list.h
 F:	security/integrity/digest_lists/methods.c
+F:	security/integrity/digest_lists/parser.c
 F:	uapi/linux/digest_lists.h
 
 HUAWEI ETHERNET DRIVER
diff --git a/security/integrity/digest_lists/Makefile b/security/integrity/digest_lists/Makefile
index 0ba66ab2e260..86cca5bb7824 100644
--- a/security/integrity/digest_lists/Makefile
+++ b/security/integrity/digest_lists/Makefile
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_DIGEST_LISTS) += digest_lists.o
 
-digest_lists-y := methods.o
+digest_lists-y := methods.o parser.o
diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h
index aadce9ca8f5f..442ab116a6a5 100644
--- a/security/integrity/digest_lists/digest_lists.h
+++ b/security/integrity/digest_lists/digest_lists.h
@@ -148,4 +148,7 @@ struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo,
 				    const char *label);
 struct digest_item *digest_list_del(u8 *digest, enum hash_algo algo, u8 actions,
 				    struct digest_list_item *digest_list);
+
+int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions,
+		      u8 *digest, enum hash_algo algo, const char *label);
 #endif /*__DIGEST_LISTS_INTERNAL_H*/
diff --git a/security/integrity/digest_lists/parser.c b/security/integrity/digest_lists/parser.c
new file mode 100644
index 000000000000..7ed6765a9dc4
--- /dev/null
+++ b/security/integrity/digest_lists/parser.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: parser.c
+ *      Functions to parse digest lists.
+ */
+
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+
+#include "digest_lists.h"
+#include "../integrity.h"
+
+/**
+ * digest_list_validate - validate format of digest list
+ * @size: buffer size
+ * @buf: buffer containing the digest list
+ *
+ * This function validates the format of the passed digest list.
+ *
+ * Returns 0 if the digest list was successfully validated, -EINVAL otherwise.
+ */
+static int digest_list_validate(loff_t size, void *buf)
+{
+	void *bufp = buf, *bufendp = buf + size;
+	struct compact_list_hdr *hdr;
+	size_t digest_len;
+
+	while (bufp < bufendp) {
+		if (bufp + sizeof(*hdr) > bufendp) {
+			pr_err("invalid data\n");
+			return -EINVAL;
+		}
+
+		hdr = bufp;
+
+		if (hdr->version != 1) {
+			pr_err("unsupported version\n");
+			return -EINVAL;
+		}
+
+		hdr->type = le16_to_cpu(hdr->type);
+		hdr->modifiers = le16_to_cpu(hdr->modifiers);
+		hdr->algo = le16_to_cpu(hdr->algo);
+		hdr->count = le32_to_cpu(hdr->count);
+		hdr->datalen = le32_to_cpu(hdr->datalen);
+
+		if (hdr->algo >= HASH_ALGO__LAST) {
+			pr_err("invalid hash algorithm\n");
+			return -EINVAL;
+		}
+
+		digest_len = hash_digest_size[hdr->algo];
+
+		if (hdr->type >= COMPACT__LAST ||
+		    hdr->type == COMPACT_DIGEST_LIST) {
+			pr_err("invalid type %d\n", hdr->type);
+			return -EINVAL;
+		}
+
+		bufp += sizeof(*hdr);
+
+		if (hdr->datalen != hdr->count * digest_len ||
+		    bufp + hdr->datalen > bufendp) {
+			pr_err("invalid data\n");
+			return -EINVAL;
+		}
+
+		bufp += hdr->count * digest_len;
+	}
+
+	return 0;
+}
+
+/**
+ * _digest_list_parse - parse digest list and add/delete digests
+ * @size: buffer size
+ * @buf: buffer containing the digest list
+ * @op: operation to be performed
+ * @digest_list: digest list digests being added/deleted belong to
+ *
+ * This function parses the digest list and adds or delete the digests in the
+ * found digest blocks.
+ *
+ * Returns the buffer size if all digests were successfully added or deleted,
+ * the size of the already parsed buffer on error.
+ */
+static int _digest_list_parse(loff_t size, void *buf, enum ops op,
+			      struct digest_list_item *digest_list)
+{
+	void *bufp = buf, *bufendp = buf + size;
+	struct compact_list_hdr *hdr;
+	struct digest_item *d;
+	size_t digest_len;
+	int i;
+
+	while (bufp < bufendp) {
+		if (bufp + sizeof(*hdr) > bufendp)
+			break;
+
+		hdr = bufp;
+		bufp += sizeof(*hdr);
+
+		digest_len = hash_digest_size[hdr->algo];
+
+		for (i = 0; i < hdr->count && bufp + digest_len <= bufendp;
+		     i++, bufp += digest_len) {
+			switch (op) {
+			case DIGEST_LIST_ADD:
+				d = digest_add(bufp, hdr->algo, hdr->type,
+					       digest_list, bufp - buf,
+					       (void *)hdr - buf);
+				break;
+			case DIGEST_LIST_DEL:
+				d = digest_del(bufp, hdr->algo, hdr->type,
+					       digest_list, bufp - buf,
+					       (void *)hdr - buf);
+				break;
+			default:
+				break;
+			}
+
+			if (IS_ERR(d)) {
+				pr_err("failed to %s a digest from %s\n",
+				       (op == DIGEST_LIST_ADD) ?
+				       "add" : "delete", digest_list->label);
+				goto out;
+			}
+		}
+	}
+out:
+	return bufp - buf;
+}
+
+/**
+ * get_digest_list - get the digest list extracted digests will be associated to
+ * @size: buffer size
+ * @buf: buffer containing the digest list
+ * @op: digest list operation
+ * @actions: actions performed on the digest list being processed
+ * @digest: digest of the digest list
+ * @algo: digest algorithm
+ * @label: label to identify the digest list (e.g. file name)
+ *
+ * This function retrieves the digest list item for the passed digest and
+ * algorithm. If it is not found at addition time, this function creates a new
+ * one.
+ *
+ * This function also ensures that the actions done at the time of deletion
+ * match the actions done at the time of addition (it would guarantee that also
+ * deletion is notified to remote verifiers).
+ *
+ * Returns the retrieved/created digest list item on success, an error pointer
+ * otherwise.
+ */
+static struct digest_list_item *get_digest_list(loff_t size, void *buf,
+						enum ops op, u8 actions,
+						u8 *digest, enum hash_algo algo,
+						const char *label)
+{
+	struct digest_item *d;
+	struct digest_list_item *digest_list;
+	int digest_len = hash_digest_size[algo];
+
+	switch (op) {
+	case DIGEST_LIST_ADD:
+		/* Add digest list to be associated to each digest. */
+		d = digest_list_add(digest, algo, size, buf, actions, label);
+		if (IS_ERR(d))
+			return (void *)d;
+
+		digest_list = d->refs[0].digest_list;
+		break;
+	case DIGEST_LIST_DEL:
+		/* Lookup digest list to delete the references. */
+		d = digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL,
+				  NULL);
+		if (!d) {
+			print_hex_dump(KERN_ERR,
+				       "digest list digest not found: ",
+				       DUMP_PREFIX_NONE, digest_len, 1, digest,
+				       digest_len, true);
+			return ERR_PTR(-ENOENT);
+		}
+
+		digest_list = d->refs[0].digest_list;
+
+		/* Reject deletion if actions on delete differ from add. */
+		if (digest_list->actions != actions) {
+			pr_err("actions mismatch, add: %d, del: %d\n",
+			       digest_list->actions, actions);
+			return ERR_PTR(-EPERM);
+		}
+
+		break;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+
+	return digest_list;
+}
+
+/**
+ * digest_list_parse - parse a digest list
+ * @size: buffer size
+ * @buf: buffer containing the digest list
+ * @op: digest list operation
+ * @actions: actions performed on the digest list being processed
+ * @digest: digest of the digest list
+ * @algo: digest algorithm
+ * @label: label to identify the digest list (e.g. file name)
+ *
+ * This function parses the passed digest list and executed the requested
+ * operation. If the operation cannot be successfully executed, this function
+ * performs a rollback to the previous state.
+ *
+ * Returns the buffer size on success, a negative value otherwise.
+ */
+int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions,
+		      u8 *digest, enum hash_algo algo, const char *label)
+{
+	struct digest_item *d;
+	struct digest_list_item *digest_list;
+	enum ops rollback_op = (op == DIGEST_LIST_ADD) ?
+			       DIGEST_LIST_DEL : DIGEST_LIST_ADD;
+	int ret, rollback_size;
+
+	ret = digest_list_validate(size, buf);
+	if (ret < 0)
+		return ret;
+
+	digest_list = get_digest_list(size, buf, op, actions, digest, algo,
+				      label);
+	if (IS_ERR(digest_list))
+		return PTR_ERR(digest_list);
+
+	ret = _digest_list_parse(size, buf, op, digest_list);
+	if (ret < 0)
+		goto out;
+
+	if (ret != size) {
+		rollback_size = ret;
+
+		ret = _digest_list_parse(rollback_size, buf, rollback_op,
+					 digest_list);
+		if (ret != rollback_size)
+			pr_err("rollback failed\n");
+
+		ret = -EINVAL;
+	}
+out:
+	/* Delete digest list on unsuccessful add or successful delete. */
+	if ((op == DIGEST_LIST_ADD && ret < 0) ||
+	    (op == DIGEST_LIST_DEL && ret == size)) {
+		d = digest_list_del(digest, algo, actions, digest_list);
+		if (IS_ERR(d))
+			return PTR_ERR(d);
+	}
+
+	return ret;
+}
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 07/12] digest_lists: Interfaces - digest_list_add, digest_list_del
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (5 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 06/12] digest_lists: Parser Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 08/12] digest_lists: Interfaces - digest_lists_loaded Roberto Sassu
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces <securityfs>/integrity/digest_lists/digest_list_add,
which can be used to upload a digest list and add the digests to the hash
table; passed data are interpreted as file path if the first byte is / or
as the digest list itself otherwise. ima_measure_critical_data() is called
to calculate the digest of the digest list and eventually, if an
appropriate rule is set in the IMA policy, to measure it.

It also introduces <securityfs>/integrity/digest_lists/digest_list_del,
which can be used to upload a digest list and delete the digests from the
hash table; data are interpreted in the same way as described for
digest_list_add.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst       |  28 +++
 MAINTAINERS                                   |   1 +
 include/linux/kernel_read_file.h              |   1 +
 security/integrity/digest_lists/Makefile      |   2 +-
 .../integrity/digest_lists/digest_lists.h     |   3 +-
 security/integrity/digest_lists/fs.c          | 210 ++++++++++++++++++
 security/integrity/integrity.h                |   4 +
 7 files changed, 247 insertions(+), 2 deletions(-)
 create mode 100644 security/integrity/digest_lists/fs.c

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 04ea4b3790e0..85a34a5ad7ce 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -644,3 +644,31 @@ to roll back when one of the steps fails.
 
 #. ``digest_list_parse()`` deletes the ``digest_list_item`` on unsuccessful
    add or successful delete.
+
+
+Interfaces
+----------
+
+This section introduces the interfaces in
+``<securityfs>/integrity/digest_lists`` necessary to interact with Huawei
+Digest Lists.
+
+
+``digest_list_add``
+~~~~~~~~~~~~~~~~~~~
+
+``digest_list_add`` can be used to upload a digest list and add the digests
+to the hash table; passed data are interpreted as file path if the first
+byte is ``/`` or as the digest list itself otherwise.
+
+``ima_measure_critical_data()`` is called to calculate the digest of the
+digest list and eventually, if an appropriate rule is set in the IMA
+policy, to measure it.
+
+
+``digest_list_del``
+~~~~~~~~~~~~~~~~~~~
+
+``digest_list_del`` can be used to upload a digest list and delete the
+digests from the hash table; data are interpreted in the same way as
+described for ``digest_list_add``.
diff --git a/MAINTAINERS b/MAINTAINERS
index 31d280acf5fb..c86b410f2c2c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8389,6 +8389,7 @@ T:	git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
 F:	Documentation/security/digest_lists.rst
 F:	include/linux/digest_list.h
 F:	security/integrity/digest_lists/digest_list.h
+F:	security/integrity/digest_lists/fs.c
 F:	security/integrity/digest_lists/methods.c
 F:	security/integrity/digest_lists/parser.c
 F:	uapi/linux/digest_lists.h
diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h
index 575ffa1031d3..636ecdfdc616 100644
--- a/include/linux/kernel_read_file.h
+++ b/include/linux/kernel_read_file.h
@@ -14,6 +14,7 @@
 	id(KEXEC_INITRAMFS, kexec-initramfs)	\
 	id(POLICY, security-policy)		\
 	id(X509_CERTIFICATE, x509-certificate)	\
+	id(DIGEST_LIST, digest-list)	\
 	id(MAX_ID, )
 
 #define __fid_enumify(ENUM, dummy) READING_ ## ENUM,
diff --git a/security/integrity/digest_lists/Makefile b/security/integrity/digest_lists/Makefile
index 86cca5bb7824..00ca06d4bdfd 100644
--- a/security/integrity/digest_lists/Makefile
+++ b/security/integrity/digest_lists/Makefile
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_DIGEST_LISTS) += digest_lists.o
 
-digest_lists-y := methods.o parser.o
+digest_lists-y := methods.o parser.o fs.o
diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h
index 442ab116a6a5..4b74995848dc 100644
--- a/security/integrity/digest_lists/digest_lists.h
+++ b/security/integrity/digest_lists/digest_lists.h
@@ -28,7 +28,8 @@
 #include <linux/hash_info.h>
 #include <uapi/linux/digest_lists.h>
 
-#define MAX_DIGEST_SIZE	64
+#include "../integrity.h"
+
 #define HASH_BITS 10
 #define MEASURE_HTABLE_SIZE (1 << HASH_BITS)
 
diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c
new file mode 100644
index 000000000000..b407c5f7b659
--- /dev/null
+++ b/security/integrity/digest_lists/fs.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: fs.c
+ *      Functions for the interfaces exposed in securityfs.
+ */
+
+
+#include <linux/fcntl.h>
+#include <linux/kernel_read_file.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/seq_file.h>
+#include <linux/rculist.h>
+#include <linux/rcupdate.h>
+#include <linux/parser.h>
+#include <linux/vmalloc.h>
+#include <linux/namei.h>
+#include <linux/ima.h>
+
+#include "digest_lists.h"
+
+static struct dentry *digest_lists_dir;
+static struct dentry *digest_list_add_dentry;
+static struct dentry *digest_list_del_dentry;
+
+static ssize_t digest_list_read(char *path, enum ops op)
+{
+	void *data = NULL;
+	char *datap;
+	size_t size;
+	u8 actions = 0;
+	bool measured = false;
+	struct file *file;
+	u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
+	enum hash_algo algo = HASH_ALGO__LAST;
+	int rc, pathlen = strlen(path);
+
+	/* remove \n */
+	datap = path;
+	strsep(&datap, "\n");
+
+	file = filp_open(path, O_RDONLY, 0);
+	if (IS_ERR(file)) {
+		pr_err("unable to open file: %s (%ld)", path, PTR_ERR(file));
+		return PTR_ERR(file);
+	}
+
+	rc = kernel_read_file(file, 0, &data, INT_MAX, NULL,
+			      READING_DIGEST_LIST);
+	if (rc < 0) {
+		pr_err("unable to read file: %s (%d)", path, rc);
+		goto out;
+	}
+
+	size = rc;
+
+	ima_measure_critical_data("digest_lists", "file_upload", data, size,
+				  false, digest, &algo, &measured);
+	if (algo == HASH_ALGO__LAST) {
+		rc = -EINVAL;
+		goto out_vfree;
+	}
+
+	if (measured)
+		actions |= COMPACT_ACTION_IMA_MEASURED;
+
+	rc = digest_list_parse(size, data, op, actions, digest, algo, "");
+	if (rc < 0)
+		pr_err("unable to upload digest list %s (%d)\n", path, rc);
+out_vfree:
+	vfree(data);
+out:
+	fput(file);
+
+	if (rc < 0)
+		return rc;
+
+	return pathlen;
+}
+
+static ssize_t digest_list_write(struct file *file, const char __user *buf,
+				 size_t datalen, loff_t *ppos)
+{
+	char *data;
+	ssize_t result;
+	enum ops op = DIGEST_LIST_ADD;
+	struct dentry *dentry = file_dentry(file);
+	u8 digest[IMA_MAX_DIGEST_SIZE];
+	enum hash_algo algo = HASH_ALGO__LAST;
+	u8 actions = 0;
+	bool measured = false;
+
+	/* No partial writes. */
+	result = -EINVAL;
+	if (*ppos != 0)
+		goto out;
+
+	result = -EFBIG;
+	if (datalen > 64 * 1024 * 1024 - 1)
+		goto out;
+
+	data = memdup_user_nul(buf, datalen);
+	if (IS_ERR(data)) {
+		result = PTR_ERR(data);
+		goto out;
+	}
+
+	if (dentry == digest_list_del_dentry)
+		op = DIGEST_LIST_DEL;
+
+	result = -EPERM;
+
+	if (data[0] == '/') {
+		result = digest_list_read(data, op);
+	} else {
+		ima_measure_critical_data("digest_lists", "buffer_upload", data,
+					  datalen, false, digest, &algo,
+					  &measured);
+		if (algo == HASH_ALGO__LAST) {
+			pr_err("failed to calculate buffer digest\n");
+			result = -EINVAL;
+			goto out_kfree;
+		}
+
+		if (measured)
+			actions |= COMPACT_ACTION_IMA_MEASURED;
+
+		result = digest_list_parse(datalen, data, op, actions, digest,
+					   algo, "");
+		if (result != datalen) {
+			pr_err("unable to upload generated digest list\n");
+			result = -EINVAL;
+		}
+	}
+out_kfree:
+	kfree(data);
+out:
+	return result;
+}
+
+static unsigned long flags;
+
+/*
+ * digest_list_open: sequentialize access to the add/del files
+ */
+static int digest_list_open(struct inode *inode, struct file *filp)
+{
+	if ((filp->f_flags & O_ACCMODE) != O_WRONLY)
+		return -EACCES;
+
+	if (test_and_set_bit(0, &flags))
+		return -EBUSY;
+
+	return 0;
+}
+
+/*
+ * digest_list_release - release the add/del files
+ */
+static int digest_list_release(struct inode *inode, struct file *file)
+{
+	clear_bit(0, &flags);
+	return 0;
+}
+
+static const struct file_operations digest_list_upload_ops = {
+	.open = digest_list_open,
+	.write = digest_list_write,
+	.read = seq_read,
+	.release = digest_list_release,
+	.llseek = generic_file_llseek,
+};
+
+int __init digest_lists_fs_init(void)
+{
+	digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir);
+	if (IS_ERR(digest_lists_dir))
+		return -1;
+
+	digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200,
+						digest_lists_dir, NULL,
+						&digest_list_upload_ops);
+	if (IS_ERR(digest_list_add_dentry))
+		goto out;
+
+	digest_list_del_dentry = securityfs_create_file("digest_list_del", 0200,
+						digest_lists_dir, NULL,
+						&digest_list_upload_ops);
+	if (IS_ERR(digest_list_del_dentry))
+		goto out;
+
+	return 0;
+out:
+	securityfs_remove(digest_list_del_dentry);
+	securityfs_remove(digest_list_add_dentry);
+	securityfs_remove(digest_lists_dir);
+	return -1;
+}
+
+late_initcall(digest_lists_fs_init);
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 547425c20e11..ac45e1599c2d 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -6,6 +6,9 @@
  * Mimi Zohar <zohar@us.ibm.com>
  */
 
+#ifndef __INTEGRITY_H
+#define __INTEGRITY_H
+
 #ifdef pr_fmt
 #undef pr_fmt
 #endif
@@ -283,3 +286,4 @@ static inline void __init add_to_platform_keyring(const char *source,
 {
 }
 #endif
+#endif /*__INTEGRITY_H*/
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 08/12] digest_lists: Interfaces - digest_lists_loaded
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (6 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 07/12] digest_lists: Interfaces - digest_list_add, digest_list_del Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 09/12] digest_lists: Interfaces - digest_label Roberto Sassu
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces the digest_lists_loaded directory in
<securityfs>/integrity/digest_lists.

It contains two files for each loaded digest list: one shows the digest
list in binary format, and the other (with .ascii prefix) shows the digest
list in ASCII format.

Files are added and removed at the same time digest lists are added and
removed.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst |  11 +
 security/integrity/digest_lists/fs.c    | 298 +++++++++++++++++++++++-
 2 files changed, 305 insertions(+), 4 deletions(-)

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 85a34a5ad7ce..8f245fae6825 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -672,3 +672,14 @@ policy, to measure it.
 ``digest_list_del`` can be used to upload a digest list and delete the
 digests from the hash table; data are interpreted in the same way as
 described for ``digest_list_add``.
+
+
+``digest_lists_loaded``
+~~~~~~~~~~~~~~~~~~~~~~~
+
+``digest_lists_loaded`` is a directory containing two files for each
+loaded digest list: one shows the digest list in binary format, and the
+other (with .ascii prefix) shows the digest list in ASCII format.
+
+Files are added and removed at the same time digest lists are added and
+removed.
diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c
index b407c5f7b659..f665ef063df7 100644
--- a/security/integrity/digest_lists/fs.c
+++ b/security/integrity/digest_lists/fs.c
@@ -29,9 +29,250 @@
 
 #include "digest_lists.h"
 
+#define HDR_ASCII_FMT \
+	"actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n"
+
 static struct dentry *digest_lists_dir;
+static struct dentry *digest_lists_loaded_dir;
 static struct dentry *digest_list_add_dentry;
 static struct dentry *digest_list_del_dentry;
+char digest_label[NAME_MAX + 1];
+
+static int parse_digest_list_filename(const char *digest_list_filename,
+				      u8 *digest, enum hash_algo *algo)
+{
+	int i;
+
+	for (i = 0; i < HASH_ALGO__LAST; i++)
+		if (!strncmp(digest_list_filename, hash_algo_name[i],
+			     strlen(hash_algo_name[i])))
+			break;
+
+	if (i == HASH_ALGO__LAST)
+		return -ENOENT;
+
+	*algo = i;
+	return hex2bin(digest, strchr(digest_list_filename, '-') + 1,
+		       hash_digest_size[*algo]);
+}
+
+/* returns pointer to hlist_node */
+static void *digest_list_start(struct seq_file *m, loff_t *pos)
+{
+	struct digest_item *d;
+	u8 digest[IMA_MAX_DIGEST_SIZE];
+	enum hash_algo algo;
+	struct compact_list_hdr *hdr;
+	u32 count = 0;
+	void *buf, *bufp, *bufendp;
+	int ret;
+
+	ret = parse_digest_list_filename(file_dentry(m->file)->d_name.name,
+					 digest, &algo);
+	if (ret < 0)
+		return NULL;
+
+	d = digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, NULL);
+	if (!d)
+		return NULL;
+
+	bufp = buf = d->refs[0].digest_list->buf;
+	bufendp = bufp + d->refs[0].digest_list->size;
+
+	while (bufp < bufendp) {
+		hdr = (struct compact_list_hdr *)bufp;
+		count += hdr->count;
+		bufp += sizeof(*hdr) + hdr->datalen;
+	}
+
+	return *pos <= count ? d : NULL;
+}
+
+static void *digest_list_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	struct digest_item *d = (struct digest_item *)v;
+	struct compact_list_hdr *hdr;
+	u32 count = 0;
+	void *buf = d->refs[0].digest_list->buf;
+	void *bufp = buf;
+	void *bufendp = bufp + d->refs[0].digest_list->size;
+
+	(*pos)++;
+
+	while (bufp < bufendp) {
+		hdr = (struct compact_list_hdr *)bufp;
+		count += hdr->count;
+		bufp += sizeof(*hdr) + hdr->datalen;
+	}
+
+	return *pos <= count ? d : NULL;
+}
+
+static void digest_list_stop(struct seq_file *m, void *v)
+{
+}
+
+static void print_digest(struct seq_file *m, u8 *digest, u32 size)
+{
+	u32 i;
+
+	for (i = 0; i < size; i++)
+		seq_printf(m, "%02x", *(digest + i));
+}
+
+static void digest_list_putc(struct seq_file *m, void *data, int datalen)
+{
+	while (datalen--)
+		seq_putc(m, *(char *)data++);
+}
+
+static int digest_list_show_common(struct seq_file *m, void *v, bool binary)
+{
+	struct digest_item *d = (struct digest_item *)v;
+	struct compact_list_hdr *hdr;
+	u32 count = 0;
+	void *buf = d->refs[0].digest_list->buf;
+	void *bufp = buf;
+	void *bufendp = bufp + d->refs[0].digest_list->size;
+
+	while (bufp < bufendp) {
+		hdr = (struct compact_list_hdr *)bufp;
+
+		if (m->index >= count + hdr->count) {
+			bufp += sizeof(*hdr) + hdr->datalen;
+			count += hdr->count;
+			continue;
+		}
+
+		if (count == m->index) {
+			if (binary)
+				digest_list_putc(m, (void *)hdr, sizeof(*hdr));
+			else
+				seq_printf(m, HDR_ASCII_FMT,
+					   d->refs[0].digest_list->actions,
+					   hdr->version,
+					   hash_algo_name[hdr->algo], hdr->type,
+					   hdr->modifiers, hdr->count,
+					   hdr->datalen);
+		}
+
+		if (binary) {
+			digest_list_putc(m, bufp + sizeof(*hdr) +
+					 (m->index - count) *
+					 hash_digest_size[hdr->algo],
+					 hash_digest_size[hdr->algo]);
+		} else {
+			print_digest(m, bufp + sizeof(*hdr) +
+				     (m->index - count) *
+				     hash_digest_size[hdr->algo],
+				     hash_digest_size[hdr->algo]);
+			seq_puts(m, "\n");
+		}
+
+		break;
+	}
+
+	return 0;
+}
+
+static int digest_list_show(struct seq_file *m, void *v)
+{
+	return digest_list_show_common(m, v, true);
+}
+
+static int digest_list_ascii_show(struct seq_file *m, void *v)
+{
+	return digest_list_show_common(m, v, false);
+}
+
+static const struct seq_operations digest_list_seqops = {
+	.start = digest_list_start,
+	.next = digest_list_next,
+	.stop = digest_list_stop,
+	.show = digest_list_show
+};
+
+static int digest_list_seq_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &digest_list_seqops);
+}
+
+static const struct file_operations digest_list_ops = {
+	.open = digest_list_seq_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+static const struct seq_operations digest_list_ascii_seqops = {
+	.start = digest_list_start,
+	.next = digest_list_next,
+	.stop = digest_list_stop,
+	.show = digest_list_ascii_show
+};
+
+static int digest_list_ascii_seq_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &digest_list_ascii_seqops);
+}
+
+static const struct file_operations digest_list_ascii_ops = {
+	.open = digest_list_ascii_seq_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+static int digest_list_get_secfs_files(char *label, u8 *digest,
+				       enum hash_algo algo, enum ops op,
+				       struct dentry **dentry,
+				       struct dentry **dentry_ascii)
+{
+	char digest_list_filename[NAME_MAX + 1] = { 0 };
+	u8 digest_str[IMA_MAX_DIGEST_SIZE * 2 + 1] = { 0 };
+	char *dot, *label_ptr;
+
+	label_ptr = strrchr(label, '/');
+	if (label_ptr)
+		label = label_ptr + 1;
+
+	bin2hex(digest_str, digest, hash_digest_size[algo]);
+
+	snprintf(digest_list_filename, sizeof(digest_list_filename),
+		 "%s-%s-%s.ascii", hash_algo_name[algo], digest_str, label);
+
+	dot = strrchr(digest_list_filename, '.');
+
+	*dot = '\0';
+	if (op == DIGEST_LIST_ADD)
+		*dentry = securityfs_create_file(digest_list_filename, 0440,
+						 digest_lists_loaded_dir, NULL,
+						 &digest_list_ops);
+	else
+		*dentry = lookup_positive_unlocked(digest_list_filename,
+						digest_lists_loaded_dir,
+						strlen(digest_list_filename));
+	*dot = '.';
+	if (IS_ERR(*dentry))
+		return PTR_ERR(*dentry);
+
+	if (op == DIGEST_LIST_ADD)
+		*dentry_ascii = securityfs_create_file(digest_list_filename,
+						0440, digest_lists_loaded_dir,
+						NULL, &digest_list_ascii_ops);
+	else
+		*dentry_ascii = lookup_positive_unlocked(digest_list_filename,
+						digest_lists_loaded_dir,
+						strlen(digest_list_filename));
+	if (IS_ERR(*dentry_ascii)) {
+		if (op == DIGEST_LIST_ADD)
+			securityfs_remove(*dentry);
+
+		return PTR_ERR(*dentry_ascii);
+	}
+
+	return 0;
+}
 
 static ssize_t digest_list_read(char *path, enum ops op)
 {
@@ -43,6 +284,7 @@ static ssize_t digest_list_read(char *path, enum ops op)
 	struct file *file;
 	u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
 	enum hash_algo algo = HASH_ALGO__LAST;
+	struct dentry *dentry, *dentry_ascii;
 	int rc, pathlen = strlen(path);
 
 	/* remove \n */
@@ -74,9 +316,27 @@ static ssize_t digest_list_read(char *path, enum ops op)
 	if (measured)
 		actions |= COMPACT_ACTION_IMA_MEASURED;
 
-	rc = digest_list_parse(size, data, op, actions, digest, algo, "");
+	rc = digest_list_get_secfs_files(path, digest, algo, op, &dentry,
+					 &dentry_ascii);
+	if (rc < 0)
+		goto out_vfree;
+
+	rc = digest_list_parse(size, data, op, actions, digest, algo,
+			       dentry->d_name.name);
 	if (rc < 0)
 		pr_err("unable to upload digest list %s (%d)\n", path, rc);
+
+	if ((rc < 0 && op == DIGEST_LIST_ADD) ||
+	    (rc == size && op == DIGEST_LIST_DEL)) {
+		/* Release reference taken in digest_list_get_secfs_files(). */
+		if (op == DIGEST_LIST_DEL) {
+			dput(dentry);
+			dput(dentry_ascii);
+		}
+
+		securityfs_remove(dentry);
+		securityfs_remove(dentry_ascii);
+	}
 out_vfree:
 	vfree(data);
 out:
@@ -94,7 +354,7 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
 	char *data;
 	ssize_t result;
 	enum ops op = DIGEST_LIST_ADD;
-	struct dentry *dentry = file_dentry(file);
+	struct dentry *dentry = file_dentry(file), *dentry_ascii;
 	u8 digest[IMA_MAX_DIGEST_SIZE];
 	enum hash_algo algo = HASH_ALGO__LAST;
 	u8 actions = 0;
@@ -135,12 +395,36 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
 		if (measured)
 			actions |= COMPACT_ACTION_IMA_MEASURED;
 
+		result = digest_list_get_secfs_files(digest_label[0] != '\0' ?
+						     digest_label : "parser",
+						     digest, algo, op,
+						     &dentry, &dentry_ascii);
+		if (result < 0)
+			goto out_kfree;
+
+		memset(digest_label, 0, sizeof(digest_label));
+
 		result = digest_list_parse(datalen, data, op, actions, digest,
-					   algo, "");
+					   algo, dentry->d_name.name);
 		if (result != datalen) {
 			pr_err("unable to upload generated digest list\n");
 			result = -EINVAL;
 		}
+
+		if ((result < 0 && op == DIGEST_LIST_ADD) ||
+		    (result == datalen && op == DIGEST_LIST_DEL)) {
+			/*
+			 * Release reference taken in
+			 * digest_list_get_secfs_files().
+			 */
+			if (op == DIGEST_LIST_DEL) {
+				dput(dentry);
+				dput(dentry_ascii);
+			}
+
+			securityfs_remove(dentry);
+			securityfs_remove(dentry_ascii);
+		}
 	}
 out_kfree:
 	kfree(data);
@@ -181,12 +465,17 @@ static const struct file_operations digest_list_upload_ops = {
 	.llseek = generic_file_llseek,
 };
 
-int __init digest_lists_fs_init(void)
+static int __init digest_lists_fs_init(void)
 {
 	digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir);
 	if (IS_ERR(digest_lists_dir))
 		return -1;
 
+	digest_lists_loaded_dir = securityfs_create_dir("digest_lists_loaded",
+							digest_lists_dir);
+	if (IS_ERR(digest_lists_loaded_dir))
+		goto out;
+
 	digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200,
 						digest_lists_dir, NULL,
 						&digest_list_upload_ops);
@@ -203,6 +492,7 @@ int __init digest_lists_fs_init(void)
 out:
 	securityfs_remove(digest_list_del_dentry);
 	securityfs_remove(digest_list_add_dentry);
+	securityfs_remove(digest_lists_loaded_dir);
 	securityfs_remove(digest_lists_dir);
 	return -1;
 }
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 09/12] digest_lists: Interfaces - digest_label
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (7 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 08/12] digest_lists: Interfaces - digest_lists_loaded Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 10/12] digest_lists: Interfaces - digest_query Roberto Sassu
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces the digest_label interface. It can be used to set a
label to be applied to the next digest list (buffer) loaded through
digest_list_add.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst |  7 +++++
 security/integrity/digest_lists/fs.c    | 34 +++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 8f245fae6825..d83279046a55 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -683,3 +683,10 @@ other (with .ascii prefix) shows the digest list in ASCII format.
 
 Files are added and removed at the same time digest lists are added and
 removed.
+
+
+``digest_label``
+~~~~~~~~~~~~~~~~
+
+``digest_label`` can be used to set a label to be applied to the next
+digest list (buffer) loaded ``through digest_list_add``.
diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c
index f665ef063df7..f6e88fac27bc 100644
--- a/security/integrity/digest_lists/fs.c
+++ b/security/integrity/digest_lists/fs.c
@@ -34,6 +34,7 @@
 
 static struct dentry *digest_lists_dir;
 static struct dentry *digest_lists_loaded_dir;
+static struct dentry *digest_label_dentry;
 static struct dentry *digest_list_add_dentry;
 static struct dentry *digest_list_del_dentry;
 char digest_label[NAME_MAX + 1];
@@ -465,6 +466,32 @@ static const struct file_operations digest_list_upload_ops = {
 	.llseek = generic_file_llseek,
 };
 
+/*
+ * digest_label_write: write label for next uploaded digest list.
+ */
+static ssize_t digest_label_write(struct file *file, const char __user *buf,
+				  size_t datalen, loff_t *ppos)
+{
+	int rc;
+
+	if (datalen >= sizeof(digest_label))
+		return -EINVAL;
+
+	rc = copy_from_user(digest_label, buf, datalen);
+	if (rc < 0)
+		return rc;
+
+	digest_label[datalen] = '\0';
+	return datalen;
+}
+
+static const struct file_operations digest_label_ops = {
+	.open = generic_file_open,
+	.write = digest_label_write,
+	.read = seq_read,
+	.llseek = generic_file_llseek,
+};
+
 static int __init digest_lists_fs_init(void)
 {
 	digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir);
@@ -488,8 +515,15 @@ static int __init digest_lists_fs_init(void)
 	if (IS_ERR(digest_list_del_dentry))
 		goto out;
 
+	digest_label_dentry = securityfs_create_file("digest_label", 0600,
+						     digest_lists_dir, NULL,
+						     &digest_label_ops);
+	if (IS_ERR(digest_label_dentry))
+		goto out;
+
 	return 0;
 out:
+	securityfs_remove(digest_label_dentry);
 	securityfs_remove(digest_list_del_dentry);
 	securityfs_remove(digest_list_add_dentry);
 	securityfs_remove(digest_lists_loaded_dir);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 10/12] digest_lists: Interfaces - digest_query
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (8 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 09/12] digest_lists: Interfaces - digest_label Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 11/12] digest_lists: Interfaces - digests_count Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 12/12] digest_lists: Tests Roberto Sassu
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces the digest_query interface, which allows to write a
query in the format ``<algo>-<digest>`` and to obtain all digest lists that
include that digest.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst |   7 ++
 security/integrity/digest_lists/fs.c    | 150 ++++++++++++++++++++++++
 2 files changed, 157 insertions(+)

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index d83279046a55..f3900a6e92f6 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -690,3 +690,10 @@ removed.
 
 ``digest_label`` can be used to set a label to be applied to the next
 digest list (buffer) loaded ``through digest_list_add``.
+
+
+``digest_query``
+~~~~~~~~~~~~~~~~
+
+``digest_query``: allows to write a query in the format ``<algo>-<digest>``
+and to obtain all digest lists that include that digest.
diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c
index f6e88fac27bc..bdfeb8797760 100644
--- a/security/integrity/digest_lists/fs.c
+++ b/security/integrity/digest_lists/fs.c
@@ -31,12 +31,17 @@
 
 #define HDR_ASCII_FMT \
 	"actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n"
+#define QUERY_RESULT_FMT \
+	"%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n"
+#define QUERY_RESULT_DIGEST_LIST_FMT "%s (actions: %d): type: %d, size: %lld\n"
 
 static struct dentry *digest_lists_dir;
 static struct dentry *digest_lists_loaded_dir;
 static struct dentry *digest_label_dentry;
+static struct dentry *digest_query_dentry;
 static struct dentry *digest_list_add_dentry;
 static struct dentry *digest_list_del_dentry;
+char digest_query[CRYPTO_MAX_ALG_NAME + 1 + IMA_MAX_DIGEST_SIZE * 2 + 1];
 char digest_label[NAME_MAX + 1];
 
 static int parse_digest_list_filename(const char *digest_list_filename,
@@ -224,6 +229,83 @@ static const struct file_operations digest_list_ascii_ops = {
 	.release = seq_release,
 };
 
+static void *digest_query_start(struct seq_file *m, loff_t *pos)
+{
+	struct digest_item *d;
+	u8 digest[IMA_MAX_DIGEST_SIZE];
+	enum hash_algo algo;
+	loff_t count = 0;
+	enum compact_types type = 0;
+	struct digest_list_item_ref *ref;
+	int ret, refs;
+
+	ret = parse_digest_list_filename(digest_query, digest, &algo);
+	if (ret < 0)
+		return NULL;
+
+	for (type = 0; type < COMPACT__LAST; type++) {
+		d = digest_lookup(digest, algo, type, NULL, NULL);
+		if (!d)
+			continue;
+
+		rcu_read_lock();
+		for (ref = rcu_dereference(d->refs), refs = 0;
+		     ref != NULL && !digest_list_ref_is_last(ref);
+		     ref++, refs++)
+			;
+		rcu_read_unlock();
+
+		count += refs;
+
+		if (count > *pos)
+			break;
+	}
+
+	if (type == COMPACT__LAST)
+		return NULL;
+
+	return d->refs ? d->refs + (*pos - (count - refs)) : NULL;
+}
+
+static void *digest_query_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	struct digest_list_item_ref *refs = (struct digest_list_item_ref *)v;
+
+	refs++;
+	(*pos)++;
+
+	return (refs->digest_list) ? refs : NULL;
+}
+
+static void digest_query_stop(struct seq_file *m, void *v)
+{
+}
+
+static int digest_query_show(struct seq_file *m, void *v)
+{
+	struct digest_list_item_ref *refs = (struct digest_list_item_ref *)v;
+	struct digest_list_item *digest_list = refs->digest_list;
+	struct compact_list_hdr *hdr;
+
+	if (digest_list_ref_invalidated(refs))
+		return 0;
+
+	hdr = get_hdr_ref(refs);
+
+	if (!refs->digest_offset) {
+		seq_printf(m, QUERY_RESULT_DIGEST_LIST_FMT, digest_list->label,
+			   digest_list->actions, COMPACT_DIGEST_LIST,
+			   digest_list->size);
+		return 0;
+	}
+
+	seq_printf(m, QUERY_RESULT_FMT, digest_list->label,
+		   digest_list->actions, hdr->version,
+		   hash_algo_name[hdr->algo], hdr->type, hdr->modifiers,
+		   hdr->count, hdr->datalen);
+	return 0;
+}
+
 static int digest_list_get_secfs_files(char *label, u8 *digest,
 				       enum hash_algo algo, enum ops op,
 				       struct dentry **dentry,
@@ -492,6 +574,67 @@ static const struct file_operations digest_label_ops = {
 	.llseek = generic_file_llseek,
 };
 
+static const struct seq_operations digest_query_seqops = {
+	.start = digest_query_start,
+	.next = digest_query_next,
+	.stop = digest_query_stop,
+	.show = digest_query_show,
+};
+
+/*
+ * digest_query_open: open to write a query or read the result.
+ */
+static int digest_query_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &flags))
+		return -EBUSY;
+
+	if (file->f_flags & O_WRONLY)
+		return 0;
+
+	return seq_open(file, &digest_query_seqops);
+}
+
+/*
+ * digest_query_open: write digest query (<algo>-<digest>).
+ */
+static ssize_t digest_query_write(struct file *file, const char __user *buf,
+				  size_t datalen, loff_t *ppos)
+{
+	int rc;
+
+	if (datalen >= sizeof(digest_query))
+		return -EINVAL;
+
+	rc = copy_from_user(digest_query, buf, datalen);
+	if (rc < 0)
+		return rc;
+
+	digest_query[datalen] = '\0';
+	return datalen;
+}
+
+/*
+ * digest_query_release - release the digest_query file
+ */
+static int digest_query_release(struct inode *inode, struct file *file)
+{
+	clear_bit(0, &flags);
+
+	if (file->f_flags & O_WRONLY)
+		return 0;
+
+	return seq_release(inode, file);
+}
+
+static const struct file_operations digest_query_ops = {
+	.open = digest_query_open,
+	.write = digest_query_write,
+	.read = seq_read,
+	.release = digest_query_release,
+	.llseek = generic_file_llseek,
+};
+
 static int __init digest_lists_fs_init(void)
 {
 	digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir);
@@ -521,8 +664,15 @@ static int __init digest_lists_fs_init(void)
 	if (IS_ERR(digest_label_dentry))
 		goto out;
 
+	digest_query_dentry = securityfs_create_file("digest_query", 0600,
+						     digest_lists_dir, NULL,
+						     &digest_query_ops);
+	if (IS_ERR(digest_query_dentry))
+		goto out;
+
 	return 0;
 out:
+	securityfs_remove(digest_query_dentry);
 	securityfs_remove(digest_label_dentry);
 	securityfs_remove(digest_list_del_dentry);
 	securityfs_remove(digest_list_add_dentry);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 11/12] digest_lists: Interfaces - digests_count
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (9 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 10/12] digest_lists: Interfaces - digest_query Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  2021-06-25 16:56 ` [RFC][PATCH 12/12] digest_lists: Tests Roberto Sassu
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces the digests_count interface, which shows the current
number of digests stored in the hash table by type.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst |  7 +++++
 security/integrity/digest_lists/fs.c    | 35 +++++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index f3900a6e92f6..25b5665bbeaa 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -697,3 +697,10 @@ digest list (buffer) loaded ``through digest_list_add``.
 
 ``digest_query``: allows to write a query in the format ``<algo>-<digest>``
 and to obtain all digest lists that include that digest.
+
+
+``digests_count``
+~~~~~~~~~~~~~~~~~
+
+``digests_count`` shows the current number of digests stored in the hash
+table by type.
diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c
index bdfeb8797760..37091db50df5 100644
--- a/security/integrity/digest_lists/fs.c
+++ b/security/integrity/digest_lists/fs.c
@@ -37,6 +37,7 @@
 
 static struct dentry *digest_lists_dir;
 static struct dentry *digest_lists_loaded_dir;
+static struct dentry *digests_count;
 static struct dentry *digest_label_dentry;
 static struct dentry *digest_query_dentry;
 static struct dentry *digest_list_add_dentry;
@@ -44,6 +45,33 @@ static struct dentry *digest_list_del_dentry;
 char digest_query[CRYPTO_MAX_ALG_NAME + 1 + IMA_MAX_DIGEST_SIZE * 2 + 1];
 char digest_label[NAME_MAX + 1];
 
+static char *types_str[COMPACT__LAST] = {
+	[COMPACT_PARSER] = "Parser",
+	[COMPACT_FILE] = "File",
+	[COMPACT_METADATA] = "Metadata",
+	[COMPACT_DIGEST_LIST] = "Digest list",
+};
+
+static ssize_t digest_lists_show_htable_len(struct file *filp, char __user *buf,
+					    size_t count, loff_t *ppos)
+{
+	char tmpbuf[1024];
+	ssize_t len = 0;
+	int i;
+
+	for (i = COMPACT_PARSER; i < COMPACT__LAST; i++)
+		len += scnprintf(tmpbuf + len, sizeof(tmpbuf) - len,
+				 "%s digests: %li\n", types_str[i],
+				 atomic_long_read(&htable[i].len));
+
+	return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
+}
+
+static const struct file_operations htable_len_ops = {
+	.read = digest_lists_show_htable_len,
+	.llseek = generic_file_llseek,
+};
+
 static int parse_digest_list_filename(const char *digest_list_filename,
 				      u8 *digest, enum hash_algo *algo)
 {
@@ -646,6 +674,12 @@ static int __init digest_lists_fs_init(void)
 	if (IS_ERR(digest_lists_loaded_dir))
 		goto out;
 
+	digests_count = securityfs_create_file("digests_count", 0440,
+					       digest_lists_dir, NULL,
+					       &htable_len_ops);
+	if (IS_ERR(digests_count))
+		goto out;
+
 	digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200,
 						digest_lists_dir, NULL,
 						&digest_list_upload_ops);
@@ -676,6 +710,7 @@ static int __init digest_lists_fs_init(void)
 	securityfs_remove(digest_label_dentry);
 	securityfs_remove(digest_list_del_dentry);
 	securityfs_remove(digest_list_add_dentry);
+	securityfs_remove(digests_count);
 	securityfs_remove(digest_lists_loaded_dir);
 	securityfs_remove(digest_lists_dir);
 	return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [RFC][PATCH 12/12] digest_lists: Tests
  2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
                   ` (10 preceding siblings ...)
  2021-06-25 16:56 ` [RFC][PATCH 11/12] digest_lists: Interfaces - digests_count Roberto Sassu
@ 2021-06-25 16:56 ` Roberto Sassu
  11 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-25 16:56 UTC (permalink / raw)
  To: zohar
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Roberto Sassu

This patch introduces a number of tests to ensure that the digest lists
feature works as expected:

- ``digest_list_add_del_test_file_upload``;
- ``digest_list_add_del_test_file_upload_fault``;
- ``digest_list_add_del_test_buffer_upload``;
- ``digest_list_add_del_test_buffer_upload_fault``;
- ``digest_list_fuzzing_test``.

The tests are in ``tools/testing/selftests/digest_lists/selftest.c``.

A description of the tests can be found in
Documentation/security/digest_lists.rst.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/digest_lists.rst       |   50 +
 MAINTAINERS                                   |    1 +
 tools/testing/selftests/Makefile              |    1 +
 tools/testing/selftests/digest_lists/Makefile |    6 +
 tools/testing/selftests/digest_lists/common.c |  109 ++
 tools/testing/selftests/digest_lists/common.h |   37 +
 tools/testing/selftests/digest_lists/config   |    3 +
 .../testing/selftests/digest_lists/selftest.c | 1169 +++++++++++++++++
 8 files changed, 1376 insertions(+)
 create mode 100644 tools/testing/selftests/digest_lists/Makefile
 create mode 100644 tools/testing/selftests/digest_lists/common.c
 create mode 100644 tools/testing/selftests/digest_lists/common.h
 create mode 100644 tools/testing/selftests/digest_lists/config
 create mode 100644 tools/testing/selftests/digest_lists/selftest.c

diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 25b5665bbeaa..fe1313f52e2d 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -704,3 +704,53 @@ and to obtain all digest lists that include that digest.
 
 ``digests_count`` shows the current number of digests stored in the hash
 table by type.
+
+
+Testing
+=======
+
+This section introduces a number of tests to ensure that Huawei Digest
+Lists works as expected:
+
+- ``digest_list_add_del_test_file_upload``;
+- ``digest_list_add_del_test_file_upload_fault``;
+- ``digest_list_add_del_test_buffer_upload``;
+- ``digest_list_add_del_test_buffer_upload_fault``;
+- ``digest_list_fuzzing_test``.
+
+The tests are in ``tools/testing/selftests/digest_lists/selftest.c``.
+
+The first four tests randomly perform add, delete and query of digest
+lists. They internally keep track at any time of the digest lists that are
+currently uploaded to the kernel.
+
+Also, digest lists are generated randomly by selecting an arbitrary digest
+algorithm and an arbitrary the number of digests. To ensure a good number
+of collisions, digests are a sequence of zeros, except for the first four
+bytes that are set with a random number within a defined range.
+
+When a query operation is selected, a digest is chosen by getting another
+random number within the same range. Then, the tests count how many times
+the digest is found in the internally stored digest lists and in the query
+result obtained from the kernel. The tests are successful if the obtained
+numbers are the same.
+
+The ``file_upload`` variant creates a temporary file from a generated
+digest list and sends its path to the kernel, so that the file is uploaded.
+The ``digest_upload`` variant directly sends the digest list buffer to the
+kernel (it will be done by the user space parser after it converts a digest
+list not in the compact format).
+
+The ``fault`` variant performs the test by enabling the ad-hoc fault
+injection mechanism in the kernel (accessible through
+``<debugfs>/fail_digest_lists``). The fault injection mechanism randomly
+injects errors during the addition and deletion of digest lists. When an
+error occurs, the rollback mechanism performs the reverse operation until
+the point the error occurred, so that the kernel is left in the same state
+as when the requested operation began. Since the kernel returns the error
+to user space, the tests also know that the operation didn't succeed and
+behave accordingly (they also revert the internal state).
+
+Lastly, the fuzzing test simply sends randomly generated digest lists to
+the kernel, to ensure that the parser is robust enough to handle malformed
+data.
diff --git a/MAINTAINERS b/MAINTAINERS
index c86b410f2c2c..359c8ce912e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8392,6 +8392,7 @@ F:	security/integrity/digest_lists/digest_list.h
 F:	security/integrity/digest_lists/fs.c
 F:	security/integrity/digest_lists/methods.c
 F:	security/integrity/digest_lists/parser.c
+F:	tools/testing/selftests/digest_lists/
 F:	uapi/linux/digest_lists.h
 
 HUAWEI ETHERNET DRIVER
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index bc3299a20338..f5938fe9f3a7 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -8,6 +8,7 @@ TARGETS += clone3
 TARGETS += core
 TARGETS += cpufreq
 TARGETS += cpu-hotplug
+TARGETS += digest_lists
 TARGETS += drivers/dma-buf
 TARGETS += efivarfs
 TARGETS += exec
diff --git a/tools/testing/selftests/digest_lists/Makefile b/tools/testing/selftests/digest_lists/Makefile
new file mode 100644
index 000000000000..4e88b5677cc6
--- /dev/null
+++ b/tools/testing/selftests/digest_lists/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -Wl,-no-as-needed -Wall -ggdb common.c
+LDFLAGS += -lcrypto
+
+TEST_GEN_PROGS := selftest
+include ../lib.mk
diff --git a/tools/testing/selftests/digest_lists/common.c b/tools/testing/selftests/digest_lists/common.c
new file mode 100644
index 000000000000..a1d0706f8c5f
--- /dev/null
+++ b/tools/testing/selftests/digest_lists/common.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: common.c
+ *      Common functions.
+ */
+
+#include <sys/random.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hash_info.h>
+
+#include "common.h"
+
+int write_buffer(char *path, char *buffer, size_t buffer_len)
+{
+	ssize_t to_write = buffer_len, written = 0;
+	int ret = 0, fd;
+
+	fd = open(path, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+
+	while (to_write) {
+		written = write(fd, buffer + buffer_len - to_write, to_write);
+		if (written <= 0) {
+			ret = -errno;
+			break;
+		}
+
+		to_write -= written;
+	}
+
+	close(fd);
+	return ret;
+}
+
+int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc,
+		bool is_char)
+{
+	ssize_t len = 0, read_len;
+	int ret = 0, fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	if (alloc) {
+		*buffer = NULL;
+		*buffer_len = 0;
+	}
+
+	while (1) {
+		if (alloc) {
+			if (*buffer_len == len) {
+				*buffer_len += BUFFER_SIZE;
+				*buffer = realloc(*buffer, *buffer_len + 1);
+				if (!*buffer) {
+					ret = -ENOMEM;
+					goto out;
+				}
+			}
+		}
+
+		read_len = read(fd, *buffer + len, *buffer_len - len);
+		if (read_len < 0) {
+			ret = -errno;
+			goto out;
+		}
+
+		if (!read_len)
+			break;
+
+		len += read_len;
+	}
+
+	*buffer_len = len;
+	if (is_char)
+		(*buffer)[(*buffer_len)++] = '\0';
+out:
+	close(fd);
+	if (ret < 0) {
+		if (alloc) {
+			free(*buffer);
+			*buffer = NULL;
+		}
+	}
+
+	return ret;
+}
diff --git a/tools/testing/selftests/digest_lists/common.h b/tools/testing/selftests/digest_lists/common.h
new file mode 100644
index 000000000000..60c275f42009
--- /dev/null
+++ b/tools/testing/selftests/digest_lists/common.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: common.h
+ *      Header of common.c
+ */
+
+#include <sys/random.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hash_info.h>
+
+#define BUFFER_SIZE 1024
+
+int write_buffer(char *path, char *buffer, size_t buffer_len);
+int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc,
+		bool is_char);
diff --git a/tools/testing/selftests/digest_lists/config b/tools/testing/selftests/digest_lists/config
new file mode 100644
index 000000000000..faafc742974c
--- /dev/null
+++ b/tools/testing/selftests/digest_lists/config
@@ -0,0 +1,3 @@
+CONFIG_DIGEST_LISTS=y
+CONFIG_FAULT_INJECTION=y
+CONFIG_FAULT_INJECTION_DEBUG_FS=y
diff --git a/tools/testing/selftests/digest_lists/selftest.c b/tools/testing/selftests/digest_lists/selftest.c
new file mode 100644
index 000000000000..6727e1f3f8fd
--- /dev/null
+++ b/tools/testing/selftests/digest_lists/selftest.c
@@ -0,0 +1,1169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: selftest.c
+ *      Functions to test Huawei Digest Lists.
+ */
+
+#include <sys/random.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hash_info.h>
+
+#include <openssl/evp.h>
+
+#include "common.h"
+#include "../kselftest_harness.h"
+
+#define HDR_ASCII_FMT \
+	"actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n"
+#define QUERY_RESULT_FMT \
+	"%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n"
+#define QUERY_RESULT_DIGEST_LIST_FMT "%s (actions: %d): type: %d, size: %lld\n"
+
+enum compact_types { COMPACT_KEY, COMPACT_PARSER, COMPACT_FILE,
+		     COMPACT_METADATA, COMPACT_DIGEST_LIST, COMPACT__LAST };
+
+enum compact_modifiers { COMPACT_MOD_IMMUTABLE, COMPACT_MOD__LAST };
+
+enum compact_actions { COMPACT_ACTION_IMA_MEASURED,
+		       COMPACT_ACTION_IMA_APPRAISED,
+		       COMPACT_ACTION_IMA_APPRAISED_DIGSIG,
+		       COMPACT_ACTION__LAST };
+
+enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST };
+
+struct compact_list_hdr {
+	__u8 version;
+	__u8 _reserved;
+	__le16 type;
+	__le16 modifiers;
+	__le16 algo;
+	__le32 count;
+	__le32 datalen;
+} __packed;
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+#define MD5_DIGEST_SIZE 16
+#define SHA1_DIGEST_SIZE 20
+#define RMD160_DIGEST_SIZE 20
+#define SHA256_DIGEST_SIZE 32
+#define SHA384_DIGEST_SIZE 48
+#define SHA512_DIGEST_SIZE 64
+#define SHA224_DIGEST_SIZE 28
+#define RMD128_DIGEST_SIZE 16
+#define RMD256_DIGEST_SIZE 32
+#define RMD320_DIGEST_SIZE 40
+#define WP256_DIGEST_SIZE 32
+#define WP384_DIGEST_SIZE 48
+#define WP512_DIGEST_SIZE 64
+#define TGR128_DIGEST_SIZE 16
+#define TGR160_DIGEST_SIZE 20
+#define TGR192_DIGEST_SIZE 24
+#define SM3256_DIGEST_SIZE 32
+#define STREEBOG256_DIGEST_SIZE 32
+#define STREEBOG512_DIGEST_SIZE 64
+
+#define DIGEST_LIST_PATH_TEMPLATE "/tmp/digest_list.XXXXXX"
+#define PARSER_BASENAME "manage_digest_lists"
+
+#define INTEGRITY_DIR "/sys/kernel/security/integrity"
+
+#define DIGEST_LIST_DIR INTEGRITY_DIR "/digest_lists"
+#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query"
+#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_label"
+#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add"
+#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del"
+#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded"
+#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count"
+
+#define IMA_POLICY_PATH INTEGRITY_DIR "/ima/policy"
+#define IMA_MEASUREMENTS_PATH INTEGRITY_DIR "/ima/ascii_runtime_measurements"
+
+#define DIGEST_LIST_DEBUGFS_DIR "/sys/kernel/debug/fail_digest_lists"
+#define DIGEST_LIST_DEBUGFS_TASK_FILTER DIGEST_LIST_DEBUGFS_DIR "/task-filter"
+#define DIGEST_LIST_DEBUGFS_PROBABILITY DIGEST_LIST_DEBUGFS_DIR "/probability"
+#define DIGEST_LIST_DEBUGFS_TIMES DIGEST_LIST_DEBUGFS_DIR "/times"
+#define DIGEST_LIST_DEBUGFS_VERBOSE DIGEST_LIST_DEBUGFS_DIR "/verbose"
+#define PROCFS_SELF_FAULT "/proc/self/make-it-fail"
+
+#define MAX_LINE_LENGTH 512
+#define LABEL_LEN 32
+#define MAX_DIGEST_COUNT 100
+#define MAX_DIGEST_LISTS 100
+#define MAX_DIGEST_BLOCKS 10
+#define MAX_DIGEST_VALUE 10
+#define MAX_SEARCH_ATTEMPTS 10
+#define NUM_QUERIES 1000
+#define MAX_DIGEST_LIST_SIZE 10000
+#define NUM_ITERATIONS 100000
+
+enum upload_types { UPLOAD_FILE, UPLOAD_BUFFER };
+
+const char *const hash_algo_name[HASH_ALGO__LAST] = {
+	[HASH_ALGO_MD4]		= "md4",
+	[HASH_ALGO_MD5]		= "md5",
+	[HASH_ALGO_SHA1]	= "sha1",
+	[HASH_ALGO_RIPE_MD_160]	= "rmd160",
+	[HASH_ALGO_SHA256]	= "sha256",
+	[HASH_ALGO_SHA384]	= "sha384",
+	[HASH_ALGO_SHA512]	= "sha512",
+	[HASH_ALGO_SHA224]	= "sha224",
+	[HASH_ALGO_RIPE_MD_128]	= "rmd128",
+	[HASH_ALGO_RIPE_MD_256]	= "rmd256",
+	[HASH_ALGO_RIPE_MD_320]	= "rmd320",
+	[HASH_ALGO_WP_256]	= "wp256",
+	[HASH_ALGO_WP_384]	= "wp384",
+	[HASH_ALGO_WP_512]	= "wp512",
+	[HASH_ALGO_TGR_128]	= "tgr128",
+	[HASH_ALGO_TGR_160]	= "tgr160",
+	[HASH_ALGO_TGR_192]	= "tgr192",
+	[HASH_ALGO_SM3_256]	= "sm3",
+	[HASH_ALGO_STREEBOG_256] = "streebog256",
+	[HASH_ALGO_STREEBOG_512] = "streebog512",
+};
+
+const int hash_digest_size[HASH_ALGO__LAST] = {
+	[HASH_ALGO_MD4]		= MD5_DIGEST_SIZE,
+	[HASH_ALGO_MD5]		= MD5_DIGEST_SIZE,
+	[HASH_ALGO_SHA1]	= SHA1_DIGEST_SIZE,
+	[HASH_ALGO_RIPE_MD_160]	= RMD160_DIGEST_SIZE,
+	[HASH_ALGO_SHA256]	= SHA256_DIGEST_SIZE,
+	[HASH_ALGO_SHA384]	= SHA384_DIGEST_SIZE,
+	[HASH_ALGO_SHA512]	= SHA512_DIGEST_SIZE,
+	[HASH_ALGO_SHA224]	= SHA224_DIGEST_SIZE,
+	[HASH_ALGO_RIPE_MD_128]	= RMD128_DIGEST_SIZE,
+	[HASH_ALGO_RIPE_MD_256]	= RMD256_DIGEST_SIZE,
+	[HASH_ALGO_RIPE_MD_320]	= RMD320_DIGEST_SIZE,
+	[HASH_ALGO_WP_256]	= WP256_DIGEST_SIZE,
+	[HASH_ALGO_WP_384]	= WP384_DIGEST_SIZE,
+	[HASH_ALGO_WP_512]	= WP512_DIGEST_SIZE,
+	[HASH_ALGO_TGR_128]	= TGR128_DIGEST_SIZE,
+	[HASH_ALGO_TGR_160]	= TGR160_DIGEST_SIZE,
+	[HASH_ALGO_TGR_192]	= TGR192_DIGEST_SIZE,
+	[HASH_ALGO_SM3_256]	= SM3256_DIGEST_SIZE,
+	[HASH_ALGO_STREEBOG_256] = STREEBOG256_DIGEST_SIZE,
+	[HASH_ALGO_STREEBOG_512] = STREEBOG512_DIGEST_SIZE,
+};
+
+struct digest_list_item {
+	loff_t size;
+	u8 *buf;
+	u8 actions;
+	char digest_str[64 * 2 + 1];
+	enum hash_algo algo;
+	char filename_suffix[6 + 1];
+};
+
+static const char hex_asc[] = "0123456789abcdef";
+
+#define hex_asc_lo(x)	hex_asc[((x) & 0x0f)]
+#define hex_asc_hi(x)	hex_asc[((x) & 0xf0) >> 4]
+
+static inline char *hex_byte_pack(char *buf, unsigned char byte)
+{
+	*buf++ = hex_asc_hi(byte);
+	*buf++ = hex_asc_lo(byte);
+	return buf;
+}
+
+/* from lib/hexdump.c (Linux kernel) */
+static int hex_to_bin(char ch)
+{
+	if ((ch >= '0') && (ch <= '9'))
+		return ch - '0';
+	ch = tolower(ch);
+	if ((ch >= 'a') && (ch <= 'f'))
+		return ch - 'a' + 10;
+	return -1;
+}
+
+int _hex2bin(unsigned char *dst, const char *src, size_t count)
+{
+	while (count--) {
+		int hi = hex_to_bin(*src++);
+		int lo = hex_to_bin(*src++);
+
+		if ((hi < 0) || (lo < 0))
+			return -1;
+
+		*dst++ = (hi << 4) | lo;
+	}
+	return 0;
+}
+
+char *_bin2hex(char *dst, const void *src, size_t count)
+{
+	const unsigned char *_src = src;
+
+	while (count--)
+		dst = hex_byte_pack(dst, *_src++);
+	return dst;
+}
+
+u32 num_max_digest_lists = MAX_DIGEST_LISTS;
+u32 digest_lists_pos;
+struct digest_list_item *digest_lists[MAX_DIGEST_LISTS];
+
+enum hash_algo ima_hash_algo = HASH_ALGO__LAST;
+
+static enum hash_algo get_ima_hash_algo(void)
+{
+	char *measurement_list, *measurement_list_ptr;
+	size_t measurement_list_len;
+	int ret, i = 0;
+
+	if (ima_hash_algo != HASH_ALGO__LAST)
+		return ima_hash_algo;
+
+	ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list,
+			  &measurement_list_len, true, true);
+	if (ret < 0)
+		return ret;
+
+	measurement_list_ptr = measurement_list;
+	while ((strsep(&measurement_list_ptr, " ")) && i++ < 2)
+		;
+
+	for (i = 0; i < HASH_ALGO__LAST; i++) {
+		if (!strncmp(hash_algo_name[i], measurement_list_ptr,
+			     strlen(hash_algo_name[i]))) {
+			ima_hash_algo = i;
+			break;
+		}
+	}
+
+	free(measurement_list);
+	return ima_hash_algo;
+}
+
+int calc_digest(u8 *digest, void *data, u64 len, enum hash_algo algo)
+{
+	EVP_MD_CTX *mdctx;
+	const EVP_MD *md;
+	int ret = -EINVAL;
+
+	OpenSSL_add_all_algorithms();
+
+	md = EVP_get_digestbyname(hash_algo_name[algo]);
+	if (!md)
+		goto out;
+
+	mdctx = EVP_MD_CTX_create();
+	if (!mdctx)
+		goto out;
+
+	if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
+		goto out_mdctx;
+
+	if (EVP_DigestUpdate(mdctx, data, len) != 1)
+		goto out_mdctx;
+
+	if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1)
+		goto out_mdctx;
+
+	ret = 0;
+out_mdctx:
+	EVP_MD_CTX_destroy(mdctx);
+out:
+	EVP_cleanup();
+	return ret;
+}
+
+int calc_file_digest(u8 *digest, char *path, enum hash_algo algo)
+{
+	void *data = MAP_FAILED;
+	struct stat st;
+	int fd, ret = 0;
+
+	if (stat(path, &st) == -1)
+		return -EACCES;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	if (st.st_size) {
+		data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+		if (data == MAP_FAILED) {
+			ret = -ENOMEM;
+			goto out;
+		}
+	}
+
+	ret = calc_digest(digest, data, st.st_size, algo);
+out:
+	if (data != MAP_FAILED)
+		munmap(data, st.st_size);
+
+	close(fd);
+	return ret;
+}
+
+static struct digest_list_item *digest_list_generate(void)
+{
+	struct digest_list_item *digest_list;
+	struct compact_list_hdr *hdr_array;
+	u8 *buf_ptr;
+	u32 num_digest_blocks = 0;
+	u8 digest[64];
+	int ret, i, j;
+
+	digest_list = calloc(1, sizeof(*digest_list));
+	if (!digest_list)
+		return NULL;
+
+	while (!num_digest_blocks) {
+		ret = getrandom(&num_digest_blocks,
+				sizeof(num_digest_blocks), 0);
+		if (ret < 0)
+			goto out;
+
+		num_digest_blocks = num_digest_blocks % MAX_DIGEST_BLOCKS;
+	}
+
+	hdr_array = calloc(num_digest_blocks, sizeof(*hdr_array));
+	if (!hdr_array)
+		goto out;
+
+	for (i = 0; i < num_digest_blocks; i++) {
+		ret = getrandom(&hdr_array[i], sizeof(hdr_array[i]), 0);
+		if (ret < 0)
+			goto out;
+
+		hdr_array[i].version = 1;
+		/* COMPACT_DIGEST_LIST type is not allowed. */
+		hdr_array[i].type = hdr_array[i].type % (COMPACT__LAST - 1);
+		hdr_array[i].modifiers =
+		    hdr_array[i].modifiers % (1 << COMPACT_MOD_IMMUTABLE) + 1;
+		hdr_array[i].algo = hdr_array[i].algo % HASH_ALGO_RIPE_MD_128;
+		hdr_array[i].count = hdr_array[i].count % MAX_DIGEST_COUNT;
+
+		while (!hdr_array[i].count) {
+			ret = getrandom(&hdr_array[i].count,
+					sizeof(hdr_array[i].count), 0);
+			if (ret < 0)
+				goto out;
+
+			hdr_array[i].count =
+				hdr_array[i].count % MAX_DIGEST_COUNT;
+		}
+
+		hdr_array[i].datalen =
+		    hdr_array[i].count * hash_digest_size[hdr_array[i].algo];
+
+		digest_list->size += sizeof(*hdr_array) + hdr_array[i].datalen;
+	}
+
+	digest_list->buf = calloc(digest_list->size, sizeof(unsigned char));
+	if (!digest_list->buf) {
+		free(digest_list);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	buf_ptr = digest_list->buf;
+
+	for (i = 0; i < num_digest_blocks; i++) {
+		memcpy(buf_ptr, &hdr_array[i], sizeof(*hdr_array));
+		buf_ptr += sizeof(*hdr_array);
+
+		for (j = 0; j < hdr_array[i].count; j++) {
+			ret = getrandom(buf_ptr, sizeof(u32), 0);
+			if (ret < 0)
+				goto out;
+
+			*(u32 *)buf_ptr = *(u32 *)buf_ptr % MAX_DIGEST_VALUE;
+			buf_ptr += hash_digest_size[hdr_array[i].algo];
+		}
+	}
+
+	digest_list->algo = get_ima_hash_algo();
+	if (digest_list->algo == HASH_ALGO__LAST) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	ret = calc_digest(digest, digest_list->buf, digest_list->size,
+			  digest_list->algo);
+	if (ret < 0)
+		goto out;
+
+	_bin2hex(digest_list->digest_str, digest,
+		 hash_digest_size[digest_list->algo]);
+
+	ret = 0;
+out:
+	if (ret < 0) {
+		free(digest_list->buf);
+		free(digest_list);
+	}
+
+	free(hdr_array);
+	return !ret ? digest_list : NULL;
+}
+
+static struct digest_list_item *digest_list_generate_random(void)
+{
+	struct digest_list_item *digest_list;
+	struct compact_list_hdr *hdr;
+	u32 size = 0;
+	u8 digest[64];
+	int ret;
+
+	digest_list = calloc(1, sizeof(*digest_list));
+	if (!digest_list)
+		return NULL;
+
+	while (!size) {
+		ret = getrandom(&size, sizeof(size), 0);
+		if (ret < 0)
+			goto out;
+
+		size = size % MAX_DIGEST_LIST_SIZE;
+	}
+
+	digest_list->size = size;
+	digest_list->buf = calloc(digest_list->size, sizeof(unsigned char));
+	if (!digest_list->buf) {
+		free(digest_list);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = getrandom(digest_list->buf, digest_list->size, 0);
+	if (ret < 0)
+		goto out;
+
+	hdr = (struct compact_list_hdr *)digest_list->buf;
+	hdr->version = 1;
+	hdr->type = hdr->type % (COMPACT__LAST - 1);
+	hdr->algo = hdr->algo % HASH_ALGO__LAST;
+
+	digest_list->algo = get_ima_hash_algo();
+	if (digest_list->algo == HASH_ALGO__LAST) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	ret = calc_digest(digest, digest_list->buf, digest_list->size,
+			  digest_list->algo);
+	if (ret < 0)
+		goto out;
+
+	_bin2hex(digest_list->digest_str, digest,
+		 hash_digest_size[digest_list->algo]);
+
+	ret = 0;
+out:
+	if (ret < 0) {
+		free(digest_list->buf);
+		free(digest_list);
+	}
+
+	return !ret ? digest_list : NULL;
+}
+
+static int digest_list_upload(struct digest_list_item *digest_list, enum ops op,
+			      enum upload_types upload_type, char *parser_path,
+			      char *parser_mode)
+{
+	char path_template[] = DIGEST_LIST_PATH_TEMPLATE;
+	char *path_upload = DIGEST_LIST_ADD_PATH, *basename;
+	unsigned char *buffer = digest_list->buf;
+	size_t buffer_len = digest_list->size;
+	unsigned char rnd[3];
+	int ret = 0, fd;
+
+	if (op == DIGEST_LIST_ADD) {
+		if (upload_type == UPLOAD_FILE) {
+			fd = mkstemp(path_template);
+			if (fd < 0)
+				return -EPERM;
+
+			close(fd);
+			ret = write_buffer(path_template,
+					   (char *)digest_list->buf,
+					   digest_list->size);
+			if (ret < 0)
+				goto out;
+
+			buffer = (unsigned char *)path_template;
+			buffer_len = strlen(path_template);
+		} else {
+			ret = getrandom(rnd, sizeof(rnd), 0);
+			if (ret < 0)
+				goto out;
+
+			_bin2hex(path_template +
+				 sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, rnd,
+				 sizeof(rnd));
+		}
+
+		memcpy(digest_list->filename_suffix,
+		       path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7,
+		       6);
+	} else {
+		memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7,
+		       digest_list->filename_suffix, 6);
+		path_upload = DIGEST_LIST_DEL_PATH;
+		if (upload_type == UPLOAD_FILE) {
+			buffer = (unsigned char *)path_template;
+			buffer_len = strlen(path_template);
+		}
+	}
+
+	if (upload_type == UPLOAD_BUFFER) {
+		basename = strrchr(path_template, '/');
+		ret = write_buffer(DIGEST_LABEL_PATH, basename,
+				   strlen(basename));
+		if (ret < 0)
+			goto out;
+	}
+
+	ret = write_buffer(path_upload, (char *)buffer, buffer_len);
+out:
+	if (ret < 0 || op == DIGEST_LIST_DEL)
+		unlink(path_template);
+
+	return ret;
+}
+
+static int digest_list_check(struct digest_list_item *digest_list, enum ops op)
+{
+	char path[PATH_MAX];
+	u8 digest_list_buf[MAX_LINE_LENGTH];
+	char digest_list_info[MAX_LINE_LENGTH];
+	ssize_t size = digest_list->size;
+	struct compact_list_hdr *hdr;
+	struct stat st;
+	int ret = 0, i, fd, path_len, len, read_len;
+
+	path_len = snprintf(path, sizeof(path), "%s/%s-%s-digest_list.%s.ascii",
+			    DIGEST_LISTS_LOADED_PATH,
+			    hash_algo_name[digest_list->algo],
+			    digest_list->digest_str,
+			    digest_list->filename_suffix);
+
+	path[path_len - 6] = '\0';
+
+	if (op == DIGEST_LIST_DEL) {
+		if (stat(path, &st) != -1)
+			return -EEXIST;
+
+		path[path_len - 6] = '.';
+
+		if (stat(path, &st) != -1)
+			return -EEXIST;
+
+		return 0;
+	}
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	while (size) {
+		len = read(fd, digest_list_buf, sizeof(digest_list_buf));
+		if (len <= 0) {
+			ret = -errno;
+			goto out;
+		}
+
+		if (memcmp(digest_list_buf,
+			   digest_list->buf + digest_list->size - size, len)) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		size -= len;
+	}
+
+	close(fd);
+
+	path[path_len - 6] = '.';
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	size = digest_list->size;
+	while (size) {
+		hdr = (struct compact_list_hdr *)(digest_list->buf +
+						  digest_list->size - size);
+
+		len = snprintf(digest_list_info, sizeof(digest_list_info),
+			       HDR_ASCII_FMT, digest_list->actions,
+			       hdr->version, hash_algo_name[hdr->algo],
+			       hdr->type, hdr->modifiers, hdr->count,
+			       hdr->datalen);
+
+		read_len = read(fd, digest_list_buf, len);
+
+		if (read_len != len ||
+		    memcmp(digest_list_info, digest_list_buf, len)) {
+			ret = -EIO;
+			goto out;
+		}
+
+		size -= sizeof(*hdr);
+
+		for (i = 0; i < hdr->count; i++) {
+			_bin2hex(digest_list_info,
+				 digest_list->buf + digest_list->size - size,
+				 hash_digest_size[hdr->algo]);
+
+			read_len = read(fd, digest_list_buf,
+					hash_digest_size[hdr->algo] * 2 + 1);
+
+			if (read_len != hash_digest_size[hdr->algo] * 2 + 1 ||
+			    memcmp(digest_list_info, digest_list_buf,
+				   read_len - 1) ||
+				   digest_list_buf[read_len - 1] != '\n') {
+				ret = -EIO;
+				goto out;
+			}
+
+			size -= hash_digest_size[hdr->algo];
+		}
+	}
+out:
+	close(fd);
+	return ret;
+}
+
+static int digest_list_query(u8 *digest, enum hash_algo algo,
+			     char **query_result)
+{
+	ssize_t len, to_write, written;
+	char query[256] = { 0 };
+	size_t query_result_len;
+	int ret = 0, fd;
+
+	len = snprintf(query, sizeof(query), "%s-", hash_algo_name[algo]);
+
+	_bin2hex(query + len, digest, hash_digest_size[algo]);
+	len += hash_digest_size[algo] * 2 + 1;
+
+	fd = open(DIGEST_QUERY_PATH, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+
+	to_write = len;
+
+	while (to_write) {
+		written = write(fd, query + len - to_write, to_write);
+		if (written <= 0) {
+			ret = -errno;
+			break;
+		}
+
+		to_write -= written;
+	}
+
+	close(fd);
+	if (ret < 0)
+		return ret;
+
+	return read_buffer(DIGEST_QUERY_PATH, query_result, &query_result_len,
+			   true, true);
+}
+
+static int *get_count_gen_lists(u8 *digest, enum hash_algo algo)
+{
+	struct compact_list_hdr *hdr;
+	u8 *buf_ptr;
+	loff_t size;
+	int i, j, *count;
+
+	count = calloc(num_max_digest_lists, sizeof(*count));
+	if (!count)
+		return count;
+
+	for (i = 0; i < num_max_digest_lists; i++) {
+		if (!digest_lists[i])
+			continue;
+
+		size = digest_lists[i]->size;
+		buf_ptr = digest_lists[i]->buf;
+
+		while (size) {
+			hdr = (struct compact_list_hdr *)buf_ptr;
+			if (hdr->algo != algo) {
+				buf_ptr += sizeof(*hdr) + hdr->datalen;
+				size -= sizeof(*hdr) + hdr->datalen;
+				continue;
+			}
+
+			buf_ptr += sizeof(*hdr);
+			size -= sizeof(*hdr);
+
+			for (j = 0; j < hdr->count; j++) {
+				if (!memcmp(digest, buf_ptr,
+				    hash_digest_size[algo]))
+					count[i]++;
+				buf_ptr += hash_digest_size[algo];
+				size -= hash_digest_size[algo];
+			}
+		}
+	}
+
+	return count;
+}
+
+static int *get_count_kernel_query(u8 *digest, enum hash_algo algo)
+{
+	char *query_result = NULL, *query_result_ptr, *line;
+	char digest_list_info[MAX_LINE_LENGTH];
+	char label[256];
+	struct compact_list_hdr *hdr;
+	struct digest_list_item *digest_list;
+	size_t size, size_info;
+	int ret, i, *count = NULL;
+
+	count = calloc(num_max_digest_lists, sizeof(*count));
+	if (!count)
+		return count;
+
+	ret = digest_list_query(digest, algo, &query_result);
+	if (ret < 0)
+		goto out;
+
+	query_result_ptr = query_result;
+
+	while ((line = strsep(&query_result_ptr, "\n"))) {
+		if (!strlen(line))
+			continue;
+
+		for (i = 0; i < num_max_digest_lists; i++) {
+			if (!digest_lists[i])
+				continue;
+
+			digest_list = digest_lists[i];
+			size = digest_list->size;
+
+			while (size) {
+				hdr =
+				  (struct compact_list_hdr *)(digest_list->buf +
+						digest_list->size - size);
+				size -= sizeof(*hdr) + hdr->datalen;
+
+				snprintf(label, sizeof(label),
+					 "%s-%s-digest_list.%s",
+					 hash_algo_name[digest_list->algo],
+					 digest_list->digest_str,
+					 digest_list->filename_suffix);
+
+				/* From digest_query_show(). */
+				size_info = snprintf(digest_list_info,
+					 sizeof(digest_list_info),
+					 QUERY_RESULT_FMT, label,
+					 digest_list->actions, hdr->version,
+					 hash_algo_name[hdr->algo], hdr->type,
+					 hdr->modifiers, hdr->count,
+					 hdr->datalen);
+
+				/* strsep() replaced '\n' with '\0' in line. */
+				digest_list_info[size_info - 1] = '\0';
+
+				if (!strcmp(digest_list_info, line)) {
+					count[i]++;
+					break;
+				}
+			}
+		}
+	}
+out:
+	free(query_result);
+	if (ret < 0)
+		free(count);
+
+	return (!ret) ? count : NULL;
+}
+
+static int compare_count(u32 value, enum hash_algo algo,
+			 struct __test_metadata *_metadata)
+{
+	int *count_gen_list_array, *count_kernel_query_array;
+	int count_gen_list = 0, count_kernel_query = 0;
+	u8 digest[64] = { 0 };
+	int i;
+
+	*(u32 *)digest = value;
+
+	count_gen_list_array = get_count_gen_lists(digest, algo);
+	if (!count_gen_list_array)
+		return -EINVAL;
+
+	count_kernel_query_array = get_count_kernel_query(digest, algo);
+	if (!count_kernel_query_array) {
+		free(count_gen_list_array);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_max_digest_lists; i++) {
+		count_gen_list += count_gen_list_array[i];
+		count_kernel_query += count_kernel_query_array[i];
+	}
+
+	TH_LOG("value: %d, algo: %s, gen list digests: %d, kernel digests: %d",
+	       value, hash_algo_name[algo], count_gen_list, count_kernel_query);
+	free(count_gen_list_array);
+	free(count_kernel_query_array);
+	return (count_gen_list == count_kernel_query) ? 0 : -EINVAL;
+}
+
+static void digest_list_delete_all(struct __test_metadata *_metadata,
+				   enum upload_types upload_type,
+				   char *parser_path)
+{
+	int ret, i;
+
+	for (i = 0; i < MAX_DIGEST_LISTS; i++) {
+		if (!digest_lists[i])
+			continue;
+
+		ret = digest_list_upload(digest_lists[i], DIGEST_LIST_DEL,
+					 upload_type, parser_path, "normal");
+		ASSERT_EQ(0, ret) {
+			TH_LOG("digest_list_upload() failed\n");
+		}
+
+		free(digest_lists[i]->buf);
+		free(digest_lists[i]);
+		digest_lists[i] = NULL;
+	}
+}
+
+FIXTURE(test)
+{
+	enum upload_types upload_type;
+};
+
+FIXTURE_SETUP(test)
+{
+}
+
+FIXTURE_TEARDOWN(test)
+{
+	digest_list_delete_all(_metadata, self->upload_type, NULL);
+}
+
+static int enable_fault_injection(void)
+{
+	int ret;
+
+	ret = write_buffer(DIGEST_LIST_DEBUGFS_TASK_FILTER, "Y", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = write_buffer(DIGEST_LIST_DEBUGFS_PROBABILITY, "1", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = write_buffer(DIGEST_LIST_DEBUGFS_TIMES, "10000", 5);
+	if (ret < 0)
+		return ret;
+
+	ret = write_buffer(DIGEST_LIST_DEBUGFS_VERBOSE, "1", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = write_buffer(PROCFS_SELF_FAULT, "1", 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void digest_list_add_del_test(struct __test_metadata *_metadata,
+				     int fault_injection,
+				     enum upload_types upload_type)
+{
+	u32 value;
+	enum ops op;
+	enum hash_algo algo;
+	int ret, i, cur_queries = 1;
+
+	while (cur_queries <= NUM_QUERIES) {
+		ret = getrandom(&op, 1, 0);
+		ASSERT_EQ(1, ret) {
+			TH_LOG("getrandom() failed\n");
+		}
+
+		op = op % 2;
+
+		switch (op) {
+		case DIGEST_LIST_ADD:
+			TH_LOG("add digest list...");
+			for (digest_lists_pos = 0;
+			     digest_lists_pos < num_max_digest_lists;
+			     digest_lists_pos++)
+				if (!digest_lists[digest_lists_pos])
+					break;
+
+			if (digest_lists_pos == num_max_digest_lists)
+				continue;
+
+			digest_lists[digest_lists_pos] = digest_list_generate();
+			ASSERT_NE(NULL, digest_lists[digest_lists_pos]) {
+				TH_LOG("digest_list_generate() failed");
+			}
+
+			ret = digest_list_upload(digest_lists[digest_lists_pos],
+						 op, upload_type, NULL, NULL);
+			/* Handle failures from fault injection. */
+			if (fault_injection && ret < 0) {
+				TH_LOG("handle failure...");
+				ret = digest_list_check(
+						digest_lists[digest_lists_pos],
+						DIGEST_LIST_DEL);
+				ASSERT_EQ(0, ret) {
+					TH_LOG("digest_list_check() failed");
+				}
+
+				free(digest_lists[digest_lists_pos]->buf);
+				free(digest_lists[digest_lists_pos]);
+				digest_lists[digest_lists_pos] = NULL;
+				break;
+			}
+
+			ASSERT_EQ(0, ret) {
+				TH_LOG("digest_list_upload() failed");
+			}
+
+			ret = digest_list_check(digest_lists[digest_lists_pos],
+						op);
+			ASSERT_EQ(0, ret) {
+				TH_LOG("digest_list_check() failed");
+			}
+
+			break;
+		case DIGEST_LIST_DEL:
+			TH_LOG("delete digest list...");
+			for (digest_lists_pos = 0;
+			     digest_lists_pos < num_max_digest_lists;
+			     digest_lists_pos++)
+				if (digest_lists[digest_lists_pos])
+					break;
+
+			if (digest_lists_pos == num_max_digest_lists)
+				continue;
+
+			for (i = 0; i < MAX_SEARCH_ATTEMPTS; i++) {
+				ret = getrandom(&digest_lists_pos,
+						sizeof(digest_lists_pos), 0);
+				ASSERT_EQ(sizeof(digest_lists_pos), ret) {
+					TH_LOG("getrandom() failed");
+				}
+
+				digest_lists_pos =
+					digest_lists_pos % num_max_digest_lists;
+
+				if (digest_lists[digest_lists_pos])
+					break;
+			}
+
+			if (i == MAX_SEARCH_ATTEMPTS) {
+				for (digest_lists_pos = 0;
+				     digest_lists_pos < num_max_digest_lists;
+				     digest_lists_pos++)
+					if (digest_lists[digest_lists_pos])
+						break;
+
+				if (digest_lists_pos == num_max_digest_lists)
+					continue;
+			}
+
+			ret = digest_list_upload(digest_lists[digest_lists_pos],
+						 op, upload_type, NULL, NULL);
+			ASSERT_EQ(0, ret) {
+				TH_LOG("digest_list_upload() failed");
+			}
+
+			ret = digest_list_check(digest_lists[digest_lists_pos],
+						op);
+			ASSERT_EQ(0, ret) {
+				TH_LOG("digest_list_check() failed");
+			}
+
+			free(digest_lists[digest_lists_pos]->buf);
+			free(digest_lists[digest_lists_pos]);
+			digest_lists[digest_lists_pos] = NULL;
+			break;
+		default:
+			break;
+		}
+
+		ret = getrandom(&value, sizeof(value), 0);
+		ASSERT_EQ(sizeof(value), ret) {
+			TH_LOG("getrandom() failed");
+		}
+
+		value = value % 10;
+
+		if (value != 1)
+			continue;
+
+		ret = getrandom(&value, sizeof(value), 0);
+		ASSERT_EQ(sizeof(value), ret) {
+			TH_LOG("getrandom() failed");
+		}
+
+		value = value % MAX_DIGEST_VALUE;
+
+		ret = getrandom(&algo, sizeof(algo), 0);
+		ASSERT_EQ(sizeof(algo), ret) {
+			TH_LOG("getrandom() failed");
+		}
+
+		algo = algo % HASH_ALGO_RIPE_MD_128;
+
+		ret = compare_count(value, algo, _metadata);
+		ASSERT_EQ(0, ret) {
+			TH_LOG("count mismatch");
+		}
+
+		TH_LOG("query digest lists (%d/%d)...", cur_queries,
+		       NUM_QUERIES);
+
+		cur_queries++;
+	}
+}
+
+TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload, UINT_MAX)
+{
+	self->upload_type = UPLOAD_FILE;
+	digest_list_add_del_test(_metadata, 0, self->upload_type);
+}
+
+TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload_fault, UINT_MAX)
+{
+	int ret;
+
+	self->upload_type = UPLOAD_FILE;
+
+	ret = enable_fault_injection();
+	ASSERT_EQ(0, ret) {
+		TH_LOG("enable_fault_injection() failed");
+	}
+
+	digest_list_add_del_test(_metadata, 1, self->upload_type);
+}
+
+TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload, UINT_MAX)
+{
+	self->upload_type = UPLOAD_BUFFER;
+	digest_list_add_del_test(_metadata, 0, self->upload_type);
+}
+
+TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload_fault, UINT_MAX)
+{
+	int ret;
+
+	self->upload_type = UPLOAD_BUFFER;
+
+	ret = enable_fault_injection();
+	ASSERT_EQ(0, ret) {
+		TH_LOG("enable_fault_injection() failed");
+	}
+
+	digest_list_add_del_test(_metadata, 1, self->upload_type);
+}
+
+FIXTURE(test_fuzzing)
+{
+};
+
+FIXTURE_SETUP(test_fuzzing)
+{
+}
+
+FIXTURE_TEARDOWN(test_fuzzing)
+{
+}
+
+TEST_F_TIMEOUT(test_fuzzing, digest_list_fuzzing_test, UINT_MAX)
+{
+	char digests_count_before[256] = { 0 };
+	char *digests_count_before_ptr = digests_count_before;
+	char digests_count_after[256] = { 0 };
+	char *digests_count_after_ptr = digests_count_after;
+	size_t len = sizeof(digests_count_before) - 1;
+	int ret, i;
+
+	ret = read_buffer(DIGESTS_COUNT, &digests_count_before_ptr, &len,
+			  false, true);
+	ASSERT_EQ(0, ret) {
+		TH_LOG("read_buffer() failed");
+	}
+
+	for (i = 1; i <= NUM_ITERATIONS; i++) {
+		TH_LOG("add digest list (%d/%d)...", i, NUM_ITERATIONS);
+
+		digest_lists[0] = digest_list_generate_random();
+		ASSERT_NE(NULL, digest_lists[0]) {
+			TH_LOG("digest_list_generate() failed");
+		}
+
+		ret = digest_list_upload(digest_lists[0], DIGEST_LIST_ADD,
+					 UPLOAD_FILE, NULL, NULL);
+		if (!ret) {
+			ret = digest_list_check(digest_lists[0],
+						DIGEST_LIST_ADD);
+			ASSERT_EQ(0, ret) {
+				TH_LOG("digest_list_check() failed");
+			}
+
+			ret = digest_list_upload(digest_lists[0],
+						 DIGEST_LIST_DEL, UPLOAD_FILE,
+						 NULL, NULL);
+			ASSERT_EQ(0, ret) {
+				TH_LOG("digest_list_upload() failed");
+			}
+
+			ret = digest_list_check(digest_lists[0],
+						DIGEST_LIST_DEL);
+			ASSERT_EQ(0, ret) {
+				TH_LOG("digest_list_check() failed");
+			}
+		}
+
+		free(digest_lists[0]->buf);
+		free(digest_lists[0]);
+		digest_lists[0] = NULL;
+	}
+
+	ret = read_buffer(DIGESTS_COUNT, &digests_count_after_ptr, &len, false,
+			  true);
+	ASSERT_EQ(0, ret) {
+		TH_LOG("read_buffer() failed");
+	}
+
+	ASSERT_STREQ(digests_count_before, digests_count_after);
+}
+
+TEST_HARNESS_MAIN
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data()
  2021-06-25 16:56 ` [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data() Roberto Sassu
@ 2021-06-25 18:26   ` Mimi Zohar
  0 siblings, 0 replies; 25+ messages in thread
From: Mimi Zohar @ 2021-06-25 18:26 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel, Paul Moore, Stephen Smalley,
	selinux, Prakhar Srivastava, Tushar Sugandhi,
	Lakshmi Ramasubramanian

On Fri, 2021-06-25 at 18:56 +0200, Roberto Sassu wrote:
> ima_measure_critical_data() allows any caller in the kernel to provide a
> buffer, so that is measured by IMA if an appropriate policy is set. Some
> information that could be useful to the callers are the digest of the
> buffer included in the new measurement entry, the digest algorithm and
> whether the buffer was measured.
> 
> This patch modifies the definition of ima_measure_critical_data() to
> include three new parameters: digest, algo and measured. If they are NULL,
> the function behaves as before and just measures the buffer, if requested
> with the IMA policy. Otherwise, it also writes the digest, algorithm and
> whether the buffer is measured to the provided pointers.
> 
> If the pointers are not NULL, the digest is calculated also if there is no
> matching rule in the IMA policy.

As much as possible, let's not define additional
ima_measure_critical_data() arguments.  Probably the only new variable
really need is "digest".  The hash algorithm doesn't change.  How about
defining and exporting a new function to return the system defined
ima_hash_algo.  In terms of failure, have ima_measure_critical_data()
return errno.

thanks,

Mimi


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-25 16:56 ` [RFC][PATCH 03/12] digest_lists: Basic definitions Roberto Sassu
@ 2021-06-27 10:53   ` Greg KH
  2021-06-27 15:23     ` Matthew Wilcox
  2021-06-28  8:30     ` Roberto Sassu
  0 siblings, 2 replies; 25+ messages in thread
From: Greg KH @ 2021-06-27 10:53 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

On Fri, Jun 25, 2021 at 06:56:05PM +0200, Roberto Sassu wrote:
> --- /dev/null
> +++ b/include/uapi/linux/digest_lists.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
> + *
> + * Author: Roberto Sassu <roberto.sassu@huawei.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.

As you already have the SPDX line up there, you do not need this
paragraph.  Please remove it from all of the new files you have added in
this series.

> + *
> + * File: digest_lists.h

We know the filename, no need to have it here again.

> + *      Digest list definitions exported to user space.

Now this is what probably needs more information...

> + */
> +
> +#ifndef _UAPI__LINUX_DIGEST_LISTS_H
> +#define _UAPI__LINUX_DIGEST_LISTS_H
> +
> +#include <linux/types.h>
> +#include <linux/hash_info.h>
> +
> +enum compact_types { COMPACT_KEY, COMPACT_PARSER, COMPACT_FILE,
> +		     COMPACT_METADATA, COMPACT_DIGEST_LIST, COMPACT__LAST };
> +
> +enum compact_modifiers { COMPACT_MOD_IMMUTABLE, COMPACT_MOD__LAST };
> +
> +enum compact_actions { COMPACT_ACTION_IMA_MEASURED,
> +		       COMPACT_ACTION_IMA_APPRAISED,
> +		       COMPACT_ACTION_IMA_APPRAISED_DIGSIG,
> +		       COMPACT_ACTION__LAST };
> +
> +enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST };
> +

For enums you export to userspace, you need to specify the values so
that all compilers get them right.

> +struct compact_list_hdr {
> +	__u8 version;

You should never need a version, that way lies madness.

> +	__u8 _reserved;

You better be testing this for 0, right?

> +	__le16 type;
> +	__le16 modifiers;
> +	__le16 algo;
> +	__le32 count;
> +	__le32 datalen;

Why are user/kernel apis specified in little endian format?  Why would
that matter?  Shouldn't they just be "native" endian?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 04/12] digest_lists: Objects
  2021-06-25 16:56 ` [RFC][PATCH 04/12] digest_lists: Objects Roberto Sassu
@ 2021-06-27 10:56   ` Greg KH
  2021-06-28  8:14     ` Roberto Sassu
  0 siblings, 1 reply; 25+ messages in thread
From: Greg KH @ 2021-06-27 10:56 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

On Fri, Jun 25, 2021 at 06:56:06PM +0200, Roberto Sassu wrote:
> +++ b/security/integrity/digest_lists/digest_lists.h
> @@ -0,0 +1,117 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2005,2006,2007,2008 IBM Corporation
> + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
> + *
> + * Author: Roberto Sassu <roberto.sassu@huawei.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + *
> + * File: digest_lists.h
> + *      Unexported definitions for digest lists.

Unexported to whom?

> + */
> +
> +#ifndef __DIGEST_LISTS_INTERNAL_H
> +#define __DIGEST_LISTS_INTERNAL_H
> +
> +#include <linux/types.h>
> +#include <linux/crypto.h>
> +#include <linux/fs.h>
> +#include <linux/security.h>
> +#include <linux/hash.h>
> +#include <linux/tpm.h>
> +#include <linux/audit.h>
> +#include <crypto/hash_info.h>
> +#include <linux/hash_info.h>
> +#include <uapi/linux/digest_lists.h>
> +
> +#define MAX_DIGEST_SIZE	64
> +#define HASH_BITS 10
> +#define MEASURE_HTABLE_SIZE (1 << HASH_BITS)
> +
> +struct digest_list_item {
> +	loff_t size;
> +	u8 *buf;
> +	u8 actions;
> +	u8 digest[64];
> +	enum hash_algo algo;
> +	const char *label;
> +};
> +
> +struct digest_list_item_ref {
> +	struct digest_list_item *digest_list;
> +	loff_t digest_offset;
> +	loff_t hdr_offset;
> +};
> +
> +struct digest_item {
> +	/* hash table pointers */
> +	struct hlist_node hnext;
> +	/* digest list references (protected by RCU) */
> +	struct digest_list_item_ref *refs;
> +};
> +
> +struct h_table {
> +	atomic_long_t len;

Why is this atomic?  Why would that matter?

> +	struct hlist_head queue[MEASURE_HTABLE_SIZE];
> +};
> +
> +static inline unsigned int hash_key(u8 *digest)
> +{
> +	return (digest[0] | digest[1] << 8) % MEASURE_HTABLE_SIZE;
> +}

Don't we have hashing functions in the kernel already?

> +
> +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);
> +}

pointer math feels rough, are you shure you want to do this this way?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-27 10:53   ` Greg KH
@ 2021-06-27 15:23     ` Matthew Wilcox
  2021-06-27 15:35       ` Greg KH
  2021-06-28  8:30     ` Roberto Sassu
  1 sibling, 1 reply; 25+ messages in thread
From: Matthew Wilcox @ 2021-06-27 15:23 UTC (permalink / raw)
  To: Greg KH
  Cc: Roberto Sassu, zohar, linux-integrity, linux-security-module,
	linux-doc, linux-kselftest, linux-kernel

On Sun, Jun 27, 2021 at 12:53:47PM +0200, Greg KH wrote:
> > +enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST };
> > +
> 
> For enums you export to userspace, you need to specify the values so
> that all compilers get them right.

I've never heard that rule before.  Where does it come from?
https://en.cppreference.com/w/c/language/enum
says:

  If enumeration-constant is not followed by = constant-expression,
  its value is the value one greater than the value of the previous
  enumerator in the same enumeration. The value of the first enumerator
  (if it does not use = constant-expression) is zero.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-27 15:23     ` Matthew Wilcox
@ 2021-06-27 15:35       ` Greg KH
  0 siblings, 0 replies; 25+ messages in thread
From: Greg KH @ 2021-06-27 15:35 UTC (permalink / raw)
  To: Matthew Wilcox, Arnd Bergmann
  Cc: Roberto Sassu, zohar, linux-integrity, linux-security-module,
	linux-doc, linux-kselftest, linux-kernel

On Sun, Jun 27, 2021 at 04:23:26PM +0100, Matthew Wilcox wrote:
> On Sun, Jun 27, 2021 at 12:53:47PM +0200, Greg KH wrote:
> > > +enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST };
> > > +
> > 
> > For enums you export to userspace, you need to specify the values so
> > that all compilers get them right.
> 
> I've never heard that rule before.  Where does it come from?
> https://en.cppreference.com/w/c/language/enum
> says:
> 
>   If enumeration-constant is not followed by = constant-expression,
>   its value is the value one greater than the value of the previous
>   enumerator in the same enumeration. The value of the first enumerator
>   (if it does not use = constant-expression) is zero.
> 

I thought it was in the Documentation/driver-api/ioctl.rst file, but I
can't find it right now.  Maybe it was something that Arnd said?

Arnd, is this still an issue?  For some reason I thought it was always
good to have uapi .h enums be explicit as to the value in them,
otherwise some C compilers might produce other values if they were not
specified?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* RE: [RFC][PATCH 04/12] digest_lists: Objects
  2021-06-27 10:56   ` Greg KH
@ 2021-06-28  8:14     ` Roberto Sassu
  2021-06-28  8:47       ` Greg KH
  0 siblings, 1 reply; 25+ messages in thread
From: Roberto Sassu @ 2021-06-28  8:14 UTC (permalink / raw)
  To: Greg KH
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

> From: Greg KH [mailto:gregkh@linuxfoundation.org]
> Sent: Sunday, June 27, 2021 12:56 PM
> On Fri, Jun 25, 2021 at 06:56:06PM +0200, Roberto Sassu wrote:
> > +++ b/security/integrity/digest_lists/digest_lists.h
> > @@ -0,0 +1,117 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2005,2006,2007,2008 IBM Corporation
> > + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
> > + *
> > + * Author: Roberto Sassu <roberto.sassu@huawei.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License as
> > + * published by the Free Software Foundation, version 2 of the
> > + * License.
> > + *
> > + * File: digest_lists.h
> > + *      Unexported definitions for digest lists.
> 
> Unexported to whom?

Hi Greg

I meant not placed in include/linux.

> > + */
> > +
> > +#ifndef __DIGEST_LISTS_INTERNAL_H
> > +#define __DIGEST_LISTS_INTERNAL_H
> > +
> > +#include <linux/types.h>
> > +#include <linux/crypto.h>
> > +#include <linux/fs.h>
> > +#include <linux/security.h>
> > +#include <linux/hash.h>
> > +#include <linux/tpm.h>
> > +#include <linux/audit.h>
> > +#include <crypto/hash_info.h>
> > +#include <linux/hash_info.h>
> > +#include <uapi/linux/digest_lists.h>
> > +
> > +#define MAX_DIGEST_SIZE	64
> > +#define HASH_BITS 10
> > +#define MEASURE_HTABLE_SIZE (1 << HASH_BITS)
> > +
> > +struct digest_list_item {
> > +	loff_t size;
> > +	u8 *buf;
> > +	u8 actions;
> > +	u8 digest[64];
> > +	enum hash_algo algo;
> > +	const char *label;
> > +};
> > +
> > +struct digest_list_item_ref {
> > +	struct digest_list_item *digest_list;
> > +	loff_t digest_offset;
> > +	loff_t hdr_offset;
> > +};
> > +
> > +struct digest_item {
> > +	/* hash table pointers */
> > +	struct hlist_node hnext;
> > +	/* digest list references (protected by RCU) */
> > +	struct digest_list_item_ref *refs;
> > +};
> > +
> > +struct h_table {
> > +	atomic_long_t len;
> 
> Why is this atomic?  Why would that matter?

Yes, it shouldn't be. There are not concurrent updates.

> > +	struct hlist_head queue[MEASURE_HTABLE_SIZE];
> > +};
> > +
> > +static inline unsigned int hash_key(u8 *digest)
> > +{
> > +	return (digest[0] | digest[1] << 8) % MEASURE_HTABLE_SIZE;
> > +}
> 
> Don't we have hashing functions in the kernel already?

We had a discussion before:

https://lore.kernel.org/linux-integrity/1587739544.5190.14.camel@linux.ibm.com/

It seems there is no real advantage in hashing a digest.

> > +
> > +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);
> > +}
> 
> pointer math feels rough, are you shure you want to do this this way?

Maybe, I could change digest_list_item_ref to:

struct digest_list_item_ref {
	struct digest_list_item *digest_list;
	u8 *digest;
	struct compact_list_hdr *hdr;
};

where digest and hdr are calculated in the same way.

Or you have a different suggestion?

Thanks

Roberto

HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063
Managing Director: Li Peng, Li Jian, Shi Yanli

> thanks,
> 
> greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* RE: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-27 10:53   ` Greg KH
  2021-06-27 15:23     ` Matthew Wilcox
@ 2021-06-28  8:30     ` Roberto Sassu
  2021-06-28  8:46       ` Greg KH
  1 sibling, 1 reply; 25+ messages in thread
From: Roberto Sassu @ 2021-06-28  8:30 UTC (permalink / raw)
  To: Greg KH
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

> From: Greg KH [mailto:gregkh@linuxfoundation.org]
> Sent: Sunday, June 27, 2021 12:54 PM
> On Fri, Jun 25, 2021 at 06:56:05PM +0200, Roberto Sassu wrote:
> > --- /dev/null
> > +++ b/include/uapi/linux/digest_lists.h
> > @@ -0,0 +1,43 @@
> > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> > +/*
> > + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
> > + *
> > + * Author: Roberto Sassu <roberto.sassu@huawei.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License as
> > + * published by the Free Software Foundation, version 2 of the
> > + * License.
> 
> As you already have the SPDX line up there, you do not need this
> paragraph.  Please remove it from all of the new files you have added in
> this series.

Ok.

> > + *
> > + * File: digest_lists.h
> 
> We know the filename, no need to have it here again.
> 
> > + *      Digest list definitions exported to user space.
> 
> Now this is what probably needs more information...

Ok. Yes, these definitions are useful to generate digest lists
in user space.

> > + */
> > +
> > +#ifndef _UAPI__LINUX_DIGEST_LISTS_H
> > +#define _UAPI__LINUX_DIGEST_LISTS_H
> > +
> > +#include <linux/types.h>
> > +#include <linux/hash_info.h>
> > +
> > +enum compact_types { COMPACT_KEY, COMPACT_PARSER,
> COMPACT_FILE,
> > +		     COMPACT_METADATA, COMPACT_DIGEST_LIST,
> COMPACT__LAST };
> > +
> > +enum compact_modifiers { COMPACT_MOD_IMMUTABLE,
> COMPACT_MOD__LAST };
> > +
> > +enum compact_actions { COMPACT_ACTION_IMA_MEASURED,
> > +		       COMPACT_ACTION_IMA_APPRAISED,
> > +		       COMPACT_ACTION_IMA_APPRAISED_DIGSIG,
> > +		       COMPACT_ACTION__LAST };
> > +
> > +enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST };
> > +
> 
> For enums you export to userspace, you need to specify the values so
> that all compilers get them right.
> 
> > +struct compact_list_hdr {
> > +	__u8 version;
> 
> You should never need a version, that way lies madness.

We wanted to have a way to switch to a new format, if necessary.

> > +	__u8 _reserved;
> 
> You better be testing this for 0, right?

Ok, will do.

> > +	__le16 type;
> > +	__le16 modifiers;
> > +	__le16 algo;
> > +	__le32 count;
> > +	__le32 datalen;
> 
> Why are user/kernel apis specified in little endian format?  Why would
> that matter?  Shouldn't they just be "native" endian?

I thought this would make it clear that the kernel always expects the
digest lists to be in little endian.

Thanks

Roberto

HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063
Managing Director: Li Peng, Li Jian, Shi Yanli

> thanks,
> 
> greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-28  8:30     ` Roberto Sassu
@ 2021-06-28  8:46       ` Greg KH
  2021-06-28  9:27         ` Roberto Sassu
  0 siblings, 1 reply; 25+ messages in thread
From: Greg KH @ 2021-06-28  8:46 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

On Mon, Jun 28, 2021 at 08:30:32AM +0000, Roberto Sassu wrote:
> > > +struct compact_list_hdr {
> > > +	__u8 version;
> > 
> > You should never need a version, that way lies madness.
> 
> We wanted to have a way to switch to a new format, if necessary.

Then just add a new ioctl if you need that in the future, no need to try
to cram it into this one.

> > > +	__le16 type;
> > > +	__le16 modifiers;
> > > +	__le16 algo;
> > > +	__le32 count;
> > > +	__le32 datalen;
> > 
> > Why are user/kernel apis specified in little endian format?  Why would
> > that matter?  Shouldn't they just be "native" endian?
> 
> I thought this would make it clear that the kernel always expects the
> digest lists to be in little endian.

Why would a big endian system expect the data from userspace to be in
little endian?  Shouldn't this always just be "native" endian given that
this is not something that is being sent to hardware?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 04/12] digest_lists: Objects
  2021-06-28  8:14     ` Roberto Sassu
@ 2021-06-28  8:47       ` Greg KH
  0 siblings, 0 replies; 25+ messages in thread
From: Greg KH @ 2021-06-28  8:47 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

On Mon, Jun 28, 2021 at 08:14:41AM +0000, Roberto Sassu wrote:
> > From: Greg KH [mailto:gregkh@linuxfoundation.org]
> > Sent: Sunday, June 27, 2021 12:56 PM
> > On Fri, Jun 25, 2021 at 06:56:06PM +0200, Roberto Sassu wrote:
> > > +++ b/security/integrity/digest_lists/digest_lists.h
> > > @@ -0,0 +1,117 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Copyright (C) 2005,2006,2007,2008 IBM Corporation
> > > + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
> > > + *
> > > + * Author: Roberto Sassu <roberto.sassu@huawei.com>
> > > + *
> > > + * This program is free software; you can redistribute it and/or
> > > + * modify it under the terms of the GNU General Public License as
> > > + * published by the Free Software Foundation, version 2 of the
> > > + * License.
> > > + *
> > > + * File: digest_lists.h
> > > + *      Unexported definitions for digest lists.
> > 
> > Unexported to whom?
> 
> Hi Greg
> 
> I meant not placed in include/linux.

That's obvious based on the location of the file :)

> > > +
> > > +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);
> > > +}
> > 
> > pointer math feels rough, are you shure you want to do this this way?
> 
> Maybe, I could change digest_list_item_ref to:
> 
> struct digest_list_item_ref {
> 	struct digest_list_item *digest_list;
> 	u8 *digest;
> 	struct compact_list_hdr *hdr;
> };
> 
> where digest and hdr are calculated in the same way.

That works better, no need to do pointer math if you do not have to.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* RE: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-28  8:46       ` Greg KH
@ 2021-06-28  9:27         ` Roberto Sassu
  2021-06-28  9:32           ` Greg KH
  0 siblings, 1 reply; 25+ messages in thread
From: Roberto Sassu @ 2021-06-28  9:27 UTC (permalink / raw)
  To: Greg KH
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

> From: Greg KH [mailto:gregkh@linuxfoundation.org]
> Sent: Monday, June 28, 2021 10:46 AM
> On Mon, Jun 28, 2021 at 08:30:32AM +0000, Roberto Sassu wrote:
> > > > +struct compact_list_hdr {
> > > > +	__u8 version;
> > >
> > > You should never need a version, that way lies madness.
> >
> > We wanted to have a way to switch to a new format, if necessary.
> 
> Then just add a new ioctl if you need that in the future, no need to try
> to cram it into this one.

Given that digest lists are generated elsewhere, it would be still
unclear when the ioctl() would be issued. Maybe the kernel needs
to parse both v1 and v2 digest lists (I expect that v1 cannot be easily
converted to v2, if they are signed).

 It would be also unpractical if digest lists are loaded at kernel
initialization time (I didn't send the patch yet).

> > > > +	__le16 type;
> > > > +	__le16 modifiers;
> > > > +	__le16 algo;
> > > > +	__le32 count;
> > > > +	__le32 datalen;
> > >
> > > Why are user/kernel apis specified in little endian format?  Why would
> > > that matter?  Shouldn't they just be "native" endian?
> >
> > I thought this would make it clear that the kernel always expects the
> > digest lists to be in little endian.
> 
> Why would a big endian system expect the data from userspace to be in
> little endian?  Shouldn't this always just be "native" endian given that
> this is not something that is being sent to hardware?

The digest list might come from a system with different endianness.

Roberto

HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063
Managing Director: Li Peng, Li Jian, Shi Yanli

> thanks,
> 
> greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-28  9:27         ` Roberto Sassu
@ 2021-06-28  9:32           ` Greg KH
  2021-06-28  9:51             ` Roberto Sassu
  0 siblings, 1 reply; 25+ messages in thread
From: Greg KH @ 2021-06-28  9:32 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

On Mon, Jun 28, 2021 at 09:27:05AM +0000, Roberto Sassu wrote:
> > From: Greg KH [mailto:gregkh@linuxfoundation.org]
> > Sent: Monday, June 28, 2021 10:46 AM
> > On Mon, Jun 28, 2021 at 08:30:32AM +0000, Roberto Sassu wrote:
> > > > > +struct compact_list_hdr {
> > > > > +	__u8 version;
> > > >
> > > > You should never need a version, that way lies madness.
> > >
> > > We wanted to have a way to switch to a new format, if necessary.
> > 
> > Then just add a new ioctl if you need that in the future, no need to try
> > to cram it into this one.
> 
> Given that digest lists are generated elsewhere, it would be still
> unclear when the ioctl() would be issued. Maybe the kernel needs
> to parse both v1 and v2 digest lists (I expect that v1 cannot be easily
> converted to v2, if they are signed).
> 
>  It would be also unpractical if digest lists are loaded at kernel
> initialization time (I didn't send the patch yet).

Then that is up to your api design, I do not know.  But note that
"version" fields almost always never work, so be careful about assuming
that this will solve any future issues.

> > > > > +	__le16 type;
> > > > > +	__le16 modifiers;
> > > > > +	__le16 algo;
> > > > > +	__le32 count;
> > > > > +	__le32 datalen;
> > > >
> > > > Why are user/kernel apis specified in little endian format?  Why would
> > > > that matter?  Shouldn't they just be "native" endian?
> > >
> > > I thought this would make it clear that the kernel always expects the
> > > digest lists to be in little endian.
> > 
> > Why would a big endian system expect the data from userspace to be in
> > little endian?  Shouldn't this always just be "native" endian given that
> > this is not something that is being sent to hardware?
> 
> The digest list might come from a system with different endianness.

Ok, I have no idea what digests really are used for then.  So stick with
little endian and be sure to properly convert within the kernel as
needed.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* RE: [RFC][PATCH 03/12] digest_lists: Basic definitions
  2021-06-28  9:32           ` Greg KH
@ 2021-06-28  9:51             ` Roberto Sassu
  0 siblings, 0 replies; 25+ messages in thread
From: Roberto Sassu @ 2021-06-28  9:51 UTC (permalink / raw)
  To: Greg KH
  Cc: zohar, linux-integrity, linux-security-module, linux-doc,
	linux-kselftest, linux-kernel

> From: Greg KH [mailto:gregkh@linuxfoundation.org]
> Sent: Monday, June 28, 2021 11:32 AM
> On Mon, Jun 28, 2021 at 09:27:05AM +0000, Roberto Sassu wrote:
> > > From: Greg KH [mailto:gregkh@linuxfoundation.org]
> > > Sent: Monday, June 28, 2021 10:46 AM
> > > On Mon, Jun 28, 2021 at 08:30:32AM +0000, Roberto Sassu wrote:
> > > > > > +struct compact_list_hdr {
> > > > > > +	__u8 version;
> > > > >
> > > > > You should never need a version, that way lies madness.
> > > >
> > > > We wanted to have a way to switch to a new format, if necessary.
> > >
> > > Then just add a new ioctl if you need that in the future, no need to try
> > > to cram it into this one.
> >
> > Given that digest lists are generated elsewhere, it would be still
> > unclear when the ioctl() would be issued. Maybe the kernel needs
> > to parse both v1 and v2 digest lists (I expect that v1 cannot be easily
> > converted to v2, if they are signed).
> >
> >  It would be also unpractical if digest lists are loaded at kernel
> > initialization time (I didn't send the patch yet).
> 
> Then that is up to your api design, I do not know.  But note that
> "version" fields almost always never work, so be careful about assuming
> that this will solve any future issues.
> 
> > > > > > +	__le16 type;
> > > > > > +	__le16 modifiers;
> > > > > > +	__le16 algo;
> > > > > > +	__le32 count;
> > > > > > +	__le32 datalen;
> > > > >
> > > > > Why are user/kernel apis specified in little endian format?  Why would
> > > > > that matter?  Shouldn't they just be "native" endian?
> > > >
> > > > I thought this would make it clear that the kernel always expects the
> > > > digest lists to be in little endian.
> > >
> > > Why would a big endian system expect the data from userspace to be in
> > > little endian?  Shouldn't this always just be "native" endian given that
> > > this is not something that is being sent to hardware?
> >
> > The digest list might come from a system with different endianness.
> 
> Ok, I have no idea what digests really are used for then.  So stick with
> little endian and be sure to properly convert within the kernel as
> needed.

The most intuitive use case is to extend secure boot to the OS.

The kernel might be configured to accept only digest lists signed
with a trusted key.

Once the database is populated, execution and mmap can be denied
if the calculated file or metadata digest is not found in the database
(the file has not been released by the vendor).

Roberto

HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063
Managing Director: Li Peng, Li Jian, Shi Yanli

> thanks,
> 
> greg k-h

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2021-06-28  9:51 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-25 16:56 [RFC][PATCH 00/12] Huawei Digest Lists Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 01/12] ima: Add digest, algo, measured parameters to ima_measure_critical_data() Roberto Sassu
2021-06-25 18:26   ` Mimi Zohar
2021-06-25 16:56 ` [RFC][PATCH 02/12] digest_lists: Overview Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 03/12] digest_lists: Basic definitions Roberto Sassu
2021-06-27 10:53   ` Greg KH
2021-06-27 15:23     ` Matthew Wilcox
2021-06-27 15:35       ` Greg KH
2021-06-28  8:30     ` Roberto Sassu
2021-06-28  8:46       ` Greg KH
2021-06-28  9:27         ` Roberto Sassu
2021-06-28  9:32           ` Greg KH
2021-06-28  9:51             ` Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 04/12] digest_lists: Objects Roberto Sassu
2021-06-27 10:56   ` Greg KH
2021-06-28  8:14     ` Roberto Sassu
2021-06-28  8:47       ` Greg KH
2021-06-25 16:56 ` [RFC][PATCH 05/12] digest_lists: Methods Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 06/12] digest_lists: Parser Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 07/12] digest_lists: Interfaces - digest_list_add, digest_list_del Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 08/12] digest_lists: Interfaces - digest_lists_loaded Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 09/12] digest_lists: Interfaces - digest_label Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 10/12] digest_lists: Interfaces - digest_query Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 11/12] digest_lists: Interfaces - digests_count Roberto Sassu
2021-06-25 16:56 ` [RFC][PATCH 12/12] digest_lists: Tests Roberto Sassu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).