This patch set depends on: - https://lore.kernel.org/linux-integrity/20210723085304.1760138-1-roberto.sas... - https://lore.kernel.org/linux-integrity/20210705115650.3373599-1-roberto.sas...
I still kept pointer math to optimize the size of the digest_list_item_ref structure. Replacing offsets with pointers would cause the size of the structure to double. I could do this in the next version of the patch set if the size change is acceptable.
Digest Lists Integrity Module (DIGLIM) is a new component added to the integrity subsystem in the kernel, primarily aiming to aid Integrity Measurement Architecture (IMA) in the process of checking the integrity of file content and metadata. It accomplishes this task by storing reference values coming from software vendors and by reporting whether or not the digest of file content or metadata calculated by IMA (or EVM) is found among those values. In this way, IMA can decide, depending on the result of a query, if a measurement should be taken or access to the file should be granted. The Security Assumptions section explains more in detail why this component has been placed in the kernel.
The main benefits of using IMA in conjunction with DIGLIM are the ability to implement advanced remote attestation schemes based on the usage of a TPM key for establishing a TLS secure channel [1][2], and to reduce the burden on Linux distribution vendors to extend secure boot at OS level to applications.
DIGLIM does not have the complexity of feature-rich databases. In fact, its main functionality comes from the hash table primitives already in the kernel. It does not have an ad-hoc storage module, it just indexes data in a fixed format (digest lists, a set of concatenated digests preceded by a header), copied to kernel memory as they are. Lastly, it does not support database-oriented languages such as SQL, but only accepts a digest and its algorithm as a query.
The only digest list format supported by DIGLIM is called compact. However, Linux distribution vendors don't have to generate new digest lists in this format for the packages they release, as already available information, such as RPM headers and DEB package metadata, can be already used as a source for reference values (they already include file digests), with a user space parser taking care of the conversion to the compact format.
Although one might perceive that storing file or metadata digests for a Linux distribution would significantly increase the memory usage, this does not seem to be the case. As an anticipation of the evaluation done in the Preliminary Performance Evaluation section, protecting binaries and shared libraries of a minimal Fedora 33 installation requires 208K of memory for the digest lists plus 556K for indexing.
In exchange for a slightly increased memory usage, DIGLIM improves the performance of the integrity subsystem. In the considered scenario, IMA measurement and appraisal with digest lists requires respectively less than one quarter and less than half the time, compared to the current solution.
DIGLIM also keeps track of whether digest lists have been processed in some way (e.g. measured or appraised by IMA). This is important for example for remote attestation, so that remote verifiers understand what has been uploaded to the kernel.
DIGLIM behaves like 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, DIGLIM exposes to user space, through securityfs, the digest lists currently loaded, the number of digests added, a query interface and an interface to set digest list labels.
[1] LSS EU 2019
- slides: https://static.sched.com/hosted_files/lsseu2019/bd/secure_attested_communica... - video: https://youtu.be/mffdQgkvDNY
[2] FutureTPM EU project, final review meeting demo
- slides: https://futuretpm.eu/images/07-3-FutureTPM-Final-Review-Slides-WP6-Device-Ma... - video: https://vimeo.com/528251864/4c1d55abcd
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
DIGLIM further enhances the capabilities offered by IMA-based solutions and, at the same time, makes them more practical to adopt by reusing existing sources as reference values for integrity decisions.
Possible sources for digest lists are:
- RPM headers; - Debian repository metadata.
Benefits for IMA Measurement
One of the issues that arises when files are measured by the OS is that, due to parallel execution, the order in which file accesses happen cannot be predicted. Since the TPM Platform Configuration Register (PCR) extend operation, executed after each file measurement, cryptographically binds the current measurement to the previous ones, the PCR value at the end of a workload cannot be predicted too.
Thus, even if the usage of a TPM key, bound to a PCR value, should be allowed when only good files were accessed, the TPM could unexpectedly deny an operation on that key if files accesses did not happen as stated by the key policy (which allows only one of the possible sequences).
DIGLIM solves this issue by making the PCR value stable over the time and not dependent on file accesses. The following figure depicts the current and the new approaches:
IMA measurement list (current)
entry# 1st boot 2nd boot 3rd boot +----+---------------+ +----+---------------+ +----+---------------+ 1: | 10 | file1 measur. | | 10 | file3 measur. | | 10 | file2 measur. | +----+---------------+ +----+---------------+ +----+---------------+ 2: | 10 | file2 measur. | | 10 | file2 measur. | | 10 | file3 measur. | +----+---------------+ +----+---------------+ +----+---------------+ 3: | 10 | file3 measur. | | 10 | file1 measur. | | 10 | file4 measur. | +----+---------------+ +----+---------------+ +----+---------------+
PCR: Extend != Extend != Extend file1, file2, file3 file3, file2, file1 file2, file3, file4
PCR Extend definition:
PCR(new value) = Hash(Hash(meas. entry), PCR(previous value))
A new entry in the measurement list is created by IMA for each file access. Assuming that file1, file2 and file3 are files provided by the software vendor, file4 is an unknown file, the first two PCR values above represent a good system state, the third a bad system state. The PCR values are the result of the PCR extend operation performed for each measurement entry with the digest of the measurement entry as an input.
IMA measurement list (with DIGLIM)
dlist +--------------+ | header | +--------------+ | file1 digest | | file2 digest | | file3 digest | +--------------+
dlist is a digest list containing the digest of file1, file2 and file3. In the intended scenario, it is generated by a software vendor at the end of the building process, and retrieved by the administrator of the system where the digest list is loaded.
entry# 1st boot 2nd boot 3rd boot +----+---------------+ +----+---------------+ +----+---------------+ 0: | 11 | dlist measur. | | 11 | dlist measur. | | 11 | dlist measur. | +----+---------------+ +----+---------------+ +----+---------------+ 1: < file1 measur. skip > < file3 measur. skip > < file2 measur. skip >
2: < file2 measur. skip > < file2 measur. skip > < file3 measur. skip > +----+---------------+ 3: < file3 measur. skip > < file1 measur. skip > | 11 | file4 measur. | +----+---------------+
PCR: Extend = Extend != Extend dlist dlist dlist, file4
The first entry in the measurement list contains the digest of the digest list uploaded to the kernel at kernel initialization time.
When a file is accessed, IMA queries DIGLIM with the calculated file digest and, if it is found, IMA skips the measurement.
Thus, the only information sent to remote verifiers are: the list of files that could possibly be accessed (from the digest list), but not if they were accessed and when; the measurement of unknown files.
Despite providing less information, this solution has the advantage that the good system state (i.e. when only file1, file2 and file3 are accessed) now can be represented with a deterministic PCR value (the PCR is extended only with the measurement of the digest list). Also, the bad system state can still be distinguished from the good state (the PCR is extended also with the measurement of file4).
If a TPM key is bound to the good PCR value, the TPM would allow the key to be used if file1, file2 or file3 are accessed, regardless of the sequence in which they are accessed (the PCR value does not change), and would revoke the permission when the unknown file4 is accessed (the PCR value changes). If a system is able to establish a TLS connection with a peer, this implicitly means that the system was in a good state (i.e. file4 was not accessed, otherwise the TPM would have denied the usage of the TPM key due to the key policy).
Benefits for IMA Appraisal
Extending secure boot to applications means being able to verify the provenance of files accessed. IMA does it by verifying file signatures with a key that it trusts, which requires Linux distribution vendors to additionally include in the package header a signature for each file that must be verified (there is the dedicated RPMTAG_FILESIGNATURES section in the RPM header).
The proposed approach would be instead to verify data provenance from already available metadata (file digests) in existing packages. IMA would verify the signature of package metadata and search file digests extracted from package metadata and added to the hash table in the kernel.
For RPMs, file digests can be found in the RPMTAG_FILEDIGESTS section of RPMTAG_IMMUTABLE, whose signature is in RPMTAG_RSAHEADER. For DEBs, file digests (unsafe to use due to a weak digest algorithm) can be found in the md5sum file, which can be indirectly verified from Release.gpg.
The following figure highlights the differences between the current and the proposed approach.
IMA appraisal (current solution, with file signatures):
appraise +-----------+ V | +-------------------------+-----+ +-------+-----+ | | RPM header | | ima rpm | file1 | sig | | | ... | | plugin +-------+-----+ +-----+ | file1 sig [to be added] | sig |--------> ... | IMA | | ... | | +-------+-----+ +-----+ | fileN sig [to be added] | | | fileN | sig | +-------------------------+-----+ +-------+-----+
In this case, file signatures must be added to the RPM header, so that the ima rpm plugin can extract them together with the file content. The RPM header signature is not used.
IMA appraisal (with DIGLIM):
kernel hash table with RPM header content +---+ +--------------+ | |--->| file1 digest | +---+ +--------------+ ... +---+ appraise (file1) | | <--------------+ +----------------+-----+ +---+ | | RPM header | | ^ | | ... | | digest_list | | | file1 digest | sig | rpm plugin | +-------+ +-----+ | ... | |-------------+--->| file1 | | IMA | | fileN digest | | +-------+ +-----+ +----------------+-----+ | ^ | +------------------------------------+ appraise (RPM header)
In this case, the RPM header is used as it is, and its signature is used for IMA appraisal. Then, the digest_list rpm plugin executes the user space parser to parse the RPM header and add the extracted digests to an hash table in the kernel. IMA appraisal of the files in the RPM package consists in searching their digest in the hash table.
Other than reusing available information as digest list, another advantage is the lower computational overhead compared to the solution with file signatures (only one signature verification for many files and digest lookup, instead of per file signature verification, see Preliminary Performance Evaluation for more details).
Lifecycle
The lifecycle of DIGLIM is represented in the following figure:
Vendor premises (release process with modifications):
+------------+ +-----------------------+ +------------------------+ | 1. build a | | 2. generate and sign | | 3. publish the package | | package |-->| a digest list from |-->| and digest list in | | | | packaged files | | a repository | +------------+ +-----------------------+ +------------------------+ | | User premises: | V +---------------------+ +------------------------+ +-----------------+ | 6. use digest lists | | 5. download the digest | | 4. download and | | for measurement |<--| list and upload to |<--| install the | | and/or appraisal | | the kernel | | package | +---------------------+ +------------------------+ +-----------------+
The figure above represents all the steps when a digest list is generated separately. However, as mentioned in Contribution, in most cases existing packages can be already used as a source for digest lists, limiting the effort for software vendors.
If, for example, RPMs are used as a source for digest lists, the figure above becomes:
Vendor premises (release process without modifications):
+------------+ +------------------------+ | 1. build a | | 2. publish the package | | package |-->| in a repository |---------------------+ | | | | | +------------+ +------------------------+ | | | User premises: | V +---------------------+ +------------------------+ +-----------------+ | 5. use digest lists | | 4. extract digest list | | 3. download and | | for measurement |<--| from the package |<--| install the | | and/or appraisal | | and upload to the | | package | | | | kernel | | | +---------------------+ +------------------------+ +-----------------+
Step 4 can be performed with the digest_list rpm plugin and the user space parser, without changes to rpm itself.
Security Assumptions
As mentioned in the Introduction, DIGLIM will be primarily used in conjunction with IMA to enforce a mandatory policy on all user space processes, including those owned by root. Even root, in a system with a locked-down kernel, cannot affect the enforcement of the mandatory policy or, if changes are permitted, it cannot do so without being detected.
Given that the target of the enforcement are user space processes, DIGLIM cannot be placed in the target, as a Mandatory Access Control (MAC) design is required to have the components responsible to enforce the mandatory policy separated from the target.
While locking-down a system and limiting actions with a mandatory policy is generally perceived by users as an obstacle, it has noteworthy benefits for the users themselves.
First, it would timely block attempts by malicious software to steal or misuse user assets. Although users could query the package managers to detect them, detection would happen after the fact, or it wouldn't happen at all if the malicious software tampered with package managers. With a mandatory policy enforced by the kernel, users would still be able to decide which software they want to be executed except that, unlike package managers, the kernel is not affected by user space processes or root.
Second, it might make systems more easily verifiable from outside, due to the limited actions the system allows. When users connect to a server, not only they would be able to verify the server identity, which is already possible with communication protocols like TLS, but also if the software running on that server can be trusted to handle their sensitive data.
Adoption
A former version of DIGLIM is used in the following OSes:
- openEuler 20.09 https://github.com/openeuler-mirror/kernel/tree/openEuler-20.09 - openEuler 21.03 https://github.com/openeuler-mirror/kernel/tree/openEuler-21.03
Originally, DIGLIM was part of IMA (known as IMA Digest Lists). In this version, it has been redesigned as a standalone module with an API that makes its functionality accessible by IMA and, eventually, other subsystems.
User Space Support
Digest lists can be generated and managed with digest-list-tools:
https://github.com/openeuler-mirror/digest-list-tools
It includes two main applications:
- gen_digest_lists: generates digest lists from files in the filesystem or from the RPM database (more digest list sources can be supported); - manage_digest_lists: converts and uploads digest lists to the kernel.
Integration with rpm is done with the digest_list plugin:
https://gitee.com/src-openeuler/rpm/blob/master/Add-digest-list-plugin.patch
This plugin writes the RPM header and its signature to a file, so that the file is ready to be appraised by IMA, and calls the user space parser to convert and upload the digest list to the kernel.
Simple Usage Example (Tested with Fedora 33)
1. Digest list generation (RPM headers and their signature are copied to the specified directory):
# 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/diglim/digest_query # cat /sys/kernel/security/integrity/diglim/digest_query sha256-[...]-0-file_list-rpm-coreutils-8.32-18.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 106, datalen: 3392
4. Second digest list query:
# echo sha256-$(sha256sum /bin/zip) > /sys/kernel/security/integrity/diglim/digest_query # cat /sys/kernel/security/integrity/diglim/digest_query sha256-[...]-0-file_list-rpm-zip-3.0-27.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 4, datalen: 128
Preliminary Performance Evaluation
This section provides an initial estimation of the overhead introduced by DIGLIM. The estimation has been performed on a Fedora 33 virtual machine with 1447 packages installed. The virtual machine has 16 vCPU (host CPU: AMD Ryzen Threadripper PRO 3955WX 16-Cores) and 2G of RAM (host memory: 64G). The virtual machine also has a vTPM with libtpms and swtpm as backend.
After writing the RPM headers to files, the size of the directory containing them is 36M.
After converting the RPM headers to the compact digest list, the size of the data being uploaded to the kernel is 3.6M.
The time to load the entire RPM database is 0.628s.
After loading the digest lists to the kernel, the slab usage due to indexing is (obtained with slab_nomerge in the kernel command line):
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 118144 118144 100% 0,03K 923 128 3692K digest_list_item_ref_cache 102400 102400 100% 0,03K 800 128 3200K digest_item_cache 2646 2646 100% 0,09K 63 42 252K digest_list_item_cache
The stats, obtained from the digests_count interface, introduced later, are:
Parser digests: 0 File digests: 99100 Metadata digests: 0 Digest list digests: 1423
On this installation, this would be the worst case in which all files are measured and/or appraised, which is currently not recommended without enforcing an integrity policy protecting mutable files. Infoflow LSM is a component to accomplish this task:
https://patchwork.kernel.org/project/linux-integrity/cover/20190818235745.14...
The first manageable goal of IMA with DIGLIM is to use an execution policy, with measurement and/or appraisal of files executed or mapped in memory as executable (in addition to kernel modules and firmware). In this case, the digest list contains the digest only for those files. The numbers above change as follows.
After converting the RPM headers to the compact digest list, the size of the data being uploaded to the kernel is 208K.
The time to load the digest of binaries and shared libraries is 0.062s.
After loading the digest lists to the kernel, the slab usage due to indexing is:
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 7168 7168 100% 0,03K 56 128 224K digest_list_item_ref_cache 7168 7168 100% 0,03K 56 128 224K digest_item_cache 1134 1134 100% 0,09K 27 42 108K digest_list_item_cache
The stats, obtained from the digests_count interface, are:
Parser digests: 0 File digests: 5986 Metadata digests: 0 Digest list digests: 1104
Comparison with IMA
This section compares the performance between the current solution for IMA measurement and appraisal, and IMA with DIGLIM.
Workload A (without DIGLIM):
1. cat file[0-5985] > /dev/null
Workload B (with DIGLIM):
1. echo $PWD/0-file_list-compact-file[0-1103] > <securityfs>/integrity/diglim/digest_list_add 2. cat file[0-5985] > /dev/null
Workload A execution time without IMA policy:
real 0m0,155s user 0m0,008s sys 0m0,066s
Measurement
IMA policy:
measure fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow pcr=11 ima_template=ima-sig
use_diglim is a policy keyword not yet supported by IMA.
Workload A execution time with IMA and 5986 files with signature measured:
real 0m8,273s user 0m0,008s sys 0m2,537s
Workload B execution time with IMA, 1104 digest lists with signature measured and uploaded to the kernel, and 5986 files with signature accessed but not measured (due to the file digest being found in the hash table):
real 0m1,837s user 0m0,036s sys 0m0,583s
Appraisal
IMA policy:
appraise fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow
use_diglim is a policy keyword not yet supported by IMA.
Workload A execution time with IMA and 5986 files with file signature appraised:
real 0m2,197s user 0m0,011s sys 0m2,022s
Workload B execution time with IMA, 1104 digest lists with signature appraised and uploaded to the kernel, and with 5986 files with signature not verified (due to the file digest being found in the hash table):
real 0m0,982s user 0m0,020s sys 0m0,865s
Changelog
v1: - remove 'ima: Add digest, algo, measured parameters to ima_measure_critical_data()', replaced by: https://lore.kernel.org/linux-integrity/20210705090922.3321178-1-roberto.sas... - add 'Lifecycle' subsection to better clarify how digest lists are generated and used (suggested by Greg KH) - remove 'Possible Usages' subsection and add 'Benefits for IMA Measurement' and 'Benefits for IMA Appraisal' subsubsections - add 'Preliminary Performance Evaluation' subsection - declare digest_offset and hdr_offset in the digest_list_item_ref structure as u32 (sufficient for digest lists of 4G) to make room for a list_head structure (digest_list_item_ref size: 32) - implement digest list reference management with a linked list instead of an array - reorder structure members for better alignment (suggested by Mauro) - rename digest_lookup() to __digest_lookup() (suggested by Mauro) - introduce an object cache for each defined structure - replace atomic_long_t with unsigned long in h_table structure definition (suggested by Greg KH) - remove GPL2 license text and file names (suggested by Greg KH) - ensure that the _reserved field of compact_list_hdr is equal to zero (suggested by Greg KH) - dynamically allocate the buffer in digest_lists_show_htable_len() to avoid frame size warning (reported by kernel test robot, dynamic allocation suggested by Mauro) - split documentation in multiple files and reference the source code (suggested by Mauro) - use #ifdef in include/linux/diglim.h - improve generation of event name for IMA measurements - add new patch to introduce the 'Remote Attestation' section in the documentation - fix assignment of actions variable in digest_list_read() and digest_list_write() - always release dentry reference when digest_list_get_secfs_files() is called - rewrite add/del and query interfaces to take advantage of m->private - prevent deletion of a digest list only if there are actions done at addition time that are not currently being performed - fix doc warnings (replace Returns with Return:) - perform queries of digest list digests in the existing tests - add new tests: digest_list_add_del_test_file_upload_measured, digest_list_check_measurement_list_test_file_upload and digest_list_check_measurement_list_test_buffer_upload - don't return a value from digest_del(), digest_list_ref_del, and digest_list_del() - improve Makefile for tests
Roberto Sassu (12): diglim: Overview diglim: Basic definitions diglim: Objects diglim: Methods diglim: Parser diglim: Interfaces - digest_list_add, digest_list_del diglim: Interfaces - digest_lists_loaded diglim: Interfaces - digest_label diglim: Interfaces - digest_query diglim: Interfaces - digests_count diglim: Remote Attestation diglim: Tests
.../security/diglim/architecture.rst | 45 + .../security/diglim/implementation.rst | 255 +++ Documentation/security/diglim/index.rst | 14 + .../security/diglim/introduction.rst | 631 ++++++++ .../security/diglim/remote_attestation.rst | 87 ++ Documentation/security/diglim/tests.rst | 66 + Documentation/security/index.rst | 1 + MAINTAINERS | 19 + include/linux/diglim.h | 28 + include/linux/kernel_read_file.h | 1 + include/uapi/linux/diglim.h | 51 + security/integrity/Kconfig | 1 + security/integrity/Makefile | 1 + security/integrity/diglim/Kconfig | 11 + security/integrity/diglim/Makefile | 8 + security/integrity/diglim/diglim.h | 157 ++ security/integrity/diglim/fs.c | 782 ++++++++++ security/integrity/diglim/methods.c | 499 ++++++ security/integrity/diglim/parser.c | 274 ++++ security/integrity/integrity.h | 4 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/diglim/Makefile | 19 + tools/testing/selftests/diglim/common.c | 115 ++ tools/testing/selftests/diglim/common.h | 31 + tools/testing/selftests/diglim/config | 3 + tools/testing/selftests/diglim/selftest.c | 1382 +++++++++++++++++ 26 files changed, 4486 insertions(+) create mode 100644 Documentation/security/diglim/architecture.rst create mode 100644 Documentation/security/diglim/implementation.rst create mode 100644 Documentation/security/diglim/index.rst create mode 100644 Documentation/security/diglim/introduction.rst create mode 100644 Documentation/security/diglim/remote_attestation.rst create mode 100644 Documentation/security/diglim/tests.rst create mode 100644 include/linux/diglim.h create mode 100644 include/uapi/linux/diglim.h create mode 100644 security/integrity/diglim/Kconfig create mode 100644 security/integrity/diglim/Makefile create mode 100644 security/integrity/diglim/diglim.h create mode 100644 security/integrity/diglim/fs.c create mode 100644 security/integrity/diglim/methods.c create mode 100644 security/integrity/diglim/parser.c create mode 100644 tools/testing/selftests/diglim/Makefile create mode 100644 tools/testing/selftests/diglim/common.c create mode 100644 tools/testing/selftests/diglim/common.h create mode 100644 tools/testing/selftests/diglim/config create mode 100644 tools/testing/selftests/diglim/selftest.c
Add an overview of DIGLIM to Documentation/security/diglim/introduction.rst and the architecture to Documentation/security/diglim/architecture.rst
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- .../security/diglim/architecture.rst | 45 ++ Documentation/security/diglim/index.rst | 11 + .../security/diglim/introduction.rst | 631 ++++++++++++++++++ Documentation/security/index.rst | 1 + MAINTAINERS | 9 + 5 files changed, 697 insertions(+) create mode 100644 Documentation/security/diglim/architecture.rst create mode 100644 Documentation/security/diglim/index.rst create mode 100644 Documentation/security/diglim/introduction.rst
diff --git a/Documentation/security/diglim/architecture.rst b/Documentation/security/diglim/architecture.rst new file mode 100644 index 000000000000..a54fe2453715 --- /dev/null +++ b/Documentation/security/diglim/architecture.rst @@ -0,0 +1,45 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Architecture +============ + +This section introduces the high level architecture of DIGLIM. + +:: + + 5. add/delete from hash table and add refs to digest list + +---------------------------------------------+ + | +-----+ +-------------+ +--+ + | | key |-->| digest refs |-->...-->| | + V +-----+ +-------------+ +--+ + +-------------+ +-----+ +-------------+ + | digest list | | key |-->| digest refs | + | (compact) | +-----+ +-------------+ + +-------------+ +-----+ +-------------+ + ^ 4. copy to | key |-->| digest refs | + | kernel memory +-----+ +-------------+ kernel space + -------------------------------------------------------------------------- + ^ ^ user space + |<----------------+ 3b. upload | + +-------------+ +------------+ | 6. query digest + | digest list | | user space | 2b. convert + | (compact) | | parser | + +-------------+ +------------+ + 1a. upload ^ 1b. read + | + +------------+ + | RPM header | + +------------+ + + +As mentioned 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 a linked list of digest list references to locate the +digests in the digest list) (step 5). + +Finally, digests can be searched from user space through a securityfs file +(step 6) or by the kernel itself. diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst new file mode 100644 index 000000000000..0fc5ab019bc0 --- /dev/null +++ b/Documentation/security/diglim/index.rst @@ -0,0 +1,11 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +Digest Lists Integrity Module (DIGLIM) +====================================== + +.. toctree:: + :maxdepth: 1 + + introduction + architecture diff --git a/Documentation/security/diglim/introduction.rst b/Documentation/security/diglim/introduction.rst new file mode 100644 index 000000000000..d8d8b2a17222 --- /dev/null +++ b/Documentation/security/diglim/introduction.rst @@ -0,0 +1,631 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Introduction +============ + +Digest Lists Integrity Module (DIGLIM) is a new component added to the +integrity subsystem in the kernel, primarily aiming to aid Integrity +Measurement Architecture (IMA) in the process of checking the integrity of +file content and metadata. It accomplishes this task by storing reference +values coming from software vendors and by reporting whether or not the +digest of file content or metadata calculated by IMA (or EVM) is found +among those values. In this way, IMA can decide, depending on the result of +a query, if a measurement should be taken or access to the file should be +granted. The `Security Assumptions`_ section explains more in detail why +this component has been placed in the kernel. + +The main benefits of using IMA in conjunction with DIGLIM are the ability +to implement advanced remote attestation schemes based on the usage of a +TPM key for establishing a TLS secure channel [1][2], and to reduce the +burden on Linux distribution vendors to extend secure boot at OS level to +applications. + +DIGLIM does not have the complexity of feature-rich databases. In fact, its +main functionality comes from the hash table primitives already in the +kernel. It does not have an ad-hoc storage module, it just indexes data in +a fixed format (digest lists, a set of concatenated digests preceded by a +header), copied to kernel memory as they are. Lastly, it does not support +database-oriented languages such as SQL, but only accepts a digest and its +algorithm as a query. + +The only digest list format supported by DIGLIM is called ``compact``. +However, Linux distribution vendors don't have to generate new digest lists +in this format for the packages they release, as already available +information, such as RPM headers and DEB package metadata, can be already +used as a source for reference values (they already include file digests), +with a user space parser taking care of the conversion to the compact +format. + +Although one might perceive that storing file or metadata digests for a +Linux distribution would significantly increase the memory usage, this does +not seem to be the case. As an anticipation of the evaluation done in the +`Preliminary Performance Evaluation`_ section, protecting binaries and +shared libraries of a minimal Fedora 33 installation requires 208K of +memory for the digest lists plus 556K for indexing. + +In exchange for a slightly increased memory usage, DIGLIM improves the +performance of the integrity subsystem. In the considered scenario, IMA +measurement and appraisal with digest lists requires respectively less than +one quarter and less than half the time, compared to the current solution. + +DIGLIM also keeps track of whether digest lists have been processed in some +way (e.g. measured or appraised by IMA). This is important for example for +remote attestation, so that remote verifiers understand what has been +uploaded to the kernel. + +DIGLIM behaves like 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, DIGLIM exposes to user space, through securityfs, the digest lists +currently loaded, the number of digests added, a query interface and an +interface to set digest list labels. + +[1] LSS EU 2019 + +- slides: + https://static.sched.com/hosted_files/lsseu2019/bd/secure_attested_communica... +- video: https://youtu.be/mffdQgkvDNY + +[2] FutureTPM EU project, final review meeting demo + +- slides: + https://futuretpm.eu/images/07-3-FutureTPM-Final-Review-Slides-WP6-Device-Ma... +- video: https://vimeo.com/528251864/4c1d55abcd + + +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 +------------ + +DIGLIM further enhances the capabilities offered by IMA-based solutions +and, at the same time, makes them more practical to adopt by reusing +existing sources as reference values for integrity decisions. + +Possible sources for digest lists are: + +- RPM headers; +- Debian repository metadata. + + +Benefits for IMA Measurement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One of the issues that arises when files are measured by the OS is that, +due to parallel execution, the order in which file accesses happen cannot +be predicted. Since the TPM Platform Configuration Register (PCR) extend +operation, executed after each file measurement, cryptographically binds +the current measurement to the previous ones, the PCR value at the end of a +workload cannot be predicted too. + +Thus, even if the usage of a TPM key, bound to a PCR value, should be +allowed when only good files were accessed, the TPM could unexpectedly deny +an operation on that key if files accesses did not happen as stated by the +key policy (which allows only one of the possible sequences). + +DIGLIM solves this issue by making the PCR value stable over the time and +not dependent on file accesses. The following figure depicts the current +and the new approaches: + +:: + + IMA measurement list (current) + + entry# 1st boot 2nd boot 3rd boot + +----+---------------+ +----+---------------+ +----+---------------+ + 1: | 10 | file1 measur. | | 10 | file3 measur. | | 10 | file2 measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + 2: | 10 | file2 measur. | | 10 | file2 measur. | | 10 | file3 measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + 3: | 10 | file3 measur. | | 10 | file1 measur. | | 10 | file4 measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + + PCR: Extend != Extend != Extend + file1, file2, file3 file3, file2, file1 file2, file3, file4 + + + PCR Extend definition: + + PCR(new value) = Hash(Hash(meas. entry), PCR(previous value)) + +A new entry in the measurement list is created by IMA for each file access. +Assuming that ``file1``, ``file2`` and ``file3`` are files provided by the +software vendor, ``file4`` is an unknown file, the first two PCR values +above represent a good system state, the third a bad system state. The PCR +values are the result of the PCR extend operation performed for each +measurement entry with the digest of the measurement entry as an input. + +:: + + IMA measurement list (with DIGLIM) + + dlist + +--------------+ + | header | + +--------------+ + | file1 digest | + | file2 digest | + | file3 digest | + +--------------+ + +``dlist`` is a digest list containing the digest of ``file1``, ``file2`` +and ``file3``. In the intended scenario, it is generated by a software +vendor at the end of the building process, and retrieved by the +administrator of the system where the digest list is loaded. + +:: + + entry# 1st boot 2nd boot 3rd boot + +----+---------------+ +----+---------------+ +----+---------------+ + 0: | 11 | dlist measur. | | 11 | dlist measur. | | 11 | dlist measur. | + +----+---------------+ +----+---------------+ +----+---------------+ + 1: < file1 measur. skip > < file3 measur. skip > < file2 measur. skip > + + 2: < file2 measur. skip > < file2 measur. skip > < file3 measur. skip > + +----+---------------+ + 3: < file3 measur. skip > < file1 measur. skip > | 11 | file4 measur. | + +----+---------------+ + + PCR: Extend = Extend != Extend + dlist dlist dlist, file4 + + +The first entry in the measurement list contains the digest of the digest +list uploaded to the kernel at kernel initialization time. + +When a file is accessed, IMA queries DIGLIM with the calculated file digest +and, if it is found, IMA skips the measurement. + +Thus, the only information sent to remote verifiers are: the list of +files that could possibly be accessed (from the digest list), but not if +they were accessed and when; the measurement of unknown files. + +Despite providing less information, this solution has the advantage that +the good system state (i.e. when only ``file1``, ``file2`` and ``file3`` +are accessed) now can be represented with a deterministic PCR value (the +PCR is extended only with the measurement of the digest list). Also, the +bad system state can still be distinguished from the good state (the PCR is +extended also with the measurement of ``file4``). + +If a TPM key is bound to the good PCR value, the TPM would allow the key to +be used if ``file1``, ``file2`` or ``file3`` are accessed, regardless of +the sequence in which they are accessed (the PCR value does not change), +and would revoke the permission when the unknown ``file4`` is accessed (the +PCR value changes). If a system is able to establish a TLS connection with +a peer, this implicitly means that the system was in a good state (i.e. +``file4`` was not accessed, otherwise the TPM would have denied the usage +of the TPM key due to the key policy). + + +Benefits for IMA Appraisal +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Extending secure boot to applications means being able to verify the +provenance of files accessed. IMA does it by verifying file signatures with +a key that it trusts, which requires Linux distribution vendors to +additionally include in the package header a signature for each file that +must be verified (there is the dedicated ``RPMTAG_FILESIGNATURES`` section +in the RPM header). + +The proposed approach would be instead to verify data provenance from +already available metadata (file digests) in existing packages. IMA would +verify the signature of package metadata and search file digests extracted +from package metadata and added to the hash table in the kernel. + +For RPMs, file digests can be found in the ``RPMTAG_FILEDIGESTS`` section +of ``RPMTAG_IMMUTABLE``, whose signature is in ``RPMTAG_RSAHEADER``. For +DEBs, file digests (unsafe to use due to a weak digest algorithm) can be +found in the ``md5sum`` file, which can be indirectly verified from +``Release.gpg``. + +The following figure highlights the differences between the current and the +proposed approach. + +:: + + IMA appraisal (current solution, with file signatures): + + appraise + +-----------+ + V | + +-------------------------+-----+ +-------+-----+ | + | RPM header | | ima rpm | file1 | sig | | + | ... | | plugin +-------+-----+ +-----+ + | file1 sig [to be added] | sig |--------> ... | IMA | + | ... | | +-------+-----+ +-----+ + | fileN sig [to be added] | | | fileN | sig | + +-------------------------+-----+ +-------+-----+ + +In this case, file signatures must be added to the RPM header, so that the +``ima`` rpm plugin can extract them together with the file content. The RPM +header signature is not used. + +:: + + IMA appraisal (with DIGLIM): + + kernel hash table + with RPM header content + +---+ +--------------+ + | |--->| file1 digest | + +---+ +--------------+ + ... + +---+ appraise (file1) + | | <--------------+ + +----------------+-----+ +---+ | + | RPM header | | ^ | + | ... | | digest_list | | + | file1 digest | sig | rpm plugin | +-------+ +-----+ + | ... | |-------------+--->| file1 | | IMA | + | fileN digest | | +-------+ +-----+ + +----------------+-----+ | + ^ | + +------------------------------------+ + appraise (RPM header) + +In this case, the RPM header is used as it is, and its signature is used +for IMA appraisal. Then, the ``digest_list`` rpm plugin executes the user +space parser to parse the RPM header and add the extracted digests to an +hash table in the kernel. IMA appraisal of the files in the RPM package +consists in searching their digest in the hash table. + +Other than reusing available information as digest list, another advantage +is the lower computational overhead compared to the solution with file +signatures (only one signature verification for many files and digest +lookup, instead of per file signature verification, see `Preliminary +Performance Evaluation`_ for more details). + + +Lifecycle +--------- + +The lifecycle of DIGLIM is represented in the following figure: + +:: + + Vendor premises (release process with modifications): + + +------------+ +-----------------------+ +------------------------+ + | 1. build a | | 2. generate and sign | | 3. publish the package | + | package |-->| a digest list from |-->| and digest list in | + | | | packaged files | | a repository | + +------------+ +-----------------------+ +------------------------+ + | + | + User premises: | + V + +---------------------+ +------------------------+ +-----------------+ + | 6. use digest lists | | 5. download the digest | | 4. download and | + | for measurement |<--| list and upload to |<--| install the | + | and/or appraisal | | the kernel | | package | + +---------------------+ +------------------------+ +-----------------+ + +The figure above represents all the steps when a digest list is +generated separately. However, as mentioned in `Contribution`_, in most +cases existing packages can be already used as a source for digest lists, +limiting the effort for software vendors. + +If, for example, RPMs are used as a source for digest lists, the figure +above becomes: + +:: + + Vendor premises (release process without modifications): + + +------------+ +------------------------+ + | 1. build a | | 2. publish the package | + | package |-->| in a repository |---------------------+ + | | | | | + +------------+ +------------------------+ | + | + | + User premises: | + V + +---------------------+ +------------------------+ +-----------------+ + | 5. use digest lists | | 4. extract digest list | | 3. download and | + | for measurement |<--| from the package |<--| install the | + | and/or appraisal | | and upload to the | | package | + | | | kernel | | | + +---------------------+ +------------------------+ +-----------------+ + +Step 4 can be performed with the ``digest_list`` rpm plugin and the user +space parser, without changes to rpm itself. + + +Security Assumptions +-------------------- + +As mentioned in the `Introduction`_, DIGLIM will be primarily used in +conjunction with IMA to enforce a mandatory policy on all user space +processes, including those owned by root. Even root, in a system with a +locked-down kernel, cannot affect the enforcement of the mandatory policy +or, if changes are permitted, it cannot do so without being detected. + +Given that the target of the enforcement are user space processes, DIGLIM +cannot be placed in the target, as a Mandatory Access Control (MAC) design +is required to have the components responsible to enforce the mandatory +policy separated from the target. + +While locking-down a system and limiting actions with a mandatory policy is +generally perceived by users as an obstacle, it has noteworthy benefits for +the users themselves. + +First, it would timely block attempts by malicious software to steal or +misuse user assets. Although users could query the package managers to +detect them, detection would happen after the fact, or it wouldn't happen +at all if the malicious software tampered with package managers. With a +mandatory policy enforced by the kernel, users would still be able to +decide which software they want to be executed except that, unlike package +managers, the kernel is not affected by user space processes or root. + +Second, it might make systems more easily verifiable from outside, due to +the limited actions the system allows. When users connect to a server, not +only they would be able to verify the server identity, which is already +possible with communication protocols like TLS, but also if the software +running on that server can be trusted to handle their sensitive data. + + +Adoption +-------- + +A former version of DIGLIM is used in the following OSes: + +- openEuler 20.09 + https://github.com/openeuler-mirror/kernel/tree/openEuler-20.09 + +- openEuler 21.03 + https://github.com/openeuler-mirror/kernel/tree/openEuler-21.03 + +Originally, DIGLIM was part of IMA (known as IMA Digest Lists). In this +version, it has been redesigned as a standalone module with an API that +makes its functionality accessible by IMA and, eventually, other +subsystems. + +User Space Support +------------------ + +Digest lists can be generated and managed with ``digest-list-tools``: + +https://github.com/openeuler-mirror/digest-list-tools + +It includes two main applications: + +- ``gen_digest_lists``: generates digest lists from files in the + filesystem or from the RPM database (more digest list sources can be + supported); +- ``manage_digest_lists``: converts and uploads digest lists to the + kernel. + +Integration with rpm is done with the ``digest_list`` plugin: + +https://gitee.com/src-openeuler/rpm/blob/master/Add-digest-list-plugin.patch + +This plugin writes the RPM header and its signature to a file, so that the +file is ready to be appraised by IMA, and calls the user space parser to +convert and upload the digest list to the kernel. + + +Simple Usage Example (Tested with Fedora 33) +-------------------------------------------- + +1. Digest list generation (RPM headers and their signature are copied to + the specified directory): + +.. code-block:: bash + + # mkdir /etc/digest_lists + # gen_digest_lists -t file -f rpm+db -d /etc/digest_lists -o add + +2. Digest list upload with the user space parser: + +.. code-block:: bash + + # manage_digest_lists -p add-digest -d /etc/digest_lists + +3. First digest list query: + +.. code-block:: bash + + # echo sha256-$(sha256sum /bin/cat) > /sys/kernel/security/integrity/diglim/digest_query + # cat /sys/kernel/security/integrity/diglim/digest_query + sha256-[...]-0-file_list-rpm-coreutils-8.32-18.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 106, datalen: 3392 + +4. Second digest list query: + +.. code-block:: bash + + # echo sha256-$(sha256sum /bin/zip) > /sys/kernel/security/integrity/diglim/digest_query + # cat /sys/kernel/security/integrity/diglim/digest_query + sha256-[...]-0-file_list-rpm-zip-3.0-27.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 4, datalen: 128 + + +Preliminary Performance Evaluation +---------------------------------- + +This section provides an initial estimation of the overhead introduced by +DIGLIM. The estimation has been performed on a Fedora 33 virtual machine +with 1447 packages installed. The virtual machine has 16 vCPU (host CPU: +AMD Ryzen Threadripper PRO 3955WX 16-Cores) and 2G of RAM (host memory: +64G). The virtual machine also has a vTPM with libtpms and swtpm as +backend. + +After writing the RPM headers to files, the size of the directory +containing them is 36M. + +After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 3.6M. + +The time to load the entire RPM database is 0.628s. + +After loading the digest lists to the kernel, the slab usage due to +indexing is (obtained with slab_nomerge in the kernel command line): + +:: + + OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME + 118144 118144 100% 0,03K 923 128 3692K digest_list_item_ref_cache + 102400 102400 100% 0,03K 800 128 3200K digest_item_cache + 2646 2646 100% 0,09K 63 42 252K digest_list_item_cache + +The stats, obtained from the ``digests_count`` interface, introduced later, +are: + +:: + + Parser digests: 0 + File digests: 99100 + Metadata digests: 0 + Digest list digests: 1423 + +On this installation, this would be the worst case in which all files are +measured and/or appraised, which is currently not recommended without +enforcing an integrity policy protecting mutable files. Infoflow LSM is a +component to accomplish this task: + +https://patchwork.kernel.org/project/linux-integrity/cover/20190818235745.14... + +The first manageable goal of IMA with DIGLIM is to use an execution policy, +with measurement and/or appraisal of files executed or mapped in memory as +executable (in addition to kernel modules and firmware). In this +case, the digest list contains the digest only for those files. The numbers +above change as follows. + +After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 208K. + +The time to load the digest of binaries and shared libraries is 0.062s. + +After loading the digest lists to the kernel, the slab usage due to +indexing is: + +:: + + OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME + 7168 7168 100% 0,03K 56 128 224K digest_list_item_ref_cache + 7168 7168 100% 0,03K 56 128 224K digest_item_cache + 1134 1134 100% 0,09K 27 42 108K digest_list_item_cache + + +The stats, obtained from the ``digests_count`` interface, are: + +:: + + Parser digests: 0 + File digests: 5986 + Metadata digests: 0 + Digest list digests: 1104 + + +Comparison with IMA +~~~~~~~~~~~~~~~~~~~ + +This section compares the performance between the current solution for IMA +measurement and appraisal, and IMA with DIGLIM. + + +Workload A (without DIGLIM): + +#. cat file[0-5985] > /dev/null + + +Workload B (with DIGLIM): + +#. echo $PWD/0-file_list-compact-file[0-1103] > <securityfs>/integrity/diglim/digest_list_add +#. cat file[0-5985] > /dev/null + + +Workload A execution time without IMA policy: + +:: + + real 0m0,155s + user 0m0,008s + sys 0m0,066s + + +Measurement +........... + +IMA policy: + +:: + + measure fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow pcr=11 ima_template=ima-sig + +``use_diglim`` is a policy keyword not yet supported by IMA. + + +Workload A execution time with IMA and 5986 files with signature measured: + +:: + + real 0m8,273s + user 0m0,008s + sys 0m2,537s + + +Workload B execution time with IMA, 1104 digest lists with signature +measured and uploaded to the kernel, and 5986 files with signature accessed +but not measured (due to the file digest being found in the hash table): + +:: + + real 0m1,837s + user 0m0,036s + sys 0m0,583s + + +Appraisal +......... + +IMA policy: + +:: + + appraise fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow + +``use_diglim`` is a policy keyword not yet supported by IMA. + + +Workload A execution time with IMA and 5986 files with file signature +appraised: + +:: + + real 0m2,197s + user 0m0,011s + sys 0m2,022s + + +Workload B execution time with IMA, 1104 digest lists with signature +appraised and uploaded to the kernel, and with 5986 files with signature +not verified (due to the file digest being found in the hash table): + +:: + + real 0m0,982s + user 0m0,020s + sys 0m0,865s diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 16335de04e8c..6c3aea41c55b 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -17,3 +17,4 @@ Security Documentation tpm/index digsig landlock + diglim/index diff --git a/MAINTAINERS b/MAINTAINERS index 6c8be735cc91..c914dadd7e65 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5452,6 +5452,15 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-gpio-mm.c
+DIGLIM +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/diglim/architecture.rst +F: Documentation/security/diglim/index.rst +F: Documentation/security/diglim/introduction.rst + DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net L: linux-i2c@vger.kernel.org
Em Mon, 26 Jul 2021 18:36:49 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Add an overview of DIGLIM to Documentation/security/diglim/introduction.rst and the architecture to Documentation/security/diglim/architecture.rst
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/architecture.rst | 45 ++ Documentation/security/diglim/index.rst | 11 + .../security/diglim/introduction.rst | 631 ++++++++++++++++++ Documentation/security/index.rst | 1 + MAINTAINERS | 9 + 5 files changed, 697 insertions(+) create mode 100644 Documentation/security/diglim/architecture.rst create mode 100644 Documentation/security/diglim/index.rst create mode 100644 Documentation/security/diglim/introduction.rst
diff --git a/Documentation/security/diglim/architecture.rst b/Documentation/security/diglim/architecture.rst new file mode 100644 index 000000000000..a54fe2453715 --- /dev/null +++ b/Documentation/security/diglim/architecture.rst @@ -0,0 +1,45 @@ +.. SPDX-License-Identifier: GPL-2.0
+Architecture +============
+This section introduces the high level architecture of DIGLIM.
+::
- 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
"before"? This is at the beginning of this document ;-)
You should probably add a reference to introduction.rst here, like:
As mentioned at Documentation/security/diglim/introduction.rst, ...
+the compact format (step 1a) or can be uploaded indirectly by the user +space parser if they are in an alternative format (steps 1b-3b).
+During upload, the kernel makes a copy of the digest list to the kernel +memory (step 4), and creates the necessary structures to index the digests +(hash table and a linked list of digest list references to locate the +digests in the digest list) (step 5).
+Finally, digests can be searched from user space through a securityfs file +(step 6) or by the kernel itself.
This probably applies to Documentation/security as a hole, but the best is to split the documents on two separate parts: - the kAPI and internals; - the admin-guide part.
The audience for the admin-guide is distribution pagagers and syssadmins.
diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst new file mode 100644 index 000000000000..0fc5ab019bc0 --- /dev/null +++ b/Documentation/security/diglim/index.rst @@ -0,0 +1,11 @@ +.. SPDX-License-Identifier: GPL-2.0
+====================================== +Digest Lists Integrity Module (DIGLIM) +======================================
+.. toctree::
- :maxdepth: 1
- introduction
- architecture
diff --git a/Documentation/security/diglim/introduction.rst b/Documentation/security/diglim/introduction.rst new file mode 100644 index 000000000000..d8d8b2a17222 --- /dev/null +++ b/Documentation/security/diglim/introduction.rst @@ -0,0 +1,631 @@ +.. SPDX-License-Identifier: GPL-2.0
+Introduction +============
+Digest Lists Integrity Module (DIGLIM) is a new component added to the +integrity subsystem in the kernel, primarily aiming to aid Integrity
I would replace:
"is a new component added to" -> "is a component of"
As this is the kind of text that tends to be outdated with time... Imagine someone reading this paragraph maybe 10 years in the future ;-)
+Measurement Architecture (IMA) in the process of checking the integrity of +file content and metadata. It accomplishes this task by storing reference +values coming from software vendors and by reporting whether or not the +digest of file content or metadata calculated by IMA (or EVM) is found +among those values. In this way, IMA can decide, depending on the result of +a query, if a measurement should be taken or access to the file should be +granted. The `Security Assumptions`_ section explains more in detail why +this component has been placed in the kernel.
+The main benefits of using IMA in conjunction with DIGLIM are the ability +to implement advanced remote attestation schemes based on the usage of a +TPM key for establishing a TLS secure channel [1][2], and to reduce the +burden on Linux distribution vendors to extend secure boot at OS level to +applications.
+DIGLIM does not have the complexity of feature-rich databases. In fact, its +main functionality comes from the hash table primitives already in the +kernel. It does not have an ad-hoc storage module, it just indexes data in +a fixed format (digest lists, a set of concatenated digests preceded by a +header), copied to kernel memory as they are. Lastly, it does not support +database-oriented languages such as SQL, but only accepts a digest and its +algorithm as a query.
+The only digest list format supported by DIGLIM is called ``compact``. +However, Linux distribution vendors don't have to generate new digest lists +in this format for the packages they release, as already available +information, such as RPM headers and DEB package metadata, can be already +used as a source for reference values (they already include file digests),
-ETOMANY_already
as "already" available... can be "already" ... "already" include...
I would simplify the above text removing such redundancy.
+with a user space parser taking care of the conversion to the compact +format.
+Although one might perceive that storing file or metadata digests for a +Linux distribution would significantly increase the memory usage, this does +not seem to be the case. As an anticipation of the evaluation done in the +`Preliminary Performance Evaluation`_ section, protecting binaries and +shared libraries of a minimal Fedora 33 installation requires 208K of +memory for the digest lists plus 556K for indexing.
+In exchange for a slightly increased memory usage, DIGLIM improves the +performance of the integrity subsystem. In the considered scenario, IMA +measurement and appraisal with digest lists requires respectively less than +one quarter and less than half the time, compared to the current solution.
I found this paragraph a little bit confusing to understand. Could you please improve the description?
I mean:
what improved by one quarter? what improved by "less than half of the time"?
+DIGLIM also keeps track of whether digest lists have been processed in some +way (e.g. measured or appraised by IMA). This is important for example for +remote attestation, so that remote verifiers understand what has been +uploaded to the kernel.
+DIGLIM behaves like 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).
I don't think it makes sense to compare it with a transactional database.
I would say, instead, something like:
The inserts on DIGLIM are atomic: if an error occurs during the addition of a digest list, it rolls back the entire insert operation.
+This capability has been tested with an ad-hoc fault injection mechanism +capable of simulating failures during the operations.
+Finally, DIGLIM exposes to user space, through securityfs, the digest lists +currently loaded, the number of digests added, a query interface and an +interface to set digest list labels.
+[1] LSS EU 2019
+- slides:
+- video: https://youtu.be/mffdQgkvDNY
+[2] FutureTPM EU project, final review meeting demo
+- slides:
+- video: https://vimeo.com/528251864/4c1d55abcd
The above won't generate any cross-references with Sphinx.
For it correct syntax, see: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#cita...
+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.
Sounds a weird description for me. (ISC)2 defines integrity on its glossary[1] as:
"Guarding against improper information modification or destruction and includes ensuring information non-repudiation and authenticity."
[1] https://www.isc2.org/Certifications/CISSP/CISSP-Student-Glossary
+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 +------------
I would rename this chapter to "Benefits".
+DIGLIM further enhances the capabilities offered by IMA-based solutions +and, at the same time, makes them more practical to adopt by reusing +existing sources as reference values for integrity decisions.
+Possible sources for digest lists are:
+- RPM headers; +- Debian repository metadata.
+Benefits for IMA Measurement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+One of the issues that arises when files are measured by the OS is that, +due to parallel execution, the order in which file accesses happen cannot +be predicted. Since the TPM Platform Configuration Register (PCR) extend +operation, executed after each file measurement, cryptographically binds +the current measurement to the previous ones, the PCR value at the end of a +workload cannot be predicted too.
+Thus, even if the usage of a TPM key, bound to a PCR value, should be +allowed when only good files were accessed, the TPM could unexpectedly deny +an operation on that key if files accesses did not happen as stated by the +key policy (which allows only one of the possible sequences).
+DIGLIM solves this issue by making the PCR value stable over the time and +not dependent on file accesses. The following figure depicts the current +and the new approaches:
+::
- IMA measurement list (current)
- entry# 1st boot 2nd boot 3rd boot
+----+---------------+ +----+---------------+ +----+---------------+
- 1: | 10 | file1 measur. | | 10 | file3 measur. | | 10 | file2 measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- 2: | 10 | file2 measur. | | 10 | file2 measur. | | 10 | file3 measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- 3: | 10 | file3 measur. | | 10 | file1 measur. | | 10 | file4 measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- PCR: Extend != Extend != Extend
file1, file2, file3 file3, file2, file1 file2, file3, file4
- PCR Extend definition:
PCR(new value) = Hash(Hash(meas. entry), PCR(previous value))
+A new entry in the measurement list is created by IMA for each file access. +Assuming that ``file1``, ``file2`` and ``file3`` are files provided by the +software vendor, ``file4`` is an unknown file, the first two PCR values +above represent a good system state, the third a bad system state. The PCR +values are the result of the PCR extend operation performed for each +measurement entry with the digest of the measurement entry as an input.
+::
- IMA measurement list (with DIGLIM)
- dlist
- +--------------+
- | header |
- +--------------+
- | file1 digest |
- | file2 digest |
- | file3 digest |
- +--------------+
+``dlist`` is a digest list containing the digest of ``file1``, ``file2`` +and ``file3``. In the intended scenario, it is generated by a software +vendor at the end of the building process, and retrieved by the +administrator of the system where the digest list is loaded.
+::
- entry# 1st boot 2nd boot 3rd boot
+----+---------------+ +----+---------------+ +----+---------------+
- 0: | 11 | dlist measur. | | 11 | dlist measur. | | 11 | dlist measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- 1: < file1 measur. skip > < file3 measur. skip > < file2 measur. skip >
- 2: < file2 measur. skip > < file2 measur. skip > < file3 measur. skip >
+----+---------------+
- 3: < file3 measur. skip > < file1 measur. skip > | 11 | file4 measur. |
+----+---------------+
- PCR: Extend = Extend != Extend
dlist dlist dlist, file4
+The first entry in the measurement list contains the digest of the digest +list uploaded to the kernel at kernel initialization time.
+When a file is accessed, IMA queries DIGLIM with the calculated file digest +and, if it is found, IMA skips the measurement.
+Thus, the only information sent to remote verifiers are: the list of +files that could possibly be accessed (from the digest list), but not if +they were accessed and when; the measurement of unknown files.
+Despite providing less information, this solution has the advantage that +the good system state (i.e. when only ``file1``, ``file2`` and ``file3`` +are accessed) now can be represented with a deterministic PCR value (the +PCR is extended only with the measurement of the digest list). Also, the +bad system state can still be distinguished from the good state (the PCR is +extended also with the measurement of ``file4``).
+If a TPM key is bound to the good PCR value, the TPM would allow the key to +be used if ``file1``, ``file2`` or ``file3`` are accessed, regardless of +the sequence in which they are accessed (the PCR value does not change), +and would revoke the permission when the unknown ``file4`` is accessed (the +PCR value changes). If a system is able to establish a TLS connection with +a peer, this implicitly means that the system was in a good state (i.e. +``file4`` was not accessed, otherwise the TPM would have denied the usage +of the TPM key due to the key policy).
+Benefits for IMA Appraisal +~~~~~~~~~~~~~~~~~~~~~~~~~~
+Extending secure boot to applications means being able to verify the +provenance of files accessed. IMA does it by verifying file signatures with +a key that it trusts, which requires Linux distribution vendors to +additionally include in the package header a signature for each file that +must be verified (there is the dedicated ``RPMTAG_FILESIGNATURES`` section +in the RPM header).
+The proposed approach would be instead to verify data provenance from +already available metadata (file digests) in existing packages. IMA would +verify the signature of package metadata and search file digests extracted +from package metadata and added to the hash table in the kernel.
+For RPMs, file digests can be found in the ``RPMTAG_FILEDIGESTS`` section +of ``RPMTAG_IMMUTABLE``, whose signature is in ``RPMTAG_RSAHEADER``. For +DEBs, file digests (unsafe to use due to a weak digest algorithm) can be +found in the ``md5sum`` file, which can be indirectly verified from +``Release.gpg``.
+The following figure highlights the differences between the current and the +proposed approach.
+::
- IMA appraisal (current solution, with file signatures):
appraise
+-----------+
V |
- +-------------------------+-----+ +-------+-----+ |
- | RPM header | | ima rpm | file1 | sig | |
- | ... | | plugin +-------+-----+ +-----+
- | file1 sig [to be added] | sig |--------> ... | IMA |
- | ... | | +-------+-----+ +-----+
- | fileN sig [to be added] | | | fileN | sig |
- +-------------------------+-----+ +-------+-----+
+In this case, file signatures must be added to the RPM header, so that the +``ima`` rpm plugin can extract them together with the file content. The RPM +header signature is not used.
+::
- IMA appraisal (with DIGLIM):
kernel hash table
with RPM header content
+---+ +--------------+
| |--->| file1 digest |
+---+ +--------------+
...
+---+ appraise (file1)
| | <--------------+
- +----------------+-----+ +---+ |
- | RPM header | | ^ |
- | ... | | digest_list | |
- | file1 digest | sig | rpm plugin | +-------+ +-----+
- | ... | |-------------+--->| file1 | | IMA |
- | fileN digest | | +-------+ +-----+
- +----------------+-----+ |
^ |
+------------------------------------+
appraise (RPM header)
+In this case, the RPM header is used as it is, and its signature is used +for IMA appraisal. Then, the ``digest_list`` rpm plugin executes the user +space parser to parse the RPM header and add the extracted digests to an +hash table in the kernel. IMA appraisal of the files in the RPM package +consists in searching their digest in the hash table.
+Other than reusing available information as digest list, another advantage +is the lower computational overhead compared to the solution with file +signatures (only one signature verification for many files and digest +lookup, instead of per file signature verification, see `Preliminary +Performance Evaluation`_ for more details).
+Lifecycle +---------
+The lifecycle of DIGLIM is represented in the following figure:
+::
You could just use:
The lifecycle of DIGLIM is represented in the following figure::
- Vendor premises (release process with modifications):
- +------------+ +-----------------------+ +------------------------+
- | 1. build a | | 2. generate and sign | | 3. publish the package |
- | package |-->| a digest list from |-->| and digest list in |
- | | | packaged files | | a repository |
- +------------+ +-----------------------+ +------------------------+
|
|
- User premises: |
V
- +---------------------+ +------------------------+ +-----------------+
- | 6. use digest lists | | 5. download the digest | | 4. download and |
- | for measurement |<--| list and upload to |<--| install the |
- | and/or appraisal | | the kernel | | package |
- +---------------------+ +------------------------+ +-----------------+
+The figure above represents all the steps when a digest list is +generated separately. However, as mentioned in `Contribution`_, in most +cases existing packages can be already used as a source for digest lists, +limiting the effort for software vendors.
+If, for example, RPMs are used as a source for digest lists, the figure +above becomes:
+::
Same here.
- Vendor premises (release process without modifications):
- +------------+ +------------------------+
- | 1. build a | | 2. publish the package |
- | package |-->| in a repository |---------------------+
- | | | | |
- +------------+ +------------------------+ |
|
|
- User premises: |
V
- +---------------------+ +------------------------+ +-----------------+
- | 5. use digest lists | | 4. extract digest list | | 3. download and |
- | for measurement |<--| from the package |<--| install the |
- | and/or appraisal | | and upload to the | | package |
- | | | kernel | | |
- +---------------------+ +------------------------+ +-----------------+
+Step 4 can be performed with the ``digest_list`` rpm plugin and the user +space parser, without changes to rpm itself.
+Security Assumptions +--------------------
+As mentioned in the `Introduction`_, DIGLIM will be primarily used in +conjunction with IMA to enforce a mandatory policy on all user space +processes, including those owned by root. Even root, in a system with a +locked-down kernel, cannot affect the enforcement of the mandatory policy +or, if changes are permitted, it cannot do so without being detected.
+Given that the target of the enforcement are user space processes, DIGLIM +cannot be placed in the target, as a Mandatory Access Control (MAC) design +is required to have the components responsible to enforce the mandatory +policy separated from the target.
+While locking-down a system and limiting actions with a mandatory policy is +generally perceived by users as an obstacle, it has noteworthy benefits for +the users themselves.
+First, it would timely block attempts by malicious software to steal or +misuse user assets. Although users could query the package managers to +detect them, detection would happen after the fact, or it wouldn't happen +at all if the malicious software tampered with package managers. With a +mandatory policy enforced by the kernel, users would still be able to +decide which software they want to be executed except that, unlike package +managers, the kernel is not affected by user space processes or root.
+Second, it might make systems more easily verifiable from outside, due to +the limited actions the system allows. When users connect to a server, not +only they would be able to verify the server identity, which is already +possible with communication protocols like TLS, but also if the software +running on that server can be trusted to handle their sensitive data.
+Adoption +--------
+A former version of DIGLIM is used in the following OSes:
+- openEuler 20.09
+- openEuler 21.03
+Originally, DIGLIM was part of IMA (known as IMA Digest Lists). In this +version, it has been redesigned as a standalone module with an API that +makes its functionality accessible by IMA and, eventually, other +subsystems.
+User Space Support +------------------
+Digest lists can be generated and managed with ``digest-list-tools``:
+https://github.com/openeuler-mirror/digest-list-tools
+It includes two main applications:
+- ``gen_digest_lists``: generates digest lists from files in the
- filesystem or from the RPM database (more digest list sources can be
- supported);
+- ``manage_digest_lists``: converts and uploads digest lists to the
- kernel.
+Integration with rpm is done with the ``digest_list`` plugin:
+https://gitee.com/src-openeuler/rpm/blob/master/Add-digest-list-plugin.patch
+This plugin writes the RPM header and its signature to a file, so that the +file is ready to be appraised by IMA, and calls the user space parser to +convert and upload the digest list to the kernel.
+Simple Usage Example (Tested with Fedora 33) +--------------------------------------------
+1. Digest list generation (RPM headers and their signature are copied to
- the specified directory):
+.. code-block:: bash
- # mkdir /etc/digest_lists
- # gen_digest_lists -t file -f rpm+db -d /etc/digest_lists -o add
+2. Digest list upload with the user space parser:
+.. code-block:: bash
- # manage_digest_lists -p add-digest -d /etc/digest_lists
+3. First digest list query:
+.. code-block:: bash
- # echo sha256-$(sha256sum /bin/cat) > /sys/kernel/security/integrity/diglim/digest_query
- # cat /sys/kernel/security/integrity/diglim/digest_query
- sha256-[...]-0-file_list-rpm-coreutils-8.32-18.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 106, datalen: 3392
+4. Second digest list query:
+.. code-block:: bash
- # echo sha256-$(sha256sum /bin/zip) > /sys/kernel/security/integrity/diglim/digest_query
- # cat /sys/kernel/security/integrity/diglim/digest_query
- sha256-[...]-0-file_list-rpm-zip-3.0-27.fc33.x86_64 (actions: 0): version: 1, algo: sha256, type: 2, modifiers: 1, count: 4, datalen: 128
+Preliminary Performance Evaluation +----------------------------------
+This section provides an initial estimation of the overhead introduced by +DIGLIM. The estimation has been performed on a Fedora 33 virtual machine +with 1447 packages installed. The virtual machine has 16 vCPU (host CPU: +AMD Ryzen Threadripper PRO 3955WX 16-Cores) and 2G of RAM (host memory: +64G). The virtual machine also has a vTPM with libtpms and swtpm as +backend.
+After writing the RPM headers to files, the size of the directory +containing them is 36M.
+After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 3.6M.
+The time to load the entire RPM database is 0.628s.
+After loading the digest lists to the kernel, the slab usage due to +indexing is (obtained with slab_nomerge in the kernel command line):
+::
- OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
- 118144 118144 100% 0,03K 923 128 3692K digest_list_item_ref_cache
- 102400 102400 100% 0,03K 800 128 3200K digest_item_cache
- 2646 2646 100% 0,09K 63 42 252K digest_list_item_cache
+The stats, obtained from the ``digests_count`` interface, introduced later, +are:
+::
- Parser digests: 0
- File digests: 99100
- Metadata digests: 0
- Digest list digests: 1423
+On this installation, this would be the worst case in which all files are +measured and/or appraised, which is currently not recommended without +enforcing an integrity policy protecting mutable files. Infoflow LSM is a +component to accomplish this task:
+https://patchwork.kernel.org/project/linux-integrity/cover/20190818235745.14...
+The first manageable goal of IMA with DIGLIM is to use an execution policy, +with measurement and/or appraisal of files executed or mapped in memory as +executable (in addition to kernel modules and firmware). In this +case, the digest list contains the digest only for those files. The numbers +above change as follows.
+After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 208K.
+The time to load the digest of binaries and shared libraries is 0.062s.
+After loading the digest lists to the kernel, the slab usage due to +indexing is:
+::
- OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
- 7168 7168 100% 0,03K 56 128 224K digest_list_item_ref_cache
- 7168 7168 100% 0,03K 56 128 224K digest_item_cache
- 1134 1134 100% 0,09K 27 42 108K digest_list_item_cache
+The stats, obtained from the ``digests_count`` interface, are:
+::
- Parser digests: 0
- File digests: 5986
- Metadata digests: 0
- Digest list digests: 1104
+Comparison with IMA +~~~~~~~~~~~~~~~~~~~
+This section compares the performance between the current solution for IMA +measurement and appraisal, and IMA with DIGLIM.
+Workload A (without DIGLIM):
+#. cat file[0-5985] > /dev/null
+Workload B (with DIGLIM):
+#. echo $PWD/0-file_list-compact-file[0-1103] > <securityfs>/integrity/diglim/digest_list_add +#. cat file[0-5985] > /dev/null
+Workload A execution time without IMA policy:
+::
- real 0m0,155s
- user 0m0,008s
- sys 0m0,066s
+Measurement +...........
+IMA policy:
+::
- measure fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow pcr=11 ima_template=ima-sig
+``use_diglim`` is a policy keyword not yet supported by IMA.
+Workload A execution time with IMA and 5986 files with signature measured:
+::
- real 0m8,273s
- user 0m0,008s
- sys 0m2,537s
+Workload B execution time with IMA, 1104 digest lists with signature +measured and uploaded to the kernel, and 5986 files with signature accessed +but not measured (due to the file digest being found in the hash table):
+::
- real 0m1,837s
- user 0m0,036s
- sys 0m0,583s
+Appraisal +.........
+IMA policy:
+::
- appraise fowner=2000 func=FILE_CHECK mask=MAY_READ use_diglim=allow
+``use_diglim`` is a policy keyword not yet supported by IMA.
+Workload A execution time with IMA and 5986 files with file signature +appraised:
+::
- real 0m2,197s
- user 0m0,011s
- sys 0m2,022s
+Workload B execution time with IMA, 1104 digest lists with signature +appraised and uploaded to the kernel, and with 5986 files with signature +not verified (due to the file digest being found in the hash table):
+::
- real 0m0,982s
- user 0m0,020s
- sys 0m0,865s
diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 16335de04e8c..6c3aea41c55b 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -17,3 +17,4 @@ Security Documentation tpm/index digsig landlock
- diglim/index
diff --git a/MAINTAINERS b/MAINTAINERS index 6c8be735cc91..c914dadd7e65 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5452,6 +5452,15 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-gpio-mm.c +DIGLIM +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/diglim/architecture.rst +F: Documentation/security/diglim/index.rst +F: Documentation/security/diglim/introduction.rst
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net L: linux-i2c@vger.kernel.org
Thanks, Mauro
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 1:10 PM Em Mon, 26 Jul 2021 18:36:49 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Add an overview of DIGLIM to
Documentation/security/diglim/introduction.rst
and the architecture to Documentation/security/diglim/architecture.rst
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/architecture.rst | 45 ++ Documentation/security/diglim/index.rst | 11 + .../security/diglim/introduction.rst | 631 ++++++++++++++++++ Documentation/security/index.rst | 1 + MAINTAINERS | 9 + 5 files changed, 697 insertions(+) create mode 100644 Documentation/security/diglim/architecture.rst create mode 100644 Documentation/security/diglim/index.rst create mode 100644 Documentation/security/diglim/introduction.rst
diff --git a/Documentation/security/diglim/architecture.rst
b/Documentation/security/diglim/architecture.rst
new file mode 100644 index 000000000000..a54fe2453715 --- /dev/null +++ b/Documentation/security/diglim/architecture.rst @@ -0,0 +1,45 @@ +.. SPDX-License-Identifier: GPL-2.0
+Architecture +============
+This section introduces the high level architecture of DIGLIM.
+::
- 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
"before"? This is at the beginning of this document ;-)
You should probably add a reference to introduction.rst here, like:
As mentioned at Documentation/security/diglim/introduction.rst, ...
Hi Mauro
ok.
+the compact format (step 1a) or can be uploaded indirectly by the user +space parser if they are in an alternative format (steps 1b-3b).
+During upload, the kernel makes a copy of the digest list to the kernel +memory (step 4), and creates the necessary structures to index the digests +(hash table and a linked list of digest list references to locate the +digests in the digest list) (step 5).
+Finally, digests can be searched from user space through a securityfs file +(step 6) or by the kernel itself.
This probably applies to Documentation/security as a hole, but the best is to split the documents on two separate parts:
- the kAPI and internals;
- the admin-guide part.
The audience for the admin-guide is distribution pagagers and syssadmins.
Ok. I will create an admin-guide.
diff --git a/Documentation/security/diglim/index.rst
b/Documentation/security/diglim/index.rst
new file mode 100644 index 000000000000..0fc5ab019bc0 --- /dev/null +++ b/Documentation/security/diglim/index.rst @@ -0,0 +1,11 @@ +.. SPDX-License-Identifier: GPL-2.0
+====================================== +Digest Lists Integrity Module (DIGLIM) +======================================
+.. toctree::
- :maxdepth: 1
- introduction
- architecture
diff --git a/Documentation/security/diglim/introduction.rst
b/Documentation/security/diglim/introduction.rst
new file mode 100644 index 000000000000..d8d8b2a17222 --- /dev/null +++ b/Documentation/security/diglim/introduction.rst @@ -0,0 +1,631 @@ +.. SPDX-License-Identifier: GPL-2.0
+Introduction +============
+Digest Lists Integrity Module (DIGLIM) is a new component added to the +integrity subsystem in the kernel, primarily aiming to aid Integrity
I would replace:
"is a new component added to" -> "is a component of"
As this is the kind of text that tends to be outdated with time... Imagine someone reading this paragraph maybe 10 years in the future ;-)
Ok.
+Measurement Architecture (IMA) in the process of checking the integrity of +file content and metadata. It accomplishes this task by storing reference +values coming from software vendors and by reporting whether or not the +digest of file content or metadata calculated by IMA (or EVM) is found +among those values. In this way, IMA can decide, depending on the result
of
+a query, if a measurement should be taken or access to the file should be +granted. The `Security Assumptions`_ section explains more in detail why +this component has been placed in the kernel.
+The main benefits of using IMA in conjunction with DIGLIM are the ability +to implement advanced remote attestation schemes based on the usage of
a
+TPM key for establishing a TLS secure channel [1][2], and to reduce the +burden on Linux distribution vendors to extend secure boot at OS level to +applications.
+DIGLIM does not have the complexity of feature-rich databases. In fact, its +main functionality comes from the hash table primitives already in the +kernel. It does not have an ad-hoc storage module, it just indexes data in +a fixed format (digest lists, a set of concatenated digests preceded by a +header), copied to kernel memory as they are. Lastly, it does not support +database-oriented languages such as SQL, but only accepts a digest and its +algorithm as a query.
+The only digest list format supported by DIGLIM is called ``compact``. +However, Linux distribution vendors don't have to generate new digest lists +in this format for the packages they release, as already available +information, such as RPM headers and DEB package metadata, can be
already
+used as a source for reference values (they already include file digests),
-ETOMANY_already
as "already" available... can be "already" ... "already" include...
I would simplify the above text removing such redundancy.
Ok.
+with a user space parser taking care of the conversion to the compact +format.
+Although one might perceive that storing file or metadata digests for a +Linux distribution would significantly increase the memory usage, this does +not seem to be the case. As an anticipation of the evaluation done in the +`Preliminary Performance Evaluation`_ section, protecting binaries and +shared libraries of a minimal Fedora 33 installation requires 208K of +memory for the digest lists plus 556K for indexing.
+In exchange for a slightly increased memory usage, DIGLIM improves the +performance of the integrity subsystem. In the considered scenario, IMA +measurement and appraisal with digest lists requires respectively less than +one quarter and less than half the time, compared to the current solution.
I found this paragraph a little bit confusing to understand. Could you please improve the description?
I mean:
what improved by one quarter? what improved by "less than half of the time"?
Ok. I didn't want to make the text too heavy. The tests are described in the Preliminary Performance Evaluation section.
+DIGLIM also keeps track of whether digest lists have been processed in
some
+way (e.g. measured or appraised by IMA). This is important for example for +remote attestation, so that remote verifiers understand what has been +uploaded to the kernel.
+DIGLIM behaves like 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).
I don't think it makes sense to compare it with a transactional database.
I would say, instead, something like:
The inserts on DIGLIM are atomic: if an error occurs during the addition of a digest list, it rolls back the entire insert operation.
Ok, better.
+This capability has been tested with an ad-hoc fault injection mechanism +capable of simulating failures during the operations.
+Finally, DIGLIM exposes to user space, through securityfs, the digest lists +currently loaded, the number of digests added, a query interface and an +interface to set digest list labels.
+[1] LSS EU 2019
+- slides:
https://static.sched.com/hosted_files/lsseu2019/bd/secure_attested_commu nication_channels_lss_eu_2019.pdf
+- video: https://youtu.be/mffdQgkvDNY
+[2] FutureTPM EU project, final review meeting demo
+- slides:
Device-Management-Use-Case-HWDU.pdf
+- video: https://vimeo.com/528251864/4c1d55abcd
The above won't generate any cross-references with Sphinx.
For it correct syntax, see: https://www.sphinx- doc.org/en/master/usage/restructuredtext/basics.html#citations
Ok, will fix it.
+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.
Sounds a weird description for me. (ISC)2 defines integrity on its glossary[1] as:
"Guarding against improper information modification or destruction and includes ensuring information non-repudiation and authenticity."
[1] https://www.isc2.org/Certifications/CISSP/CISSP-Student-Glossary
Ok, I meant integrity in the context of trusted computing.
https://trustedcomputinggroup.org/wp-content/uploads/IWG_ArchitecturePartII_...
In general the term "integrity" is used to denote the pristine state of a component (page 13).
+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 +------------
I would rename this chapter to "Benefits".
Ok.
+DIGLIM further enhances the capabilities offered by IMA-based solutions +and, at the same time, makes them more practical to adopt by reusing +existing sources as reference values for integrity decisions.
+Possible sources for digest lists are:
+- RPM headers; +- Debian repository metadata.
+Benefits for IMA Measurement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+One of the issues that arises when files are measured by the OS is that, +due to parallel execution, the order in which file accesses happen cannot +be predicted. Since the TPM Platform Configuration Register (PCR) extend +operation, executed after each file measurement, cryptographically binds +the current measurement to the previous ones, the PCR value at the end of
a
+workload cannot be predicted too.
+Thus, even if the usage of a TPM key, bound to a PCR value, should be +allowed when only good files were accessed, the TPM could unexpectedly
deny
+an operation on that key if files accesses did not happen as stated by the +key policy (which allows only one of the possible sequences).
+DIGLIM solves this issue by making the PCR value stable over the time and +not dependent on file accesses. The following figure depicts the current +and the new approaches:
+::
- IMA measurement list (current)
- entry# 1st boot 2nd boot 3rd boot
+----+---------------+ +----+---------------+ +----+---------------+
- 1: | 10 | file1 measur. | | 10 | file3 measur. | | 10 | file2 measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- 2: | 10 | file2 measur. | | 10 | file2 measur. | | 10 | file3 measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- 3: | 10 | file3 measur. | | 10 | file1 measur. | | 10 | file4 measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- PCR: Extend != Extend != Extend
file1, file2, file3 file3, file2, file1 file2, file3, file4
- PCR Extend definition:
PCR(new value) = Hash(Hash(meas. entry), PCR(previous value))
+A new entry in the measurement list is created by IMA for each file access. +Assuming that ``file1``, ``file2`` and ``file3`` are files provided by the +software vendor, ``file4`` is an unknown file, the first two PCR values +above represent a good system state, the third a bad system state. The PCR +values are the result of the PCR extend operation performed for each +measurement entry with the digest of the measurement entry as an input.
+::
- IMA measurement list (with DIGLIM)
- dlist
- +--------------+
- | header |
- +--------------+
- | file1 digest |
- | file2 digest |
- | file3 digest |
- +--------------+
+``dlist`` is a digest list containing the digest of ``file1``, ``file2`` +and ``file3``. In the intended scenario, it is generated by a software +vendor at the end of the building process, and retrieved by the +administrator of the system where the digest list is loaded.
+::
- entry# 1st boot 2nd boot 3rd boot
+----+---------------+ +----+---------------+ +----+---------------+
- 0: | 11 | dlist measur. | | 11 | dlist measur. | | 11 | dlist measur. |
+----+---------------+ +----+---------------+ +----+---------------+
- 1: < file1 measur. skip > < file3 measur. skip > < file2 measur. skip >
- 2: < file2 measur. skip > < file2 measur. skip > < file3 measur. skip >
+----+---------------+
- 3: < file3 measur. skip > < file1 measur. skip > | 11 | file4 measur. |
+----+---------------+
- PCR: Extend = Extend != Extend
dlist dlist dlist, file4
+The first entry in the measurement list contains the digest of the digest +list uploaded to the kernel at kernel initialization time.
+When a file is accessed, IMA queries DIGLIM with the calculated file digest +and, if it is found, IMA skips the measurement.
+Thus, the only information sent to remote verifiers are: the list of +files that could possibly be accessed (from the digest list), but not if +they were accessed and when; the measurement of unknown files.
+Despite providing less information, this solution has the advantage that +the good system state (i.e. when only ``file1``, ``file2`` and ``file3`` +are accessed) now can be represented with a deterministic PCR value (the +PCR is extended only with the measurement of the digest list). Also, the +bad system state can still be distinguished from the good state (the PCR is +extended also with the measurement of ``file4``).
+If a TPM key is bound to the good PCR value, the TPM would allow the key
to
+be used if ``file1``, ``file2`` or ``file3`` are accessed, regardless of +the sequence in which they are accessed (the PCR value does not change), +and would revoke the permission when the unknown ``file4`` is accessed
(the
+PCR value changes). If a system is able to establish a TLS connection with +a peer, this implicitly means that the system was in a good state (i.e. +``file4`` was not accessed, otherwise the TPM would have denied the usage +of the TPM key due to the key policy).
+Benefits for IMA Appraisal +~~~~~~~~~~~~~~~~~~~~~~~~~~
+Extending secure boot to applications means being able to verify the +provenance of files accessed. IMA does it by verifying file signatures with +a key that it trusts, which requires Linux distribution vendors to +additionally include in the package header a signature for each file that +must be verified (there is the dedicated ``RPMTAG_FILESIGNATURES``
section
+in the RPM header).
+The proposed approach would be instead to verify data provenance from +already available metadata (file digests) in existing packages. IMA would +verify the signature of package metadata and search file digests extracted +from package metadata and added to the hash table in the kernel.
+For RPMs, file digests can be found in the ``RPMTAG_FILEDIGESTS`` section +of ``RPMTAG_IMMUTABLE``, whose signature is in
``RPMTAG_RSAHEADER``. For
+DEBs, file digests (unsafe to use due to a weak digest algorithm) can be +found in the ``md5sum`` file, which can be indirectly verified from +``Release.gpg``.
+The following figure highlights the differences between the current and the +proposed approach.
+::
- IMA appraisal (current solution, with file signatures):
appraise
+-----------+
V |
- +-------------------------+-----+ +-------+-----+ |
- | RPM header | | ima rpm | file1 | sig | |
- | ... | | plugin +-------+-----+ +-----+
- | file1 sig [to be added] | sig |--------> ... | IMA |
- | ... | | +-------+-----+ +-----+
- | fileN sig [to be added] | | | fileN | sig |
- +-------------------------+-----+ +-------+-----+
+In this case, file signatures must be added to the RPM header, so that the +``ima`` rpm plugin can extract them together with the file content. The
RPM
+header signature is not used.
+::
- IMA appraisal (with DIGLIM):
kernel hash table
with RPM header content
+---+ +--------------+
| |--->| file1 digest |
+---+ +--------------+
...
+---+ appraise (file1)
| | <--------------+
- +----------------+-----+ +---+ |
- | RPM header | | ^ |
- | ... | | digest_list | |
- | file1 digest | sig | rpm plugin | +-------+ +-----+
- | ... | |-------------+--->| file1 | | IMA |
- | fileN digest | | +-------+ +-----+
- +----------------+-----+ |
^ |
+------------------------------------+
appraise (RPM header)
+In this case, the RPM header is used as it is, and its signature is used +for IMA appraisal. Then, the ``digest_list`` rpm plugin executes the user +space parser to parse the RPM header and add the extracted digests to an +hash table in the kernel. IMA appraisal of the files in the RPM package +consists in searching their digest in the hash table.
+Other than reusing available information as digest list, another advantage +is the lower computational overhead compared to the solution with file +signatures (only one signature verification for many files and digest +lookup, instead of per file signature verification, see `Preliminary +Performance Evaluation`_ for more details).
+Lifecycle +---------
+The lifecycle of DIGLIM is represented in the following figure:
+::
You could just use:
The lifecycle of DIGLIM is represented in the following figure::
- Vendor premises (release process with modifications):
- +------------+ +-----------------------+ +------------------------+
- | 1. build a | | 2. generate and sign | | 3. publish the package |
- | package |-->| a digest list from |-->| and digest list in |
- | | | packaged files | | a repository |
- +------------+ +-----------------------+ +------------------------+
|
|
- User premises: |
V
- +---------------------+ +------------------------+ +-----------------+
- | 6. use digest lists | | 5. download the digest | | 4. download and |
- | for measurement |<--| list and upload to |<--| install the |
- | and/or appraisal | | the kernel | | package |
- +---------------------+ +------------------------+ +-----------------+
+The figure above represents all the steps when a digest list is +generated separately. However, as mentioned in `Contribution`_, in most +cases existing packages can be already used as a source for digest lists, +limiting the effort for software vendors.
+If, for example, RPMs are used as a source for digest lists, the figure +above becomes:
+::
Same here.
Ok.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
- Vendor premises (release process without modifications):
- +------------+ +------------------------+
- | 1. build a | | 2. publish the package |
- | package |-->| in a repository |---------------------+
- | | | | |
- +------------+ +------------------------+ |
|
|
- User premises: |
V
- +---------------------+ +------------------------+ +-----------------+
- | 5. use digest lists | | 4. extract digest list | | 3. download and |
- | for measurement |<--| from the package |<--| install the |
- | and/or appraisal | | and upload to the | | package |
- | | | kernel | | |
- +---------------------+ +------------------------+ +-----------------+
+Step 4 can be performed with the ``digest_list`` rpm plugin and the user +space parser, without changes to rpm itself.
+Security Assumptions +--------------------
+As mentioned in the `Introduction`_, DIGLIM will be primarily used in +conjunction with IMA to enforce a mandatory policy on all user space +processes, including those owned by root. Even root, in a system with a +locked-down kernel, cannot affect the enforcement of the mandatory
policy
+or, if changes are permitted, it cannot do so without being detected.
+Given that the target of the enforcement are user space processes, DIGLIM +cannot be placed in the target, as a Mandatory Access Control (MAC)
design
+is required to have the components responsible to enforce the mandatory +policy separated from the target.
+While locking-down a system and limiting actions with a mandatory policy
is
+generally perceived by users as an obstacle, it has noteworthy benefits for +the users themselves.
+First, it would timely block attempts by malicious software to steal or +misuse user assets. Although users could query the package managers to +detect them, detection would happen after the fact, or it wouldn't happen +at all if the malicious software tampered with package managers. With a +mandatory policy enforced by the kernel, users would still be able to +decide which software they want to be executed except that, unlike
package
+managers, the kernel is not affected by user space processes or root.
+Second, it might make systems more easily verifiable from outside, due to +the limited actions the system allows. When users connect to a server, not +only they would be able to verify the server identity, which is already +possible with communication protocols like TLS, but also if the software +running on that server can be trusted to handle their sensitive data.
+Adoption +--------
+A former version of DIGLIM is used in the following OSes:
+- openEuler 20.09
+- openEuler 21.03
+Originally, DIGLIM was part of IMA (known as IMA Digest Lists). In this +version, it has been redesigned as a standalone module with an API that +makes its functionality accessible by IMA and, eventually, other +subsystems.
+User Space Support +------------------
+Digest lists can be generated and managed with ``digest-list-tools``:
+https://github.com/openeuler-mirror/digest-list-tools
+It includes two main applications:
+- ``gen_digest_lists``: generates digest lists from files in the
- filesystem or from the RPM database (more digest list sources can be
- supported);
+- ``manage_digest_lists``: converts and uploads digest lists to the
- kernel.
+Integration with rpm is done with the ``digest_list`` plugin:
+https://gitee.com/src-openeuler/rpm/blob/master/Add-digest-list-
plugin.patch
+This plugin writes the RPM header and its signature to a file, so that the +file is ready to be appraised by IMA, and calls the user space parser to +convert and upload the digest list to the kernel.
+Simple Usage Example (Tested with Fedora 33) +--------------------------------------------
+1. Digest list generation (RPM headers and their signature are copied to
- the specified directory):
+.. code-block:: bash
- # mkdir /etc/digest_lists
- # gen_digest_lists -t file -f rpm+db -d /etc/digest_lists -o add
+2. Digest list upload with the user space parser:
+.. code-block:: bash
- # manage_digest_lists -p add-digest -d /etc/digest_lists
+3. First digest list query:
+.. code-block:: bash
- # echo sha256-$(sha256sum /bin/cat) >
/sys/kernel/security/integrity/diglim/digest_query
- # cat /sys/kernel/security/integrity/diglim/digest_query
- sha256-[...]-0-file_list-rpm-coreutils-8.32-18.fc33.x86_64 (actions: 0):
version: 1, algo: sha256, type: 2, modifiers: 1, count: 106, datalen: 3392
+4. Second digest list query:
+.. code-block:: bash
- # echo sha256-$(sha256sum /bin/zip) >
/sys/kernel/security/integrity/diglim/digest_query
- # cat /sys/kernel/security/integrity/diglim/digest_query
- sha256-[...]-0-file_list-rpm-zip-3.0-27.fc33.x86_64 (actions: 0): version: 1,
algo: sha256, type: 2, modifiers: 1, count: 4, datalen: 128
+Preliminary Performance Evaluation +----------------------------------
+This section provides an initial estimation of the overhead introduced by +DIGLIM. The estimation has been performed on a Fedora 33 virtual machine +with 1447 packages installed. The virtual machine has 16 vCPU (host CPU: +AMD Ryzen Threadripper PRO 3955WX 16-Cores) and 2G of RAM (host
memory:
+64G). The virtual machine also has a vTPM with libtpms and swtpm as +backend.
+After writing the RPM headers to files, the size of the directory +containing them is 36M.
+After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 3.6M.
+The time to load the entire RPM database is 0.628s.
+After loading the digest lists to the kernel, the slab usage due to +indexing is (obtained with slab_nomerge in the kernel command line):
+::
- OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
- 118144 118144 100% 0,03K 923 128 3692K
digest_list_item_ref_cache
- 102400 102400 100% 0,03K 800 128 3200K digest_item_cache
- 2646 2646 100% 0,09K 63 42 252K digest_list_item_cache
+The stats, obtained from the ``digests_count`` interface, introduced later, +are:
+::
- Parser digests: 0
- File digests: 99100
- Metadata digests: 0
- Digest list digests: 1423
+On this installation, this would be the worst case in which all files are +measured and/or appraised, which is currently not recommended without +enforcing an integrity policy protecting mutable files. Infoflow LSM is a +component to accomplish this task:
integrity/cover/20190818235745.1417-1-roberto.sassu@huawei.com/
+The first manageable goal of IMA with DIGLIM is to use an execution policy, +with measurement and/or appraisal of files executed or mapped in memory
as
+executable (in addition to kernel modules and firmware). In this +case, the digest list contains the digest only for those files. The numbers +above change as follows.
+After converting the RPM headers to the compact digest list, the size of +the data being uploaded to the kernel is 208K.
+The time to load the digest of binaries and shared libraries is 0.062s.
+After loading the digest lists to the kernel, the slab usage due to +indexing is:
+::
- OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
- 7168 7168 100% 0,03K 56 128 224K digest_list_item_ref_cache
- 7168 7168 100% 0,03K 56 128 224K digest_item_cache
- 1134 1134 100% 0,09K 27 42 108K digest_list_item_cache
+The stats, obtained from the ``digests_count`` interface, are:
+::
- Parser digests: 0
- File digests: 5986
- Metadata digests: 0
- Digest list digests: 1104
+Comparison with IMA +~~~~~~~~~~~~~~~~~~~
+This section compares the performance between the current solution for
IMA
+measurement and appraisal, and IMA with DIGLIM.
+Workload A (without DIGLIM):
+#. cat file[0-5985] > /dev/null
+Workload B (with DIGLIM):
+#. echo $PWD/0-file_list-compact-file[0-1103] >
<securityfs>/integrity/diglim/digest_list_add
+#. cat file[0-5985] > /dev/null
+Workload A execution time without IMA policy:
+::
- real 0m0,155s
- user 0m0,008s
- sys 0m0,066s
+Measurement +...........
+IMA policy:
+::
- measure fowner=2000 func=FILE_CHECK mask=MAY_READ
use_diglim=allow pcr=11 ima_template=ima-sig
+``use_diglim`` is a policy keyword not yet supported by IMA.
+Workload A execution time with IMA and 5986 files with signature
measured:
+::
- real 0m8,273s
- user 0m0,008s
- sys 0m2,537s
+Workload B execution time with IMA, 1104 digest lists with signature +measured and uploaded to the kernel, and 5986 files with signature
accessed
+but not measured (due to the file digest being found in the hash table):
+::
- real 0m1,837s
- user 0m0,036s
- sys 0m0,583s
+Appraisal +.........
+IMA policy:
+::
- appraise fowner=2000 func=FILE_CHECK mask=MAY_READ
use_diglim=allow
+``use_diglim`` is a policy keyword not yet supported by IMA.
+Workload A execution time with IMA and 5986 files with file signature +appraised:
+::
- real 0m2,197s
- user 0m0,011s
- sys 0m2,022s
+Workload B execution time with IMA, 1104 digest lists with signature +appraised and uploaded to the kernel, and with 5986 files with signature +not verified (due to the file digest being found in the hash table):
+::
- real 0m0,982s
- user 0m0,020s
- sys 0m0,865s
diff --git a/Documentation/security/index.rst
b/Documentation/security/index.rst
index 16335de04e8c..6c3aea41c55b 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -17,3 +17,4 @@ Security Documentation tpm/index digsig landlock
- diglim/index
diff --git a/MAINTAINERS b/MAINTAINERS index 6c8be735cc91..c914dadd7e65 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5452,6 +5452,15 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-gpio-mm.c
+DIGLIM +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/diglim/architecture.rst +F: Documentation/security/diglim/index.rst +F: Documentation/security/diglim/introduction.rst
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net L: linux-i2c@vger.kernel.org
Thanks, Mauro
Introduce the basic definitions, exported to user space, to use digest lists. The definitions, added to include/uapi/linux/diglim.h, are documented in Documentation/security/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- .../security/diglim/implementation.rst | 97 +++++++++++++++++++ Documentation/security/diglim/index.rst | 1 + MAINTAINERS | 2 + include/uapi/linux/diglim.h | 51 ++++++++++ 4 files changed, 151 insertions(+) create mode 100644 Documentation/security/diglim/implementation.rst create mode 100644 include/uapi/linux/diglim.h
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst new file mode 100644 index 000000000000..59a180b3bb3f --- /dev/null +++ b/Documentation/security/diglim/implementation.rst @@ -0,0 +1,97 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Implementation +============== + +This section describes the implementation of DIGLIM. + + +Basic Definitions +----------------- + +This section introduces the basic definitions required to use DIGLIM. + + +Compact Digest List Format +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. kernel-doc:: include/uapi/linux/diglim.h + :identifiers: compact_list_hdr + +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 DIGLIM 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 DIGLIM 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 which digests have been uploaded to the +kernel. + + +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/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst index 0fc5ab019bc0..4771134c2f0d 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -9,3 +9,4 @@ Digest Lists Integrity Module (DIGLIM)
introduction architecture + implementation diff --git a/MAINTAINERS b/MAINTAINERS index c914dadd7e65..f61f5239468a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5458,8 +5458,10 @@ L: linux-integrity@vger.kernel.org S: Supported T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/diglim/architecture.rst +F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: include/uapi/linux/diglim.h
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/include/uapi/linux/diglim.h b/include/uapi/linux/diglim.h new file mode 100644 index 000000000000..8a33d1f0fefb --- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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 + * + * DIGLIM definitions exported to user space, useful for generating digest + * lists. + */ + +#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated digests + * @version: version of the digest list + * @_reserved: field reserved for future use + * @type: type of digest list among enum compact_types + * @modifiers: additional attributes among (1 << enum compact_modifiers) + * @algo: digest algorithm + * @count: number of digests + * @datalen: length of concatenated digests + * + * A digest list is a set of blocks composed by struct compact_list_hdr and + * the following concatenated digests. + */ +struct compact_list_hdr { + __u8 version; + __u8 _reserved; + __le16 type; + __le16 modifiers; + __le16 algo; + __le32 count; + __le32 datalen; +} __packed; +#endif /*_UAPI__LINUX_DIGLIM_H*/
On Mon, Jul 26, 2021 at 06:36:50PM +0200, Roberto Sassu wrote:
--- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum compact_modifiers)
I do not understand this description, what does it mean?
- @algo: digest algorithm
Is this also a #define or an enum? Where is the list of them?
- @count: number of digests
- @datalen: length of concatenated digests
Where does this count and length come into play as nothing else is in this structure?
- A digest list is a set of blocks composed by struct compact_list_hdr and
- the following concatenated digests.
- */
+struct compact_list_hdr {
- __u8 version;
- __u8 _reserved;
You MUST check this for 0 today, and document it above. If not, you can never use it in the future.
- __le16 type;
- __le16 modifiers;
- __le16 algo;
- __le32 count;
- __le32 datalen;
+} __packed;
+#endif /*_UAPI__LINUX_DIGLIM_H*/
2.25.1
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 4:44 PM On Mon, Jul 26, 2021 at 06:36:50PM +0200, Roberto Sassu wrote:
--- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum compact_modifiers)
I do not understand this description, what does it mean?
Hi Greg
yes, it is not very clear.
@modifiers is a bitmask where each bit corresponds to a different attribute. enum compact_modifiers defines which bit position is assigned to each attribute.
- @algo: digest algorithm
Is this also a #define or an enum? Where is the list of them?
@algo is an enum defined in include/uapi/linux/hash_info.h.
- @count: number of digests
- @datalen: length of concatenated digests
Where does this count and length come into play as nothing else is in this structure?
Each digest list must begin with this structure. From it, the parser knows how much data it should expect afterwards. After the data, there could be another or more blocks of this structure and following data.
There is an example in the 'Compact Digest List Example' subsection, in Documentation/security/diglim/implementation.rst.
- A digest list is a set of blocks composed by struct compact_list_hdr and
- the following concatenated digests.
- */
+struct compact_list_hdr {
- __u8 version;
- __u8 _reserved;
You MUST check this for 0 today, and document it above. If not, you can never use it in the future.
Ok, yes. I will add it.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
- __le16 type;
- __le16 modifiers;
- __le16 algo;
- __le32 count;
- __le32 datalen;
+} __packed;
+#endif /*_UAPI__LINUX_DIGLIM_H*/
2.25.1
On Tue, Jul 27, 2021 at 03:35:16PM +0000, Roberto Sassu wrote:
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 4:44 PM On Mon, Jul 26, 2021 at 06:36:50PM +0200, Roberto Sassu wrote:
--- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum compact_modifiers)
I do not understand this description, what does it mean?
Hi Greg
yes, it is not very clear.
@modifiers is a bitmask where each bit corresponds to a different attribute. enum compact_modifiers defines which bit position is assigned to each attribute.
Watch out with endian issues and bitmasks... Anyway, please document this.
- @algo: digest algorithm
Is this also a #define or an enum? Where is the list of them?
@algo is an enum defined in include/uapi/linux/hash_info.h.
Please say that.
- @count: number of digests
- @datalen: length of concatenated digests
Where does this count and length come into play as nothing else is in this structure?
Each digest list must begin with this structure. From it, the parser knows how much data it should expect afterwards. After the data, there could be another or more blocks of this structure and following data.
Ah, that was not obvious at all :)
Why do you not have a __u8 data[]; type field as the last one here for that memory so you can access it easier?
thanks,
greg k-h
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 5:44 PM On Tue, Jul 27, 2021 at 03:35:16PM +0000, Roberto Sassu wrote:
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 4:44 PM On Mon, Jul 26, 2021 at 06:36:50PM +0200, Roberto Sassu wrote:
--- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating
digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated
digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum
compact_modifiers)
I do not understand this description, what does it mean?
Hi Greg
yes, it is not very clear.
@modifiers is a bitmask where each bit corresponds to a different attribute. enum compact_modifiers defines which bit position is assigned to each attribute.
Watch out with endian issues and bitmasks... Anyway, please document this.
- @algo: digest algorithm
Is this also a #define or an enum? Where is the list of them?
@algo is an enum defined in include/uapi/linux/hash_info.h.
Please say that.
- @count: number of digests
- @datalen: length of concatenated digests
Where does this count and length come into play as nothing else is in this structure?
Each digest list must begin with this structure. From it, the parser knows how much data it should expect afterwards. After the data, there could be another or more blocks of this structure and following data.
Ah, that was not obvious at all :)
Why do you not have a __u8 data[]; type field as the last one here for that memory so you can access it easier?
After the digest list is parsed, I'm accessing the digest with the offset from the beginning of the digest list. If the offset was relative to the header, it could have been useful. I could add the new field, but I'm afraid of the incompatibility with existing tools that we have.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
greg k-h
On Tue, Jul 27, 2021 at 04:09:37PM +0000, Roberto Sassu wrote:
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 5:44 PM On Tue, Jul 27, 2021 at 03:35:16PM +0000, Roberto Sassu wrote:
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 4:44 PM On Mon, Jul 26, 2021 at 06:36:50PM +0200, Roberto Sassu wrote:
--- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating
digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated
digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum
compact_modifiers)
I do not understand this description, what does it mean?
Hi Greg
yes, it is not very clear.
@modifiers is a bitmask where each bit corresponds to a different attribute. enum compact_modifiers defines which bit position is assigned to each attribute.
Watch out with endian issues and bitmasks... Anyway, please document this.
- @algo: digest algorithm
Is this also a #define or an enum? Where is the list of them?
@algo is an enum defined in include/uapi/linux/hash_info.h.
Please say that.
- @count: number of digests
- @datalen: length of concatenated digests
Where does this count and length come into play as nothing else is in this structure?
Each digest list must begin with this structure. From it, the parser knows how much data it should expect afterwards. After the data, there could be another or more blocks of this structure and following data.
Ah, that was not obvious at all :)
Why do you not have a __u8 data[]; type field as the last one here for that memory so you can access it easier?
After the digest list is parsed, I'm accessing the digest with the offset from the beginning of the digest list. If the offset was relative to the header, it could have been useful. I could add the new field, but I'm afraid of the incompatibility with existing tools that we have.
What tools? This isn't a feature in the kernel yet, so we have no legacy to support, right?
thanks,
greg k-h
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 6:13 PM On Tue, Jul 27, 2021 at 04:09:37PM +0000, Roberto Sassu wrote:
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 5:44 PM On Tue, Jul 27, 2021 at 03:35:16PM +0000, Roberto Sassu wrote:
From: Greg KH [mailto:gregkh@linuxfoundation.org] Sent: Tuesday, July 27, 2021 4:44 PM On Mon, Jul 26, 2021 at 06:36:50PM +0200, Roberto Sassu wrote:
--- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating
digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated
digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum
compact_modifiers)
I do not understand this description, what does it mean?
Hi Greg
yes, it is not very clear.
@modifiers is a bitmask where each bit corresponds to a different attribute. enum compact_modifiers defines which bit position is assigned to each attribute.
Watch out with endian issues and bitmasks... Anyway, please document this.
- @algo: digest algorithm
Is this also a #define or an enum? Where is the list of them?
@algo is an enum defined in include/uapi/linux/hash_info.h.
Please say that.
- @count: number of digests
- @datalen: length of concatenated digests
Where does this count and length come into play as nothing else is in this structure?
Each digest list must begin with this structure. From it, the parser knows how much data it should expect afterwards. After the data, there could
be
another or more blocks of this structure and following data.
Ah, that was not obvious at all :)
Why do you not have a __u8 data[]; type field as the last one here for that memory so you can access it easier?
After the digest list is parsed, I'm accessing the digest with the offset from the beginning of the digest list. If the offset was relative to the header, it
could
have been useful. I could add the new field, but I'm afraid of the
incompatibility
with existing tools that we have.
What tools? This isn't a feature in the kernel yet, so we have no legacy to support, right?
Yes, right. We shouldn't be limited by previously written code.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
greg k-h
Em Mon, 26 Jul 2021 18:36:50 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Introduce the basic definitions, exported to user space, to use digest lists. The definitions, added to include/uapi/linux/diglim.h, are documented in Documentation/security/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 97 +++++++++++++++++++ Documentation/security/diglim/index.rst | 1 + MAINTAINERS | 2 + include/uapi/linux/diglim.h | 51 ++++++++++ 4 files changed, 151 insertions(+) create mode 100644 Documentation/security/diglim/implementation.rst create mode 100644 include/uapi/linux/diglim.h
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst new file mode 100644 index 000000000000..59a180b3bb3f --- /dev/null +++ b/Documentation/security/diglim/implementation.rst @@ -0,0 +1,97 @@ +.. SPDX-License-Identifier: GPL-2.0
+Implementation +==============
+This section describes the implementation of DIGLIM.
+Basic Definitions +-----------------
+This section introduces the basic definitions required to use DIGLIM.
+Compact Digest List Format +~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. kernel-doc:: include/uapi/linux/diglim.h
- :identifiers: compact_list_hdr
+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 DIGLIM 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 DIGLIM 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 which digests have been uploaded to the +kernel.
+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/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst index 0fc5ab019bc0..4771134c2f0d 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -9,3 +9,4 @@ Digest Lists Integrity Module (DIGLIM) introduction architecture
- implementation
diff --git a/MAINTAINERS b/MAINTAINERS index c914dadd7e65..f61f5239468a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5458,8 +5458,10 @@ L: linux-integrity@vger.kernel.org S: Supported T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/diglim/architecture.rst +F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: include/uapi/linux/diglim.h DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/include/uapi/linux/diglim.h b/include/uapi/linux/diglim.h new file mode 100644 index 000000000000..8a33d1f0fefb --- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum compact_modifiers)
- @algo: digest algorithm
- @count: number of digests
- @datalen: length of concatenated digests
- A digest list is a set of blocks composed by struct compact_list_hdr and
- the following concatenated digests.
- */
+struct compact_list_hdr {
- __u8 version;
- __u8 _reserved;
- __le16 type;
- __le16 modifiers;
- __le16 algo;
- __le32 count;
- __le32 datalen;
+} __packed; +#endif /*_UAPI__LINUX_DIGLIM_H*/
Besides Greg's notes, I'm wondering why to enforce a particular endness here. I mean, this is uAPI. I would expect it to use the CPU endianness instead, in order to avoid uneeded conversions.
Thanks, Mauro
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 1:31 PM Em Mon, 26 Jul 2021 18:36:50 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Introduce the basic definitions, exported to user space, to use digest lists. The definitions, added to include/uapi/linux/diglim.h, are documented in Documentation/security/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 97 +++++++++++++++++++ Documentation/security/diglim/index.rst | 1 + MAINTAINERS | 2 + include/uapi/linux/diglim.h | 51 ++++++++++ 4 files changed, 151 insertions(+) create mode 100644 Documentation/security/diglim/implementation.rst create mode 100644 include/uapi/linux/diglim.h
diff --git a/Documentation/security/diglim/implementation.rst
b/Documentation/security/diglim/implementation.rst
new file mode 100644 index 000000000000..59a180b3bb3f --- /dev/null +++ b/Documentation/security/diglim/implementation.rst @@ -0,0 +1,97 @@ +.. SPDX-License-Identifier: GPL-2.0
+Implementation +==============
+This section describes the implementation of DIGLIM.
+Basic Definitions +-----------------
+This section introduces the basic definitions required to use DIGLIM.
+Compact Digest List Format +~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. kernel-doc:: include/uapi/linux/diglim.h
- :identifiers: compact_list_hdr
+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 DIGLIM 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 DIGLIM 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 which digests have been uploaded to
the
+kernel.
+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/Documentation/security/diglim/index.rst
b/Documentation/security/diglim/index.rst
index 0fc5ab019bc0..4771134c2f0d 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -9,3 +9,4 @@ Digest Lists Integrity Module (DIGLIM)
introduction architecture
- implementation
diff --git a/MAINTAINERS b/MAINTAINERS index c914dadd7e65..f61f5239468a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5458,8 +5458,10 @@ L: linux-integrity@vger.kernel.org S: Supported T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/diglim/architecture.rst +F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: include/uapi/linux/diglim.h
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/include/uapi/linux/diglim.h b/include/uapi/linux/diglim.h new file mode 100644 index 000000000000..8a33d1f0fefb --- /dev/null +++ b/include/uapi/linux/diglim.h @@ -0,0 +1,51 @@ +/* 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
- DIGLIM definitions exported to user space, useful for generating digest
- lists.
- */
+#ifndef _UAPI__LINUX_DIGLIM_H +#define _UAPI__LINUX_DIGLIM_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 - header of the following concatenated digests
- @version: version of the digest list
- @_reserved: field reserved for future use
- @type: type of digest list among enum compact_types
- @modifiers: additional attributes among (1 << enum compact_modifiers)
- @algo: digest algorithm
- @count: number of digests
- @datalen: length of concatenated digests
- A digest list is a set of blocks composed by struct compact_list_hdr and
- the following concatenated digests.
- */
+struct compact_list_hdr {
- __u8 version;
- __u8 _reserved;
- __le16 type;
- __le16 modifiers;
- __le16 algo;
- __le32 count;
- __le32 datalen;
+} __packed; +#endif /*_UAPI__LINUX_DIGLIM_H*/
Besides Greg's notes, I'm wondering why to enforce a particular endness here. I mean, this is uAPI. I would expect it to use the CPU endianness instead, in order to avoid uneeded conversions.
Also Greg had the same concern. I hoped the Lifecycle section clarified the fact that digest lists are generated by software vendors not the local system. Should I add something more in the documentation?
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
Thanks, Mauro
Em Wed, 28 Jul 2021 11:45:02 +0000 Roberto Sassu roberto.sassu@huawei.com escreveu:
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 1:31 PM Em Mon, 26 Jul 2021 18:36:50 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
+struct compact_list_hdr {
- __u8 version;
- __u8 _reserved;
- __le16 type;
- __le16 modifiers;
- __le16 algo;
- __le32 count;
- __le32 datalen;
+} __packed; +#endif /*_UAPI__LINUX_DIGLIM_H*/
Besides Greg's notes, I'm wondering why to enforce a particular endness here. I mean, this is uAPI. I would expect it to use the CPU endianness instead, in order to avoid uneeded conversions.
Also Greg had the same concern. I hoped the Lifecycle section clarified the fact that digest lists are generated by software vendors not the local system. Should I add something more in the documentation?
It shouldn't matter what kind of endness software vendors use on userspace (either CPU or a fixed endiannes - either LE or BE).
I mean, I won't doubt that some package tools use LE while others would use BE. At some point, this needs to be converted to CPU endiannes.
IMO, the best would be to isolate whatever RPM/DEB/... endianness is used on userspace from what the Kernel will use internally.
Just my 2 cents.
Regards, Mauro
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 3:08 PM Em Wed, 28 Jul 2021 11:45:02 +0000 Roberto Sassu roberto.sassu@huawei.com escreveu:
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 1:31 PM Em Mon, 26 Jul 2021 18:36:50 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
+struct compact_list_hdr {
- __u8 version;
- __u8 _reserved;
- __le16 type;
- __le16 modifiers;
- __le16 algo;
- __le32 count;
- __le32 datalen;
+} __packed; +#endif /*_UAPI__LINUX_DIGLIM_H*/
Besides Greg's notes, I'm wondering why to enforce a particular endness here. I mean, this is uAPI. I would expect it to use the CPU endianness instead, in order to avoid uneeded conversions.
Also Greg had the same concern. I hoped the Lifecycle section clarified the fact that digest lists are generated by software vendors not the local system. Should I add something more in the documentation?
It shouldn't matter what kind of endness software vendors use on userspace (either CPU or a fixed endiannes - either LE or BE).
I mean, I won't doubt that some package tools use LE while others would use BE. At some point, this needs to be converted to CPU endiannes.
If you let digest list generators decide the endianness, probably it is necessary to also add the endianness information in the structure. Otherwise, the kernel wouldn't know what to do.
If the kernel knows that the digest list is always in little endian, it simply calls le32_to_cpu().
IMO, the best would be to isolate whatever RPM/DEB/... endianness is used on userspace from what the Kernel will use internally.
This is a different case. The conversion happens if the digest list is not in native format. The kernel can also parse an untouched digest list if it is in native format.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
Just my 2 cents.
Regards, Mauro
Define the objects to manage digest lists:
- digest_list_item: a digest list loaded into the kernel; - digest_list_item_ref: a reference to a digest list; - digest_item: a digest of a digest list.
Also define some helpers for the objects. More information can be found in Documentation/security/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- .../security/diglim/implementation.rst | 105 ++++++++++++++ MAINTAINERS | 1 + security/integrity/diglim/diglim.h | 134 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 security/integrity/diglim/diglim.h
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 59a180b3bb3f..6002049612a1 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -95,3 +95,108 @@ with digest lists:
- ``DIGEST_LIST_ADD``: the digest list is being added; - ``DIGEST_LIST_DEL``: the digest list is being deleted. + + +Objects +------- + +This section defines the objects to manage digest lists. + +.. kernel-doc:: security/integrity/diglim/diglim.h + +They are represented in the following class diagram: + +:: + + digest_offset, + hdr_offset---------------+ + | + | + +------------------+ | +----------------------+ + | digest_list_item |--- N:1 ---| digest_list_item_ref | + +------------------+ +----------------------+ + | + 1:N + | + +-------------+ + | digest_item | + +-------------+ + +A ``digest_list_item`` is associated to one or multiple +``digest_list_item_ref``, one for each digest it contains. However, +a ``digest_list_item_ref`` is associated to only one ``digest_list_item``, +as it represents a single location within a specific digest list. + +Given that a ``digest_list_item_ref`` represents a single location, it is +associated to only one ``digest_item``. However, a ``digest_item`` can have +multiple references (as it might appears multiple times within the same +digest list or in different digest lists, if it is duplicated). + +All digest list references are stored for a given digest, so that a query +result can include the OR of the modifiers and actions of each referenced +digest list. + +The relationship between the described objects can be graphically +represented as: + +:: + + Hash table +-------------+ +-------------+ + PARSER +-----+ | digest_item | | digest_item | + FILE | key |-->| |-->...-->| | + METADATA +-----+ |ref0|...|refN| |ref0|...|refN| + +-------------+ +-------------+ + ref0: | | refN: + digest_offset | +-----------------------------+ digest_offset + hdr_offset | | hdr_offset + | | + V V + +--------------------+ + | digest_list_item | + | | + | size, buf, actions | + +--------------------+ + ^ + | + Hash table +-------------+ +-------------+ + DIGEST_LIST +-----+ |ref0 | |ref0 | + | key |-->| |-->...-->| | + +-----+ | digest_item | | digest_item | + +-------------+ +-------------+ + +The reference for the digest of the digest list differs from the references +for the other digest types. ``digest_offset`` and ``hdr_offset`` are set to +zero, so that the digest of the digest list is retrieved from the +``digest_list_item`` structure directly (see ``get_digest()`` below). + +Finally, this section defines useful helpers to access a digest or the +header the digest belongs to. For example: + +:: + + 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 f61f5239468a..f7592d41367d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5462,6 +5462,7 @@ F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst F: include/uapi/linux/diglim.h +F: security/integrity/diglim/diglim.h
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h new file mode 100644 index 000000000000..578253d7e1d1 --- /dev/null +++ b/security/integrity/diglim/diglim.h @@ -0,0 +1,134 @@ +/* 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 + * + * Definitions only used inside DIGLIM. + */ + +#ifndef __DIGLIM_INTERNAL_H +#define __DIGLIM_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/diglim.h> + +#define MAX_DIGEST_SIZE 64 +#define HASH_BITS 10 +#define DIGLIM_HTABLE_SIZE (1 << HASH_BITS) + +/** + * struct digest_list_item - a digest list loaded into the kernel + * + * @size: size of the digest list buffer + * @buf: digest list buffer + * @digest: digest of the digest list + * @label: label used to identify the digest list (e.g. file name) + * @actions: actions performed on the digest list + * @algo: digest algorithm + */ +struct digest_list_item { + loff_t size; + u8 *buf; + u8 digest[64]; + const char *label; + u8 actions; + enum hash_algo algo; +}; + +/** + * struct digest_list_item_ref - a reference to a digest list + * + * @list: linked list pointers + * @digest_list: pointer to struct digest_list_item + * @digest_offset: offset of the digest in the referenced digest list + * @hdr_offset: offset of the header the digest refers to in the digest list + */ +struct digest_list_item_ref { + struct list_head list; + struct digest_list_item *digest_list; + u32 digest_offset; + u32 hdr_offset; +}; + +/** + * struct digest_item - a digest of a digest list + * + * @hnext: pointers of the hash table + * @refs: linked list of struct digest_list_item_ref + */ +struct digest_item { + struct hlist_node hnext; + struct list_head refs; +}; + +struct h_table { + unsigned long len; + struct hlist_head queue[DIGLIM_HTABLE_SIZE]; +}; + +static inline unsigned int hash_key(u8 *digest) +{ + return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE; +} + +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 /*__DIGLIM_INTERNAL_H*/
Em Mon, 26 Jul 2021 18:36:51 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Define the objects to manage digest lists:
- digest_list_item: a digest list loaded into the kernel;
- digest_list_item_ref: a reference to a digest list;
- digest_item: a digest of a digest list.
Also define some helpers for the objects. More information can be found in Documentation/security/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 105 ++++++++++++++ MAINTAINERS | 1 + security/integrity/diglim/diglim.h | 134 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 security/integrity/diglim/diglim.h
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 59a180b3bb3f..6002049612a1 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -95,3 +95,108 @@ with digest lists:
- ``DIGEST_LIST_ADD``: the digest list is being added;
- ``DIGEST_LIST_DEL``: the digest list is being deleted.
+Objects +-------
+This section defines the objects to manage digest lists.
+.. kernel-doc:: security/integrity/diglim/diglim.h
+They are represented in the following class diagram:
+::
- digest_offset,
- hdr_offset---------------+
|
|
- +------------------+ | +----------------------+
- | digest_list_item |--- N:1 ---| digest_list_item_ref |
- +------------------+ +----------------------+
|
1:N
|
+-------------+
| digest_item |
+-------------+
+A ``digest_list_item`` is associated to one or multiple +``digest_list_item_ref``, one for each digest it contains. However, +a ``digest_list_item_ref`` is associated to only one ``digest_list_item``, +as it represents a single location within a specific digest list.
+Given that a ``digest_list_item_ref`` represents a single location, it is +associated to only one ``digest_item``. However, a ``digest_item`` can have +multiple references (as it might appears multiple times within the same +digest list or in different digest lists, if it is duplicated).
+All digest list references are stored for a given digest, so that a query +result can include the OR of the modifiers and actions of each referenced +digest list.
+The relationship between the described objects can be graphically +represented as:
+::
Just merge "::" at the previous line (everywhere).
- 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)
I would write this to avoid ending a line with an open parenthesis. You could, for instance, write it as:
static inline struct compact_list_hdr * get_hdr(struct digest_list_item *digest_list, off_t hdr_offset)
if you also want to avoid to have a line bigger than 80 columns.
- {
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 f61f5239468a..f7592d41367d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5462,6 +5462,7 @@ F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst F: include/uapi/linux/diglim.h +F: security/integrity/diglim/diglim.h DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h new file mode 100644 index 000000000000..578253d7e1d1 --- /dev/null +++ b/security/integrity/diglim/diglim.h @@ -0,0 +1,134 @@ +/* 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
- Definitions only used inside DIGLIM.
- */
+#ifndef __DIGLIM_INTERNAL_H +#define __DIGLIM_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/diglim.h>
+#define MAX_DIGEST_SIZE 64 +#define HASH_BITS 10 +#define DIGLIM_HTABLE_SIZE (1 << HASH_BITS)
+/**
- struct digest_list_item - a digest list loaded into the kernel
- @size: size of the digest list buffer
- @buf: digest list buffer
- @digest: digest of the digest list
- @label: label used to identify the digest list (e.g. file name)
- @actions: actions performed on the digest list
- @algo: digest algorithm
- */
+struct digest_list_item {
- loff_t size;
- u8 *buf;
- u8 digest[64];
- const char *label;
- u8 actions;
- enum hash_algo algo;
+};
+/**
- struct digest_list_item_ref - a reference to a digest list
- @list: linked list pointers
- @digest_list: pointer to struct digest_list_item
- @digest_offset: offset of the digest in the referenced digest list
- @hdr_offset: offset of the header the digest refers to in the digest list
- */
+struct digest_list_item_ref {
- struct list_head list;
- struct digest_list_item *digest_list;
- u32 digest_offset;
- u32 hdr_offset;
+};
+/**
- struct digest_item - a digest of a digest list
- @hnext: pointers of the hash table
- @refs: linked list of struct digest_list_item_ref
- */
+struct digest_item {
- struct hlist_node hnext;
- struct list_head refs;
+};
+struct h_table {
- unsigned long len;
- struct hlist_head queue[DIGLIM_HTABLE_SIZE];
+};
+static inline unsigned int hash_key(u8 *digest) +{
- return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE;
+}
+static inline struct compact_list_hdr *get_hdr(
struct digest_list_item *digest_list,
loff_t hdr_offset)
Same here with regards to open parenthesis.
+{
- 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;
+}
I would also document the above static inline functions and struct h_table.
+#endif /*__DIGLIM_INTERNAL_H*/
Thanks, Mauro
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 1:39 PM Em Mon, 26 Jul 2021 18:36:51 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Define the objects to manage digest lists:
- digest_list_item: a digest list loaded into the kernel;
- digest_list_item_ref: a reference to a digest list;
- digest_item: a digest of a digest list.
Also define some helpers for the objects. More information can be found in Documentation/security/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 105 ++++++++++++++ MAINTAINERS | 1 + security/integrity/diglim/diglim.h | 134 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 security/integrity/diglim/diglim.h
diff --git a/Documentation/security/diglim/implementation.rst
b/Documentation/security/diglim/implementation.rst
index 59a180b3bb3f..6002049612a1 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -95,3 +95,108 @@ with digest lists:
- ``DIGEST_LIST_ADD``: the digest list is being added;
- ``DIGEST_LIST_DEL``: the digest list is being deleted.
+Objects +-------
+This section defines the objects to manage digest lists.
+.. kernel-doc:: security/integrity/diglim/diglim.h
+They are represented in the following class diagram:
+::
- digest_offset,
- hdr_offset---------------+
|
|
- +------------------+ | +----------------------+
- | digest_list_item |--- N:1 ---| digest_list_item_ref |
- +------------------+ +----------------------+
|
1:N
|
+-------------+
| digest_item |
+-------------+
+A ``digest_list_item`` is associated to one or multiple +``digest_list_item_ref``, one for each digest it contains. However, +a ``digest_list_item_ref`` is associated to only one ``digest_list_item``, +as it represents a single location within a specific digest list.
+Given that a ``digest_list_item_ref`` represents a single location, it is +associated to only one ``digest_item``. However, a ``digest_item`` can have +multiple references (as it might appears multiple times within the same +digest list or in different digest lists, if it is duplicated).
+All digest list references are stored for a given digest, so that a query +result can include the OR of the modifiers and actions of each referenced +digest list.
+The relationship between the described objects can be graphically +represented as:
+::
Just merge "::" at the previous line (everywhere).
Ok.
- 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)
I would write this to avoid ending a line with an open parenthesis. You could, for instance, write it as:
static inline struct compact_list_hdr * get_hdr(struct digest_list_item *digest_list, off_t hdr_offset)
if you also want to avoid to have a line bigger than 80 columns.
Ok.
- {
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 f61f5239468a..f7592d41367d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5462,6 +5462,7 @@ F:
Documentation/security/diglim/implementation.rst
F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst F: include/uapi/linux/diglim.h +F: security/integrity/diglim/diglim.h
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/security/integrity/diglim/diglim.h
b/security/integrity/diglim/diglim.h
new file mode 100644 index 000000000000..578253d7e1d1 --- /dev/null +++ b/security/integrity/diglim/diglim.h @@ -0,0 +1,134 @@ +/* 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
- Definitions only used inside DIGLIM.
- */
+#ifndef __DIGLIM_INTERNAL_H +#define __DIGLIM_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/diglim.h>
+#define MAX_DIGEST_SIZE 64 +#define HASH_BITS 10 +#define DIGLIM_HTABLE_SIZE (1 << HASH_BITS)
+/**
- struct digest_list_item - a digest list loaded into the kernel
- @size: size of the digest list buffer
- @buf: digest list buffer
- @digest: digest of the digest list
- @label: label used to identify the digest list (e.g. file name)
- @actions: actions performed on the digest list
- @algo: digest algorithm
- */
+struct digest_list_item {
- loff_t size;
- u8 *buf;
- u8 digest[64];
- const char *label;
- u8 actions;
- enum hash_algo algo;
+};
+/**
- struct digest_list_item_ref - a reference to a digest list
- @list: linked list pointers
- @digest_list: pointer to struct digest_list_item
- @digest_offset: offset of the digest in the referenced digest list
- @hdr_offset: offset of the header the digest refers to in the digest list
- */
+struct digest_list_item_ref {
- struct list_head list;
- struct digest_list_item *digest_list;
- u32 digest_offset;
- u32 hdr_offset;
+};
+/**
- struct digest_item - a digest of a digest list
- @hnext: pointers of the hash table
- @refs: linked list of struct digest_list_item_ref
- */
+struct digest_item {
- struct hlist_node hnext;
- struct list_head refs;
+};
+struct h_table {
- unsigned long len;
- struct hlist_head queue[DIGLIM_HTABLE_SIZE];
+};
+static inline unsigned int hash_key(u8 *digest) +{
- return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE;
+}
+static inline struct compact_list_hdr *get_hdr(
struct digest_list_item *digest_list,
loff_t hdr_offset)
Same here with regards to open parenthesis.
+{
- 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;
+}
I would also document the above static inline functions and struct h_table.
Ok.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
+#endif /*__DIGLIM_INTERNAL_H*/
Thanks, Mauro
Introduce the methods requires to manage the three objects defined.
- digest_item methods: - digest_add() - digest_del() - __digest_lookup() - diglim_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/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- .../security/diglim/implementation.rst | 9 + MAINTAINERS | 2 + include/linux/diglim.h | 28 + security/integrity/Kconfig | 1 + security/integrity/Makefile | 1 + security/integrity/diglim/Kconfig | 11 + security/integrity/diglim/Makefile | 8 + security/integrity/diglim/diglim.h | 20 +- security/integrity/diglim/methods.c | 499 ++++++++++++++++++ 9 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 include/linux/diglim.h create mode 100644 security/integrity/diglim/Kconfig create mode 100644 security/integrity/diglim/Makefile create mode 100644 security/integrity/diglim/methods.c
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 6002049612a1..54af23b2f5f1 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -200,3 +200,12 @@ 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. + +.. kernel-doc:: security/integrity/diglim/methods.c diff --git a/MAINTAINERS b/MAINTAINERS index f7592d41367d..9e085a36654a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5461,8 +5461,10 @@ F: Documentation/security/diglim/architecture.rst F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h +F: security/integrity/diglim/methods.c
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/include/linux/diglim.h b/include/linux/diglim.h new file mode 100644 index 000000000000..d4b4548a288b --- /dev/null +++ b/include/linux/diglim.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu roberto.sassu@huawei.com + * + * DIGLIM functions available for use by kernel subsystems. + */ + +#ifndef __DIGLIM_H +#define __DIGLIM_H + +#include <crypto/hash_info.h> +#include <uapi/linux/diglim.h> + +#ifdef CONFIG_DIGLIM +extern int diglim_digest_get_info(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 *modifiers, + u8 *actions); +#else +static inline int diglim_digest_get_info(u8 *digest, enum hash_algo algo, + enum compact_types type, + u16 *modifiers, u8 *actions) +{ + return -ENOENT; +} +#endif /*CONFIG_DIGLIM*/ +#endif /*__DIGLIM_H*/ diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 71f0177e8716..8f94f4dcc052 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/diglim/Kconfig"
endif # if INTEGRITY diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 7ee39d66cf16..d6166550a6b8 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_DIGLIM) += diglim/ diff --git a/security/integrity/diglim/Kconfig b/security/integrity/diglim/Kconfig new file mode 100644 index 000000000000..436a76a14337 --- /dev/null +++ b/security/integrity/diglim/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Digest Lists Integrity Module (DIGLIM) +# +config DIGLIM + bool "Digest Lists Integrity Module (DIGLIM)" + select SECURITYFS + select CRYPTO + select CRYPTO_HASH_INFO + help + DIGLIM provides reference values for file content and metadata, + that can be used for measurement and appraisal with IMA. diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile new file mode 100644 index 000000000000..b761ed8cfb3e --- /dev/null +++ b/security/integrity/diglim/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for building Digest Lists Integrity Module (DIGLIM). +# + +obj-$(CONFIG_DIGLIM) += diglim.o + +diglim-y := methods.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 578253d7e1d1..25851e7d4906 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -20,7 +20,7 @@ #include <linux/audit.h> #include <crypto/hash_info.h> #include <linux/hash_info.h> -#include <uapi/linux/diglim.h> +#include <linux/diglim.h>
#define MAX_DIGEST_SIZE 64 #define HASH_BITS 10 @@ -81,6 +81,8 @@ static inline unsigned int hash_key(u8 *digest) return (digest[0] | digest[1] << 8) % DIGLIM_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) @@ -131,4 +133,20 @@ static inline u8 *get_digest_ref(struct digest_list_item_ref *ref)
return ref->digest_list->buf + ref->digest_offset; } + +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); +void 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); +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, + struct digest_list_item *digest_list); #endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/methods.c b/security/integrity/diglim/methods.c new file mode 100644 index 000000000000..7ed61399cfe8 --- /dev/null +++ b/security/integrity/diglim/methods.c @@ -0,0 +1,499 @@ +// 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 + * + * Functions to manage digest lists. + */ + +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/fault-inject.h> + +#include "diglim.h" +#include "../integrity.h" + +/* Define a cache for each object type. */ +static struct kmem_cache *digest_list_item_cache __read_mostly; +static struct kmem_cache *digest_list_item_ref_cache __read_mostly; +static struct kmem_cache *digest_item_cache __read_mostly; + +/* Define a hash table for each digest type. */ +struct h_table htable[COMPACT__LAST] = {{ + .queue[0 ... DIGLIM_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +}}; + +#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS +static DECLARE_FAULT_ATTR(fail_diglim); + +static int __init fail_diglim_debugfs(void) +{ + struct dentry *dir = fault_create_debugfs_attr("fail_diglim", NULL, + &fail_diglim); + + return PTR_ERR_OR_ZERO(dir); +} + +static inline bool should_fail_diglim(void) +{ + return should_fail(&fail_diglim, 1); +} + +late_initcall(fail_diglim_debugfs); +#else +static inline bool should_fail_diglim(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. + * diglim_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 linked list of references to the + * digest lists containing the found digest. + * + * Return: 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) { + list_for_each_entry_rcu(ref, &d->refs, list) { + 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; +} + +/** + * diglim_digest_get_info - lookup digest and return 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 lists 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. + * + * Return: 0 if the digest is found, -ENOENT otherwise. + */ +int diglim_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). + * + * Return: 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_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]; + + /* Allocate a new reference. */ + if (!should_fail_diglim()) + new_ref = kmem_cache_alloc(digest_list_item_ref_cache, + GFP_KERNEL); + if (!new_ref) { + print_hex_dump(KERN_ERR, "digest list ref allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return -ENOMEM; + } + + /* Set the new reference. */ + new_ref->digest_list = digest_list; + /* Converting loff_t -> u32 is fine as long as the digest list < 4G. */ + new_ref->digest_offset = digest_offset; + new_ref->hdr_offset = hdr_offset; + + list_add_tail_rcu(&new_ref->list, &d->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 + * linked list of references stored for each digest item. If the reference is + * found (if not, it is a bug), the function deletes it from the linked list. + */ +static void 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 *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]; + + /* Search for a digest list reference. */ + list_for_each_entry(ref, &d->refs, list) + if (ref->digest_list == digest_list) + break; + + if (!ref) { + print_hex_dump(KERN_ERR, "digest list ref not found: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return; + } + + list_del_rcu(&ref->list); + kmem_cache_free(digest_list_item_ref_cache, ref); + + print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); +} + +/** + * 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 digest list reference + * (buffer + digest_offset), or from the digest field of a digest list item, + * for a digest list. + * + * 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. + * + * Proper locking must be provided by the caller. + * + * Return: 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_diglim()) + d = kmem_cache_alloc(digest_item_cache, 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); + } + + INIT_LIST_HEAD(&d->refs); + + /* 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) { + kmem_cache_free(digest_item_cache, 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)]); + 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 linked + * list of references of the digest item. + * + * If the last reference was deleted, the digest item is also deleted and + * removed from the hash table. + * + * Proper locking must be provided by the caller. + */ +void 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]; + + /* 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; + } + + /* Delete a reference of the passed digest list. */ + digest_list_ref_del(d, digest_list, digest_offset, hdr_offset); + + print_hex_dump_debug(!list_empty(&d->refs) ? + "digest del duplicate: " : "digest del: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + + /* Return if there are still references. */ + if (!list_empty(&d->refs)) + return; + + /* + * Remove the digest item from the hash table and free it if there are + * no more references left. + */ + hlist_del_rcu(&d->hnext); + htable[type].len--; + kmem_cache_free(digest_item_cache, d); +} + +/** + * 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. + * + * Proper locking must be provided by the caller. + * + * Return: 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_diglim()) + digest_list = kmem_cache_alloc(digest_list_item_cache, + 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->buf = NULL; + digest_list->size = size; + + if (!should_fail_diglim()) + 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); + kmem_cache_free(digest_list_item_cache, 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); + kmem_cache_free(digest_list_item_cache, 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 will be executed only for digest lists that were previously + * added. + * + * Proper locking must be provided by the caller. + */ +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, + struct digest_list_item *digest_list) +{ + /* Delete the digest item associated to the digest list. */ + digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); + + /* + * Free the buffer and the digest list item allocated when the digest + * list was added. + */ + kfree(digest_list->buf); + kmem_cache_free(digest_list_item_cache, digest_list); +} + +static int __init digest_list_cache_init(void) +{ + digest_list_item_cache = kmem_cache_create("digest_list_item_cache", + sizeof(struct digest_list_item), + 0, SLAB_PANIC, NULL); + + digest_list_item_ref_cache = kmem_cache_create( + "digest_list_item_ref_cache", + sizeof(struct digest_list_item_ref), 0, + SLAB_PANIC, NULL); + + digest_item_cache = kmem_cache_create("digest_item_cache", + sizeof(struct digest_item), 0, + SLAB_PANIC, NULL); + + return 0; +} + +late_initcall(digest_list_cache_init)
Em Mon, 26 Jul 2021 18:36:52 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Introduce the methods requires to manage the three objects defined.
digest_item methods:
- digest_add()
- digest_del()
- __digest_lookup()
- diglim_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/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 9 + MAINTAINERS | 2 + include/linux/diglim.h | 28 + security/integrity/Kconfig | 1 + security/integrity/Makefile | 1 + security/integrity/diglim/Kconfig | 11 + security/integrity/diglim/Makefile | 8 + security/integrity/diglim/diglim.h | 20 +- security/integrity/diglim/methods.c | 499 ++++++++++++++++++ 9 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 include/linux/diglim.h create mode 100644 security/integrity/diglim/Kconfig create mode 100644 security/integrity/diglim/Makefile create mode 100644 security/integrity/diglim/methods.c
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 6002049612a1..54af23b2f5f1 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -200,3 +200,12 @@ 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.
+.. kernel-doc:: security/integrity/diglim/methods.c diff --git a/MAINTAINERS b/MAINTAINERS index f7592d41367d..9e085a36654a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5461,8 +5461,10 @@ F: Documentation/security/diglim/architecture.rst F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h +F: security/integrity/diglim/methods.c DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/include/linux/diglim.h b/include/linux/diglim.h new file mode 100644 index 000000000000..d4b4548a288b --- /dev/null +++ b/include/linux/diglim.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
- Author: Roberto Sassu roberto.sassu@huawei.com
- DIGLIM functions available for use by kernel subsystems.
- */
+#ifndef __DIGLIM_H +#define __DIGLIM_H
+#include <crypto/hash_info.h> +#include <uapi/linux/diglim.h>
+#ifdef CONFIG_DIGLIM +extern int diglim_digest_get_info(u8 *digest, enum hash_algo algo,
enum compact_types type, u16 *modifiers,
u8 *actions);
+#else +static inline int diglim_digest_get_info(u8 *digest, enum hash_algo algo,
enum compact_types type,
u16 *modifiers, u8 *actions)
+{
- return -ENOENT;
+} +#endif /*CONFIG_DIGLIM*/ +#endif /*__DIGLIM_H*/ diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 71f0177e8716..8f94f4dcc052 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/diglim/Kconfig" endif # if INTEGRITY diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 7ee39d66cf16..d6166550a6b8 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_DIGLIM) += diglim/ diff --git a/security/integrity/diglim/Kconfig b/security/integrity/diglim/Kconfig new file mode 100644 index 000000000000..436a76a14337 --- /dev/null +++ b/security/integrity/diglim/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Digest Lists Integrity Module (DIGLIM) +# +config DIGLIM
- bool "Digest Lists Integrity Module (DIGLIM)"
- select SECURITYFS
- select CRYPTO
- select CRYPTO_HASH_INFO
- help
DIGLIM provides reference values for file content and metadata,
that can be used for measurement and appraisal with IMA.
diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile new file mode 100644 index 000000000000..b761ed8cfb3e --- /dev/null +++ b/security/integrity/diglim/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for building Digest Lists Integrity Module (DIGLIM). +#
+obj-$(CONFIG_DIGLIM) += diglim.o
+diglim-y := methods.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 578253d7e1d1..25851e7d4906 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -20,7 +20,7 @@ #include <linux/audit.h> #include <crypto/hash_info.h> #include <linux/hash_info.h> -#include <uapi/linux/diglim.h> +#include <linux/diglim.h> #define MAX_DIGEST_SIZE 64 #define HASH_BITS 10 @@ -81,6 +81,8 @@ static inline unsigned int hash_key(u8 *digest) return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE; } +extern struct h_table htable[COMPACT__LAST];
it sounds somewhat risky to use just "htable" for a var declared as external.
static inline struct compact_list_hdr *get_hdr( struct digest_list_item *digest_list, loff_t hdr_offset) @@ -131,4 +133,20 @@ static inline u8 *get_digest_ref(struct digest_list_item_ref *ref) return ref->digest_list->buf + ref->digest_offset; }
+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);
+void 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);
+void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions,
struct digest_list_item *digest_list);
#endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/methods.c b/security/integrity/diglim/methods.c new file mode 100644 index 000000000000..7ed61399cfe8 --- /dev/null +++ b/security/integrity/diglim/methods.c @@ -0,0 +1,499 @@ +// 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
- Functions to manage digest lists.
- */
+#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/fault-inject.h>
+#include "diglim.h" +#include "../integrity.h"
+/* Define a cache for each object type. */ +static struct kmem_cache *digest_list_item_cache __read_mostly; +static struct kmem_cache *digest_list_item_ref_cache __read_mostly; +static struct kmem_cache *digest_item_cache __read_mostly;
+/* Define a hash table for each digest type. */ +struct h_table htable[COMPACT__LAST] = {{
- .queue[0 ... DIGLIM_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
+}};
+#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS +static DECLARE_FAULT_ATTR(fail_diglim);
+static int __init fail_diglim_debugfs(void) +{
- struct dentry *dir = fault_create_debugfs_attr("fail_diglim", NULL,
&fail_diglim);
- return PTR_ERR_OR_ZERO(dir);
+}
+static inline bool should_fail_diglim(void) +{
- return should_fail(&fail_diglim, 1);
+}
+late_initcall(fail_diglim_debugfs); +#else +static inline bool should_fail_diglim(void) +{
- return false;
+} +#endif
I guess this is a matter of personal preference, but, IMO, it is a lot better to place the debugfs stuff on a separate source file, avoiding ugly #ifdefs in the middle of the code.
Ok, the current code is too small to deserve a separate file, but if later patches would add more stuff, then I would opt to have this on a separate file.
+/**
- __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.
- diglim_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 linked list of references to the
- digest lists containing the found digest.
- Return: 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) {
list_for_each_entry_rcu(ref, &d->refs, list) {
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;
+}
+/**
- diglim_digest_get_info - lookup digest and return 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 lists 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.
- Return: 0 if the digest is found, -ENOENT otherwise.
- */
+int diglim_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).
- Return: 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_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];
- /* Allocate a new reference. */
- if (!should_fail_diglim())
new_ref = kmem_cache_alloc(digest_list_item_ref_cache,
GFP_KERNEL);
- if (!new_ref) {
print_hex_dump(KERN_ERR, "digest list ref allocation failed: ",
DUMP_PREFIX_NONE, digest_len, 1, digest,
digest_len, true);
return -ENOMEM;
- }
- /* Set the new reference. */
- new_ref->digest_list = digest_list;
- /* Converting loff_t -> u32 is fine as long as the digest list < 4G. */
- new_ref->digest_offset = digest_offset;
- new_ref->hdr_offset = hdr_offset;
- list_add_tail_rcu(&new_ref->list, &d->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
- linked list of references stored for each digest item. If the reference is
- found (if not, it is a bug), the function deletes it from the linked list.
- */
+static void 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 *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];
- /* Search for a digest list reference. */
- list_for_each_entry(ref, &d->refs, list)
if (ref->digest_list == digest_list)
break;
- if (!ref) {
print_hex_dump(KERN_ERR, "digest list ref not found: ",
DUMP_PREFIX_NONE, digest_len, 1, digest,
digest_len, true);
return;
- }
- list_del_rcu(&ref->list);
- kmem_cache_free(digest_list_item_ref_cache, ref);
- print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE,
digest_len, 1, digest, digest_len, true);
+}
+/**
- 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 digest list reference
- (buffer + digest_offset), or from the digest field of a digest list item,
- for a digest list.
- 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.
- Proper locking must be provided by the caller.
- Return: 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_diglim())
d = kmem_cache_alloc(digest_item_cache, 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);
- }
- INIT_LIST_HEAD(&d->refs);
- /* 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) {
kmem_cache_free(digest_item_cache, 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)]);
- 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 linked
- list of references of the digest item.
- If the last reference was deleted, the digest item is also deleted and
- removed from the hash table.
- Proper locking must be provided by the caller.
- */
+void 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];
- /* 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;
- }
- /* Delete a reference of the passed digest list. */
- digest_list_ref_del(d, digest_list, digest_offset, hdr_offset);
- print_hex_dump_debug(!list_empty(&d->refs) ?
"digest del duplicate: " : "digest del: ",
DUMP_PREFIX_NONE, digest_len, 1, digest,
digest_len, true);
- /* Return if there are still references. */
- if (!list_empty(&d->refs))
return;
- /*
* Remove the digest item from the hash table and free it if there are
* no more references left.
*/
- hlist_del_rcu(&d->hnext);
- htable[type].len--;
- kmem_cache_free(digest_item_cache, d);
+}
+/**
- 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.
- Proper locking must be provided by the caller.
- Return: 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_diglim())
digest_list = kmem_cache_alloc(digest_list_item_cache,
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->buf = NULL;
- digest_list->size = size;
- if (!should_fail_diglim())
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);
kmem_cache_free(digest_list_item_cache, 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);
kmem_cache_free(digest_list_item_cache, 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 will be executed only for digest lists that were previously
- added.
- Proper locking must be provided by the caller.
- */
+void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions,
struct digest_list_item *digest_list)
+{
- /* Delete the digest item associated to the digest list. */
- digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0);
- /*
* Free the buffer and the digest list item allocated when the digest
* list was added.
*/
- kfree(digest_list->buf);
- kmem_cache_free(digest_list_item_cache, digest_list);
+}
+static int __init digest_list_cache_init(void) +{
- digest_list_item_cache = kmem_cache_create("digest_list_item_cache",
sizeof(struct digest_list_item),
0, SLAB_PANIC, NULL);
- digest_list_item_ref_cache = kmem_cache_create(
"digest_list_item_ref_cache",
sizeof(struct digest_list_item_ref), 0,
SLAB_PANIC, NULL);
- digest_item_cache = kmem_cache_create("digest_item_cache",
sizeof(struct digest_item), 0,
SLAB_PANIC, NULL);
- return 0;
+}
+late_initcall(digest_list_cache_init)
Thanks, Mauro
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 2:19 PM Em Mon, 26 Jul 2021 18:36:52 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Introduce the methods requires to manage the three objects defined.
digest_item methods:
- digest_add()
- digest_del()
- __digest_lookup()
- diglim_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/diglim/implementation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 9 + MAINTAINERS | 2 + include/linux/diglim.h | 28 + security/integrity/Kconfig | 1 + security/integrity/Makefile | 1 + security/integrity/diglim/Kconfig | 11 + security/integrity/diglim/Makefile | 8 + security/integrity/diglim/diglim.h | 20 +- security/integrity/diglim/methods.c | 499 ++++++++++++++++++ 9 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 include/linux/diglim.h create mode 100644 security/integrity/diglim/Kconfig create mode 100644 security/integrity/diglim/Makefile create mode 100644 security/integrity/diglim/methods.c
diff --git a/Documentation/security/diglim/implementation.rst
b/Documentation/security/diglim/implementation.rst
index 6002049612a1..54af23b2f5f1 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -200,3 +200,12 @@ 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.
+.. kernel-doc:: security/integrity/diglim/methods.c diff --git a/MAINTAINERS b/MAINTAINERS index f7592d41367d..9e085a36654a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5461,8 +5461,10 @@ F:
Documentation/security/diglim/architecture.rst
F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h +F: security/integrity/diglim/methods.c
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/include/linux/diglim.h b/include/linux/diglim.h new file mode 100644 index 000000000000..d4b4548a288b --- /dev/null +++ b/include/linux/diglim.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
- Author: Roberto Sassu roberto.sassu@huawei.com
- DIGLIM functions available for use by kernel subsystems.
- */
+#ifndef __DIGLIM_H +#define __DIGLIM_H
+#include <crypto/hash_info.h> +#include <uapi/linux/diglim.h>
+#ifdef CONFIG_DIGLIM +extern int diglim_digest_get_info(u8 *digest, enum hash_algo algo,
enum compact_types type, u16 *modifiers,
u8 *actions);
+#else +static inline int diglim_digest_get_info(u8 *digest, enum hash_algo algo,
enum compact_types type,
u16 *modifiers, u8 *actions)
+{
- return -ENOENT;
+} +#endif /*CONFIG_DIGLIM*/ +#endif /*__DIGLIM_H*/ diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 71f0177e8716..8f94f4dcc052 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/diglim/Kconfig"
endif # if INTEGRITY diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 7ee39d66cf16..d6166550a6b8 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_DIGLIM) += diglim/ diff --git a/security/integrity/diglim/Kconfig
b/security/integrity/diglim/Kconfig
new file mode 100644 index 000000000000..436a76a14337 --- /dev/null +++ b/security/integrity/diglim/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Digest Lists Integrity Module (DIGLIM) +# +config DIGLIM
- bool "Digest Lists Integrity Module (DIGLIM)"
- select SECURITYFS
- select CRYPTO
- select CRYPTO_HASH_INFO
- help
DIGLIM provides reference values for file content and metadata,
that can be used for measurement and appraisal with IMA.
diff --git a/security/integrity/diglim/Makefile
b/security/integrity/diglim/Makefile
new file mode 100644 index 000000000000..b761ed8cfb3e --- /dev/null +++ b/security/integrity/diglim/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for building Digest Lists Integrity Module (DIGLIM). +#
+obj-$(CONFIG_DIGLIM) += diglim.o
+diglim-y := methods.o diff --git a/security/integrity/diglim/diglim.h
b/security/integrity/diglim/diglim.h
index 578253d7e1d1..25851e7d4906 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -20,7 +20,7 @@ #include <linux/audit.h> #include <crypto/hash_info.h> #include <linux/hash_info.h> -#include <uapi/linux/diglim.h> +#include <linux/diglim.h>
#define MAX_DIGEST_SIZE 64 #define HASH_BITS 10 @@ -81,6 +81,8 @@ static inline unsigned int hash_key(u8 *digest) return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE; }
+extern struct h_table htable[COMPACT__LAST];
it sounds somewhat risky to use just "htable" for a var declared as external.
Ok, adding diglim_ as prefix should be enough.
static inline struct compact_list_hdr *get_hdr( struct digest_list_item *digest_list, loff_t hdr_offset) @@ -131,4 +133,20 @@ static inline u8 *get_digest_ref(struct
digest_list_item_ref *ref)
return ref->digest_list->buf + ref->digest_offset; }
+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);
+void 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);
+void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions,
struct digest_list_item *digest_list);
#endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/methods.c
b/security/integrity/diglim/methods.c
new file mode 100644 index 000000000000..7ed61399cfe8 --- /dev/null +++ b/security/integrity/diglim/methods.c @@ -0,0 +1,499 @@ +// 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
- Functions to manage digest lists.
- */
+#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/fault-inject.h>
+#include "diglim.h" +#include "../integrity.h"
+/* Define a cache for each object type. */ +static struct kmem_cache *digest_list_item_cache __read_mostly; +static struct kmem_cache *digest_list_item_ref_cache __read_mostly; +static struct kmem_cache *digest_item_cache __read_mostly;
+/* Define a hash table for each digest type. */ +struct h_table htable[COMPACT__LAST] = {{
- .queue[0 ... DIGLIM_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
+}};
+#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS +static DECLARE_FAULT_ATTR(fail_diglim);
+static int __init fail_diglim_debugfs(void) +{
- struct dentry *dir = fault_create_debugfs_attr("fail_diglim", NULL,
&fail_diglim);
- return PTR_ERR_OR_ZERO(dir);
+}
+static inline bool should_fail_diglim(void) +{
- return should_fail(&fail_diglim, 1);
+}
+late_initcall(fail_diglim_debugfs); +#else +static inline bool should_fail_diglim(void) +{
- return false;
+} +#endif
I guess this is a matter of personal preference, but, IMO, it is a lot better to place the debugfs stuff on a separate source file, avoiding ugly #ifdefs in the middle of the code.
Ok, the current code is too small to deserve a separate file, but if later patches would add more stuff, then I would opt to have this on a separate file.
Ok.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
+/**
- __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.
- diglim_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 linked list of references to the
- digest lists containing the found digest.
- Return: 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) {
list_for_each_entry_rcu(ref, &d->refs, list) {
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;
+}
+/**
- diglim_digest_get_info - lookup digest and return 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 lists 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.
- Return: 0 if the digest is found, -ENOENT otherwise.
- */
+int diglim_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).
- Return: 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_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];
- /* Allocate a new reference. */
- if (!should_fail_diglim())
new_ref = kmem_cache_alloc(digest_list_item_ref_cache,
GFP_KERNEL);
- if (!new_ref) {
print_hex_dump(KERN_ERR, "digest list ref allocation failed: ",
DUMP_PREFIX_NONE, digest_len, 1, digest,
digest_len, true);
return -ENOMEM;
- }
- /* Set the new reference. */
- new_ref->digest_list = digest_list;
- /* Converting loff_t -> u32 is fine as long as the digest list < 4G. */
- new_ref->digest_offset = digest_offset;
- new_ref->hdr_offset = hdr_offset;
- list_add_tail_rcu(&new_ref->list, &d->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
- linked list of references stored for each digest item. If the reference is
- found (if not, it is a bug), the function deletes it from the linked list.
- */
+static void 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 *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];
- /* Search for a digest list reference. */
- list_for_each_entry(ref, &d->refs, list)
if (ref->digest_list == digest_list)
break;
- if (!ref) {
print_hex_dump(KERN_ERR, "digest list ref not found: ",
DUMP_PREFIX_NONE, digest_len, 1, digest,
digest_len, true);
return;
- }
- list_del_rcu(&ref->list);
- kmem_cache_free(digest_list_item_ref_cache, ref);
- print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE,
digest_len, 1, digest, digest_len, true);
+}
+/**
- 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 digest list reference
- (buffer + digest_offset), or from the digest field of a digest list item,
- for a digest list.
- 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.
- Proper locking must be provided by the caller.
- Return: 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_diglim())
d = kmem_cache_alloc(digest_item_cache, 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);
- }
- INIT_LIST_HEAD(&d->refs);
- /* 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) {
kmem_cache_free(digest_item_cache, 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)]);
- 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
linked
- list of references of the digest item.
- If the last reference was deleted, the digest item is also deleted and
- removed from the hash table.
- Proper locking must be provided by the caller.
- */
+void 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];
- /* 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;
- }
- /* Delete a reference of the passed digest list. */
- digest_list_ref_del(d, digest_list, digest_offset, hdr_offset);
- print_hex_dump_debug(!list_empty(&d->refs) ?
"digest del duplicate: " : "digest del: ",
DUMP_PREFIX_NONE, digest_len, 1, digest,
digest_len, true);
- /* Return if there are still references. */
- if (!list_empty(&d->refs))
return;
- /*
* Remove the digest item from the hash table and free it if there are
* no more references left.
*/
- hlist_del_rcu(&d->hnext);
- htable[type].len--;
- kmem_cache_free(digest_item_cache, d);
+}
+/**
- 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.
- Proper locking must be provided by the caller.
- Return: 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_diglim())
digest_list = kmem_cache_alloc(digest_list_item_cache,
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->buf = NULL;
- digest_list->size = size;
- if (!should_fail_diglim())
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);
kmem_cache_free(digest_list_item_cache, 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);
kmem_cache_free(digest_list_item_cache, 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 will be executed only for digest lists that were previously
- added.
- Proper locking must be provided by the caller.
- */
+void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions,
struct digest_list_item *digest_list)
+{
- /* Delete the digest item associated to the digest list. */
- digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0);
- /*
* Free the buffer and the digest list item allocated when the digest
* list was added.
*/
- kfree(digest_list->buf);
- kmem_cache_free(digest_list_item_cache, digest_list);
+}
+static int __init digest_list_cache_init(void) +{
- digest_list_item_cache =
kmem_cache_create("digest_list_item_cache",
sizeof(struct digest_list_item),
0, SLAB_PANIC, NULL);
- digest_list_item_ref_cache = kmem_cache_create(
"digest_list_item_ref_cache",
sizeof(struct digest_list_item_ref), 0,
SLAB_PANIC, NULL);
- digest_item_cache = kmem_cache_create("digest_item_cache",
sizeof(struct digest_item), 0,
SLAB_PANIC, NULL);
- return 0;
+}
+late_initcall(digest_list_cache_init)
Thanks, Mauro
Introduce the necessary functions to parse a digest list and to execute the requested operation.
The main function is digest_list_parse(), which coordinates the various steps required to add or delete a digest list, and has the logic to roll back when one of the steps fails.
A more detailed description about the steps can be found in Documentation/security/diglim/implementation.rst
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- .../security/diglim/implementation.rst | 35 +++ MAINTAINERS | 1 + security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/diglim.h | 3 + security/integrity/diglim/parser.c | 274 ++++++++++++++++++ 5 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 security/integrity/diglim/parser.c
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 54af23b2f5f1..9d679567a037 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -209,3 +209,38 @@ This section introduces the methods requires to manage the three objects defined.
.. kernel-doc:: security/integrity/diglim/methods.c + + +Parser +------ + +This section introduces the necessary functions to parse a digest list and +to execute the requested operation. + +.. kernel-doc:: security/integrity/diglim/parser.c + +The main function is digest_list_parse(), which coordinates the various +steps required to add or delete a digest list, and has the logic to roll +back when one of the steps fails. + +#. Calls digest_list_validate() to validate the passed buffer containing + the digest list to ensure that the format is correct. + +#. Calls get_digest_list() to create a new digest_list_item for the add + operation, or to retrieve the existing one for the delete operation. + get_digest_list() refuses to add digest lists that were previously + added and to delete digest lists that weren't previously added. Also, + get_digest_list() refuses to delete digest lists if there are actions + done at addition time that are not currently being performed (it would + guarantee that also deletion is notified to remote verifiers). + +#. Calls _digest_list_parse() which takes the created/retrieved + struct digest_list_item and adds or delete the digests included in the + digest list. + +#. If an error occurred, performs a rollback to the previous state, by + calling _digest_list_parse() with the opposite operation and the buffer + size at the time the error occurred. + +#. digest_list_parse() deletes the struct digest_list_item on unsuccessful + add or successful delete. diff --git a/MAINTAINERS b/MAINTAINERS index 9e085a36654a..77c3613c600a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5465,6 +5465,7 @@ F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h F: security/integrity/diglim/methods.c +F: security/integrity/diglim/parser.c
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile index b761ed8cfb3e..34e4e154fff3 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@
obj-$(CONFIG_DIGLIM) += diglim.o
-diglim-y := methods.o +diglim-y := methods.o parser.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 25851e7d4906..3adc218a0325 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -149,4 +149,7 @@ struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, const char *label); void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, struct digest_list_item *digest_list); + +int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, const char *label); #endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/parser.c b/security/integrity/diglim/parser.c new file mode 100644 index 000000000000..89a48945b460 --- /dev/null +++ b/security/integrity/diglim/parser.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu roberto.sassu@huawei.com + * + * Functions to parse digest lists. + */ + +#include <linux/vmalloc.h> +#include <linux/module.h> + +#include "diglim.h" +#include "../integrity.h" + +/** + * digest_list_validate - validate format of digest list + * @size: buffer size + * @buf: buffer containing the digest list + * + * This function validates the format of the passed digest list. + * + * Return: 0 if the digest list was successfully validated, -EINVAL otherwise. + */ +static int digest_list_validate(loff_t size, void *buf) +{ + void *bufp = buf, *bufendp = buf + size; + struct compact_list_hdr *hdr; + size_t digest_len; + + while (bufp < bufendp) { + if (bufp + sizeof(*hdr) > bufendp) { + pr_err("invalid data\n"); + return -EINVAL; + } + + hdr = bufp; + + if (hdr->version != 1) { + pr_err("unsupported version\n"); + return -EINVAL; + } + + if (hdr->_reserved != 0) { + pr_err("unexpected value for _reserved field\n"); + return -EINVAL; + } + + hdr->type = le16_to_cpu(hdr->type); + hdr->modifiers = le16_to_cpu(hdr->modifiers); + hdr->algo = le16_to_cpu(hdr->algo); + hdr->count = le32_to_cpu(hdr->count); + hdr->datalen = le32_to_cpu(hdr->datalen); + + if (hdr->algo >= HASH_ALGO__LAST) { + pr_err("invalid hash algorithm\n"); + return -EINVAL; + } + + digest_len = hash_digest_size[hdr->algo]; + + if (hdr->type >= COMPACT__LAST || + hdr->type == COMPACT_DIGEST_LIST) { + pr_err("invalid type %d\n", hdr->type); + return -EINVAL; + } + + bufp += sizeof(*hdr); + + if (hdr->datalen != hdr->count * digest_len || + bufp + hdr->datalen > bufendp) { + pr_err("invalid data\n"); + return -EINVAL; + } + + bufp += hdr->count * digest_len; + } + + return 0; +} + +/** + * _digest_list_parse - parse digest list and add/delete digests + * @size: buffer size + * @buf: buffer containing the digest list + * @op: operation to be performed + * @digest_list: digest list digests being added/deleted belong to + * + * This function parses the digest list and adds or delete the digests in the + * found digest blocks. + * + * Return: the buffer size if all digests were successfully added or deleted, + * the size of the already parsed buffer on error. + */ +static int _digest_list_parse(loff_t size, void *buf, enum ops op, + struct digest_list_item *digest_list) +{ + void *bufp = buf, *bufendp = buf + size; + struct compact_list_hdr *hdr; + struct digest_item *d = ERR_PTR(-EINVAL); + size_t digest_len; + int i; + + while (bufp < bufendp) { + if (bufp + sizeof(*hdr) > bufendp) + break; + + hdr = bufp; + bufp += sizeof(*hdr); + + digest_len = hash_digest_size[hdr->algo]; + + for (i = 0; i < hdr->count && bufp + digest_len <= bufendp; + i++, bufp += digest_len) { + switch (op) { + case DIGEST_LIST_ADD: + d = digest_add(bufp, hdr->algo, hdr->type, + digest_list, bufp - buf, + (void *)hdr - buf); + if (IS_ERR(d)) { + pr_err( + "failed to add a digest from %s\n", + digest_list->label); + goto out; + } + + break; + case DIGEST_LIST_DEL: + digest_del(bufp, hdr->algo, hdr->type, + digest_list, bufp - buf, + (void *)hdr - buf); + break; + default: + break; + } + } + } +out: + return bufp - buf; +} + +/** + * get_digest_list - get the digest list extracted digests will be associated to + * @size: buffer size + * @buf: buffer containing the digest list + * @op: digest list operation + * @actions: actions performed on the digest list being processed + * @digest: digest of the digest list + * @algo: digest algorithm + * @label: label to identify the digest list (e.g. file name) + * + * This function retrieves the digest list item for the passed digest and + * algorithm. If it is not found at addition time, this function creates a new + * one. + * + * This function prevents the imbalance of digests (references left after + * delete) by ensuring that only digest lists that were previously added can be + * deleted. + * + * This function also ensures that the actions done at the time of addition are + * also performed at the time of deletion (it would guarantee that also deletion + * is notified to remote verifiers). + * + * Return: the retrieved/created digest list item on success, an error pointer + * otherwise. + */ +static struct digest_list_item *get_digest_list(loff_t size, void *buf, + enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, + const char *label) +{ + struct digest_item *d; + struct digest_list_item *digest_list; + int digest_len = hash_digest_size[algo]; + + switch (op) { + case DIGEST_LIST_ADD: + /* Add digest list to be associated to each digest. */ + d = digest_list_add(digest, algo, size, buf, actions, label); + if (IS_ERR(d)) + return (void *)d; + + digest_list = list_first_entry(&d->refs, + struct digest_list_item_ref, list)->digest_list; + break; + case DIGEST_LIST_DEL: + /* Lookup digest list to delete the references. */ + d = __digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, + NULL); + if (!d) { + print_hex_dump(KERN_ERR, + "digest list digest not found: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-ENOENT); + } + + digest_list = list_first_entry(&d->refs, + struct digest_list_item_ref, list)->digest_list; + + /* + * Reject deletion if there are actions done at addition time + * that are currently not being performed. + */ + if ((digest_list->actions & actions) != digest_list->actions) { + pr_err("missing actions, add: %d, del: %d\n", + digest_list->actions, actions); + return ERR_PTR(-EPERM); + } + + break; + default: + return ERR_PTR(-EINVAL); + } + + return digest_list; +} + +/** + * digest_list_parse - parse a digest list + * @size: buffer size + * @buf: buffer containing the digest list + * @op: digest list operation + * @actions: actions performed on the digest list being processed + * @digest: digest of the digest list + * @algo: digest algorithm + * @label: label to identify the digest list (e.g. file name) + * + * This function parses the passed digest list and executed the requested + * operation. If the operation cannot be successfully executed, this function + * performs a rollback to the previous state. + * + * Return: the buffer size on success, a negative value otherwise. + */ +int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions, + u8 *digest, enum hash_algo algo, const char *label) +{ + struct digest_list_item *digest_list; + enum ops rollback_op = (op == DIGEST_LIST_ADD) ? + DIGEST_LIST_DEL : DIGEST_LIST_ADD; + int ret, rollback_size; + + ret = digest_list_validate(size, buf); + if (ret < 0) + return ret; + + digest_list = get_digest_list(size, buf, op, actions, digest, algo, + label); + if (IS_ERR(digest_list)) + return PTR_ERR(digest_list); + + ret = _digest_list_parse(size, buf, op, digest_list); + if (ret < 0) + goto out; + + if (ret != size) { + rollback_size = ret; + + ret = _digest_list_parse(rollback_size, buf, rollback_op, + digest_list); + if (ret != rollback_size) + pr_err("rollback failed\n"); + + ret = -EINVAL; + } +out: + /* Delete digest list on unsuccessful add or successful delete. */ + if ((op == DIGEST_LIST_ADD && ret < 0) || + (op == DIGEST_LIST_DEL && ret == size)) + digest_list_del(digest, algo, actions, digest_list); + + return ret; +}
Em Mon, 26 Jul 2021 18:36:53 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Introduce the necessary functions to parse a digest list and to execute the requested operation.
The main function is digest_list_parse(), which coordinates the various steps required to add or delete a digest list, and has the logic to roll back when one of the steps fails.
A more detailed description about the steps can be found in Documentation/security/diglim/implementation.rst
LGTM.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 35 +++ MAINTAINERS | 1 + security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/diglim.h | 3 + security/integrity/diglim/parser.c | 274 ++++++++++++++++++ 5 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 security/integrity/diglim/parser.c
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 54af23b2f5f1..9d679567a037 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -209,3 +209,38 @@ This section introduces the methods requires to manage the three objects defined. .. kernel-doc:: security/integrity/diglim/methods.c
+Parser +------
+This section introduces the necessary functions to parse a digest list and +to execute the requested operation.
+.. kernel-doc:: security/integrity/diglim/parser.c
+The main function is digest_list_parse(), which coordinates the various +steps required to add or delete a digest list, and has the logic to roll +back when one of the steps fails.
+#. Calls digest_list_validate() to validate the passed buffer containing
- the digest list to ensure that the format is correct.
+#. Calls get_digest_list() to create a new digest_list_item for the add
- operation, or to retrieve the existing one for the delete operation.
- get_digest_list() refuses to add digest lists that were previously
- added and to delete digest lists that weren't previously added. Also,
- get_digest_list() refuses to delete digest lists if there are actions
- done at addition time that are not currently being performed (it would
- guarantee that also deletion is notified to remote verifiers).
+#. Calls _digest_list_parse() which takes the created/retrieved
- struct digest_list_item and adds or delete the digests included in the
- digest list.
+#. If an error occurred, performs a rollback to the previous state, by
- calling _digest_list_parse() with the opposite operation and the buffer
- size at the time the error occurred.
+#. digest_list_parse() deletes the struct digest_list_item on unsuccessful
- add or successful delete.
diff --git a/MAINTAINERS b/MAINTAINERS index 9e085a36654a..77c3613c600a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5465,6 +5465,7 @@ F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h F: security/integrity/diglim/methods.c +F: security/integrity/diglim/parser.c DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile index b761ed8cfb3e..34e4e154fff3 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGLIM) += diglim.o -diglim-y := methods.o +diglim-y := methods.o parser.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 25851e7d4906..3adc218a0325 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -149,4 +149,7 @@ struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, const char *label); void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, struct digest_list_item *digest_list);
+int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions,
u8 *digest, enum hash_algo algo, const char *label);
#endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/parser.c b/security/integrity/diglim/parser.c new file mode 100644 index 000000000000..89a48945b460 --- /dev/null +++ b/security/integrity/diglim/parser.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (C) 2005,2006,2007,2008 IBM Corporation
- Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
- Author: Roberto Sassu roberto.sassu@huawei.com
- Functions to parse digest lists.
- */
+#include <linux/vmalloc.h> +#include <linux/module.h>
+#include "diglim.h" +#include "../integrity.h"
+/**
- digest_list_validate - validate format of digest list
- @size: buffer size
- @buf: buffer containing the digest list
- This function validates the format of the passed digest list.
- Return: 0 if the digest list was successfully validated, -EINVAL otherwise.
- */
+static int digest_list_validate(loff_t size, void *buf) +{
- void *bufp = buf, *bufendp = buf + size;
- struct compact_list_hdr *hdr;
- size_t digest_len;
- while (bufp < bufendp) {
if (bufp + sizeof(*hdr) > bufendp) {
pr_err("invalid data\n");
return -EINVAL;
}
hdr = bufp;
if (hdr->version != 1) {
pr_err("unsupported version\n");
return -EINVAL;
}
if (hdr->_reserved != 0) {
pr_err("unexpected value for _reserved field\n");
return -EINVAL;
}
hdr->type = le16_to_cpu(hdr->type);
hdr->modifiers = le16_to_cpu(hdr->modifiers);
hdr->algo = le16_to_cpu(hdr->algo);
hdr->count = le32_to_cpu(hdr->count);
hdr->datalen = le32_to_cpu(hdr->datalen);
if (hdr->algo >= HASH_ALGO__LAST) {
pr_err("invalid hash algorithm\n");
return -EINVAL;
}
digest_len = hash_digest_size[hdr->algo];
if (hdr->type >= COMPACT__LAST ||
hdr->type == COMPACT_DIGEST_LIST) {
pr_err("invalid type %d\n", hdr->type);
return -EINVAL;
}
bufp += sizeof(*hdr);
if (hdr->datalen != hdr->count * digest_len ||
bufp + hdr->datalen > bufendp) {
pr_err("invalid data\n");
return -EINVAL;
}
bufp += hdr->count * digest_len;
- }
- return 0;
+}
+/**
- _digest_list_parse - parse digest list and add/delete digests
- @size: buffer size
- @buf: buffer containing the digest list
- @op: operation to be performed
- @digest_list: digest list digests being added/deleted belong to
- This function parses the digest list and adds or delete the digests in the
- found digest blocks.
- Return: the buffer size if all digests were successfully added or deleted,
- the size of the already parsed buffer on error.
- */
+static int _digest_list_parse(loff_t size, void *buf, enum ops op,
struct digest_list_item *digest_list)
+{
- void *bufp = buf, *bufendp = buf + size;
- struct compact_list_hdr *hdr;
- struct digest_item *d = ERR_PTR(-EINVAL);
- size_t digest_len;
- int i;
- while (bufp < bufendp) {
if (bufp + sizeof(*hdr) > bufendp)
break;
hdr = bufp;
bufp += sizeof(*hdr);
digest_len = hash_digest_size[hdr->algo];
for (i = 0; i < hdr->count && bufp + digest_len <= bufendp;
i++, bufp += digest_len) {
switch (op) {
case DIGEST_LIST_ADD:
d = digest_add(bufp, hdr->algo, hdr->type,
digest_list, bufp - buf,
(void *)hdr - buf);
if (IS_ERR(d)) {
pr_err(
"failed to add a digest from %s\n",
digest_list->label);
goto out;
}
break;
case DIGEST_LIST_DEL:
digest_del(bufp, hdr->algo, hdr->type,
digest_list, bufp - buf,
(void *)hdr - buf);
break;
default:
break;
}
}
- }
+out:
- return bufp - buf;
+}
+/**
- get_digest_list - get the digest list extracted digests will be associated to
- @size: buffer size
- @buf: buffer containing the digest list
- @op: digest list operation
- @actions: actions performed on the digest list being processed
- @digest: digest of the digest list
- @algo: digest algorithm
- @label: label to identify the digest list (e.g. file name)
- This function retrieves the digest list item for the passed digest and
- algorithm. If it is not found at addition time, this function creates a new
- one.
- This function prevents the imbalance of digests (references left after
- delete) by ensuring that only digest lists that were previously added can be
- deleted.
- This function also ensures that the actions done at the time of addition are
- also performed at the time of deletion (it would guarantee that also deletion
- is notified to remote verifiers).
- Return: the retrieved/created digest list item on success, an error pointer
- otherwise.
- */
+static struct digest_list_item *get_digest_list(loff_t size, void *buf,
enum ops op, u8 actions,
u8 *digest, enum hash_algo algo,
const char *label)
+{
- struct digest_item *d;
- struct digest_list_item *digest_list;
- int digest_len = hash_digest_size[algo];
- switch (op) {
- case DIGEST_LIST_ADD:
/* Add digest list to be associated to each digest. */
d = digest_list_add(digest, algo, size, buf, actions, label);
if (IS_ERR(d))
return (void *)d;
digest_list = list_first_entry(&d->refs,
struct digest_list_item_ref, list)->digest_list;
break;
- case DIGEST_LIST_DEL:
/* Lookup digest list to delete the references. */
d = __digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL,
NULL);
if (!d) {
print_hex_dump(KERN_ERR,
"digest list digest not found: ",
DUMP_PREFIX_NONE, digest_len, 1, digest,
digest_len, true);
return ERR_PTR(-ENOENT);
}
digest_list = list_first_entry(&d->refs,
struct digest_list_item_ref, list)->digest_list;
/*
* Reject deletion if there are actions done at addition time
* that are currently not being performed.
*/
if ((digest_list->actions & actions) != digest_list->actions) {
pr_err("missing actions, add: %d, del: %d\n",
digest_list->actions, actions);
return ERR_PTR(-EPERM);
}
break;
- default:
return ERR_PTR(-EINVAL);
- }
- return digest_list;
+}
+/**
- digest_list_parse - parse a digest list
- @size: buffer size
- @buf: buffer containing the digest list
- @op: digest list operation
- @actions: actions performed on the digest list being processed
- @digest: digest of the digest list
- @algo: digest algorithm
- @label: label to identify the digest list (e.g. file name)
- This function parses the passed digest list and executed the requested
- operation. If the operation cannot be successfully executed, this function
- performs a rollback to the previous state.
- Return: the buffer size on success, a negative value otherwise.
- */
+int digest_list_parse(loff_t size, void *buf, enum ops op, u8 actions,
u8 *digest, enum hash_algo algo, const char *label)
+{
- struct digest_list_item *digest_list;
- enum ops rollback_op = (op == DIGEST_LIST_ADD) ?
DIGEST_LIST_DEL : DIGEST_LIST_ADD;
- int ret, rollback_size;
- ret = digest_list_validate(size, buf);
- if (ret < 0)
return ret;
- digest_list = get_digest_list(size, buf, op, actions, digest, algo,
label);
- if (IS_ERR(digest_list))
return PTR_ERR(digest_list);
- ret = _digest_list_parse(size, buf, op, digest_list);
- if (ret < 0)
goto out;
- if (ret != size) {
rollback_size = ret;
ret = _digest_list_parse(rollback_size, buf, rollback_op,
digest_list);
if (ret != rollback_size)
pr_err("rollback failed\n");
ret = -EINVAL;
- }
+out:
- /* Delete digest list on unsuccessful add or successful delete. */
- if ((op == DIGEST_LIST_ADD && ret < 0) ||
(op == DIGEST_LIST_DEL && ret == size))
digest_list_del(digest, algo, actions, digest_list);
- return ret;
+}
Introduce <securityfs>/integrity/diglim/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.
Also introduce <securityfs>/integrity/diglim/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 --- .../security/diglim/implementation.rst | 9 + MAINTAINERS | 1 + include/linux/kernel_read_file.h | 1 + security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/diglim.h | 2 + security/integrity/diglim/fs.c | 239 ++++++++++++++++++ security/integrity/integrity.h | 4 + 7 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 security/integrity/diglim/fs.c
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 9d679567a037..fc0cd8810a80 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -244,3 +244,12 @@ back when one of the steps fails.
#. digest_list_parse() deletes the struct digest_list_item on unsuccessful add or successful delete. + + +Interfaces +---------- + +This section introduces the interfaces in +``<securityfs>/integrity/diglim`` necessary to interact with DIGLIM. + +.. kernel-doc:: security/integrity/diglim/fs.c diff --git a/MAINTAINERS b/MAINTAINERS index 77c3613c600a..0672128fae7f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5464,6 +5464,7 @@ F: Documentation/security/diglim/introduction.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h +F: security/integrity/diglim/fs.c F: security/integrity/diglim/methods.c F: security/integrity/diglim/parser.c
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/diglim/Makefile b/security/integrity/diglim/Makefile index 34e4e154fff3..ac345afdf5dd 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@
obj-$(CONFIG_DIGLIM) += diglim.o
-diglim-y := methods.o parser.o +diglim-y := methods.o parser.o fs.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 3adc218a0325..819703175eda 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -22,6 +22,8 @@ #include <linux/hash_info.h> #include <linux/diglim.h>
+#include "../integrity.h" + #define MAX_DIGEST_SIZE 64 #define HASH_BITS 10 #define DIGLIM_HTABLE_SIZE (1 << HASH_BITS) diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c new file mode 100644 index 000000000000..07a0d75a0e33 --- /dev/null +++ b/security/integrity/diglim/fs.c @@ -0,0 +1,239 @@ +// 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 + * + * 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 "diglim.h" + +#define MAX_DIGEST_LIST_SIZE (64 * 1024 * 1024 - 1) + +static struct dentry *diglim_dir; +/** + * DOC: 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. + */ +static struct dentry *digest_list_add_dentry; +/** + * DOC: 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. + */ +static struct dentry *digest_list_del_dentry; +char digest_label[NAME_MAX + 1]; + +/* + * digest_list_read: read and parse the digest list from the path + */ +static ssize_t digest_list_read(char *path, enum ops op) +{ + void *data = NULL; + char *datap; + size_t size; + u8 actions = 0; + struct file *file; + char event_name[NAME_MAX + 9 + 1]; + u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 }; + enum hash_algo algo; + 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; + + snprintf(event_name, sizeof(event_name), "%s_file_%s", + op == DIGEST_LIST_ADD ? "add" : "del", + file_dentry(file)->d_name.name); + + rc = ima_measure_critical_data("diglim", event_name, data, size, false, + digest, sizeof(digest)); + if (rc < 0 && rc != -EEXIST) + goto out_vfree; + + algo = ima_get_current_hash_algo(); + + if (!rc || rc == -EEXIST) + actions |= (1 << 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; +} + +/* + * digest_list_write: write the digest list path or the digest list itself + */ +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]; + char event_name[NAME_MAX + 11 + 1]; + enum hash_algo algo; + u8 actions = 0; + + /* No partial writes. */ + result = -EINVAL; + if (*ppos != 0) + goto out; + + result = -EFBIG; + if (datalen > MAX_DIGEST_LIST_SIZE) + 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 { + snprintf(event_name, sizeof(event_name), "%s_buffer_%s", + op == DIGEST_LIST_ADD ? "add" : "del", digest_label); + + result = ima_measure_critical_data("diglim", event_name, data, + datalen, false, digest, + sizeof(digest)); + if (result < 0 && result != -EEXIST) { + pr_err("failed to measure buffer digest (%ld)\n", + result); + goto out_kfree; + } + + algo = ima_get_current_hash_algo(); + + if (!result || result == -EEXIST) + actions |= (1 << 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, +}; + +static int __init diglim_fs_init(void) +{ + diglim_dir = securityfs_create_dir("diglim", integrity_dir); + if (IS_ERR(diglim_dir)) + return -1; + + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, + diglim_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, + diglim_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(diglim_dir); + return -1; +} + +late_initcall(diglim_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*/
Em Mon, 26 Jul 2021 18:36:54 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Introduce <securityfs>/integrity/diglim/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.
Also introduce <securityfs>/integrity/diglim/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.
LGTM.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
.../security/diglim/implementation.rst | 9 + MAINTAINERS | 1 + include/linux/kernel_read_file.h | 1 + security/integrity/diglim/Makefile | 2 +- security/integrity/diglim/diglim.h | 2 + security/integrity/diglim/fs.c | 239 ++++++++++++++++++ security/integrity/integrity.h | 4 + 7 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 security/integrity/diglim/fs.c
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 9d679567a037..fc0cd8810a80 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -244,3 +244,12 @@ back when one of the steps fails. #. digest_list_parse() deletes the struct digest_list_item on unsuccessful add or successful delete.
+Interfaces +----------
+This section introduces the interfaces in +``<securityfs>/integrity/diglim`` necessary to interact with DIGLIM.
+.. kernel-doc:: security/integrity/diglim/fs.c diff --git a/MAINTAINERS b/MAINTAINERS index 77c3613c600a..0672128fae7f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5464,6 +5464,7 @@ F: Documentation/security/diglim/introduction.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h +F: security/integrity/diglim/fs.c F: security/integrity/diglim/methods.c F: security/integrity/diglim/parser.c 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/diglim/Makefile b/security/integrity/diglim/Makefile index 34e4e154fff3..ac345afdf5dd 100644 --- a/security/integrity/diglim/Makefile +++ b/security/integrity/diglim/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGLIM) += diglim.o -diglim-y := methods.o parser.o +diglim-y := methods.o parser.o fs.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 3adc218a0325..819703175eda 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -22,6 +22,8 @@ #include <linux/hash_info.h> #include <linux/diglim.h> +#include "../integrity.h"
#define MAX_DIGEST_SIZE 64 #define HASH_BITS 10 #define DIGLIM_HTABLE_SIZE (1 << HASH_BITS) diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c new file mode 100644 index 000000000000..07a0d75a0e33 --- /dev/null +++ b/security/integrity/diglim/fs.c @@ -0,0 +1,239 @@ +// 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
- 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 "diglim.h"
+#define MAX_DIGEST_LIST_SIZE (64 * 1024 * 1024 - 1)
+static struct dentry *diglim_dir; +/**
- DOC: 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.
- */
+static struct dentry *digest_list_add_dentry; +/**
- DOC: 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.
- */
+static struct dentry *digest_list_del_dentry; +char digest_label[NAME_MAX + 1];
+/*
- digest_list_read: read and parse the digest list from the path
- */
+static ssize_t digest_list_read(char *path, enum ops op) +{
- void *data = NULL;
- char *datap;
- size_t size;
- u8 actions = 0;
- struct file *file;
- char event_name[NAME_MAX + 9 + 1];
- u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
- enum hash_algo algo;
- 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;
- snprintf(event_name, sizeof(event_name), "%s_file_%s",
op == DIGEST_LIST_ADD ? "add" : "del",
file_dentry(file)->d_name.name);
- rc = ima_measure_critical_data("diglim", event_name, data, size, false,
digest, sizeof(digest));
- if (rc < 0 && rc != -EEXIST)
goto out_vfree;
- algo = ima_get_current_hash_algo();
- if (!rc || rc == -EEXIST)
actions |= (1 << 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;
+}
+/*
- digest_list_write: write the digest list path or the digest list itself
- */
+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];
- char event_name[NAME_MAX + 11 + 1];
- enum hash_algo algo;
- u8 actions = 0;
- /* No partial writes. */
- result = -EINVAL;
- if (*ppos != 0)
goto out;
- result = -EFBIG;
- if (datalen > MAX_DIGEST_LIST_SIZE)
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 {
snprintf(event_name, sizeof(event_name), "%s_buffer_%s",
op == DIGEST_LIST_ADD ? "add" : "del", digest_label);
result = ima_measure_critical_data("diglim", event_name, data,
datalen, false, digest,
sizeof(digest));
if (result < 0 && result != -EEXIST) {
pr_err("failed to measure buffer digest (%ld)\n",
result);
goto out_kfree;
}
algo = ima_get_current_hash_algo();
if (!result || result == -EEXIST)
actions |= (1 << 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,
+};
+static int __init diglim_fs_init(void) +{
- diglim_dir = securityfs_create_dir("diglim", integrity_dir);
- if (IS_ERR(diglim_dir))
return -1;
- digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200,
diglim_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,
diglim_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(diglim_dir);
- return -1;
+}
+late_initcall(diglim_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*/
Hi Roberto,
On Mon, 2021-07-26 at 18:36 +0200, Roberto Sassu wrote:
/*
- digest_list_read: read and parse the digest list from the path
- */
+static ssize_t digest_list_read(char *path, enum ops op) +{
void *data = NULL;
char *datap;
size_t size;
u8 actions = 0;
struct file *file;
char event_name[NAME_MAX + 9 + 1];
u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
enum hash_algo algo;
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;
snprintf(event_name, sizeof(event_name), "%s_file_%s",
op == DIGEST_LIST_ADD ? "add" : "del",
file_dentry(file)->d_name.name);
rc = ima_measure_critical_data("diglim", event_name, data, size, false,
digest, sizeof(digest));
if (rc < 0 && rc != -EEXIST)
goto out_vfree;
The digest lists could easily be measured while reading the digest list file above in kernel_read_file(). What makes it "critical-data"? In the SELinux case, the in memory SELinux policy is being measured and re-measured to make sure it hasn't been modified. Is the digest list file data being measured more than once?
I understand that with your changes to ima_measure_critical_data(), which are now in next-integrity-testing branch, allow IMA to calculate the file data hash.
thanks,
Mimi
algo = ima_get_current_hash_algo();
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Thursday, July 29, 2021 11:21 PM Hi Roberto,
On Mon, 2021-07-26 at 18:36 +0200, Roberto Sassu wrote:
/*
- digest_list_read: read and parse the digest list from the path
- */
+static ssize_t digest_list_read(char *path, enum ops op) +{
void *data = NULL;
char *datap;
size_t size;
u8 actions = 0;
struct file *file;
char event_name[NAME_MAX + 9 + 1];
u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
enum hash_algo algo;
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;
snprintf(event_name, sizeof(event_name), "%s_file_%s",
op == DIGEST_LIST_ADD ? "add" : "del",
file_dentry(file)->d_name.name);
rc = ima_measure_critical_data("diglim", event_name, data, size, false,
digest, sizeof(digest));
if (rc < 0 && rc != -EEXIST)
goto out_vfree;
The digest lists could easily be measured while reading the digest list file above in kernel_read_file(). What makes it "critical-data"? In the SELinux case, the in memory SELinux policy is being measured and re-measured to make sure it hasn't been modified. Is the digest list file data being measured more than once?
Hi Mimi
yes, the digest lists can be measured with kernel_read_file(). I didn't send the change yet, but I added a DIGEST_LIST_CHECK hook mapped to READING_DIGEST_LIST, so that digest lists can be easily measured or appraised.
The point was that the digest of the digest list must be always calculated, as it is added to the hash table. Instead of duplicating the code, I preferred to use ima_measure_critical_data().
The advantage is also that, if the use case is to just measure digest lists, ima_measure_critical_data() could do both at the same time.
Digest lists can be seen as "critical data" in the sense that they can affect the security decision on whether to grant access to a file or not, assuming that an appropriate rule is added in the IMA policy.
I understand that with your changes to ima_measure_critical_data(), which are now in next-integrity-testing branch, allow IMA to calculate the file data hash.
Yes, correct. But actually there is another useful use case. If digest lists are not in the format supported by the kernel, the user space parser has to convert them before uploading them to the kernel.
ima_measure_critical_data() would in this case measure the converted digest list (it is written directly, without sending the file path). It is easier to attest the result, instead of determining whether the user space parser produced the expected result (by checking the files it read).
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
Mimi
algo = ima_get_current_hash_algo();
Hi Roberto,
On Fri, 2021-07-30 at 07:16 +0000, Roberto Sassu wrote:
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Thursday, July 29, 2021 11:21 PM
On Mon, 2021-07-26 at 18:36 +0200, Roberto Sassu wrote:
/*
- digest_list_read: read and parse the digest list from the path
- */
+static ssize_t digest_list_read(char *path, enum ops op) +{
void *data = NULL;
char *datap;
size_t size;
u8 actions = 0;
struct file *file;
char event_name[NAME_MAX + 9 + 1];
u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
enum hash_algo algo;
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;
snprintf(event_name, sizeof(event_name), "%s_file_%s",
op == DIGEST_LIST_ADD ? "add" : "del",
file_dentry(file)->d_name.name);
rc = ima_measure_critical_data("diglim", event_name, data, size, false,
digest, sizeof(digest));
if (rc < 0 && rc != -EEXIST)
goto out_vfree;
The digest lists could easily be measured while reading the digest list file above in kernel_read_file(). What makes it "critical-data"? In the SELinux case, the in memory SELinux policy is being measured and re-measured to make sure it hasn't been modified. Is the digest list file data being measured more than once?
Hi Mimi
yes, the digest lists can be measured with kernel_read_file(). I didn't send the change yet, but I added a DIGEST_LIST_CHECK hook mapped to READING_DIGEST_LIST, so that digest lists can be easily measured or appraised.
The point was that the digest of the digest list must be always calculated, as it is added to the hash table. Instead of duplicating the code, I preferred to use ima_measure_critical_data().
The advantage is also that, if the use case is to just measure digest lists, ima_measure_critical_data() could do both at the same time.
Digest lists can be seen as "critical data" in the sense that they can affect the security decision on whether to grant access to a file or not, assuming that an appropriate rule is added in the IMA policy.
Of course the integrity of files containing the digest lists is important, but that doesn't make them "critical data". If the integrity of these files is important, then the digest lists not only need to be measured, but they need to be signed and the resulting signature verified. Without signature verification, there is no basis on which to trust the digest lists data.
Adding the kernel_read_file() "READING_DIGEST_LIST" support in IMA does not seem to be optional. IMA would then be calculating the digest list file hash twice, once in kernel_read_file() and then, again, in ima_measure_critical_data().
I understand that with your changes to ima_measure_critical_data(), which are now in next-integrity-testing branch, allow IMA to calculate the file data hash.
Yes, correct. But actually there is another useful use case. If digest lists are not in the format supported by the kernel, the user space parser has to convert them before uploading them to the kernel.
ima_measure_critical_data() would in this case measure the converted digest list (it is written directly, without sending the file path). It is easier to attest the result, instead of determining whether the user space parser produced the expected result (by checking the files it read).
The application to properly convert the digest list file data into the appropriate format would need to be trusted. I'm concerned that not requiring the converted data to be signed and the signature verified is introducing a new integrity gap. Perhaps between an LSM policy, limiting which files may be read by the application, and an IMA policy, requiring all files read by this application to be measured and the signature verified, this integrity gap could be averted.
"critical data", in this context, should probably be used for verifying the in memory file digests and other state information haven't been compromised.
thanks,
Mimi
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 2:40 PM Hi Roberto,
On Fri, 2021-07-30 at 07:16 +0000, Roberto Sassu wrote:
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Thursday, July 29, 2021 11:21 PM
On Mon, 2021-07-26 at 18:36 +0200, Roberto Sassu wrote:
/*
- digest_list_read: read and parse the digest list from the path
- */
+static ssize_t digest_list_read(char *path, enum ops op) +{
void *data = NULL;
char *datap;
size_t size;
u8 actions = 0;
struct file *file;
char event_name[NAME_MAX + 9 + 1];
u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
enum hash_algo algo;
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;
snprintf(event_name, sizeof(event_name), "%s_file_%s",
op == DIGEST_LIST_ADD ? "add" : "del",
file_dentry(file)->d_name.name);
rc = ima_measure_critical_data("diglim", event_name, data, size,
false,
digest, sizeof(digest));
if (rc < 0 && rc != -EEXIST)
goto out_vfree;
The digest lists could easily be measured while reading the digest list file above in kernel_read_file(). What makes it "critical-data"? In the SELinux case, the in memory SELinux policy is being measured and re-measured to make sure it hasn't been modified. Is the digest list file data being measured more than once?
Hi Mimi
yes, the digest lists can be measured with kernel_read_file(). I didn't send the change yet, but I added a DIGEST_LIST_CHECK hook mapped to READING_DIGEST_LIST, so that digest lists can be easily measured or appraised.
The point was that the digest of the digest list must be always calculated, as it is added to the hash table. Instead of duplicating the code, I preferred to use ima_measure_critical_data().
The advantage is also that, if the use case is to just measure digest lists, ima_measure_critical_data() could do both at the same time.
Digest lists can be seen as "critical data" in the sense that they can affect the security decision on whether to grant access to a file or not, assuming that an appropriate rule is added in the IMA policy.
Of course the integrity of files containing the digest lists is important, but that doesn't make them "critical data". If the integrity of these files is important, then the digest lists not only need to be measured, but they need to be signed and the resulting signature verified. Without signature verification, there is no basis on which to trust the digest lists data.
The reason of storing the actions performed by IMA on the digest lists helps to determine for which purpose they can be used. If digest lists are used only for measurement purpose, it should be sufficient that digest lists are measured. The same applies for appraisal.
Adding the kernel_read_file() "READING_DIGEST_LIST" support in IMA does not seem to be optional. IMA would then be calculating the digest list file hash twice, once in kernel_read_file() and then, again, in ima_measure_critical_data().
I didn't include also this part: I retrieve the integrity_iint_cache for the opened file descriptor and I get the flags from there. If the IMA_MEASURED flag is set, it is not necessary to call also ima_measure_critical_data().
I understand that with your changes to ima_measure_critical_data(), which are now in next-integrity-testing branch, allow IMA to calculate the file data hash.
Yes, correct. But actually there is another useful use case. If digest lists are not in the format supported by the kernel, the user space parser has to convert them before uploading them to the kernel.
ima_measure_critical_data() would in this case measure the converted digest list (it is written directly, without sending the file path). It is easier to attest the result, instead of determining whether the user space parser produced the expected result (by checking the files it read).
The application to properly convert the digest list file data into the appropriate format would need to be trusted. I'm concerned that not requiring the converted data to be signed and the signature verified is introducing a new integrity gap. Perhaps between an LSM policy, limiting which files may be read by the application, and an IMA policy, requiring all files read by this application to be measured and the signature verified, this integrity gap could be averted.
It is the weakest point in the chain, yes. Relying on existing LSMs didn't seem to me a good idea, as: - a new policy must be installed - we must be sure that the policy is really enforced - we need to support different LSMs (SELinux for Fedora, Apparmor for SUSE) - there might be no LSM we can rely on
For these reasons, I developed a new LSM. Its purpose is to identify the user space parser and for each file it opens, ensure that the file has been measured or appraised by IMA. If one of these actions are missing, it will not be set in the digest list the user space parser uploads to the kernel (which means that IMA will ignore the digest list for that specific action).
"critical data", in this context, should probably be used for verifying the in memory file digests and other state information haven't been compromised.
Actually, this is what we are doing currently. To keep the implementation simple, once the file or the buffer are uploaded to the kernel, they will not be modified, just accessed through the indexes.
I could send the second part of the patch set, so that it becomes more clear how digest lists are used by IMA and how the integrity gap is filled.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
Mimi
Hi Roberto,
On Fri, 2021-07-30 at 13:16 +0000, Roberto Sassu wrote:
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 2:40 PM
"critical data", in this context, should probably be used for verifying the in memory file digests and other state information haven't been compromised.
Actually, this is what we are doing currently. To keep the implementation simple, once the file or the buffer are uploaded to the kernel, they will not be modified, just accessed through the indexes.
My main concern about digest lists is their integrity, from loading the digest lists to their being stored in memory. A while back, there was some work on defining a write once memory allocator. I don't recall whatever happened to it. This would be a perfect usecase for that memory allocator.
thanks,
Mimi
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 4:03 PM Hi Roberto,
On Fri, 2021-07-30 at 13:16 +0000, Roberto Sassu wrote:
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 2:40 PM
"critical data", in this context, should probably be used for verifying the in memory file digests and other state information haven't been compromised.
Actually, this is what we are doing currently. To keep the implementation simple, once the file or the buffer are uploaded to the kernel, they will not be modified, just accessed through the indexes.
My main concern about digest lists is their integrity, from loading the digest lists to their being stored in memory. A while back, there was some work on defining a write once memory allocator. I don't recall whatever happened to it. This would be a perfect usecase for that memory allocator.
Adding Igor in CC.
Regarding loading, everything uploaded to the kernel is carefully evaluated. This should not be a concern. Regarding making them read-only, probably if you can subvert digest lists you can also remove the read-only protection (unless you use an hypervisor).
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
Mimi
From: Roberto Sassu [mailto:roberto.sassu@huawei.com] Sent: Friday, July 30, 2021 4:25 PM
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 4:03 PM Hi Roberto,
On Fri, 2021-07-30 at 13:16 +0000, Roberto Sassu wrote:
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 2:40 PM
"critical data", in this context, should probably be used for verifying the in memory file digests and other state information haven't been compromised.
Actually, this is what we are doing currently. To keep the implementation simple, once the file or the buffer are uploaded to the kernel, they will not be modified, just accessed through the indexes.
My main concern about digest lists is their integrity, from loading the digest lists to their being stored in memory. A while back, there was some work on defining a write once memory allocator. I don't recall whatever happened to it. This would be a perfect usecase for that memory allocator.
Adding Igor in CC.
Regarding loading, everything uploaded to the kernel is carefully evaluated. This should not be a concern. Regarding making them read-only, probably if you can subvert digest lists you can also remove the read-only protection (unless you use an hypervisor).
I briefly talked with Igor. He also agreed with that, and added that it could make it more difficult for an attacker to also disable the protection. However, he is not planning to submit an update soon, so I wouldn't consider this an option for now.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
Mimi
On Mon, 2021-08-02 at 08:14 +0000, Roberto Sassu wrote:
From: Roberto Sassu [mailto:roberto.sassu@huawei.com] Sent: Friday, July 30, 2021 4:25 PM
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 4:03 PM Hi Roberto,
On Fri, 2021-07-30 at 13:16 +0000, Roberto Sassu wrote:
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Friday, July 30, 2021 2:40 PM
"critical data", in this context, should probably be used for verifying the in memory file digests and other state information haven't been compromised.
Actually, this is what we are doing currently. To keep the implementation simple, once the file or the buffer are uploaded to the kernel, they will not be modified, just accessed through the indexes.
My main concern about digest lists is their integrity, from loading the digest lists to their being stored in memory. A while back, there was some work on defining a write once memory allocator. I don't recall whatever happened to it. This would be a perfect usecase for that memory allocator.
Adding Igor in CC.
Regarding loading, everything uploaded to the kernel is carefully evaluated. This should not be a concern. Regarding making them read-only, probably if you can subvert digest lists you can also remove the read-only protection (unless you use an hypervisor).
I briefly talked with Igor. He also agreed with that, and added that it could make it more difficult for an attacker to also disable the protection. However, he is not planning to submit an update soon, so I wouldn't consider this an option for now.
Hi Roberto, Greg,
As long as others understand and agree to the risk, the IMA details can be worked out.
thanks,
Mimi
Hi Roberto,
On Fri, 2021-07-30 at 13:16 +0000, Roberto Sassu wrote:
The reason of storing the actions performed by IMA on the digest lists helps to determine for which purpose they can be used. If digest lists are used only for measurement purpose, it should be sufficient that digest lists are measured. The same applies for appraisal.
Is that assumption correct? How would you know if the digests lists are only being used one way and not the other. For example, assuming that the digest lists are stored on protected media, the digest lists could be measured, but would not necessarily be appraised.
Adding the kernel_read_file() "READING_DIGEST_LIST" support in IMA does not seem to be optional. IMA would then be calculating the digest list file hash twice, once in kernel_read_file() and then, again, in ima_measure_critical_data().
I didn't include also this part: I retrieve the integrity_iint_cache for the opened file descriptor and I get the flags from there. If the IMA_MEASURED flag is set, it is not necessary to call also ima_measure_critical_data().
Right, assuming the file is in policy, the digest would already be stored in the iint cache.
I understand that with your changes to ima_measure_critical_data(), which are now in next-integrity-testing branch, allow IMA to calculate the file data hash.
Yes, correct. But actually there is another useful use case. If digest lists are not in the format supported by the kernel, the user space parser has to convert them before uploading them to the kernel.
ima_measure_critical_data() would in this case measure the converted digest list (it is written directly, without sending the file path). It is easier to attest the result, instead of determining whether the user space parser produced the expected result (by checking the files it read).
The application to properly convert the digest list file data into the appropriate format would need to be trusted. I'm concerned that not requiring the converted data to be signed and the signature verified is introducing a new integrity gap. Perhaps between an LSM policy, limiting which files may be read by the application, and an IMA policy, requiring all files read by this application to be measured and the signature verified, this integrity gap could be averted.
It is the weakest point in the chain, yes. Relying on existing LSMs didn't seem to me a good idea, as:
- a new policy must be installed
- we must be sure that the policy is really enforced
- we need to support different LSMs (SELinux for Fedora, Apparmor for SUSE)
- there might be no LSM we can rely on
For these reasons, I developed a new LSM. Its purpose is to identify the user space parser and for each file it opens, ensure that the file has been measured or appraised by IMA. If one of these actions are missing, it will not be set in the digest list the user space parser uploads to the kernel (which means that IMA will ignore the digest list for that specific action).
Properly identifying (all) user space parser(s) would be critical. It would be simpler and safer to require the converted data be signed.
thanks,
Mimi
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Monday, August 2, 2021 4:42 PM Hi Roberto,
On Fri, 2021-07-30 at 13:16 +0000, Roberto Sassu wrote:
The reason of storing the actions performed by IMA on the digest lists helps to determine for which purpose they can be used. If digest lists are used only for measurement purpose, it should be sufficient that digest lists are measured. The same applies for appraisal.
Is that assumption correct? How would you know if the digests lists are only being used one way and not the other. For example, assuming that the digest lists are stored on protected media, the digest lists could be measured, but would not necessarily be appraised.
Hi Mimi
the actions performed by IMA on the digest lists are recorded in the digest_list_item structure. These can be retrieved when IMA calls diglim_digest_get_info() (actually it is the OR of the actions for the digest lists that contain the digest passed as a query).
At the moment, DIGLIM can only know whether a digest list has been measured or not (with the return value of ima_measure_critical_data()). In the next patch set, I add the changes to get the actions from the integrity_iint_cache().
Adding the kernel_read_file() "READING_DIGEST_LIST" support in IMA
does
not seem to be optional. IMA would then be calculating the digest list file hash twice, once in kernel_read_file() and then, again, in ima_measure_critical_data().
I didn't include also this part: I retrieve the integrity_iint_cache for the opened file descriptor and I get the flags from there. If the IMA_MEASURED flag is set, it is not necessary to call also ima_measure_critical_data().
Right, assuming the file is in policy, the digest would already be stored in the iint cache.
I understand that with your changes to ima_measure_critical_data(), which are now in next-integrity-testing branch, allow IMA to calculate the file data hash.
Yes, correct. But actually there is another useful use case. If digest lists are not in the format supported by the kernel, the user space parser has to convert them before uploading them to the kernel.
ima_measure_critical_data() would in this case measure the converted digest list (it is written directly, without sending the file path). It is easier to attest the result, instead of determining whether the user space parser produced the expected result (by checking the files it read).
The application to properly convert the digest list file data into the appropriate format would need to be trusted. I'm concerned that not requiring the converted data to be signed and the signature verified is introducing a new integrity gap. Perhaps between an LSM policy, limiting which files may be read by the application, and an IMA policy, requiring all files read by this application to be measured and the signature verified, this integrity gap could be averted.
It is the weakest point in the chain, yes. Relying on existing LSMs didn't seem to me a good idea, as:
- a new policy must be installed
- we must be sure that the policy is really enforced
- we need to support different LSMs (SELinux for Fedora, Apparmor for SUSE)
- there might be no LSM we can rely on
For these reasons, I developed a new LSM. Its purpose is to identify the user space parser and for each file it opens, ensure that the file has been measured or appraised by IMA. If one of these actions are missing, it will not be set in the digest list the user space parser uploads to the kernel (which means that IMA will ignore the digest list for that specific action).
Properly identifying (all) user space parser(s) would be critical. It would be simpler and safer to require the converted data be signed.
I agree, it would be much easier. However, it would require changes to the building infrastructure of Linux distribution vendors, which might limit the applicability of DIGLIM.
With the user space parser taking care of the conversion, distributions can do appraisal of executables and shared libraries with an update of: - the kernel: to add DIGLIM - dracut: to add required digest lists in the initial ram disk - rpm (plugin): to extract the RPM header and its signature and write them to a file that is uploaded to the kernel by the user space parser
I'm planning to append the signature at the end of the RPM header (and use appraise_type=modsig) to avoid the dependency on the 'initramfs: add support for xattrs in the initial ram disk' patch set (which I might try to resume in the future).
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
Mimi
From: Roberto Sassu Sent: Monday, August 2, 2021 5:13 PM
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Monday, August 2, 2021 4:42 PM Hi Roberto,
On Fri, 2021-07-30 at 13:16 +0000, Roberto Sassu wrote:
The reason of storing the actions performed by IMA on the digest lists helps to determine for which purpose they can be used. If digest lists are used only for measurement purpose, it should be sufficient that digest lists are measured. The same applies for appraisal.
Is that assumption correct? How would you know if the digests lists are only being used one way and not the other. For example, assuming that the digest lists are stored on protected media, the digest lists could be measured, but would not necessarily be appraised.
Hi Mimi
the actions performed by IMA on the digest lists are recorded in the digest_list_item structure. These can be retrieved when IMA calls diglim_digest_get_info() (actually it is the OR of the actions for the digest lists that contain the digest passed as a query).
At the moment, DIGLIM can only know whether a digest list has been measured or not (with the return value of ima_measure_critical_data()). In the next patch set, I add the changes to get the actions from the integrity_iint_cache().
Adding the kernel_read_file() "READING_DIGEST_LIST" support in IMA
does
not seem to be optional. IMA would then be calculating the digest list file hash twice, once in kernel_read_file() and then, again, in ima_measure_critical_data().
I didn't include also this part: I retrieve the integrity_iint_cache for the opened file descriptor and I get the flags from there. If the IMA_MEASURED flag is set, it is not necessary to call also ima_measure_critical_data().
Right, assuming the file is in policy, the digest would already be stored in the iint cache.
I understand that with your changes to ima_measure_critical_data(), which are now in next-integrity-testing branch, allow IMA to calculate the file data hash.
Yes, correct. But actually there is another useful use case. If digest lists are not in the format supported by the kernel, the user space parser has to convert them before uploading them to the kernel.
ima_measure_critical_data() would in this case measure the converted digest list (it is written directly, without sending the file path). It is easier to attest the result, instead of determining whether the user space parser produced the expected result (by checking the files it read).
The application to properly convert the digest list file data into the appropriate format would need to be trusted. I'm concerned that not requiring the converted data to be signed and the signature verified is introducing a new integrity gap. Perhaps between an LSM policy, limiting which files may be read by the application, and an IMA policy, requiring all files read by this application to be measured and the signature verified, this integrity gap could be averted.
It is the weakest point in the chain, yes. Relying on existing LSMs didn't seem to me a good idea, as:
- a new policy must be installed
- we must be sure that the policy is really enforced
- we need to support different LSMs (SELinux for Fedora, Apparmor for SUSE)
- there might be no LSM we can rely on
For these reasons, I developed a new LSM. Its purpose is to identify the user space parser and for each file it opens, ensure that the file has been measured or appraised by IMA. If one of these actions are missing, it will not be set in the digest list the user space parser uploads to the kernel (which means that IMA will ignore the digest list for that specific action).
Properly identifying (all) user space parser(s) would be critical. It would be simpler and safer to require the converted data be signed.
When a process directly uploads a buffer to the kernel, the actions are added to a digest list depending on the result of ima_measure_critical_data() and from the actions attached to the process credentials and set by the new LSM.
If a process fails the identification, the actions in the process credentials remain zero and the digest lists the process uploads will be ignored by IMA.
The actions in the process credentials are set with the actions performed on the executable by IMA, only if the digest of the executable is found in a digest list and the digest list type is COMPACT_PARSER. The parser is statically linked.
The digest list for the parser can be generated at the end of the building process and signed similarly to kernel modules (for SUSE, with pesign-obs-integration). This is the only exception to handle, other packages are not affected.
After the parser has been identified, each file operation is monitored. The LSM has to explicitly perform a second open to ensure that the file is measured/appraised before the integrity_iint_cache structure is retrieved (because IMA is called after all LSMs).
If an action is missing from the integrity_iint_cache structure, it will be cleared by the LSM in the actions attached to the process credentials, and will not be added to the digest list being uploaded.
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
I agree, it would be much easier. However, it would require changes to the building infrastructure of Linux distribution vendors, which might limit the applicability of DIGLIM.
With the user space parser taking care of the conversion, distributions can do appraisal of executables and shared libraries with an update of:
- the kernel: to add DIGLIM
- dracut: to add required digest lists in the initial ram disk
- rpm (plugin): to extract the RPM header and its signature and write them to a file that is uploaded to the kernel by the user space parser
I'm planning to append the signature at the end of the RPM header (and use appraise_type=modsig) to avoid the dependency on the 'initramfs: add support for xattrs in the initial ram disk' patch set (which I might try to resume in the future).
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
thanks,
Mimi
[Cc'ing Eric Snowberg]
Hi Roberto,
On Mon, 2021-08-02 at 16:54 +0000, Roberto Sassu wrote:
Properly identifying (all) user space parser(s) would be critical. It would be simpler and safer to require the converted data be signed.
When a process directly uploads a buffer to the kernel, the actions are added to a digest list depending on the result of ima_measure_critical_data() and from the actions attached to the process credentials and set by the new LSM.
If a process fails the identification, the actions in the process credentials remain zero and the digest lists the process uploads will be ignored by IMA.
The actions in the process credentials are set with the actions performed on the executable by IMA, only if the digest of the executable is found in a digest list and the digest list type is COMPACT_PARSER. The parser is statically linked.
The digest list for the parser can be generated at the end of the building process and signed similarly to kernel modules (for SUSE, with pesign-obs-integration). This is the only exception to handle, other packages are not affected.
Ok, so to boot strap the set of permitted digest list parsers, the digest list signature is an appended signature, generated by the build process. The key needed for verifying the signature would already be loaded on the builtin keyring.
After the parser has been identified, each file operation is monitored.
Does the new LSM need to monitor all file opens?
The LSM has to explicitly perform a second open to ensure that the file is measured/appraised before the integrity_iint_cache structure is retrieved (because IMA is called after all LSMs).
If an action is missing from the integrity_iint_cache structure, it will be cleared by the LSM in the actions attached to the process credentials, and will not be added to the digest list being uploaded.
I agree, it would be much easier. However, it would require changes to the building infrastructure of Linux distribution vendors, which might limit the applicability of DIGLIM.
I understand, but instead of the distros signing the compact digest lists, with Eric's "Enroll kernel keys thru MOK" patch set, the customer/end user could have more control over which file digests are permitted on a per system basis.
With the user space parser taking care of the conversion, distributions can do appraisal of executables and shared libraries with an update of:
- the kernel: to add DIGLIM
- dracut: to add required digest lists in the initial ram disk
- rpm (plugin): to extract the RPM header and its signature and write them to a file that is uploaded to the kernel by the user space parser
I'm planning to append the signature at the end of the RPM header (and use appraise_type=modsig) to avoid the dependency on the 'initramfs: add support for xattrs in the initial ram disk' patch set (which I might try to resume in the future).
Based on your explanation above, I surmised as much.
thanks,
Mimi
From: Mimi Zohar [mailto:zohar@linux.ibm.com] Sent: Thursday, August 5, 2021 5:38 PM [Cc'ing Eric Snowberg]
Hi Roberto,
On Mon, 2021-08-02 at 16:54 +0000, Roberto Sassu wrote:
Properly identifying (all) user space parser(s) would be critical. It would be simpler and safer to require the converted data be signed.
When a process directly uploads a buffer to the kernel, the actions are added to a digest list depending on the result of ima_measure_critical_data() and from the actions attached to the process credentials and set by the new LSM.
If a process fails the identification, the actions in the process credentials remain zero and the digest lists the process uploads will be ignored by IMA.
The actions in the process credentials are set with the actions performed on the executable by IMA, only if the digest of the executable is found in a digest list and the digest list type is COMPACT_PARSER. The parser is statically linked.
The digest list for the parser can be generated at the end of the building process and signed similarly to kernel modules (for SUSE, with pesign-obs-integration). This is the only exception to handle, other packages are not affected.
Ok, so to boot strap the set of permitted digest list parsers, the digest list signature is an appended signature, generated by the build process. The key needed for verifying the signature would already be loaded on the builtin keyring.
Hi Mimi
yes. RPM headers will have an appended signature too, so that appraisal will work.
After the parser has been identified, each file operation is monitored.
Does the new LSM need to monitor all file opens?
I would say yes. In the threat model, root is untrusted and can potentially interfere with the conversion of the original digest lists. Other than monitoring file operations, I'm also denying ptraces on the parser. Hopefully this would be sufficient, but any suggestion is more than welcome.
The good thing is that the policy of the new LSM is applied to the processes that are successfully identified as parser. Other processes are mostly unaffected.
The only limitation the new LSM would introduce is that the files being used by the parser are write-locked until the parser releases them (if files are already opened for writing by other processes, the LSM would mark the parser as untrusted and will not apply any IMA actions to the digest lists the parser uploads).
It is probably a good idea to send the patch, after I finish testing it. I will send also another patch for loading digest lists during kernel initialization (with the two new patches the non-IMA part would be complete).
The LSM has to explicitly perform a second open to ensure that the file is measured/appraised before the integrity_iint_cache structure is retrieved (because IMA is called after all LSMs).
If an action is missing from the integrity_iint_cache structure, it will be cleared by the LSM in the actions attached to the process credentials, and will not be added to the digest list being uploaded.
I agree, it would be much easier. However, it would require changes to the building infrastructure of Linux distribution vendors, which might limit the applicability of DIGLIM.
I understand, but instead of the distros signing the compact digest lists, with Eric's "Enroll kernel keys thru MOK" patch set, the customer/end user could have more control over which file digests are permitted on a per system basis.
Yes, generating custom digest lists is supported and needed if users want to run their own applications, when appraisal is enforced. But I like the idea that, if users simply want to just run anything the distribution provides, they have everything they need. Theoretically, users will be able to run appraisal in enforcing mode at the first boot after installation.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
With the user space parser taking care of the conversion, distributions can do appraisal of executables and shared libraries with an update of:
- the kernel: to add DIGLIM
- dracut: to add required digest lists in the initial ram disk
- rpm (plugin): to extract the RPM header and its signature and write them to a file that is uploaded to the kernel by the user space parser
I'm planning to append the signature at the end of the RPM header (and use appraise_type=modsig) to avoid the dependency on the 'initramfs: add support for xattrs in the initial ram disk' patch set (which I might try to resume in the future).
Based on your explanation above, I surmised as much.
thanks,
Mimi
Introduce the digest_lists_loaded directory in <securityfs>/integrity/diglim.
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 --- security/integrity/diglim/fs.c | 307 ++++++++++++++++++++++++++++++++- 1 file changed, 304 insertions(+), 3 deletions(-)
diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index 07a0d75a0e33..caf2923c9146 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -25,6 +25,17 @@ #define MAX_DIGEST_LIST_SIZE (64 * 1024 * 1024 - 1)
static struct dentry *diglim_dir; +/** + * DOC: 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. + */ +static struct dentry *digest_lists_loaded_dir; /** * DOC: digest_list_add * @@ -47,6 +58,245 @@ 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]); +} + +/* *pos is the offset of the digest list data to show. */ +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 digest_list_item *digest_list; + int ret; + + if (m->private) { + digest_list = (struct digest_list_item *)m->private; + + if (*pos == digest_list->size) + return NULL; + + return digest_list->buf + *pos; + } + + 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; + + digest_list = list_first_entry(&d->refs, + struct digest_list_item_ref, list)->digest_list; + m->private = digest_list; + return digest_list->buf; +} + +static void *digest_list_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct compact_list_hdr *hdr; + struct digest_list_item *digest_list = + (struct digest_list_item *)m->private; + void *bufp = digest_list->buf; + bool is_header = false; + + /* Determine if v points to a header or a digest. */ + while (bufp <= v) { + hdr = (struct compact_list_hdr *)bufp; + if (bufp == v) { + is_header = true; + break; + } + + bufp += sizeof(*hdr) + hdr->datalen; + } + + if (is_header) + *pos += sizeof(*hdr); + else + *pos += hash_digest_size[hdr->algo]; + + if (*pos == digest_list->size) + return NULL; + + return digest_list->buf + *pos; +} + +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 compact_list_hdr *hdr; + struct digest_list_item *digest_list = + (struct digest_list_item *)m->private; + void *bufp = digest_list->buf; + bool is_header = false; + + /* Determine if v points to a header or a digest. */ + while (bufp <= v) { + hdr = (struct compact_list_hdr *)bufp; + if (bufp == v) { + is_header = true; + break; + } + + bufp += sizeof(*hdr) + hdr->datalen; + } + + if (is_header) { + if (binary) + digest_list_putc(m, v, sizeof(*hdr)); + else + seq_printf(m, + "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n", + digest_list->actions, hdr->version, + hash_algo_name[hdr->algo], hdr->type, + hdr->modifiers, hdr->count, hdr->datalen); + return 0; + } + + if (binary) { + digest_list_putc(m, v, hash_digest_size[hdr->algo]); + } else { + print_digest(m, v, hash_digest_size[hdr->algo]); + seq_puts(m, "\n"); + } + + 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; +} + /* * digest_list_read: read and parse the digest list from the path */ @@ -60,6 +310,7 @@ static ssize_t digest_list_read(char *path, enum ops op) char event_name[NAME_MAX + 9 + 1]; u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 }; enum hash_algo algo; + struct dentry *dentry, *dentry_ascii; int rc, pathlen = strlen(path);
/* Remove \n. */ @@ -95,9 +346,27 @@ static ssize_t digest_list_read(char *path, enum ops op) if (!rc || rc == -EEXIST) actions |= (1 << 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); + + /* Release reference taken in digest_list_get_secfs_files(). */ + if (op == DIGEST_LIST_DEL) { + dput(dentry); + dput(dentry_ascii); + } + + if ((rc < 0 && op == DIGEST_LIST_ADD) || + (rc == size && op == DIGEST_LIST_DEL)) { + securityfs_remove(dentry); + securityfs_remove(dentry_ascii); + } out_vfree: vfree(data); out: @@ -116,9 +385,10 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf, size_t datalen, loff_t *ppos) { char *data; + char *digest_label_ptr; 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]; char event_name[NAME_MAX + 11 + 1]; enum hash_algo algo; @@ -147,6 +417,10 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf, if (data[0] == '/') { result = digest_list_read(data, op); } else { + /* Remove \n. */ + digest_label_ptr = digest_label; + strsep(&digest_label_ptr, "\n"); + snprintf(event_name, sizeof(event_name), "%s_buffer_%s", op == DIGEST_LIST_ADD ? "add" : "del", digest_label);
@@ -164,12 +438,33 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf, if (!result || result == -EEXIST) actions |= (1 << 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; } + + /* Release reference taken in digest_list_get_secfs_files(). */ + if (op == DIGEST_LIST_DEL) { + dput(dentry); + dput(dentry_ascii); + } + + if ((result < 0 && op == DIGEST_LIST_ADD) || + (result == datalen && op == DIGEST_LIST_DEL)) { + securityfs_remove(dentry); + securityfs_remove(dentry_ascii); + } } out_kfree: kfree(data); @@ -216,6 +511,11 @@ static int __init diglim_fs_init(void) if (IS_ERR(diglim_dir)) return -1;
+ digest_lists_loaded_dir = securityfs_create_dir("digest_lists_loaded", + diglim_dir); + if (IS_ERR(digest_lists_loaded_dir)) + goto out; + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, diglim_dir, NULL, &digest_list_upload_ops); @@ -232,6 +532,7 @@ static int __init diglim_fs_init(void) out: securityfs_remove(digest_list_del_dentry); securityfs_remove(digest_list_add_dentry); + securityfs_remove(digest_lists_loaded_dir); securityfs_remove(diglim_dir); return -1; }
Introduce 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 --- security/integrity/diglim/fs.c | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index caf2923c9146..8958e987f708 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -36,6 +36,13 @@ static struct dentry *diglim_dir; * removed. */ static struct dentry *digest_lists_loaded_dir; +/** + * DOC: 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. + */ +static struct dentry *digest_label_dentry; /** * DOC: digest_list_add * @@ -505,6 +512,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) + return -EFAULT; + + 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 diglim_fs_init(void) { diglim_dir = securityfs_create_dir("diglim", integrity_dir); @@ -528,8 +561,15 @@ static int __init diglim_fs_init(void) if (IS_ERR(digest_list_del_dentry)) goto out;
+ digest_label_dentry = securityfs_create_file("digest_label", 0600, + diglim_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);
Introduce 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 --- security/integrity/diglim/fs.c | 154 +++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+)
diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index 8958e987f708..f1c1fc56448a 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -43,6 +43,13 @@ static struct dentry *digest_lists_loaded_dir; * list (buffer) loaded through digest_list_add. */ static struct dentry *digest_label_dentry; +/** + * DOC: digest_query + * + * digest_query allows to write a query in the format <algo>-<digest> and + * to obtain all digest lists that include that digest. + */ +static struct dentry *digest_query_dentry; /** * DOC: digest_list_add * @@ -63,6 +70,7 @@ static struct dentry *digest_list_add_dentry; * described for digest_list_add. */ 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, @@ -253,6 +261,84 @@ static const struct file_operations digest_list_ascii_ops = { .release = seq_release, };
+/* + * *pos is the n-th reference to show among all the references in all digest + * items found with the query. + */ +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; + + 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; + + list_for_each_entry(ref, &d->refs, list) { + if (count++ == *pos) { + m->private = d; + return ref; + } + } + } + + return NULL; +} + +static void *digest_query_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct digest_item *d = (struct digest_item *)m->private; + struct digest_list_item_ref *cur_ref = (struct digest_list_item_ref *)v; + struct digest_list_item_ref *ref; + + (*pos)++; + + list_for_each_entry(ref, &d->refs, list) { + if (ref != cur_ref) + continue; + + if (!list_is_last(&cur_ref->list, &d->refs)) + return list_next_entry(cur_ref, list); + } + + return 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 *ref = (struct digest_list_item_ref *)v; + struct digest_list_item *digest_list = ref->digest_list; + struct compact_list_hdr *hdr = get_hdr_ref(ref); + + if (!ref->digest_offset) { + seq_printf(m, "%s (actions: %d): type: %d, size: %lld\n", + digest_list->label, digest_list->actions, + COMPACT_DIGEST_LIST, digest_list->size); + return 0; + } + + seq_printf(m, + "%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n", + 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, @@ -538,6 +624,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: sequentialize access to the add/del/query files + */ +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_write: 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) + return -EFAULT; + + digest_query[datalen] = '\0'; + return datalen; +} + +/* + * digest_query_release - release the 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 diglim_fs_init(void) { diglim_dir = securityfs_create_dir("diglim", integrity_dir); @@ -567,8 +714,15 @@ static int __init diglim_fs_init(void) if (IS_ERR(digest_label_dentry)) goto out;
+ digest_query_dentry = securityfs_create_file("digest_query", 0600, + diglim_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);
Introduce the digests_count interface, which shows the current number of digests stored in the hash table by type.
Reported-by: kernel test robot lkp@intel.com (frame size warning) Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- security/integrity/diglim/fs.c | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+)
diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index f1c1fc56448a..3b1d9616cb62 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -23,6 +23,7 @@ #include "diglim.h"
#define MAX_DIGEST_LIST_SIZE (64 * 1024 * 1024 - 1) +#define TMPBUF_SIZE 512
static struct dentry *diglim_dir; /** @@ -36,6 +37,13 @@ static struct dentry *diglim_dir; * removed. */ static struct dentry *digest_lists_loaded_dir; +/** + * DOC: digests_count + * + * digests_count shows the current number of digests stored in the hash + * table by type. + */ +static struct dentry *digests_count; /** * DOC: digest_label * @@ -73,6 +81,39 @@ 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 diglim_show_htable_len(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char *tmpbuf; + ssize_t ret, len = 0; + int i; + + tmpbuf = kmalloc(TMPBUF_SIZE, GFP_KERNEL); + if (!tmpbuf) + return -ENOMEM; + + for (i = COMPACT_PARSER; i < COMPACT__LAST; i++) + len += scnprintf(tmpbuf + len, TMPBUF_SIZE - len, + "%s digests: %lu\n", types_str[i], + htable[i].len); + + ret = simple_read_from_buffer(buf, count, ppos, tmpbuf, len); + kfree(tmpbuf); + return ret; +} + +static const struct file_operations htable_len_ops = { + .read = diglim_show_htable_len, + .llseek = generic_file_llseek, +}; + static int parse_digest_list_filename(const char *digest_list_filename, u8 *digest, enum hash_algo *algo) { @@ -696,6 +737,12 @@ static int __init diglim_fs_init(void) if (IS_ERR(digest_lists_loaded_dir)) goto out;
+ digests_count = securityfs_create_file("digests_count", 0440, + diglim_dir, NULL, + &htable_len_ops); + if (IS_ERR(digests_count)) + goto out; + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, diglim_dir, NULL, &digest_list_upload_ops); @@ -726,6 +773,7 @@ static int __init diglim_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(diglim_dir); return -1;
Em Mon, 26 Jul 2021 18:36:58 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Introduce the digests_count interface, which shows the current number of digests stored in the hash table by type.
Reported-by: kernel test robot lkp@intel.com (frame size warning) Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
Patches 7 to 10 also LGTM.
security/integrity/diglim/fs.c | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+)
diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c index f1c1fc56448a..3b1d9616cb62 100644 --- a/security/integrity/diglim/fs.c +++ b/security/integrity/diglim/fs.c @@ -23,6 +23,7 @@ #include "diglim.h" #define MAX_DIGEST_LIST_SIZE (64 * 1024 * 1024 - 1) +#define TMPBUF_SIZE 512 static struct dentry *diglim_dir; /** @@ -36,6 +37,13 @@ static struct dentry *diglim_dir;
- removed.
*/ static struct dentry *digest_lists_loaded_dir; +/**
- DOC: digests_count
- digests_count shows the current number of digests stored in the hash
- table by type.
- */
+static struct dentry *digests_count; /**
- DOC: digest_label
@@ -73,6 +81,39 @@ 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 diglim_show_htable_len(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
+{
- char *tmpbuf;
- ssize_t ret, len = 0;
- int i;
- tmpbuf = kmalloc(TMPBUF_SIZE, GFP_KERNEL);
- if (!tmpbuf)
return -ENOMEM;
- for (i = COMPACT_PARSER; i < COMPACT__LAST; i++)
len += scnprintf(tmpbuf + len, TMPBUF_SIZE - len,
"%s digests: %lu\n", types_str[i],
htable[i].len);
- ret = simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
- kfree(tmpbuf);
- return ret;
+}
+static const struct file_operations htable_len_ops = {
- .read = diglim_show_htable_len,
- .llseek = generic_file_llseek,
+};
static int parse_digest_list_filename(const char *digest_list_filename, u8 *digest, enum hash_algo *algo) { @@ -696,6 +737,12 @@ static int __init diglim_fs_init(void) if (IS_ERR(digest_lists_loaded_dir)) goto out;
- digests_count = securityfs_create_file("digests_count", 0440,
diglim_dir, NULL,
&htable_len_ops);
- if (IS_ERR(digests_count))
goto out;
- digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, diglim_dir, NULL, &digest_list_upload_ops);
@@ -726,6 +773,7 @@ static int __init diglim_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(diglim_dir); return -1;
Add more information about remote attestation with IMA and DIGLIM in Documentation/security/diglim/remote_attestation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- Documentation/security/diglim/index.rst | 1 + .../security/diglim/remote_attestation.rst | 87 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 89 insertions(+) create mode 100644 Documentation/security/diglim/remote_attestation.rst
diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst index 4771134c2f0d..0f28c5ad71c0 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -10,3 +10,4 @@ Digest Lists Integrity Module (DIGLIM) introduction architecture implementation + remote_attestation diff --git a/Documentation/security/diglim/remote_attestation.rst b/Documentation/security/diglim/remote_attestation.rst new file mode 100644 index 000000000000..83fd7581c460 --- /dev/null +++ b/Documentation/security/diglim/remote_attestation.rst @@ -0,0 +1,87 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Remote Attestation +================== + +When a digest list is added or deleted through the ``digest_list_add`` or +``digest_list_del`` interfaces, its buffer is sent to the IMA function +``ima_measure_critical_data()``. The primary reason for it is to calculate +the buffer digest, so that the digest list itself is searchable in the hash +table. + +``ima_measure_critical_data()`` can be also used to create a new +measurement entry each time this function is called, if there is an +appropriate rule in the IMA policy. Given that this function is called +during an addition or deletion of a digest list, a remote verifier can +infer from the measurement list precise information about what has been +uploaded to the kernel. + +To enable this functionality, the following rule must be added to the IMA +policy: + +:: + + measure func=CRITICAL_DATA label=diglim + + +When a file is uploaded, the workflow and the resulting IMA measurement +list are: + +.. code-block:: bash + + # echo $PWD/0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_add + # echo $PWD/0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_del + # cat /sys/kernel/security/integrity/ima/ascii_runtime_measurements + ... + 10 <template digest> ima-buf sha256:<buffer digest> add_file_0-file_list-compact-cat <buffer> + 10 <template digest> ima-buf sha256:<buffer digest> del_file_0-file_list-compact-cat <buffer> + +When a buffer is uploaded, the workflow and the resulting IMA measurement +list are: + +.. code-block:: bash + + # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label + # cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_add + # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label + # cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_del + # cat /sys/kernel/security/integrity/ima/ascii_runtime_measurements + ... + 10 <template digest> ima-buf sha256:<buffer digest> add_buffer_0-file_list-compact-cat <buffer> + 10 <template digest> ima-buf sha256:<buffer digest> del_buffer_0-file_list-compact-cat <buffer> + +In the second case, the digest list label must be set explicitly, as the +kernel cannot determine it by itself (in the first case it is derived from +the name of the file uploaded). + +The confirmation that the digest list has been processed by IMA can be +obtained by reading the ASCII representation of the digest list: + +.. code-block:: bash + + # cat /sys/kernel/security/integrity/diglim/digest_lists_loaded/sha256-<digest list digest>-0-file_list-compact-cat.ascii + actions: 1, version: 1, algo: sha256, type: 2, modifiers: 1, count: 1, datalen: 32 + 87e5bd81850e11eeec2d3bb696b626b2a7f45673241cbbd64769c83580432869 + +In this output, ``actions`` is set to 1 (``COMPACT_ACTION_IMA_MEASURED`` +bit set). + + +DIGLIM guarantees that the information reported in the IMA measurement list +is complete. If digest list loading is not recorded, digest query results +are ignored by IMA. If the addition was recorded, deletion can be performed +only if also the deletion is recorded. This can be seen in the following +sequence of commands: + +.. code-block:: bash + + # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label + # cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_add + # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label + # /tmp/cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_del + diglim: actions mismatch, add: 1, del: 0 + diglim: unable to upload generated digest list + /tmp/cat: write error: Invalid argument + +Digest list measurement is avoided with the execution of ``/tmp/cat``, for +which a dont_measure rule was previously added in the IMA policy. diff --git a/MAINTAINERS b/MAINTAINERS index 0672128fae7f..a7c502685109 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5461,6 +5461,7 @@ F: Documentation/security/diglim/architecture.rst F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: Documentation/security/diglim/remote_attestation.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h
Em Mon, 26 Jul 2021 18:36:59 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Add more information about remote attestation with IMA and DIGLIM in Documentation/security/diglim/remote_attestation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
Documentation/security/diglim/index.rst | 1 + .../security/diglim/remote_attestation.rst | 87 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 89 insertions(+) create mode 100644 Documentation/security/diglim/remote_attestation.rst
diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst index 4771134c2f0d..0f28c5ad71c0 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -10,3 +10,4 @@ Digest Lists Integrity Module (DIGLIM) introduction architecture implementation
- remote_attestation
diff --git a/Documentation/security/diglim/remote_attestation.rst b/Documentation/security/diglim/remote_attestation.rst new file mode 100644 index 000000000000..83fd7581c460 --- /dev/null +++ b/Documentation/security/diglim/remote_attestation.rst @@ -0,0 +1,87 @@ +.. SPDX-License-Identifier: GPL-2.0
+Remote Attestation +==================
+When a digest list is added or deleted through the ``digest_list_add`` or +``digest_list_del`` interfaces, its buffer is sent to the IMA function +``ima_measure_critical_data()``. The primary reason for it is to calculate +the buffer digest, so that the digest list itself is searchable in the hash +table.
+``ima_measure_critical_data()`` can be also used to create a new +measurement entry each time this function is called, if there is an +appropriate rule in the IMA policy. Given that this function is called +during an addition or deletion of a digest list, a remote verifier can +infer from the measurement list precise information about what has been +uploaded to the kernel.
+To enable this functionality, the following rule must be added to the IMA +policy:
+::
As commented on other patches at this series, you can merge :: at the previous text line, e. g.:
policy::
does the same as:
policy:
::
but it is nicer for text-only readers, IMO.
- measure func=CRITICAL_DATA label=diglim
+When a file is uploaded, the workflow and the resulting IMA measurement +list are:
+.. code-block:: bash
- # echo $PWD/0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_add
- # echo $PWD/0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_del
- # cat /sys/kernel/security/integrity/ima/ascii_runtime_measurements
- ...
- 10 <template digest> ima-buf sha256:<buffer digest> add_file_0-file_list-compact-cat <buffer>
- 10 <template digest> ima-buf sha256:<buffer digest> del_file_0-file_list-compact-cat <buffer>
+When a buffer is uploaded, the workflow and the resulting IMA measurement +list are:
+.. code-block:: bash
- # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label
- # cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_add
- # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label
- # cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_del
- # cat /sys/kernel/security/integrity/ima/ascii_runtime_measurements
- ...
- 10 <template digest> ima-buf sha256:<buffer digest> add_buffer_0-file_list-compact-cat <buffer>
- 10 <template digest> ima-buf sha256:<buffer digest> del_buffer_0-file_list-compact-cat <buffer>
+In the second case, the digest list label must be set explicitly, as the +kernel cannot determine it by itself (in the first case it is derived from +the name of the file uploaded).
+The confirmation that the digest list has been processed by IMA can be +obtained by reading the ASCII representation of the digest list:
+.. code-block:: bash
- # cat /sys/kernel/security/integrity/diglim/digest_lists_loaded/sha256-<digest list digest>-0-file_list-compact-cat.ascii
- actions: 1, version: 1, algo: sha256, type: 2, modifiers: 1, count: 1, datalen: 32
- 87e5bd81850e11eeec2d3bb696b626b2a7f45673241cbbd64769c83580432869
+In this output, ``actions`` is set to 1 (``COMPACT_ACTION_IMA_MEASURED`` +bit set).
+DIGLIM guarantees that the information reported in the IMA measurement list +is complete. If digest list loading is not recorded, digest query results +are ignored by IMA. If the addition was recorded, deletion can be performed +only if also the deletion is recorded. This can be seen in the following +sequence of commands:
+.. code-block:: bash
- # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label
- # cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_add
- # echo 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_label
- # /tmp/cat 0-file_list-compact-cat > /sys/kernel/security/integrity/diglim/digest_list_del
- diglim: actions mismatch, add: 1, del: 0
- diglim: unable to upload generated digest list
- /tmp/cat: write error: Invalid argument
+Digest list measurement is avoided with the execution of ``/tmp/cat``, for +which a dont_measure rule was previously added in the IMA policy. diff --git a/MAINTAINERS b/MAINTAINERS index 0672128fae7f..a7c502685109 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5461,6 +5461,7 @@ F: Documentation/security/diglim/architecture.rst F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: Documentation/security/diglim/remote_attestation.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h
From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] Sent: Wednesday, July 28, 2021 2:47 PM Em Mon, 26 Jul 2021 18:36:59 +0200 Roberto Sassu roberto.sassu@huawei.com escreveu:
Add more information about remote attestation with IMA and DIGLIM in Documentation/security/diglim/remote_attestation.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com
Documentation/security/diglim/index.rst | 1 + .../security/diglim/remote_attestation.rst | 87 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 89 insertions(+) create mode 100644 Documentation/security/diglim/remote_attestation.rst
diff --git a/Documentation/security/diglim/index.rst
b/Documentation/security/diglim/index.rst
index 4771134c2f0d..0f28c5ad71c0 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -10,3 +10,4 @@ Digest Lists Integrity Module (DIGLIM) introduction architecture implementation
- remote_attestation
diff --git a/Documentation/security/diglim/remote_attestation.rst
b/Documentation/security/diglim/remote_attestation.rst
new file mode 100644 index 000000000000..83fd7581c460 --- /dev/null +++ b/Documentation/security/diglim/remote_attestation.rst @@ -0,0 +1,87 @@ +.. SPDX-License-Identifier: GPL-2.0
+Remote Attestation +==================
+When a digest list is added or deleted through the ``digest_list_add`` or +``digest_list_del`` interfaces, its buffer is sent to the IMA function +``ima_measure_critical_data()``. The primary reason for it is to calculate +the buffer digest, so that the digest list itself is searchable in the hash +table.
+``ima_measure_critical_data()`` can be also used to create a new +measurement entry each time this function is called, if there is an +appropriate rule in the IMA policy. Given that this function is called +during an addition or deletion of a digest list, a remote verifier can +infer from the measurement list precise information about what has been +uploaded to the kernel.
+To enable this functionality, the following rule must be added to the IMA +policy:
+::
As commented on other patches at this series, you can merge :: at the previous text line, e. g.:
policy::
does the same as:
policy:
::
but it is nicer for text-only readers, IMO.
Ok, will change in the next version of the patch set.
Thanks
Roberto
HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli
- measure func=CRITICAL_DATA label=diglim
+When a file is uploaded, the workflow and the resulting IMA measurement +list are:
+.. code-block:: bash
- # echo $PWD/0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_list_add
- # echo $PWD/0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_list_del
- # cat /sys/kernel/security/integrity/ima/ascii_runtime_measurements
- ...
- 10 <template digest> ima-buf sha256:<buffer digest> add_file_0-file_list-
compact-cat <buffer>
- 10 <template digest> ima-buf sha256:<buffer digest> del_file_0-file_list-
compact-cat <buffer>
+When a buffer is uploaded, the workflow and the resulting IMA
measurement
+list are:
+.. code-block:: bash
- # echo 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_label
- # cat 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_list_add
- # echo 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_label
- # cat 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_list_del
- # cat /sys/kernel/security/integrity/ima/ascii_runtime_measurements
- ...
- 10 <template digest> ima-buf sha256:<buffer digest> add_buffer_0-
file_list-compact-cat <buffer>
- 10 <template digest> ima-buf sha256:<buffer digest> del_buffer_0-file_list-
compact-cat <buffer>
+In the second case, the digest list label must be set explicitly, as the +kernel cannot determine it by itself (in the first case it is derived from +the name of the file uploaded).
+The confirmation that the digest list has been processed by IMA can be +obtained by reading the ASCII representation of the digest list:
+.. code-block:: bash
- # cat /sys/kernel/security/integrity/diglim/digest_lists_loaded/sha256-
<digest list digest>-0-file_list-compact-cat.ascii
- actions: 1, version: 1, algo: sha256, type: 2, modifiers: 1, count: 1, datalen:
32
87e5bd81850e11eeec2d3bb696b626b2a7f45673241cbbd64769c83580432869
+In this output, ``actions`` is set to 1
(``COMPACT_ACTION_IMA_MEASURED``
+bit set).
+DIGLIM guarantees that the information reported in the IMA measurement
list
+is complete. If digest list loading is not recorded, digest query results +are ignored by IMA. If the addition was recorded, deletion can be
performed
+only if also the deletion is recorded. This can be seen in the following +sequence of commands:
+.. code-block:: bash
- # echo 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_label
- # cat 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_list_add
- # echo 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_label
- # /tmp/cat 0-file_list-compact-cat >
/sys/kernel/security/integrity/diglim/digest_list_del
- diglim: actions mismatch, add: 1, del: 0
- diglim: unable to upload generated digest list
- /tmp/cat: write error: Invalid argument
+Digest list measurement is avoided with the execution of ``/tmp/cat``, for +which a dont_measure rule was previously added in the IMA policy. diff --git a/MAINTAINERS b/MAINTAINERS index 0672128fae7f..a7c502685109 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5461,6 +5461,7 @@ F:
Documentation/security/diglim/architecture.rst
F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: Documentation/security/diglim/remote_attestation.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h
Introduce a number of tests to ensure that DIGLIM works as expected:
- digest_list_add_del_test_file_upload; - digest_list_add_del_test_file_upload_fault; - digest_list_add_del_test_buffer_upload; - digest_list_add_del_test_buffer_upload_fault; - digest_list_fuzzing_test; - digest_list_add_del_test_file_upload_measured; - digest_list_check_measurement_list_test_file_upload; - digest_list_check_measurement_list_test_buffer_upload.
The tests are in tools/testing/selftests/diglim/selftest.c.
A description of the tests can be found in Documentation/security/diglim/tests.rst.
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- Documentation/security/diglim/index.rst | 1 + Documentation/security/diglim/tests.rst | 66 + MAINTAINERS | 2 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/diglim/Makefile | 19 + tools/testing/selftests/diglim/common.c | 115 ++ tools/testing/selftests/diglim/common.h | 31 + tools/testing/selftests/diglim/config | 3 + tools/testing/selftests/diglim/selftest.c | 1382 +++++++++++++++++++++ 9 files changed, 1620 insertions(+) create mode 100644 Documentation/security/diglim/tests.rst create mode 100644 tools/testing/selftests/diglim/Makefile create mode 100644 tools/testing/selftests/diglim/common.c create mode 100644 tools/testing/selftests/diglim/common.h create mode 100644 tools/testing/selftests/diglim/config create mode 100644 tools/testing/selftests/diglim/selftest.c
diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst index 0f28c5ad71c0..d4ba4ce50a59 100644 --- a/Documentation/security/diglim/index.rst +++ b/Documentation/security/diglim/index.rst @@ -11,3 +11,4 @@ Digest Lists Integrity Module (DIGLIM) architecture implementation remote_attestation + tests diff --git a/Documentation/security/diglim/tests.rst b/Documentation/security/diglim/tests.rst new file mode 100644 index 000000000000..d58ddbaeeb55 --- /dev/null +++ b/Documentation/security/diglim/tests.rst @@ -0,0 +1,66 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Testing +======= + +This section introduces a number of tests to ensure that DIGLIM works as +expected: + +- ``digest_list_add_del_test_file_upload``; +- ``digest_list_add_del_test_file_upload_fault``; +- ``digest_list_add_del_test_buffer_upload``; +- ``digest_list_add_del_test_buffer_upload_fault``; +- ``digest_list_fuzzing_test``; +- ``digest_list_add_del_test_file_upload_measured``; +- ``digest_list_check_measurement_list_test_file_upload``; +- ``digest_list_check_measurement_list_test_buffer_upload``. + +The tests are in ``tools/testing/selftests/diglim/selftest.c``. + +The first four tests randomly perform add, delete and query of digest +lists. They internally keep track at any time of the digest lists that are +currently uploaded to the kernel. + +Also, digest lists are generated randomly by selecting an arbitrary digest +algorithm and an arbitrary number of digests. To ensure a good number of +collisions, digests are a sequence of zeros, except for the first four +bytes that are set with a random number within a defined range. + +When a query operation is selected, a digest is chosen by getting another +random number within the same range. Then, the tests count how many times +the digest is found in the internally stored digest lists and in the query +result obtained from the kernel. The tests are successful if the obtained +numbers are the same. + +The ``file_upload`` variant creates a temporary file from a generated +digest list and sends its path to the kernel, so that the file is uploaded. +The ``buffer_upload`` variant directly sends the digest list buffer to the +kernel (it will be done by the user space parser after it converts a digest +list not in the compact format). + +The ``fault`` variant performs the test by enabling the ad-hoc fault +injection mechanism in the kernel (accessible through +``<debugfs>/fail_diglim``). The fault injection mechanism randomly injects +errors during the addition and deletion of digest lists. When an error +occurs, the rollback mechanism performs the reverse operation until the +point the error occurred, so that the kernel is left in the same state as +when the requested operation began. Since the kernel returns the error to +user space, the tests also know that the operation didn't succeed and +behave accordingly (they also revert the internal state). + +The fuzzing test simply sends randomly generated digest lists to the +kernel, to ensure that the parser is robust enough to handle malformed +data. + +The ``measured`` variant of the ``digest_list_add_del_test`` series checks +whether the digest lists actions are properly set after adding an IMA rule +to measure the digest lists. + +The ``digest_list_check_measurement_list_test`` tests verify the remote +attestation functionality. They verify whether IMA creates a measurement +entry for each addition and deletion of a digest list, and that the +deletion is forbidden if IMA created a measurement entry only for the +addition. + +The ``file_upload`` variant uploads a file, while the ``buffer_upload`` +variant uploads a buffer. diff --git a/MAINTAINERS b/MAINTAINERS index a7c502685109..e63061e63363 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5462,12 +5462,14 @@ F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst F: Documentation/security/diglim/remote_attestation.rst +F: Documentation/security/diglim/tests.rst F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h F: security/integrity/diglim/fs.c F: security/integrity/diglim/methods.c F: security/integrity/diglim/parser.c +F: tools/testing/selftests/diglim/
DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck linux@roeck-us.net diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index fb010a35d61a..37460b308e07 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -8,6 +8,7 @@ TARGETS += clone3 TARGETS += core TARGETS += cpufreq TARGETS += cpu-hotplug +TARGETS += diglim TARGETS += drivers/dma-buf TARGETS += efivarfs TARGETS += exec diff --git a/tools/testing/selftests/diglim/Makefile b/tools/testing/selftests/diglim/Makefile new file mode 100644 index 000000000000..100c219955d7 --- /dev/null +++ b/tools/testing/selftests/diglim/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +LDFLAGS += -lcrypto + +CFLAGS += -O2 -Wall -Wl,-no-as-needed -g -I./ -I../../../../usr/include/ \ + -L$(OUTPUT) -Wl,-rpath=./ -ggdb +LDLIBS += -lpthread + +OVERRIDE_TARGETS = 1 + +TEST_GEN_PROGS = selftest +TEST_GEN_PROGS_EXTENDED = libcommon.so + +include ../lib.mk + +$(OUTPUT)/libcommon.so: common.c + $(CC) $(CFLAGS) -shared -fPIC $< $(LDLIBS) -o $@ + +$(OUTPUT)/selftest: selftest.c $(TEST_GEN_PROGS_EXTENDED) + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) -lcommon diff --git a/tools/testing/selftests/diglim/common.c b/tools/testing/selftests/diglim/common.c new file mode 100644 index 000000000000..88ff48fed308 --- /dev/null +++ b/tools/testing/selftests/diglim/common.c @@ -0,0 +1,115 @@ +// 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 + * + * 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, int uid) +{ + ssize_t to_write = buffer_len, written = 0; + int ret = 0, ret_seteuid, fd, cur_uid = geteuid(); + + fd = open(path, O_WRONLY); + if (fd < 0) + return -errno; + + if (uid >= 0) { + ret_seteuid = seteuid(uid); + if (ret_seteuid < 0) + return ret_seteuid; + } + + while (to_write) { + written = write(fd, buffer + buffer_len - to_write, to_write); + if (written <= 0) { + ret = -errno; + break; + } + + to_write -= written; + } + + if (uid >= 0) { + ret_seteuid = seteuid(cur_uid); + if (ret_seteuid < 0) + return ret_seteuid; + } + + close(fd); + return ret; +} + +int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, + bool is_char) +{ + ssize_t len = 0, read_len; + int ret = 0, fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + if (alloc) { + *buffer = NULL; + *buffer_len = 0; + } + + while (1) { + if (alloc) { + if (*buffer_len == len) { + *buffer_len += BUFFER_SIZE; + *buffer = realloc(*buffer, *buffer_len + 1); + if (!*buffer) { + ret = -ENOMEM; + goto out; + } + } + } + + read_len = read(fd, *buffer + len, *buffer_len - len); + if (read_len < 0) { + ret = -errno; + goto out; + } + + if (!read_len) + break; + + len += read_len; + } + + *buffer_len = len; + if (is_char) + (*buffer)[(*buffer_len)++] = '\0'; +out: + close(fd); + if (ret < 0) { + if (alloc) { + free(*buffer); + *buffer = NULL; + } + } + + return ret; +} diff --git a/tools/testing/selftests/diglim/common.h b/tools/testing/selftests/diglim/common.h new file mode 100644 index 000000000000..d3e5808c06e8 --- /dev/null +++ b/tools/testing/selftests/diglim/common.h @@ -0,0 +1,31 @@ +/* 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 + * + * 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 uid); +int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, + bool is_char); diff --git a/tools/testing/selftests/diglim/config b/tools/testing/selftests/diglim/config new file mode 100644 index 000000000000..faafc742974c --- /dev/null +++ b/tools/testing/selftests/diglim/config @@ -0,0 +1,3 @@ +CONFIG_DIGEST_LISTS=y +CONFIG_FAULT_INJECTION=y +CONFIG_FAULT_INJECTION_DEBUG_FS=y diff --git a/tools/testing/selftests/diglim/selftest.c b/tools/testing/selftests/diglim/selftest.c new file mode 100644 index 000000000000..efcd83409b39 --- /dev/null +++ b/tools/testing/selftests/diglim/selftest.c @@ -0,0 +1,1382 @@ +// 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 + * + * Functions to test DIGLIM. + */ + +#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 <linux/diglim.h> + +#include <openssl/evp.h> + +#include "common.h" +#include "../kselftest_harness.h" + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +#define MD5_DIGEST_SIZE 16 +#define SHA1_DIGEST_SIZE 20 +#define RMD160_DIGEST_SIZE 20 +#define SHA256_DIGEST_SIZE 32 +#define SHA384_DIGEST_SIZE 48 +#define SHA512_DIGEST_SIZE 64 +#define SHA224_DIGEST_SIZE 28 +#define RMD128_DIGEST_SIZE 16 +#define RMD256_DIGEST_SIZE 32 +#define RMD320_DIGEST_SIZE 40 +#define WP256_DIGEST_SIZE 32 +#define WP384_DIGEST_SIZE 48 +#define WP512_DIGEST_SIZE 64 +#define TGR128_DIGEST_SIZE 16 +#define TGR160_DIGEST_SIZE 20 +#define TGR192_DIGEST_SIZE 24 +#define SM3256_DIGEST_SIZE 32 +#define STREEBOG256_DIGEST_SIZE 32 +#define STREEBOG512_DIGEST_SIZE 64 + +#define DIGEST_LIST_PATH_TEMPLATE "/tmp/digest_list.XXXXXX" + +#define INTEGRITY_DIR "/sys/kernel/security/integrity" + +#define DIGEST_LIST_DIR INTEGRITY_DIR "/diglim" +#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query" +#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_label" +#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add" +#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del" +#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded" +#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count" + +#define IMA_POLICY_PATH INTEGRITY_DIR "/ima/policy" +#define IMA_MEASUREMENTS_PATH INTEGRITY_DIR "/ima/ascii_runtime_measurements" + +#define DIGEST_LIST_DEBUGFS_DIR "/sys/kernel/debug/fail_diglim" +#define DIGEST_LIST_DEBUGFS_TASK_FILTER DIGEST_LIST_DEBUGFS_DIR "/task-filter" +#define DIGEST_LIST_DEBUGFS_PROBABILITY DIGEST_LIST_DEBUGFS_DIR "/probability" +#define DIGEST_LIST_DEBUGFS_TIMES DIGEST_LIST_DEBUGFS_DIR "/times" +#define DIGEST_LIST_DEBUGFS_VERBOSE DIGEST_LIST_DEBUGFS_DIR "/verbose" +#define PROCFS_SELF_FAULT "/proc/self/make-it-fail" + +#define MAX_LINE_LENGTH 512 +#define LABEL_LEN 32 +#define MAX_DIGEST_COUNT 100 +#define MAX_DIGEST_LISTS 100 +#define MAX_DIGEST_BLOCKS 10 +#define MAX_DIGEST_VALUE 10 +#define MAX_SEARCH_ATTEMPTS 10 +#define NUM_QUERIES 1000 +#define MAX_DIGEST_LIST_SIZE 10000 +#define NUM_ITERATIONS 100000 + +enum upload_types { UPLOAD_FILE, UPLOAD_BUFFER }; + +const char *const hash_algo_name[HASH_ALGO__LAST] = { + [HASH_ALGO_MD4] = "md4", + [HASH_ALGO_MD5] = "md5", + [HASH_ALGO_SHA1] = "sha1", + [HASH_ALGO_RIPE_MD_160] = "rmd160", + [HASH_ALGO_SHA256] = "sha256", + [HASH_ALGO_SHA384] = "sha384", + [HASH_ALGO_SHA512] = "sha512", + [HASH_ALGO_SHA224] = "sha224", + [HASH_ALGO_RIPE_MD_128] = "rmd128", + [HASH_ALGO_RIPE_MD_256] = "rmd256", + [HASH_ALGO_RIPE_MD_320] = "rmd320", + [HASH_ALGO_WP_256] = "wp256", + [HASH_ALGO_WP_384] = "wp384", + [HASH_ALGO_WP_512] = "wp512", + [HASH_ALGO_TGR_128] = "tgr128", + [HASH_ALGO_TGR_160] = "tgr160", + [HASH_ALGO_TGR_192] = "tgr192", + [HASH_ALGO_SM3_256] = "sm3", + [HASH_ALGO_STREEBOG_256] = "streebog256", + [HASH_ALGO_STREEBOG_512] = "streebog512", +}; + +const int hash_digest_size[HASH_ALGO__LAST] = { + [HASH_ALGO_MD4] = MD5_DIGEST_SIZE, + [HASH_ALGO_MD5] = MD5_DIGEST_SIZE, + [HASH_ALGO_SHA1] = SHA1_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_160] = RMD160_DIGEST_SIZE, + [HASH_ALGO_SHA256] = SHA256_DIGEST_SIZE, + [HASH_ALGO_SHA384] = SHA384_DIGEST_SIZE, + [HASH_ALGO_SHA512] = SHA512_DIGEST_SIZE, + [HASH_ALGO_SHA224] = SHA224_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_128] = RMD128_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_256] = RMD256_DIGEST_SIZE, + [HASH_ALGO_RIPE_MD_320] = RMD320_DIGEST_SIZE, + [HASH_ALGO_WP_256] = WP256_DIGEST_SIZE, + [HASH_ALGO_WP_384] = WP384_DIGEST_SIZE, + [HASH_ALGO_WP_512] = WP512_DIGEST_SIZE, + [HASH_ALGO_TGR_128] = TGR128_DIGEST_SIZE, + [HASH_ALGO_TGR_160] = TGR160_DIGEST_SIZE, + [HASH_ALGO_TGR_192] = TGR192_DIGEST_SIZE, + [HASH_ALGO_SM3_256] = SM3256_DIGEST_SIZE, + [HASH_ALGO_STREEBOG_256] = STREEBOG256_DIGEST_SIZE, + [HASH_ALGO_STREEBOG_512] = STREEBOG512_DIGEST_SIZE, +}; + +struct digest_list_item { + unsigned long long size; + u8 *buf; + u8 actions; + char digest_str[64 * 2 + 1]; + enum hash_algo algo; + char filename_suffix[6 + 1]; +}; + +static const char hex_asc[] = "0123456789abcdef"; + +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +static inline char *hex_byte_pack(char *buf, unsigned char byte) +{ + *buf++ = hex_asc_hi(byte); + *buf++ = hex_asc_lo(byte); + return buf; +} + +/* from lib/hexdump.c (Linux kernel) */ +static int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +int _hex2bin(unsigned char *dst, const char *src, size_t count) +{ + while (count--) { + int hi = hex_to_bin(*src++); + int lo = hex_to_bin(*src++); + + if ((hi < 0) || (lo < 0)) + return -1; + + *dst++ = (hi << 4) | lo; + } + return 0; +} + +char *_bin2hex(char *dst, const void *src, size_t count) +{ + const unsigned char *_src = src; + + while (count--) + dst = hex_byte_pack(dst, *_src++); + return dst; +} + +u32 num_max_digest_lists = MAX_DIGEST_LISTS; +u32 digest_lists_pos; +struct digest_list_item *digest_lists[MAX_DIGEST_LISTS]; + +enum hash_algo ima_hash_algo = HASH_ALGO__LAST; + +static enum hash_algo get_ima_hash_algo(void) +{ + char *measurement_list, *measurement_list_ptr; + size_t measurement_list_len; + int ret, i = 0; + + if (ima_hash_algo != HASH_ALGO__LAST) + return ima_hash_algo; + + ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list, + &measurement_list_len, true, true); + if (ret < 0) + return HASH_ALGO_SHA256; + + measurement_list_ptr = measurement_list; + while ((strsep(&measurement_list_ptr, " ")) && i++ < 2) + ; + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (!strncmp(hash_algo_name[i], measurement_list_ptr, + strlen(hash_algo_name[i]))) { + ima_hash_algo = i; + break; + } + } + + free(measurement_list); + return ima_hash_algo; +} + +int calc_digest(u8 *digest, void *data, u64 len, enum hash_algo algo) +{ + EVP_MD_CTX *mdctx; + const EVP_MD *md; + int ret = -EINVAL; + + OpenSSL_add_all_algorithms(); + + md = EVP_get_digestbyname(hash_algo_name[algo]); + if (!md) + goto out; + + mdctx = EVP_MD_CTX_create(); + if (!mdctx) + goto out; + + if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + goto out_mdctx; + + if (EVP_DigestUpdate(mdctx, data, len) != 1) + goto out_mdctx; + + if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1) + goto out_mdctx; + + ret = 0; +out_mdctx: + EVP_MD_CTX_destroy(mdctx); +out: + EVP_cleanup(); + return ret; +} + +int calc_file_digest(u8 *digest, char *path, enum hash_algo algo) +{ + void *data = MAP_FAILED; + struct stat st; + int fd, ret = 0; + + if (stat(path, &st) == -1) + return -EACCES; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + if (st.st_size) { + data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + ret = -ENOMEM; + goto out; + } + } + + ret = calc_digest(digest, data, st.st_size, algo); +out: + if (data != MAP_FAILED) + munmap(data, st.st_size); + + close(fd); + return ret; +} + +static struct digest_list_item *digest_list_generate(void) +{ + struct digest_list_item *digest_list; + struct compact_list_hdr *hdr_array = NULL; + 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; + hdr_array[i]._reserved = 0; + /* COMPACT_DIGEST_LIST type is not allowed. */ + hdr_array[i].type = hdr_array[i].type % (COMPACT__LAST - 1); + hdr_array[i].modifiers = + hdr_array[i].modifiers % (1 << COMPACT_MOD_IMMUTABLE) + 1; + hdr_array[i].algo = hdr_array[i].algo % HASH_ALGO_RIPE_MD_128; + hdr_array[i].count = hdr_array[i].count % MAX_DIGEST_COUNT; + + while (!hdr_array[i].count) { + ret = getrandom(&hdr_array[i].count, + sizeof(hdr_array[i].count), 0); + if (ret < 0) + goto out; + + hdr_array[i].count = + hdr_array[i].count % MAX_DIGEST_COUNT; + } + + hdr_array[i].datalen = + hdr_array[i].count * hash_digest_size[hdr_array[i].algo]; + + digest_list->size += sizeof(*hdr_array) + hdr_array[i].datalen; + } + + digest_list->buf = calloc(digest_list->size, sizeof(unsigned char)); + if (!digest_list->buf) { + 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->_reserved = 0; + 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, int uid) +{ + char path_template[] = DIGEST_LIST_PATH_TEMPLATE; + char *path_upload = DIGEST_LIST_ADD_PATH, *basename; + unsigned char *buffer = digest_list->buf; + size_t buffer_len = digest_list->size; + unsigned char rnd[3]; + int ret = 0, fd; + + if (op == DIGEST_LIST_ADD) { + if (upload_type == UPLOAD_FILE) { + fd = mkstemp(path_template); + if (fd < 0) + return -EPERM; + + fchmod(fd, 0644); + close(fd); + ret = write_buffer(path_template, + (char *)digest_list->buf, + digest_list->size, -1); + if (ret < 0) + goto out; + + buffer = (unsigned char *)path_template; + buffer_len = strlen(path_template); + } else { + ret = getrandom(rnd, sizeof(rnd), 0); + if (ret < 0) + goto out; + + _bin2hex(path_template + + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, rnd, + sizeof(rnd)); + } + + memcpy(digest_list->filename_suffix, + path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + 6); + } else { + memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + digest_list->filename_suffix, 6); + path_upload = DIGEST_LIST_DEL_PATH; + if (upload_type == UPLOAD_FILE) { + buffer = (unsigned char *)path_template; + buffer_len = strlen(path_template); + } + } + + if (upload_type == UPLOAD_BUFFER) { + basename = strrchr(path_template, '/') + 1; + ret = write_buffer(DIGEST_LABEL_PATH, basename, + strlen(basename), -1); + if (ret < 0) + goto out; + } + + ret = write_buffer(path_upload, (char *)buffer, buffer_len, uid); +out: + if ((op == DIGEST_LIST_ADD && ret < 0) || + (op == DIGEST_LIST_DEL && !ret)) + unlink(path_template); + + return ret; +} + +static int digest_list_check(struct digest_list_item *digest_list, enum ops op) +{ + char path[PATH_MAX]; + u8 digest_list_buf[MAX_LINE_LENGTH]; + char digest_list_info[MAX_LINE_LENGTH]; + ssize_t size = digest_list->size; + struct compact_list_hdr *hdr; + struct stat st; + int ret = 0, i, fd, path_len, len, read_len; + + path_len = snprintf(path, sizeof(path), "%s/%s-%s-digest_list.%s.ascii", + DIGEST_LISTS_LOADED_PATH, + hash_algo_name[digest_list->algo], + digest_list->digest_str, + digest_list->filename_suffix); + + path[path_len - 6] = '\0'; + + if (op == DIGEST_LIST_DEL) { + if (stat(path, &st) != -1) + return -EEXIST; + + path[path_len - 6] = '.'; + + if (stat(path, &st) != -1) + return -EEXIST; + + return 0; + } + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + while (size) { + len = read(fd, digest_list_buf, sizeof(digest_list_buf)); + if (len <= 0) { + ret = -errno; + goto out; + } + + if (memcmp(digest_list_buf, + digest_list->buf + digest_list->size - size, len)) { + ret = -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); + + /* From digest_list_show_common(). */ + len = snprintf(digest_list_info, sizeof(digest_list_info), + "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n", + digest_list->actions, hdr->version, + hash_algo_name[hdr->algo], hdr->type, hdr->modifiers, + hdr->count, hdr->datalen); + + read_len = read(fd, digest_list_buf, len); + + if (read_len != len || + memcmp(digest_list_info, digest_list_buf, len)) { + ret = -EIO; + goto out; + } + + size -= sizeof(*hdr); + + for (i = 0; i < hdr->count; i++) { + _bin2hex(digest_list_info, + digest_list->buf + digest_list->size - size, + hash_digest_size[hdr->algo]); + + read_len = read(fd, digest_list_buf, + hash_digest_size[hdr->algo] * 2 + 1); + + if (read_len != hash_digest_size[hdr->algo] * 2 + 1 || + memcmp(digest_list_info, digest_list_buf, + read_len - 1) || + digest_list_buf[read_len - 1] != '\n') { + ret = -EIO; + goto out; + } + + size -= hash_digest_size[hdr->algo]; + } + } +out: + close(fd); + return ret; +} + +static int digest_list_query(u8 *digest, enum hash_algo algo, + char **query_result) +{ + ssize_t len, to_write, written; + char query[256] = { 0 }; + size_t query_result_len; + int ret = 0, fd; + + len = snprintf(query, sizeof(query), "%s-", hash_algo_name[algo]); + + _bin2hex(query + len, digest, hash_digest_size[algo]); + len += hash_digest_size[algo] * 2 + 1; + + fd = open(DIGEST_QUERY_PATH, O_WRONLY); + if (fd < 0) + return -errno; + + to_write = len; + + while (to_write) { + written = write(fd, query + len - to_write, to_write); + if (written <= 0) { + ret = -errno; + break; + } + + to_write -= written; + } + + close(fd); + if (ret < 0) + return ret; + + return read_buffer(DIGEST_QUERY_PATH, query_result, &query_result_len, + true, true); +} + +static int *get_count_gen_lists(u8 *digest, enum hash_algo algo, + bool is_digest_list) +{ + struct compact_list_hdr *hdr; + u8 *buf_ptr; + unsigned long long size; + struct digest_list_item *digest_list; + u8 digest_list_digest[64]; + int i, j, *count; + + count = calloc(num_max_digest_lists, sizeof(*count)); + if (!count) + return count; + + for (i = 0; i < num_max_digest_lists; i++) { + if (!digest_lists[i]) + continue; + + digest_list = digest_lists[i]; + size = digest_lists[i]->size; + buf_ptr = digest_lists[i]->buf; + + if (is_digest_list) { + _hex2bin(digest_list_digest, digest_list->digest_str, + hash_digest_size[digest_list->algo]); + if (!memcmp(digest_list_digest, digest, + hash_digest_size[digest_list->algo])) + count[i]++; + + continue; + } + + while (size) { + 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, + bool is_digest_list) +{ + char *query_result = NULL, *query_result_ptr, *line; + char digest_list_info[MAX_LINE_LENGTH]; + char label[256]; + struct compact_list_hdr *hdr; + struct digest_list_item *digest_list; + unsigned long long size, size_info; + int ret, i, *count = NULL; + + count = calloc(num_max_digest_lists, sizeof(*count)); + if (!count) + return count; + + ret = digest_list_query(digest, algo, &query_result); + if (ret < 0) + goto out; + + query_result_ptr = query_result; + + while ((line = strsep(&query_result_ptr, "\n"))) { + if (!strlen(line)) + continue; + + for (i = 0; i < num_max_digest_lists; i++) { + if (!digest_lists[i]) + continue; + + digest_list = digest_lists[i]; + size = digest_list->size; + + if (is_digest_list) { + snprintf(label, sizeof(label), + "%s-%s-digest_list.%s", + hash_algo_name[digest_list->algo], + digest_list->digest_str, + digest_list->filename_suffix); + + /* From digest_query_show(). */ + size_info = snprintf(digest_list_info, + sizeof(digest_list_info), + "%s (actions: %d): type: %d, size: %lld\n", + label, digest_list->actions, + COMPACT_DIGEST_LIST, size); + + /* strsep() replaced '\n' with '\0' in line. */ + digest_list_info[size_info - 1] = '\0'; + + if (!strcmp(digest_list_info, line)) + count[i]++; + + continue; + } + + while (size) { + 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), + "%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n", + label, digest_list->actions, + hdr->version, + hash_algo_name[hdr->algo], hdr->type, + hdr->modifiers, hdr->count, + hdr->datalen); + + /* strsep() replaced '\n' with '\0' in line. */ + digest_list_info[size_info - 1] = '\0'; + + if (!strcmp(digest_list_info, line)) { + count[i]++; + break; + } + } + } + } +out: + free(query_result); + if (ret < 0) + free(count); + + return (!ret) ? count : NULL; +} + +static int compare_count(u8 *digest, enum hash_algo algo, + bool is_digest_list, struct __test_metadata *_metadata) +{ + int *count_gen_list_array, *count_kernel_query_array; + int count_gen_list = 0, count_kernel_query = 0; + char digest_str[64 * 2 + 1] = { 0 }; + int i; + + count_gen_list_array = get_count_gen_lists(digest, algo, + is_digest_list); + if (!count_gen_list_array) + return -EINVAL; + + count_kernel_query_array = get_count_kernel_query(digest, algo, + is_digest_list); + if (!count_kernel_query_array) { + free(count_gen_list_array); + return -EINVAL; + } + + for (i = 0; i < num_max_digest_lists; i++) { + count_gen_list += count_gen_list_array[i]; + count_kernel_query += count_kernel_query_array[i]; + } + + _bin2hex(digest_str, digest, hash_digest_size[algo]); + + TH_LOG("digest: %s, algo: %s, gen list digests: %d, kernel digests: %d", + digest_str, hash_algo_name[algo], count_gen_list, + count_kernel_query); + free(count_gen_list_array); + free(count_kernel_query_array); + return (count_gen_list == count_kernel_query) ? 0 : -EINVAL; +} + +static void digest_list_delete_all(struct __test_metadata *_metadata, + enum upload_types upload_type) +{ + int ret, i; + + for (i = 0; i < MAX_DIGEST_LISTS; i++) { + if (!digest_lists[i]) + continue; + + ret = digest_list_upload(digest_lists[i], DIGEST_LIST_DEL, + upload_type, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed\n"); + } + + free(digest_lists[i]->buf); + free(digest_lists[i]); + digest_lists[i] = NULL; + } +} + +FIXTURE(test) +{ + enum upload_types upload_type; +}; + +FIXTURE_SETUP(test) +{ +} + +FIXTURE_TEARDOWN(test) +{ + digest_list_delete_all(_metadata, self->upload_type); +} + +static int enable_fault_injection(void) +{ + int ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TASK_FILTER, "Y", 1, -1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_PROBABILITY, "1", 1, -1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TIMES, "10000", 5, -1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_VERBOSE, "1", 1, -1); + if (ret < 0) + return ret; + + ret = write_buffer(PROCFS_SELF_FAULT, "1", 1, -1); + if (ret < 0) + return ret; + + return 0; +} + +static void digest_list_add_del_test(struct __test_metadata *_metadata, + int fault_injection, + enum upload_types upload_type) +{ + u32 value; + enum ops op; + enum hash_algo algo; + u8 digest[64]; + int ret, i, cur_queries = 1; + + while (cur_queries <= NUM_QUERIES) { + ret = getrandom(&op, 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, -1); + /* Handle failures from fault injection. */ + if (fault_injection && ret < 0) { + TH_LOG("handle failure..."); + ret = digest_list_check( + digest_lists[digest_lists_pos], + DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_lists[digest_lists_pos]->buf); + free(digest_lists[digest_lists_pos]); + digest_lists[digest_lists_pos] = NULL; + break; + } + + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[digest_lists_pos], + op); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + break; + case DIGEST_LIST_DEL: + TH_LOG("delete digest list..."); + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + + for (i = 0; i < MAX_SEARCH_ATTEMPTS; i++) { + ret = getrandom(&digest_lists_pos, + sizeof(digest_lists_pos), 0); + ASSERT_EQ(sizeof(digest_lists_pos), ret) { + TH_LOG("getrandom() failed"); + } + + digest_lists_pos = + digest_lists_pos % num_max_digest_lists; + + if (digest_lists[digest_lists_pos]) + break; + } + + if (i == MAX_SEARCH_ATTEMPTS) { + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + } + + ret = digest_list_upload(digest_lists[digest_lists_pos], + op, upload_type, -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[digest_lists_pos], + op); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_lists[digest_lists_pos]->buf); + free(digest_lists[digest_lists_pos]); + digest_lists[digest_lists_pos] = NULL; + break; + default: + break; + } + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % 10; + + if (value != 1) + continue; + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % MAX_DIGEST_VALUE; + + ret = getrandom(&algo, sizeof(algo), 0); + ASSERT_EQ(sizeof(algo), ret) { + TH_LOG("getrandom() failed"); + } + + algo = algo % HASH_ALGO_RIPE_MD_128; + + memset(digest, 0, sizeof(digest)); + *(u32 *)digest = value; + + ret = compare_count(digest, algo, false, _metadata); + ASSERT_EQ(0, ret) { + TH_LOG("count mismatch"); + } + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % MAX_DIGEST_LISTS; + + if (digest_lists[value] != NULL) { + _hex2bin(digest, digest_lists[value]->digest_str, + hash_digest_size[digest_lists[value]->algo]); + + ret = compare_count(digest, digest_lists[value]->algo, + true, _metadata); + ASSERT_EQ(0, ret) { + TH_LOG("count mismatch"); + } + } + + TH_LOG("query digest lists (%d/%d)...", cur_queries, + NUM_QUERIES); + + cur_queries++; + } +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload, UINT_MAX) +{ + self->upload_type = UPLOAD_FILE; + digest_list_add_del_test(_metadata, 0, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload_fault, UINT_MAX) +{ + int ret; + + self->upload_type = UPLOAD_FILE; + + ret = enable_fault_injection(); + ASSERT_EQ(0, ret) { + TH_LOG("enable_fault_injection() failed"); + } + + digest_list_add_del_test(_metadata, 1, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload, UINT_MAX) +{ + self->upload_type = UPLOAD_BUFFER; + digest_list_add_del_test(_metadata, 0, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload_fault, UINT_MAX) +{ + int ret; + + self->upload_type = UPLOAD_BUFFER; + + ret = enable_fault_injection(); + ASSERT_EQ(0, ret) { + TH_LOG("enable_fault_injection() failed"); + } + + digest_list_add_del_test(_metadata, 1, self->upload_type); +} + +FIXTURE(test_fuzzing) +{ +}; + +FIXTURE_SETUP(test_fuzzing) +{ +} + +FIXTURE_TEARDOWN(test_fuzzing) +{ +} + +TEST_F_TIMEOUT(test_fuzzing, digest_list_fuzzing_test, UINT_MAX) +{ + char digests_count_before[256] = { 0 }; + char *digests_count_before_ptr = digests_count_before; + char digests_count_after[256] = { 0 }; + char *digests_count_after_ptr = digests_count_after; + size_t len = sizeof(digests_count_before) - 1; + 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, -1); + 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, + -1); + 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); +} + +#define IMA_MEASURE_RULES "dont_measure func=CRITICAL_DATA label=diglim euid=1000 \nmeasure func=CRITICAL_DATA label=diglim \n" + +/* This must be the last group of tests, otherwise an OOM will likely occur. */ +FIXTURE(test_measure) +{ +}; + +FIXTURE_SETUP(test_measure) +{ + char *cur_ima_policy = NULL; + size_t cur_ima_policy_len = 0; + bool rule_found = false; + int ret; + + ret = read_buffer(IMA_POLICY_PATH, &cur_ima_policy, &cur_ima_policy_len, + true, true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + rule_found = (strstr(cur_ima_policy, IMA_MEASURE_RULES) != NULL); + free(cur_ima_policy); + + if (!rule_found) { + ret = write_buffer(IMA_POLICY_PATH, IMA_MEASURE_RULES, + sizeof(IMA_MEASURE_RULES), -1); + ASSERT_EQ(0, ret) { + TH_LOG("write_buffer() failed"); + } + } +} + +FIXTURE_TEARDOWN(test_measure) +{ +} + +TEST_F_TIMEOUT(test_measure, digest_list_add_del_test_file_upload_measured, + UINT_MAX) +{ + int ret; + + digest_lists[0] = digest_list_generate(); + ASSERT_NE(NULL, digest_lists[0]) { + TH_LOG("digest_list_generate() failed"); + } + + digest_lists[0]->actions |= (1 << COMPACT_ACTION_IMA_MEASURED); + + ret = digest_list_upload(digest_lists[0], DIGEST_LIST_ADD, UPLOAD_FILE, + -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + 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, + -1); + 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; +} + +void digest_list_check_measurement_list_test_common( + struct __test_metadata *_metadata, + enum upload_types upload_type) +{ + char *measurement_list = NULL; + size_t measurement_list_len; + char event_digest_name[512]; + bool entry_found; + int ret; + + digest_lists[0] = digest_list_generate(); + ASSERT_NE(NULL, digest_lists[0]) { + TH_LOG("digest_list_generate() failed"); + } + + digest_lists[0]->actions |= (1 << COMPACT_ACTION_IMA_MEASURED); + + ret = digest_list_upload(digest_lists[0], DIGEST_LIST_ADD, upload_type, + -1); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[0], DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list, + &measurement_list_len, true, true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + snprintf(event_digest_name, sizeof(event_digest_name), + "%s:%s add_%s_digest_list.%s", + hash_algo_name[digest_lists[0]->algo], + digest_lists[0]->digest_str, + upload_type == UPLOAD_FILE ? "file" : "buffer", + digest_lists[0]->filename_suffix); + + entry_found = (strstr(measurement_list, event_digest_name) != NULL); + free(measurement_list); + + ASSERT_EQ(true, entry_found) { + TH_LOG("digest list not found in measurement list"); + } + + ret = digest_list_upload(digest_lists[0], DIGEST_LIST_DEL, upload_type, + 1000); + ASSERT_NE(0, ret) { + TH_LOG("digest_list_upload() success unexpected"); + } + + ret = digest_list_upload(digest_lists[0], DIGEST_LIST_DEL, upload_type, + -1); + 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"); + } + + measurement_list = NULL; + + ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list, + &measurement_list_len, true, true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + snprintf(event_digest_name, sizeof(event_digest_name), + "%s:%s del_%s_digest_list.%s", + hash_algo_name[digest_lists[0]->algo], + digest_lists[0]->digest_str, + upload_type == UPLOAD_FILE ? "file" : "buffer", + digest_lists[0]->filename_suffix); + + entry_found = (strstr(measurement_list, event_digest_name) != NULL); + free(measurement_list); + + ASSERT_EQ(true, entry_found) { + TH_LOG("digest list not found in measurement list"); + } + + free(digest_lists[0]->buf); + free(digest_lists[0]); + digest_lists[0] = NULL; +} + +TEST_F_TIMEOUT(test_measure, + digest_list_check_measurement_list_test_file_upload, UINT_MAX) +{ + digest_list_check_measurement_list_test_common(_metadata, UPLOAD_FILE); +} + +TEST_F_TIMEOUT(test_measure, + digest_list_check_measurement_list_test_buffer_upload, UINT_MAX) +{ + digest_list_check_measurement_list_test_common(_metadata, + UPLOAD_BUFFER); +} + +TEST_HARNESS_MAIN
linux-kselftest-mirror@lists.linaro.org