linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC][PATCH v2 00/13] integrity: Introduce a digest cache
@ 2023-08-12 10:46 Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 01/13] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
                   ` (13 more replies)
  0 siblings, 14 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Introduction
============

The main goal of Integrity Measurement Architecture (IMA) is to perform a
measurement of the file content and use it for remote attestation, to
report a possibly compromised system, using the TPM as a root of trust. It
can also prevent a system compromise from happening by checking the
calculated file digest against a known-good reference value and by denying
the current operation if there is a mismatch.


Motivation
==========

This patch set aims to address two important shortcomings: predictability
of the Platform Configuration Registers (PCRs), and the provisioning of
reference values to compare the calculated file digest against.

Remote attestation, according to Trusted Computing Group (TCG)
specifications, is done by replicating the PCR extend operation in
software with the digests in the event log (in this case the IMA
measurement list), and by comparing the obtained value with the PCR value
signed by the TPM with the quote operation.

Due to how the extend operation is performed, if measurements are done in
a different order, the final PCR value will be different. That means that
if measurements are done in parallel, there is no way to predict what the
final PCR value will be, making impossible to seal data to a PCR value. If
the PCR value was predictable, a system could for example prove its
integrity by unsealing and using its private key, without sending every
time the full list of measurements.

Provisioning reference values for file digests is also a difficult task.
The solution so far was to add file signatures to RPM packages, and
possibly to DEB packages, so that IMA can verify them. While this undoubtly
works, it also requires Linux distribution vendors to support the feature
by rebuilding all their packages, and eventually extending their PKI to
perform the additional signatures. It could also require developers extra
work to deal with the additional data.

On the other hand, since often packages carry the file digests themselves,
it won't be actually needed to add file signatures. If the kernel was able
to extract the file digests by itself, all the tasks mentioned above for
the Linux distribution vendors won't be needed too. All current and past
Linux distributions can be easily retrofitted to enable IMA appraisal with
the file digests from the packages.

Narrowing down the scope of a package parser to only extract specific
information makes it small enough to accurately verify that it cannot harm
the kernel. An additional mitigation consists in verifying the signature of
the package first, before attempting to extract the file digests.


Solution
========

To avoid a PCR is extended in a non-deterministic way, the proposed
solution is to replace individual file measurements with the measurement of
a file (the digest list) containing a set of file digests. If the
calculated digest of a file being measured/appraised matches one digest in
the set, its measurement is skipped. If otherwise there is no match, the
file digest is added to the measurement list.

The resulting measurement list, which cannot be done on the default IMA PCR
to avoid ambiguity with the default-style measurement, has the following
meaning: none/some/all files represented with the measurement of the digest
lists COULD have been accessed, without knowing IF and WHEN. Any other
measurement (other than boot_aggregate) is of a file whose digest was not
included in the digest list.

File signatures have a coarser granularity, it is per-signing key and not
per-package. A measurement list containing just the measurement of the
signing keys and the files without/invalid signature (those with valid
signature would be skipped) would be even less accurate.

To ensure a rapid and smooth deployment of IMA appraisal, the kernel has
been provided with the ability to extract file digests from the RPM
package headers, and add them to the kernel memory on demand (only when a
file from a given package is accessed). This ensures that the memory
consumption for this new feature is directly proportional to the usage of
the system.


Scope
=====

The integrity digest cache enables IMA to extend a PCR (not the default
one) in a deterministic fashion, and to appraise immutable files with file
digests from the packages, when no other appraisal method is available. It
does not yet support metadata verification with Extended Verification
Module (EVM), for which a separate patch set will be provided.


Design
======

The digest cache is a hash table of file digests, attached to the inode of
the digest list from which file digests are extracted. It is accessible,
when a given file is being measured/appraised, from the new xattr
security.digest_list, containing the path of the digest list itself.

If the calculated file digest is found in the digest cache, its measurement
is avoided, or read-only access is granted if appraisal is in enforcing
mode. Read-write access is prevented to avoid updating an unverified HMAC
of file metadata.

The digest cache can be used only if the following conditions are met:

- The 'digest_cache=content' keyword is added to the desired IMA policy
  rules;
- If the rule action is 'measure', a PCR different from the default one
  is specified;
- If the rule action is 'appraise', 'digest_cache=content' and
  'appraise_type' don't appear at the same time;
- The same action for which the digest cache is used was done also on the
  digest list;
- The digest cache is not (currently) used for measurement/appraisal of
  other digest lists.

For performance reasons, the digest cache is attached to every inode using
it, since multiple hooks can be invoked on it before the
measurement/appraisal result is cached. A reference count indicates how
many inodes use it, and only when it reaches zero, the digest cache can be
freed (for example when inodes are evicted from memory).

Two digest cache pointers have been added to the iint to distinguish for
which purpose they should be used: dig_owner points to the digest cache
created from the same inode the iint refers to, and should be used for
measurement/appraisal of other inodes; dig_user points to the digest
cache created from a different inode, and requested for
measurement/appraisal. One digest cache pointer would be confusing, as
for digest lists the digest cache was created from them, but IMA would
try to use that digest cache for measurement/appraisal of itself.

Finally, at the first digest list measurement, an iterator is executed to
sequentially read (not parse) all the digest lists in the same directory,
so that the PCR is extended in a deterministic fashion. The other
concurrent users of the digest cache have to wait until the iterator
finishes.


API
===

digest_cache_alloc(), digest_cache_parse_digest_list() and
digest_cache_new() are internal functions used during the creation and
initialization of the digest cache.

digest_cache_get() and digest_cache_free() are called by the user of the
digest cache (e.g. IMA), to obtain and free a digest cache.

digest_cache_init_htable(), digest_cache_add() and digest_cache_lookup()
are called by the digest list parsers to populate and search in a digest
cache.


Digest List Formats
===================

tlv
~~~

The Type-Length-Value (TLV) format was chosen for its extensibility.
Additional fields can be added without breaking compatibility with old
versions of the parser.

The layout of a tlv digest list is the following:

 [header: DIGEST_LIST_FILE, num fields, total len]
 [field: DIGEST_LIST_ALGO, length, value]
 [field: DIGEST_LIST_ENTRY#1, length, value (below)]
  |- [header: DIGEST_LIST_FILE, num fields, total len]
  |- [ENTRY#1_DIGEST, length, file digest]
  |- [ENTRY#1_PATH, length, file path]
 [field: DIGEST_LIST_ENTRY#N, length, value (below)]
  |- [header: DIGEST_LIST_FILE, num fields, total len]
  |- [ENTRY#N_DIGEST, length, file digest]
  |- [ENTRY#N_PATH, length, file path]

DIGEST_LIST_ALGO is a field to specify the algorithm of the file digest.
DIGEST_LIST_ENTRY is a nested TLV structure with the following fields:
ENTRY_DIGEST contains the file digest; ENTRY_PATH contains the file path.


rpm
~~~

The rpm digest list is basically a subset of the RPM package header.
Its format is:

 [RPM magic number]
 [RPMTAG_IMMUTABLE]

RPMTAG_IMMUTABLE is a section of the full RPM header containing the part
of the header that was signed, and whose signature is stored in the
RPMTAG_RSAHEADER section.


Appended Signature
~~~~~~~~~~~~~~~~~~

Digest lists can have a module-style appended signature, that can be used
for appraisal with IMA. The signature type can be PKCS#7, as for kernel
modules, or the new user asymmetric key signature.


History
=======

The original name of this work was IMA Digest Lists, which was somehow
considered too invasive. The code was moved to a separate component named
DIGLIM (DIGest Lists Integrity Module), with the purpose of removing the
complexity away of IMA, and also add the possibility of using it with other
kernel components (e.g. Integrity Policy Enforcement, or IPE).

Since it was originally proposed, in 2017, this work grew up a lot thanks
to various comments/suggestions. It became integrally part of the openEuler
distribution since end of 2020.

There are significant differences between this and the previous versions.
The most important one is moving from a centralized repository of file
digests to a per-package repository. This significantly reduces the memory
pressure, since digest lists are loaded into kernel memory only when they
are actually needed. Also, file digests are automatically unloaded from
kernel memory at the same time inodes are evicted from memory during
reclamation.


Performance
===========

The tests have been performed on a Fedora 38 virtual machine, with 8 cores
(AMD EPYC-Rome), 4GB of RAM, TPM passthrough. The signing key is an ECDSA
NIST P-384.

IMA measurement policy: no cache
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 dont_measure fsmagic=0x01021994
 measure func=BPRM_CHECK
 measure func=MMAP_CHECK


IMA measurement policy: cache
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 dont_measure fsmagic=0x01021994
 measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11
 measure func=BPRM_CHECK digest_cache=content pcr=11
 measure func=MMAP_CHECK digest_cache=content pcr=11


IMA Measurement Results
~~~~~~~~~~~~~~~~~~~~~~~

                               +-----------+-----------+-----------+
                               | # measur. | boot time |   slab    |
 +-----------------------------+-----------+-----------+-----------+
 | measure (no cache)          |    389    |  12.682s  | 231453 KB |
 +-----------------------------+-----------+-----------+-----------+
 | measure (cache, no iter)    |    175    |  12.283s  | 234224 KB |
 +-----------------------------+-----------+-----------+-----------+
 | measure (cache, iter)       |    853    |  16.430s  | 238491 KB |
 +-----------------------------+-----------+-----------+-----------+

With the iterator enabled, all 852 packages are measured. Consequently, the
boot time is longer. One possible optimization would be to exclude the
packages that don't include measured files. By disabling the iterator, it
can be seen that the packages actually used are 174 (one measurement is for
boot_aggregate).


IMA appraisal policy: no cache
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 dont_appraise fsmagic=0x01021994
 appraise func=BPRM_CHECK appraise_type=imasig
 appraise func=MMAP_CHECK appraise_type=imasig


IMA appraisal policy: cache
~~~~~~~~~~~~~~~~~~~~~~~~~~~

 dont_appraise fsmagic=0x01021994
 appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig
 appraise func=BPRM_CHECK digest_cache=content
 appraise func=MMAP_CHECK digest_cache=content


IMA Appraisal Results
~~~~~~~~~~~~~~~~~~~~~

                               +-----------+-----------+
                               | boot time |   slab    |
 +-----------------------------+-----------+-----------+
 | appraise (no cache)         |  11.995s  | 231145 KB |
 +-----------------------------+-----------+-----------+
 | appraise (cache)            |  11.879s  | 233091 KB |
 +-----------------------------+-----------+-----------+

In this test, it can be seen that the performance of the two solutions are
comparable, with the digest cache slightly ahead. The difference could be
more substantial with more file appraised.


How to Test
===========

First, it is necessary to copy the new kernel headers (tlv_parser.h,
uasym_parser.h, tlv_digest_list.h) from usr/include/linux in the kernel
source directory to /usr/include/linux.

Then, gpg must be rebuilt with the additional patches to convert the PGP
keys of the Linux distribution to the new user asymmetric key format:

 $ gpg --conv-kernel <path of PGP key> >> certs/uasym_keys.bin

This embeds the converted keys in the kernel image. Then, the following
kernel options must be enabled:

 CONFIG_INTEGRITY_DIGEST_CACHE=y
 CONFIG_UASYM_KEYS_SIGS=y
 CONFIG_UASYM_PRELOAD_PUBLIC_KEYS=y

and the kernel must be rebuilt with the patches applied. After boot, it is
necessary to build and install the digest list tool in tools/digest-lists,
and to execute (as root):

 # manage_digest_lists -o gen -d /etc/digest_lists -i rpmdb -f rpm

The new gpg must also be installed in the system, as it will be used to
convert the PGP signatures of the RPM headers to the user asymmetric key
format.

It is recommended to create an additional digest list with the following
files, by creating a file named 'list' with the content:

 /usr/bin/manage_digest_lists
 /usr/lib64/libgen-tlv-list.so
 /usr/lib64/libgen-rpm-list.so
 /usr/lib64/libparse-rpm-list.so
 /usr/lib64/libparse-tlv-list.so

Then, to create the digest list, it is sufficient to execute:

 # manage_digest_lists -i list -L -d /etc/digest_lists -o gen -f tlv

If appraisal is enabled and in enforcing mode, it is necessary to sign the
new digest list, with the sign-file tool in the scripts/ directory of the
kernel sources:

 # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/tlv-list

The final step is to add security.digest_list to each file with:

 # manage_digest_lists -i /etc/digest_lists -o add-xattr

After that, it is possible to test the integrity digest cache with the
following policy written to /etc/ima/ima-policy:

 dont_measure fsmagic=0x01021994
 measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11
 measure func=BPRM_CHECK digest_cache=content pcr=11
 measure func=MMAP_CHECK digest_cache=content pcr=11
 dont_appraise fsmagic=0x01021994
 appraise func=BPRM_CHECK digest_cache=content
 appraise func=MMAP_CHECK digest_cache=content
 appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig

Tmpfs is excluded for now, until memfd is properly handled.

Before loading the policy, it is possible to enable dynamic debug to see
which operations are done by the integrity digest cache:

 # echo "file tlv* +p" > /sys/kernel/debug/dynamic_debug/control
 # echo "file rpm* +p" > /sys/kernel/debug/dynamic_debug/control
 # echo "file digest* +p" > /sys/kernel/debug/dynamic_debug/control

Alternatively, the same strings can be set as value of the dyndbg= option
in the kernel command line.

A preliminary test, before booting the system with the new policy, is to
supply the policy to IMA in the current system with:

 # cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy

If that worked, the system can be rebooted. Systemd will take care of
loading the IMA policy at boot. The instructions have been tested on a
Fedora 38 OS.

After boot, it is possible to check the content of the measurement list:

 # cat /sys/kernel/security/ima/ascii_runtime_measurements

If only the files shipped with Fedora 38 have been executed, the
measurement list will contain only the digest lists, and not the individual
files.

Another test is to ensure that IMA prevents the execution of unknown files:

 # cp -a /bin/cat .
 # ./cat

That will work. But not on the modified binary:

 # echo 1 >> cat
 # cat
 -bash: ./cat: Permission denied

Execution will be denied, and a new entry in the measurement list will
appear (it would be probably ok to not add that entry, as access to the
file was denied):

 11 50b5a68bea0776a84eef6725f17ce474756e51c0 ima-ng sha256:15e1efee080fe54f5d7404af7e913de01671e745ce55215d89f3d6521d3884f0 /root/cat

Finally, it is possible to test the shrinking of the digest cache, by
forcing the kernel to evict inodes from memory:

 # echo 3 > /proc/sys/vm/drop_caches

The kernel log should have messages like:

 [  313.032536] DIGEST CACHE: Remove digest sha256:102900208eef27b766380135906d431dba87edaa7ec6aa72e6ebd3dd67f3a97b from digest list /etc/digest_lists/rpm-libseccomp-2.5.3-4.fc38.x86_64


Patch set dependencies
======================

This patch set depends on:

https://lore.kernel.org/linux-integrity/20230720153247.3755856-2-roberto.sassu@huaweicloud.com/

which allows to appraise RPM package headers with the PGP keys of Linux
distribution vendors.


Patch set content
=================

Patch 1 introduces a new hook to identify the loading of digest lists and
consequently appraise them.

Patches 2-4 implement the digest cache, and an iterator to prefetch the
digest lists to measure them in a deterministic way.

Patches 5-6 implement the currently supported digest list formats: tlv and
rpm. The tlv format relies on the TLV parser defined in the patch set
mentioned above.

Patches 7-9 enable the usage of the digest cache in IMA for measurement and
appraisal.

Patches 10-12 add a tool to manage digest lists.

Patch 13 adds the documentation of the integrity digest cache.


Changelog
=========

v1:
- Add documentation in Documentation/security/integrity-digest-cache.rst
- Pass the mask of IMA actions to digest_cache_alloc()
- Add a reference count to the digest cache
- Remove the path parameter from digest_cache_get(), and rely on the
  reference count to avoid the digest cache disappearing while being used
- Rename the dentry_to_check parameter of digest_cache_get() to dentry
- Rename digest_cache_get() to digest_cache_new() and add
  digest_cache_get() to set the digest cache in the iint of the inode for
  which the digest cache was requested
- Add dig_owner and dig_user to the iint, to distinguish from which inode
  the digest cache was created from, and which is using it; consequently it
  makes the digest cache usable to measure/appraise other digest caches
  (support not yet enabled)
- Add dig_owner_mutex and dig_user_mutex to serialize accesses to dig_owner
  and dig_user until they are initialized
- Enforce strong synchronization and make the contenders wait until
  dig_owner and dig_user are assigned to the iint the first time
- Move checking IMA actions on the digest list earlier, and fail if no
  action were performed (digest cache not usable)
- Remove digest_cache_put(), not needed anymore with the introduction of
  the reference count
- Fail immediately in digest_cache_lookup() if the digest algorithm is
  not set in the digest cache
- Use 64 bit mask for IMA actions on the digest list instead of 8 bit
- Return NULL in the inline version of digest_cache_get()
- Use list_add_tail() instead of list_add() in the iterator
- Copy the digest list path to a separate buffer in digest_cache_iter_dir()
- Use digest list parsers verified with Frama-C
- Explicitly disable (for now) the possibility in the IMA policy to use the
  digest cache to measure/appraise other digest lists
- Replace exit(<value>) with return <value> in manage_digest_lists.c

Roberto Sassu (13):
  ima: Introduce hook DIGEST_LIST_CHECK
  integrity: Introduce a digest cache
  integrity/digest_cache: Add functions to populate and search
  integrity/digest_cache: Prefetch digest lists in a directory
  integrity/digest_cache: Parse tlv digest lists
  integrity/digest_cache: Parse rpm digest lists
  ima: Add digest_cache policy keyword
  ima: Use digest cache for measurement
  ima: Use digest cache for appraisal
  tools: Add tool to manage digest lists
  tools/digest-lists: Add tlv digest list generator and parser
  tools/digest-lists: Add rpm digest list generator and parser
  docs: Add documentation of the integrity digest cache

 Documentation/ABI/testing/ima_policy          |   6 +-
 Documentation/security/index.rst              |   1 +
 .../security/integrity-digest-cache.rst       | 484 ++++++++++++++++++
 MAINTAINERS                                   |   2 +
 include/linux/kernel_read_file.h              |   1 +
 include/uapi/linux/tlv_digest_list.h          |  59 +++
 include/uapi/linux/xattr.h                    |   3 +
 security/integrity/Kconfig                    |  12 +
 security/integrity/Makefile                   |   4 +
 security/integrity/digest_cache.c             | 454 ++++++++++++++++
 security/integrity/digest_cache.h             | 110 ++++
 security/integrity/digest_cache_iter.c        | 160 ++++++
 .../integrity/digest_list_parsers/parsers.h   |  15 +
 security/integrity/digest_list_parsers/rpm.c  | 215 ++++++++
 security/integrity/digest_list_parsers/tlv.c  | 188 +++++++
 security/integrity/iint.c                     |  12 +
 security/integrity/ima/ima.h                  |  16 +-
 security/integrity/ima/ima_api.c              |  22 +-
 security/integrity/ima/ima_appraise.c         |  16 +-
 security/integrity/ima/ima_main.c             |  40 +-
 security/integrity/ima/ima_policy.c           |  59 ++-
 security/integrity/integrity.h                |   8 +
 tools/Makefile                                |  16 +-
 tools/digest-lists/.gitignore                 |   7 +
 tools/digest-lists/Makefile                   |  72 +++
 tools/digest-lists/common.c                   | 163 ++++++
 tools/digest-lists/common.h                   |  90 ++++
 tools/digest-lists/generators/generators.h    |  18 +
 tools/digest-lists/generators/rpm.c           | 257 ++++++++++
 tools/digest-lists/generators/tlv.c           | 168 ++++++
 tools/digest-lists/manage_digest_lists.c      | 349 +++++++++++++
 tools/digest-lists/manage_digest_lists.txt    |  82 +++
 tools/digest-lists/parsers/parsers.h          |  16 +
 tools/digest-lists/parsers/rpm.c              | 169 ++++++
 tools/digest-lists/parsers/tlv.c              | 195 +++++++
 tools/digest-lists/parsers/tlv_parser.h       |  38 ++
 36 files changed, 3501 insertions(+), 26 deletions(-)
 create mode 100644 Documentation/security/integrity-digest-cache.rst
 create mode 100644 include/uapi/linux/tlv_digest_list.h
 create mode 100644 security/integrity/digest_cache.c
 create mode 100644 security/integrity/digest_cache.h
 create mode 100644 security/integrity/digest_cache_iter.c
 create mode 100644 security/integrity/digest_list_parsers/parsers.h
 create mode 100644 security/integrity/digest_list_parsers/rpm.c
 create mode 100644 security/integrity/digest_list_parsers/tlv.c
 create mode 100644 tools/digest-lists/.gitignore
 create mode 100644 tools/digest-lists/Makefile
 create mode 100644 tools/digest-lists/common.c
 create mode 100644 tools/digest-lists/common.h
 create mode 100644 tools/digest-lists/generators/generators.h
 create mode 100644 tools/digest-lists/generators/rpm.c
 create mode 100644 tools/digest-lists/generators/tlv.c
 create mode 100644 tools/digest-lists/manage_digest_lists.c
 create mode 100644 tools/digest-lists/manage_digest_lists.txt
 create mode 100644 tools/digest-lists/parsers/parsers.h
 create mode 100644 tools/digest-lists/parsers/rpm.c
 create mode 100644 tools/digest-lists/parsers/tlv.c
 create mode 100644 tools/digest-lists/parsers/tlv_parser.h

-- 
2.34.1


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

* [RFC][PATCH v2 01/13] ima: Introduce hook DIGEST_LIST_CHECK
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 02/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Introduce a new hook to check the integrity of digest lists, whose digests
are added to the digest cache.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/ABI/testing/ima_policy | 1 +
 include/linux/kernel_read_file.h     | 1 +
 security/integrity/ima/ima.h         | 1 +
 security/integrity/ima/ima_main.c    | 3 ++-
 security/integrity/ima/ima_policy.c  | 3 +++
 5 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy
index 49db0ff288e..14d92c687ef 100644
--- a/Documentation/ABI/testing/ima_policy
+++ b/Documentation/ABI/testing/ima_policy
@@ -36,6 +36,7 @@ Description:
 				[KEXEC_KERNEL_CHECK] [KEXEC_INITRAMFS_CHECK]
 				[KEXEC_CMDLINE] [KEY_CHECK] [CRITICAL_DATA]
 				[SETXATTR_CHECK][MMAP_CHECK_REQPROT]
+				[DIGEST_LIST_CHECK]
 			mask:= [[^]MAY_READ] [[^]MAY_WRITE] [[^]MAY_APPEND]
 			       [[^]MAY_EXEC]
 			fsmagic:= hex value
diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h
index 90451e2e12b..85f602e49e2 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/ima/ima.h b/security/integrity/ima/ima.h
index c29db699c99..3aef3d8fb57 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -203,6 +203,7 @@ static inline unsigned int ima_hash_key(u8 *digest)
 	hook(KEY_CHECK, key)				\
 	hook(CRITICAL_DATA, critical_data)		\
 	hook(SETXATTR_CHECK, setxattr_check)		\
+	hook(DIGEST_LIST_CHECK, digest_list_check)	\
 	hook(MAX_CHECK, none)
 
 #define __ima_hook_enumify(ENUM, str)	ENUM,
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 365db0e43d7..81abdc8b233 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -771,7 +771,8 @@ const int read_idmap[READING_MAX_ID] = {
 	[READING_MODULE] = MODULE_CHECK,
 	[READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK,
 	[READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK,
-	[READING_POLICY] = POLICY_CHECK
+	[READING_POLICY] = POLICY_CHECK,
+	[READING_DIGEST_LIST] = DIGEST_LIST_CHECK,
 };
 
 /**
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index c9b3bd8f1bb..b32c83d8a72 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1287,6 +1287,7 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
 	case MODULE_CHECK:
 	case KEXEC_KERNEL_CHECK:
 	case KEXEC_INITRAMFS_CHECK:
+	case DIGEST_LIST_CHECK:
 		if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC |
 				     IMA_UID | IMA_FOWNER | IMA_FSUUID |
 				     IMA_INMASK | IMA_EUID | IMA_PCR |
@@ -1530,6 +1531,8 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
 				entry->func = CRITICAL_DATA;
 			else if (strcmp(args[0].from, "SETXATTR_CHECK") == 0)
 				entry->func = SETXATTR_CHECK;
+			else if (strcmp(args[0].from, "DIGEST_LIST_CHECK") == 0)
+				entry->func = DIGEST_LIST_CHECK;
 			else
 				result = -EINVAL;
 			if (!result)
-- 
2.34.1


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

* [RFC][PATCH v2 02/13] integrity: Introduce a digest cache
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 01/13] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-14 17:03   ` Jarkko Sakkinen
  2023-08-12 10:46 ` [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search Roberto Sassu
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Introduce the digest cache, a structure holding a hash table of digests,
extracted from a digest list. Its pointer is stored in the iint of the
digest list the digest cache was created from (dig_owner field), and in
the iint of the inodes for which the digest cache is used (dig_user field).
The digest cache has also a reference count to track how many iints are
referencing it.

For simplicity, the digest cache is created only once, from the first read.
Further modifications of the digest lists, if they are ever allowed, are
ignored.

Introduce two methods to manage the digest cache: digest_cache_get() and
digest_cache_free(). The first creates and returns a digest cache created
from the digest list whose path is stored in the security.digest_list xattr
of the inode being measured/appraised.

The second is called at the time an iint is freed. When the digest cache
reference count reaches zero, the digest cache is also freed.

Each digest cache pointer in the iint is protected by the respective
mutex. Dig_owner_mutex ensures that the first caller of digest_cache_get()
creates and initializes dig_owner, and ensures that the other callers wait
until the creation and initialization is complete.

Dig_user_mutex serializes calls to digest_cache_get() by processes
accessing the same inode, and ensures that only the first assigns dig_user.

The digest cache also stores which IMA actions have been done to the
digest list, and is usable for the same actions done to an inode.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 include/uapi/linux/xattr.h        |   3 +
 security/integrity/Kconfig        |  12 ++
 security/integrity/Makefile       |   1 +
 security/integrity/digest_cache.c | 317 ++++++++++++++++++++++++++++++
 security/integrity/digest_cache.h |  81 ++++++++
 security/integrity/iint.c         |  12 ++
 security/integrity/integrity.h    |   8 +
 7 files changed, 434 insertions(+)
 create mode 100644 security/integrity/digest_cache.c
 create mode 100644 security/integrity/digest_cache.h

diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
index 9463db2dfa9..8a58cf4bce6 100644
--- a/include/uapi/linux/xattr.h
+++ b/include/uapi/linux/xattr.h
@@ -54,6 +54,9 @@
 #define XATTR_IMA_SUFFIX "ima"
 #define XATTR_NAME_IMA XATTR_SECURITY_PREFIX XATTR_IMA_SUFFIX
 
+#define XATTR_DIGEST_LIST_SUFFIX "digest_list"
+#define XATTR_NAME_DIGEST_LIST XATTR_SECURITY_PREFIX XATTR_DIGEST_LIST_SUFFIX
+
 #define XATTR_SELINUX_SUFFIX "selinux"
 #define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX
 
diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig
index ec6e0d789da..df8a1f7e6e2 100644
--- a/security/integrity/Kconfig
+++ b/security/integrity/Kconfig
@@ -130,6 +130,18 @@ config INTEGRITY_AUDIT
 	  be enabled by specifying 'integrity_audit=1' on the kernel
 	  command line.
 
+config INTEGRITY_DIGEST_CACHE
+	bool "Enable the integrity digest cache"
+	depends on INTEGRITY
+	default n
+	help
+	   This option enables a cache of digests from a digest list, possibly
+	   authenticated with a signature.
+
+	   The digest cache can be used to make a TPM PCR predictable
+	   (by skipping the measurement of cached digests), or for appraisal
+	   with already available sources (e.g. RPM packages).
+
 source "security/integrity/ima/Kconfig"
 source "security/integrity/evm/Kconfig"
 
diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index d0ffe37dc1d..0c175a567ac 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -11,6 +11,7 @@ integrity-$(CONFIG_INTEGRITY_SIGNATURE) += digsig.o
 integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o
 integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyring.o
 integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o
+integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o
 integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \
 				      platform_certs/load_uefi.o \
 				      platform_certs/keyring_handler.o
diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
new file mode 100644
index 00000000000..4201c68171a
--- /dev/null
+++ b/security/integrity/digest_cache.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019  IBM Corporation
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Implement the integrity digest cache.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/init_task.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/xattr.h>
+#include <linux/kernel_read_file.h>
+#include <linux/module_signature.h>
+
+#include "integrity.h"
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+#define pr_fmt(fmt) "DIGEST CACHE: "fmt
+
+/**
+ * digest_cache_alloc - Allocate and initialize a new digest cache
+ * @path_str: Path of the digest list
+ * @digest_cache_mask: Actions done by IMA on the digest list
+ *
+ * This function allocates a new digest cache and initializes all fields of
+ * the digest_cache structure.
+ *
+ * Return: A digest_cache structure on success, NULL on error.
+ */
+static struct digest_cache *digest_cache_alloc(char *path_str,
+					       u64 digest_cache_mask)
+{
+	struct digest_cache *digest_cache;
+
+	digest_cache = kmalloc(sizeof(*digest_cache), GFP_KERNEL);
+	if (!digest_cache)
+		return digest_cache;
+
+	digest_cache->algo = HASH_ALGO__LAST;
+	digest_cache->path_str = path_str;
+	digest_cache->mask = digest_cache_mask;
+	digest_cache->slots = NULL;
+	digest_cache->num_slots = 0;
+	/*
+	 * One for dig_owner of the digest list iint, one for dig_user of the
+	 * iint of the inode for which the digest cache is used.
+	 */
+	atomic_set(&digest_cache->ref_count, 2);
+	return digest_cache;
+}
+
+/**
+ * digest_cache_free - Free all memory occupied by a digest cache
+ * @digest_cache: Digest cache
+ *
+ * This function frees the digests associated to the digest cache and the
+ * digest cache itself.
+ */
+void digest_cache_free(struct digest_cache *digest_cache)
+{
+	struct digest_cache_entry *p;
+	struct hlist_node *q;
+	int digest_len, i;
+
+	if (!digest_cache)
+		return;
+
+	pr_debug("Free cache (ref count: %d), algo: %s, digest list: %s",
+		 atomic_read(&digest_cache->ref_count),
+		 hash_algo_name[digest_cache->algo], digest_cache->path_str);
+
+	if (!atomic_dec_and_test(&digest_cache->ref_count))
+		return;
+
+	digest_len = hash_digest_size[digest_cache->algo];
+
+	for (i = 0; i < digest_cache->num_slots; i++) {
+		hlist_for_each_entry_safe(p, q, &digest_cache->slots[i],
+					  hnext) {
+			hlist_del(&p->hnext);
+			pr_debug("Remove digest %s:%*phN from digest list %s\n",
+				 hash_algo_name[digest_cache->algo],
+				 digest_len, p->digest, digest_cache->path_str);
+			kfree(p);
+		}
+	}
+
+	pr_debug("Freed cache (ref count: %d), algo: %s, digest list: %s",
+		 atomic_read(&digest_cache->ref_count),
+		 hash_algo_name[digest_cache->algo], digest_cache->path_str);
+
+	kfree(digest_cache->path_str);
+	kfree(digest_cache->slots);
+	kfree(digest_cache);
+}
+
+/**
+ * digest_cache_parse_digest_list - Parse a digest list
+ * @digest_cache: Digest cache
+ * @digest_list_path: Path of the digest list
+ * @data: Data to parse
+ * @data_len: Length of @data
+ *
+ * This function parses a digest list. First, it strips the module-style
+ * appended signature, if present. Then, it selects the parser to call from
+ * the beginning of the file name, which is expected to be in the format:
+ * <digest list format>-<digest list file name>.
+ *
+ * Return: Zero on success, a negative value on error.
+ */
+static int digest_cache_parse_digest_list(struct digest_cache *digest_cache,
+					  struct path *digest_list_path,
+					  void *data, size_t data_len)
+{
+	const size_t marker_len = strlen(MODULE_SIG_STRING);
+	const struct module_signature *sig;
+	size_t sig_len;
+	const void *p;
+	int ret = -EINVAL;
+
+	/* From ima_modsig.c */
+	if (data_len <= marker_len + sizeof(*sig))
+		goto parse;
+
+	p = data + data_len - marker_len;
+	if (memcmp(p, MODULE_SIG_STRING, marker_len))
+		goto parse;
+
+	data_len -= marker_len;
+	sig = (const struct module_signature *)(p - sizeof(*sig));
+
+	sig_len = be32_to_cpu(sig->sig_len);
+	data_len -= sig_len + sizeof(*sig);
+parse:
+	pr_debug("Parsing %s, size: %ld\n", digest_cache->path_str, data_len);
+
+	return ret;
+}
+
+/**
+ * digest_cache_new - Create a new digest cache
+ * @dentry: Dentry of the file being measured/appraised
+ *
+ * This function retrieves the path of the digest list from the
+ * security.digest_list xattr of the file being measured/appraised. It then
+ * opens and parses the digest list, and finally instantiates a new digest
+ * cache.
+ *
+ * After read, the IMA actions done on the digest list are recorded in the
+ * digest cache. The use of the digest cache is allowed for measuring/appraising
+ * a file, only if the same action has been done on the digest list itself.
+ *
+ * The invoked parser will in turn set the digest algorithm, initialize the
+ * hash table and add the extracted digests to the digest cache.
+ *
+ * Return: A new digest cache on success, NULL on error.
+ */
+static struct digest_cache *digest_cache_new(struct dentry *dentry)
+{
+	struct integrity_iint_cache *digest_list_iint;
+	struct digest_cache *digest_cache = NULL;
+	struct path digest_list_path;
+	char *path_str = NULL;
+	struct file *file;
+	void *data = NULL;
+	size_t data_len = 0;
+	struct inode *inode;
+	u64 digest_cache_mask = 0;
+	int ret;
+
+	ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_DIGEST_LIST,
+				 &path_str, 0, GFP_NOFS);
+	if (ret <= 0) {
+		pr_debug("%s xattr not found in %s\n", XATTR_NAME_DIGEST_LIST,
+			 dentry->d_name.name);
+		return digest_cache;
+	}
+
+	pr_debug("Found %s xattr in %s, digest list: %s\n",
+		 XATTR_NAME_DIGEST_LIST, dentry->d_name.name, path_str);
+
+	ret = kern_path(path_str, 0, &digest_list_path);
+	if (ret < 0) {
+		pr_debug("Cannot open digest list %s\n", path_str);
+		goto out;
+	}
+
+	inode = d_backing_inode(digest_list_path.dentry);
+
+	digest_list_iint = integrity_inode_get(inode);
+	if (!digest_list_iint) {
+		pr_debug("Cannot get integrity metadata for digest list %s\n",
+			 path_str);
+		goto out_path;
+	}
+
+	if (digest_list_iint->dig_owner) {
+		pr_debug("Cache for digest list %s exists\n", path_str);
+		digest_cache = digest_list_iint->dig_owner;
+		atomic_inc(&digest_cache->ref_count);
+		goto out_path;
+	}
+
+	mutex_lock(&digest_list_iint->dig_owner_mutex);
+
+	if (digest_list_iint->dig_owner) {
+		pr_debug("Cache for digest list %s exists\n", path_str);
+		digest_cache = digest_list_iint->dig_owner;
+		atomic_inc(&digest_cache->ref_count);
+		goto out_unlock;
+	}
+
+	file = dentry_open(&digest_list_path, O_RDONLY, &init_cred);
+	if (IS_ERR(file)) {
+		pr_debug("Unable to open digest list %s\n", path_str);
+		goto out_unlock;
+	}
+
+	/* Write-lock the file to avoid getting outdated iint->flags. */
+	ret = deny_write_access(file);
+	if (ret < 0) {
+		pr_err("Unable to write-lock digest list %s", path_str);
+		goto out_fput;
+	}
+
+	ret = kernel_read_file(file, 0, &data, INT_MAX, NULL,
+			       READING_DIGEST_LIST);
+	if (ret < 0) {
+		pr_debug("Unable to read digest list %s\n", path_str);
+		goto out_allow;
+	}
+
+	if (digest_list_iint->flags & IMA_MEASURED)
+		digest_cache_mask |= DIGEST_CACHE_MEASURE;
+	if (digest_list_iint->flags & IMA_APPRAISED_SUBMASK)
+		digest_cache_mask |= DIGEST_CACHE_APPRAISE_CONTENT;
+
+	if (!digest_cache_mask) {
+		pr_debug("No actions done on digest list %s\n", path_str);
+		ret = -ENOENT;
+		goto out_vfree;
+	}
+
+	data_len = ret;
+
+	digest_cache = digest_cache_alloc(path_str, digest_cache_mask);
+	if (!digest_cache)
+		goto out_vfree;
+
+	/* Freed by digest_cache_free(). */
+	path_str = NULL;
+
+	/*
+	 * Digest list parsers must set the digest algorithm, initialize the
+	 * hash table and add the digests.
+	 */
+	ret = digest_cache_parse_digest_list(digest_cache, &digest_list_path,
+					     data, data_len);
+	if (ret < 0) {
+		pr_debug("Error parsing digest list %s, ret: %d\n",
+			 digest_cache->path_str, ret);
+		digest_cache_free(digest_cache);
+		digest_cache = NULL;
+		goto out_vfree;
+	}
+
+	digest_list_iint->dig_owner = digest_cache;
+
+	pr_debug("New cache (ref count: %d), algo: %s, digest list: %s, mask: %llu\n",
+		 atomic_read(&digest_cache->ref_count),
+		 hash_algo_name[digest_cache->algo], digest_cache->path_str,
+		 digest_cache->mask);
+out_vfree:
+	vfree(data);
+out_allow:
+	allow_write_access(file);
+out_fput:
+	fput(file);
+out_unlock:
+	mutex_unlock(&digest_list_iint->dig_owner_mutex);
+out_path:
+	path_put(&digest_list_path);
+out:
+	kfree(path_str);
+	return digest_cache;
+}
+
+/**
+ * digest_cache_get - Obtain a digest cache and set it in the iint
+ * @dentry: Dentry of the file being measured/appraised
+ * @iint: Integrity inode cache of the file being measured/appraised
+ *
+ * Obtain a digest cache, and set it in the dig_user field of the passed iint,
+ * if not already done.
+ *
+ * Return: A digest cache on success, NULL otherwise.
+ */
+struct digest_cache *digest_cache_get(struct dentry *dentry,
+				      struct integrity_iint_cache *iint)
+{
+	if (iint->dig_user)
+		return iint->dig_user;
+
+	mutex_lock(&iint->dig_user_mutex);
+	if (!iint->dig_user)
+		iint->dig_user = digest_cache_new(dentry);
+	mutex_unlock(&iint->dig_user_mutex);
+
+	return iint->dig_user;
+}
diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
new file mode 100644
index 00000000000..ff88e8593c6
--- /dev/null
+++ b/security/integrity/digest_cache.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Header of the integrity digest cache.
+ */
+
+#ifndef _DIGEST_CACHE_H
+#define _DIGEST_CACHE_H
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <crypto/hash_info.h>
+
+/* Depth if elements were uniformly distributed in the hash table slots. */
+#define DIGEST_CACHE_HTABLE_DEPTH 30
+
+/* There is no explicit concept of metadata measurement in IMA. */
+#define DIGEST_CACHE_MEASURE   0x01
+#define DIGEST_CACHE_APPRAISE_CONTENT  0x02
+
+struct integrity_iint_cache;
+
+/**
+ * struct digest_cache - Digest cache
+ * @slots: Hash table slots
+ * @num_slots: Number of slots
+ * @ref_count: Number of references to the digest cache
+ * @algo: Algorithm of digests stored in the cache
+ * @path_str: Path of the digest list the cache was created from
+ * @mask: For which IMA actions and purpose the digest cache can be used
+ *
+ * This structure represents a cache of digests extracted from a file, to be
+ * primarily used for IMA measurement and appraisal.
+ */
+struct digest_cache {
+	struct hlist_head *slots;
+	unsigned int num_slots;
+	atomic_t ref_count;
+	enum hash_algo algo;
+	char *path_str;
+	u64 mask;
+};
+
+/**
+ * struct digest_cache_entry - Entry of a digest cache
+ * @hnext: Pointer to the next element in the collision list
+ * @digest: Stored digest
+ *
+ * This structure represents an entry of a digest cache, storing a digest.
+ */
+struct digest_cache_entry {
+	struct hlist_node hnext;
+	u8 digest[];
+} __packed;
+
+static inline unsigned int digest_cache_hash_key(u8 *digest,
+						 unsigned int num_slots)
+{
+	return (digest[0] | digest[1] << 8) % num_slots;
+}
+
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+void digest_cache_free(struct digest_cache *digest_cache);
+struct digest_cache *digest_cache_get(struct dentry *dentry,
+				      struct integrity_iint_cache *iint);
+#else
+static inline void digest_cache_free(struct digest_cache *digest_cache)
+{
+}
+
+static inline struct digest_cache *
+digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint)
+{
+	return NULL;
+}
+
+#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
+#endif /* _DIGEST_CACHE_H */
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index a462df827de..68ec73172e3 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -80,6 +80,12 @@ static void iint_free(struct integrity_iint_cache *iint)
 	iint->ima_creds_status = INTEGRITY_UNKNOWN;
 	iint->evm_status = INTEGRITY_UNKNOWN;
 	iint->measured_pcrs = 0;
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+	digest_cache_free(iint->dig_owner);
+	digest_cache_free(iint->dig_user);
+	iint->dig_owner = NULL;
+	iint->dig_user = NULL;
+#endif
 	kmem_cache_free(iint_cache, iint);
 }
 
@@ -165,6 +171,12 @@ static void init_once(void *foo)
 	iint->ima_creds_status = INTEGRITY_UNKNOWN;
 	iint->evm_status = INTEGRITY_UNKNOWN;
 	mutex_init(&iint->mutex);
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+	iint->dig_owner = NULL;
+	iint->dig_user = NULL;
+	mutex_init(&iint->dig_owner_mutex);
+	mutex_init(&iint->dig_user_mutex);
+#endif
 }
 
 static int __init integrity_iintcache_init(void)
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 7167a6e99bd..0192f81c67f 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -19,6 +19,8 @@
 #include <linux/key.h>
 #include <linux/audit.h>
 
+#include "digest_cache.h"
+
 /* iint action cache flags */
 #define IMA_MEASURE		0x00000001
 #define IMA_MEASURED		0x00000002
@@ -171,6 +173,12 @@ struct integrity_iint_cache {
 	enum integrity_status ima_creds_status:4;
 	enum integrity_status evm_status:4;
 	struct ima_digest_data *ima_hash;
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+	struct digest_cache *dig_owner;		/* created from this inode */
+	struct mutex dig_owner_mutex;		/* protects dig_owner */
+	struct digest_cache *dig_user;		/* user of the digest cache */
+	struct mutex dig_user_mutex;		/* protects dig_user */
+#endif
 };
 
 /* rbtree tree calls to lookup, insert, delete
-- 
2.34.1


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

* [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 01/13] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 02/13] integrity: Introduce a digest cache Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-14 17:13   ` Jarkko Sakkinen
  2023-08-12 10:46 ` [RFC][PATCH v2 04/13] integrity/digest_cache: Prefetch digest lists in a directory Roberto Sassu
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Add digest_cache_init_htable(), to size a hash table depending on the
number of digests to be added to the cache.

Add digest_cache_add() and digest_cache_lookup() to respectively add and
lookup a digest in the digest cache.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 security/integrity/digest_cache.c | 131 ++++++++++++++++++++++++++++++
 security/integrity/digest_cache.h |  24 ++++++
 2 files changed, 155 insertions(+)

diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
index 4201c68171a..d14d84b804b 100644
--- a/security/integrity/digest_cache.c
+++ b/security/integrity/digest_cache.c
@@ -315,3 +315,134 @@ struct digest_cache *digest_cache_get(struct dentry *dentry,
 
 	return iint->dig_user;
 }
+
+/**
+ * digest_cache_init_htable - Allocate and initialize the hash table
+ * @digest_cache: Digest cache
+ * @num_digests: Number of digests to add to the digest cache
+ *
+ * This function allocates and initializes the hash table. Its size is
+ * determined by the number of digests to add to the digest cache, known
+ * at this point by the parser calling this function.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+int digest_cache_init_htable(struct digest_cache *digest_cache,
+			     u64 num_digests)
+{
+	int i;
+
+	if (!digest_cache)
+		return 0;
+
+	digest_cache->num_slots = num_digests / DIGEST_CACHE_HTABLE_DEPTH;
+	if (!digest_cache->num_slots)
+		digest_cache->num_slots = 1;
+
+	digest_cache->slots = kmalloc_array(num_digests,
+					    sizeof(*digest_cache->slots),
+					    GFP_KERNEL);
+	if (!digest_cache->slots)
+		return -ENOMEM;
+
+	for (i = 0; i < digest_cache->num_slots; i++)
+		INIT_HLIST_HEAD(&digest_cache->slots[i]);
+
+	pr_debug("Initialized %d hash table slots for digest list %s\n",
+		 digest_cache->num_slots, digest_cache->path_str);
+	return 0;
+}
+
+/**
+ * digest_cache_add - Add a new digest to the digest cache
+ * @digest_cache: Digest cache
+ * @digest: Digest to add
+ *
+ * This function, invoked by a digest list parser, adds a digest extracted
+ * from a digest list to the digest cache.
+ *
+ * Return: Zero on success, a negative value on error.
+ */
+int digest_cache_add(struct digest_cache *digest_cache, u8 *digest)
+{
+	struct digest_cache_entry *entry;
+	unsigned int key;
+	int digest_len;
+
+	if (!digest_cache)
+		return 0;
+
+	digest_len = hash_digest_size[digest_cache->algo];
+
+	entry = kmalloc(sizeof(*entry) + digest_len, GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	memcpy(entry->digest, digest, digest_len);
+
+	key = digest_cache_hash_key(digest, digest_cache->num_slots);
+	hlist_add_head(&entry->hnext, &digest_cache->slots[key]);
+	pr_debug("Add digest %s:%*phN from digest list %s\n",
+		 hash_algo_name[digest_cache->algo], digest_len, digest,
+		 digest_cache->path_str);
+	return 0;
+}
+
+/**
+ * digest_cache_lookup - Searches a digest in the digest cache
+ * @digest_cache: Digest cache
+ * @digest: Digest to search
+ * @algo: Algorithm of the digest to search
+ * @pathname: Path of the file whose digest is looked up
+ *
+ * This function, invoked by IMA or EVM, searches the calculated digest of
+ * a file or file metadata in the digest cache acquired with
+ * digest_cache_get().
+ *
+ * Return: Zero if the digest is found, a negative value if not.
+ */
+int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
+			enum hash_algo algo, const char *pathname)
+{
+	struct digest_cache_entry *entry;
+	unsigned int key;
+	int digest_len;
+	int search_depth = 0;
+
+	if (!digest_cache)
+		return -ENOENT;
+
+	if (digest_cache->algo == HASH_ALGO__LAST) {
+		pr_debug("Algorithm not set for digest list %s\n",
+			 digest_cache->path_str);
+		return -ENOENT;
+	}
+
+	digest_len = hash_digest_size[digest_cache->algo];
+
+	if (algo != digest_cache->algo) {
+		pr_debug("Algo mismatch for file %s, digest %s:%*phN in digest list %s (%s)\n",
+			 pathname, hash_algo_name[algo], digest_len, digest,
+			 digest_cache->path_str,
+			 hash_algo_name[digest_cache->algo]);
+		return -ENOENT;
+	}
+
+	key = digest_cache_hash_key(digest, digest_cache->num_slots);
+
+	hlist_for_each_entry_rcu(entry, &digest_cache->slots[key], hnext) {
+		if (!memcmp(entry->digest, digest, digest_len)) {
+			pr_debug("Cache hit at depth %d for file %s, digest %s:%*phN in digest list %s\n",
+				 search_depth, pathname, hash_algo_name[algo],
+				 digest_len, digest, digest_cache->path_str);
+			return 0;
+		}
+
+		search_depth++;
+	}
+
+	pr_debug("Cache miss for file %s, digest %s:%*phN in digest list %s\n",
+		 pathname, hash_algo_name[algo], digest_len, digest,
+		 digest_cache->path_str);
+	return -ENOENT;
+}
diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
index ff88e8593c6..01cd70f9850 100644
--- a/security/integrity/digest_cache.h
+++ b/security/integrity/digest_cache.h
@@ -66,6 +66,11 @@ static inline unsigned int digest_cache_hash_key(u8 *digest,
 void digest_cache_free(struct digest_cache *digest_cache);
 struct digest_cache *digest_cache_get(struct dentry *dentry,
 				      struct integrity_iint_cache *iint);
+int digest_cache_init_htable(struct digest_cache *digest_cache,
+			     u64 num_digests);
+int digest_cache_add(struct digest_cache *digest_cache, u8 *digest);
+int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
+			enum hash_algo algo, const char *pathname);
 #else
 static inline void digest_cache_free(struct digest_cache *digest_cache)
 {
@@ -77,5 +82,24 @@ digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint)
 	return NULL;
 }
 
+static inline int digest_cache_init_htable(struct digest_cache *digest_cache,
+					   u64 num_digests)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int digest_cache_add(struct digest_cache *digest_cache,
+				   u8 *digest)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int digest_cache_lookup(struct digest_cache *digest_cache,
+				      u8 *digest, enum hash_algo algo,
+				      const char *pathname)
+{
+	return -ENOENT;
+}
+
 #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
 #endif /* _DIGEST_CACHE_H */
-- 
2.34.1


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

* [RFC][PATCH v2 04/13] integrity/digest_cache: Prefetch digest lists in a directory
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (2 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 05/13] integrity/digest_cache: Parse tlv digest lists Roberto Sassu
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Prefetch the digest lists in the same directory as the requested one, to
measure them in a deterministic way, and obtain a predictable PCR.

Prefetching does not imply parsing, so there won't be significant memory
consumption. However, since all PCR extend operations are done all at
the same time, this iteration is expected to introduce a noticeable delay
to the first file operation for which the digest cache is used.

This patch assumes for now that all digest lists are stored in the same
directory.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 security/integrity/Makefile            |   3 +-
 security/integrity/digest_cache.h      |   5 +
 security/integrity/digest_cache_iter.c | 160 +++++++++++++++++++++++++
 3 files changed, 167 insertions(+), 1 deletion(-)
 create mode 100644 security/integrity/digest_cache_iter.c

diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index 0c175a567ac..c856ed10fba 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -11,7 +11,8 @@ integrity-$(CONFIG_INTEGRITY_SIGNATURE) += digsig.o
 integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o
 integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyring.o
 integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o
-integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o
+integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o \
+					      digest_cache_iter.o
 integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \
 				      platform_certs/load_uefi.o \
 				      platform_certs/keyring_handler.o
diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
index 01cd70f9850..cd5c913cf7a 100644
--- a/security/integrity/digest_cache.h
+++ b/security/integrity/digest_cache.h
@@ -71,6 +71,7 @@ int digest_cache_init_htable(struct digest_cache *digest_cache,
 int digest_cache_add(struct digest_cache *digest_cache, u8 *digest);
 int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
 			enum hash_algo algo, const char *pathname);
+void digest_cache_iter_dir(struct dentry *repo_dentry);
 #else
 static inline void digest_cache_free(struct digest_cache *digest_cache)
 {
@@ -101,5 +102,9 @@ static inline int digest_cache_lookup(struct digest_cache *digest_cache,
 	return -ENOENT;
 }
 
+static inline void digest_cache_iter_dir(struct dentry *repo_dentry)
+{
+}
+
 #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
 #endif /* _DIGEST_CACHE_H */
diff --git a/security/integrity/digest_cache_iter.c b/security/integrity/digest_cache_iter.c
new file mode 100644
index 00000000000..f7641e7b365
--- /dev/null
+++ b/security/integrity/digest_cache_iter.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Implement a digest list iterator.
+ */
+
+#include <linux/fs.h>
+#include <linux/xattr.h>
+#include <linux/vmalloc.h>
+#include <linux/kernel_read_file.h>
+
+#include "integrity.h"
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+#define pr_fmt(fmt) "DIGEST CACHE ITER: "fmt
+
+static bool iterated;
+/* Ensure there is only one iteration over digest lists, make others wait. */
+DEFINE_MUTEX(iterate_mutex);
+
+struct dir_entry {
+	struct list_head list;
+	char name[];
+} __packed;
+
+struct readdir_callback {
+	struct dir_context ctx;
+	struct list_head *head;
+};
+
+/**
+ * digest_cache_iter_digest_list - Callback func to get digest lists in a dir
+ * @__ctx: iterate_dir() context
+ * @name: Name of file in the accessed dir
+ * @namelen: String length of @name
+ * @offset: Current position in the directory stream (see man readdir)
+ * @ino: Inode number
+ * @d_type: File type
+ *
+ * This function stores the names of the files in the containing directory in
+ * a linked list. Those files will be opened to trigger a measurement.
+ *
+ * Return: True to continue processing, false to stop.
+ */
+static bool digest_cache_iter_digest_list(struct dir_context *__ctx,
+					  const char *name, int namelen,
+					  loff_t offset, u64 ino,
+					  unsigned int d_type)
+{
+	struct readdir_callback *ctx = container_of(__ctx, typeof(*ctx), ctx);
+	struct dir_entry *new_entry;
+
+	if (!strcmp(name, ".") || !strcmp(name, ".."))
+		return true;
+
+	if (d_type != DT_REG)
+		return true;
+
+	new_entry = kmalloc(sizeof(*new_entry) + namelen + 1, GFP_KERNEL);
+	if (!new_entry)
+		return true;
+
+	memcpy(new_entry->name, name, namelen);
+	new_entry->name[namelen] = '\0';
+	list_add_tail(&new_entry->list, ctx->head);
+	return true;
+}
+
+/**
+ * digest_cache_iter_dir - Iterate over all files in the same digest list dir
+ * @digest_list_dentry: Digest list dentry
+ *
+ * This function iterates over all files in the directory containing the digest
+ * list provided as argument. It helps to measure digest lists in a
+ * deterministic order and make a TPM PCR predictable.
+ */
+void digest_cache_iter_dir(struct dentry *digest_list_dentry)
+{
+	struct file *dir_file;
+	struct readdir_callback buf = {
+		.ctx.actor = digest_cache_iter_digest_list,
+	};
+	struct dir_entry *p, *q;
+	struct file *file;
+	char *path_str = NULL, *dir_path_str = NULL;
+	void *data;
+	LIST_HEAD(head);
+	char *ptr;
+	int ret;
+
+	if (iterated)
+		return;
+
+	mutex_lock(&iterate_mutex);
+	if (iterated)
+		goto out;
+
+	iterated = true;
+
+	ret = vfs_getxattr_alloc(&nop_mnt_idmap, digest_list_dentry,
+				 XATTR_NAME_DIGEST_LIST, &path_str, 0,
+				 GFP_NOFS);
+	if (ret <= 0) {
+		pr_debug("%s xattr not found in %s\n", XATTR_NAME_DIGEST_LIST,
+			 digest_list_dentry->d_name.name);
+		goto out;
+	}
+
+	pr_debug("Found %s xattr in %s, digest list: %s\n",
+		 XATTR_NAME_DIGEST_LIST, digest_list_dentry->d_name.name,
+		 path_str);
+
+	ptr = strrchr(path_str, '/');
+	if (!ptr)
+		goto out;
+
+	dir_path_str = kstrndup(path_str, ptr - path_str, GFP_KERNEL);
+	if (!dir_path_str)
+		goto out;
+
+	dir_file = filp_open(dir_path_str, O_RDONLY, 0);
+
+	if (IS_ERR(dir_file)) {
+		pr_debug("Cannot access %s\n", dir_path_str);
+		goto out;
+	}
+
+	buf.head = &head;
+	iterate_dir(dir_file, &buf.ctx);
+	list_for_each_entry_safe(p, q, &head, list) {
+		pr_debug("Prefetching digest list %s/%s\n", dir_path_str,
+			 p->name);
+
+		file = file_open_root(&dir_file->f_path, p->name, O_RDONLY, 0);
+		if (IS_ERR(file))
+			continue;
+
+		data = NULL;
+
+		ret = kernel_read_file(file, 0, &data, INT_MAX, NULL,
+				       READING_DIGEST_LIST);
+		if (ret >= 0)
+			vfree(data);
+
+		fput(file);
+		list_del(&p->list);
+		kfree(p);
+	}
+
+	fput(dir_file);
+out:
+	mutex_unlock(&iterate_mutex);
+	kfree(path_str);
+	kfree(dir_path_str);
+}
-- 
2.34.1


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

* [RFC][PATCH v2 05/13] integrity/digest_cache: Parse tlv digest lists
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (3 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 04/13] integrity/digest_cache: Prefetch digest lists in a directory Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 06/13] integrity/digest_cache: Parse rpm " Roberto Sassu
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Add a parser for TLV-formatted (Type Length Value) digest lists. Their
structure is:

[header: DIGEST_LIST_FILE, num fields, total len]
[field: DIGEST_LIST_ALGO, length, value]
[field: DIGEST_LIST_ENTRY#1, length, value (below)]
 |- [header: DIGEST_LIST_FILE, num fields, total len]
 |- [ENTRY#1_DIGEST, length, file digest]
 |- [ENTRY#1_PATH, length, file path]
[field: DIGEST_LIST_ENTRY#N, length, value (below)]
 |- [header: DIGEST_LIST_FILE, num fields, total len]
 |- [ENTRY#N_DIGEST, length, file digest]
 |- [ENTRY#N_PATH, length, file path]

Defined fields are sufficient for measurement/appraisal of file content.
More fields can be introduced later (e.g. for appraisal of file metadata).

This patch defines only the callbacks (handlers) for the defined fields.
The parsing logic is already introduced in lib/tlv_parser.c.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 include/uapi/linux/tlv_digest_list.h          |  59 ++++++
 security/integrity/Makefile                   |   3 +-
 security/integrity/digest_cache.c             |   4 +
 .../integrity/digest_list_parsers/parsers.h   |  13 ++
 security/integrity/digest_list_parsers/tlv.c  | 188 ++++++++++++++++++
 5 files changed, 266 insertions(+), 1 deletion(-)
 create mode 100644 include/uapi/linux/tlv_digest_list.h
 create mode 100644 security/integrity/digest_list_parsers/parsers.h
 create mode 100644 security/integrity/digest_list_parsers/tlv.c

diff --git a/include/uapi/linux/tlv_digest_list.h b/include/uapi/linux/tlv_digest_list.h
new file mode 100644
index 00000000000..52987b63877
--- /dev/null
+++ b/include/uapi/linux/tlv_digest_list.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Export definitions of the tlv digest list.
+ */
+
+#ifndef _UAPI_LINUX_TLV_DIGEST_LIST_H
+#define _UAPI_LINUX_TLV_DIGEST_LIST_H
+
+#include <linux/types.h>
+
+#define FOR_EACH_DIGEST_LIST_TYPE(DIGEST_LIST_TYPE) \
+	DIGEST_LIST_TYPE(DIGEST_LIST_FILE) \
+	DIGEST_LIST_TYPE(DIGEST_LIST__LAST)
+
+#define FOR_EACH_FIELD(FIELD) \
+	FIELD(DIGEST_LIST_ALGO) \
+	FIELD(DIGEST_LIST_ENTRY) \
+	FIELD(FIELD__LAST)
+
+#define FOR_EACH_ENTRY_FIELD(ENTRY_FIELD) \
+	ENTRY_FIELD(ENTRY_DIGEST) \
+	ENTRY_FIELD(ENTRY_PATH) \
+	ENTRY_FIELD(ENTRY__LAST)
+
+#define GENERATE_ENUM(ENUM) ENUM,
+#define GENERATE_STRING(STRING) #STRING,
+
+/**
+ * enum digest_list_types - Type of digest list
+ *
+ * Enumerates the types of digest lists to parse.
+ */
+enum digest_list_types {
+	FOR_EACH_DIGEST_LIST_TYPE(GENERATE_ENUM)
+};
+
+/**
+ * enum fields - Digest list fields
+ *
+ * Enumerates the digest list fields.
+ */
+enum digest_list_fields {
+	FOR_EACH_FIELD(GENERATE_ENUM)
+};
+
+/**
+ * enum entry_fields - Entry-specific fields
+ *
+ * Enumerates the digest list entry-specific fields.
+ */
+enum entry_fields {
+	FOR_EACH_ENTRY_FIELD(GENERATE_ENUM)
+};
+
+#endif /* _UAPI_LINUX_TLV_DIGEST_LIST_H */
diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index c856ed10fba..3765b004e66 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -12,7 +12,8 @@ integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o
 integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyring.o
 integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o
 integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o \
-					      digest_cache_iter.o
+					      digest_cache_iter.o \
+					      digest_list_parsers/tlv.o
 integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \
 				      platform_certs/load_uefi.o \
 				      platform_certs/keyring_handler.o
diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
index d14d84b804b..818ac0ac0bf 100644
--- a/security/integrity/digest_cache.c
+++ b/security/integrity/digest_cache.c
@@ -18,6 +18,7 @@
 #include <linux/module_signature.h>
 
 #include "integrity.h"
+#include "digest_list_parsers/parsers.h"
 
 #ifdef pr_fmt
 #undef pr_fmt
@@ -141,6 +142,9 @@ static int digest_cache_parse_digest_list(struct digest_cache *digest_cache,
 parse:
 	pr_debug("Parsing %s, size: %ld\n", digest_cache->path_str, data_len);
 
+	if (!strncmp(digest_list_path->dentry->d_name.name, "tlv-", 4))
+		ret = digest_list_parse_tlv(digest_cache, data, data_len);
+
 	return ret;
 }
 
diff --git a/security/integrity/digest_list_parsers/parsers.h b/security/integrity/digest_list_parsers/parsers.h
new file mode 100644
index 00000000000..e8fff2374d8
--- /dev/null
+++ b/security/integrity/digest_list_parsers/parsers.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Digest list parsers.
+ */
+
+#include "../digest_cache.h"
+
+int digest_list_parse_tlv(struct digest_cache *digest_cache, const u8 *data,
+			  size_t data_len);
diff --git a/security/integrity/digest_list_parsers/tlv.c b/security/integrity/digest_list_parsers/tlv.c
new file mode 100644
index 00000000000..239400f5786
--- /dev/null
+++ b/security/integrity/digest_list_parsers/tlv.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Parse a tlv digest list.
+ */
+
+#define pr_fmt(fmt) "TLV DIGEST LIST: "fmt
+#include <linux/fs.h>
+#include <linux/hash_info.h>
+#include <linux/tlv_parser.h>
+#include <uapi/linux/tlv_digest_list.h>
+
+#include "parsers.h"
+
+#define kenter(FMT, ...) \
+	pr_debug("==> %s("FMT")\n", __func__, ##__VA_ARGS__)
+#define kleave(FMT, ...) \
+	pr_debug("<== %s()"FMT"\n", __func__, ##__VA_ARGS__)
+
+const char *digest_list_types_str[] = {
+	FOR_EACH_DIGEST_LIST_TYPE(GENERATE_STRING)
+};
+
+const char *digest_list_fields_str[] = {
+	FOR_EACH_FIELD(GENERATE_STRING)
+};
+
+const char *entry_fields_str[] = {
+	FOR_EACH_ENTRY_FIELD(GENERATE_STRING)
+};
+
+static int parse_digest_list_algo(struct digest_cache *digest_cache,
+				  enum digest_list_fields field,
+				  const u8 *field_data, u64 field_data_len)
+{
+	u8 algo;
+	int ret = 0;
+
+	kenter(",%u,%llu", field, field_data_len);
+
+	if (digest_cache->algo != HASH_ALGO__LAST) {
+		pr_debug("Digest algorithm already set to %s\n",
+			 hash_algo_name[digest_cache->algo]);
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	if (field_data_len != sizeof(u8)) {
+		pr_debug("Unexpected data length %llu, expected %lu\n",
+			 field_data_len, sizeof(u8));
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	algo = *field_data;
+
+	if (algo >= HASH_ALGO__LAST) {
+		pr_debug("Unexpected digest algo %u\n", algo);
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	digest_cache->algo = algo;
+	pr_debug("Digest algo: %s\n", hash_algo_name[algo]);
+out:
+	kleave(" = %d", ret);
+	return ret;
+}
+
+static int parse_entry_digest(struct digest_cache *digest_cache,
+			      enum entry_fields field, const u8 *field_data,
+			      u64 field_data_len)
+{
+	int ret = 0;
+
+	kenter(",%u,%llu", field, field_data_len);
+
+	if (field_data_len != hash_digest_size[digest_cache->algo]) {
+		pr_debug("Unexpected data length %llu, expected %d\n",
+			 field_data_len, hash_digest_size[digest_cache->algo]);
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	digest_cache_add(digest_cache, (u8 *)field_data);
+out:
+	kleave(" = %d", ret);
+	return ret;
+}
+
+static int entry_callback(void *callback_data, u64 field, const u8 *field_data,
+			  u64 field_data_len)
+{
+	struct digest_cache *digest_cache;
+	int ret;
+
+	digest_cache = (struct digest_cache *)callback_data;
+
+	switch (field) {
+	case ENTRY_DIGEST:
+		ret = parse_entry_digest(digest_cache, field, field_data,
+					 field_data_len);
+		break;
+	case ENTRY_PATH:
+		ret = 0;
+		break;
+	default:
+		pr_debug("Unhandled field %s\n", entry_fields_str[field]);
+		/* Just ignore non-relevant fields. */
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+static int parse_digest_list_entry(struct digest_cache *digest_cache,
+				   enum digest_list_fields field,
+				   const u8 *field_data, u64 field_data_len)
+{
+	int ret;
+
+	kenter(",%u,%llu", field, field_data_len);
+
+	ret = tlv_parse(DIGEST_LIST_FILE, entry_callback, digest_cache,
+			field_data, field_data_len, digest_list_types_str,
+			DIGEST_LIST__LAST, entry_fields_str, ENTRY__LAST);
+
+	kleave(" = %d", ret);
+	return ret;
+}
+
+static int digest_list_callback(void *callback_data, u64 field,
+				const u8 *field_data, u64 field_data_len)
+{
+	struct digest_cache *digest_cache;
+	int ret;
+
+	digest_cache = (struct digest_cache *)callback_data;
+
+	switch (field) {
+	case DIGEST_LIST_ALGO:
+		ret = parse_digest_list_algo(digest_cache, field, field_data,
+					     field_data_len);
+		break;
+	case DIGEST_LIST_ENTRY:
+		ret = parse_digest_list_entry(digest_cache, field, field_data,
+					      field_data_len);
+		break;
+	default:
+		pr_debug("Unhandled field %s\n",
+			 digest_list_fields_str[field]);
+		/* Just ignore non-relevant fields. */
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+int digest_list_parse_tlv(struct digest_cache *digest_cache, const u8 *data,
+			  size_t data_len)
+{
+	u64 parsed_data_type;
+	u64 parsed_num_fields;
+	u64 parsed_total_len;
+	int ret;
+
+	ret = tlv_parse_hdr(&data, &data_len, &parsed_data_type,
+			    &parsed_num_fields, &parsed_total_len,
+			    digest_list_types_str, DIGEST_LIST__LAST);
+	if (ret < 0)
+		return ret;
+
+	if (parsed_data_type != DIGEST_LIST_FILE)
+		return 0;
+
+	ret = digest_cache_init_htable(digest_cache, parsed_num_fields);
+	if (ret < 0)
+		return ret;
+
+	return tlv_parse_data(digest_list_callback, digest_cache,
+			      parsed_num_fields, data, data_len,
+			      digest_list_fields_str, FIELD__LAST);
+}
-- 
2.34.1


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

* [RFC][PATCH v2 06/13] integrity/digest_cache: Parse rpm digest lists
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (4 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 05/13] integrity/digest_cache: Parse tlv digest lists Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 07/13] ima: Add digest_cache policy keyword Roberto Sassu
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Implement a simple parser of RPM headers, that extracts the digest and the
algorithm of the packaged files from the RPMTAG_FILEDIGESTS and
RPMTAG_FILEDIGESTALGO section, and add them to the digest cache.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 security/integrity/Makefile                   |   3 +-
 security/integrity/digest_cache.c             |   2 +
 .../integrity/digest_list_parsers/parsers.h   |   2 +
 security/integrity/digest_list_parsers/rpm.c  | 215 ++++++++++++++++++
 4 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 security/integrity/digest_list_parsers/rpm.c

diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index 3765b004e66..c4c17a57d84 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -13,7 +13,8 @@ integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyrin
 integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o
 integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o \
 					      digest_cache_iter.o \
-					      digest_list_parsers/tlv.o
+					      digest_list_parsers/tlv.o \
+					      digest_list_parsers/rpm.o
 integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \
 				      platform_certs/load_uefi.o \
 				      platform_certs/keyring_handler.o
diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
index 818ac0ac0bf..fc392b925a5 100644
--- a/security/integrity/digest_cache.c
+++ b/security/integrity/digest_cache.c
@@ -144,6 +144,8 @@ static int digest_cache_parse_digest_list(struct digest_cache *digest_cache,
 
 	if (!strncmp(digest_list_path->dentry->d_name.name, "tlv-", 4))
 		ret = digest_list_parse_tlv(digest_cache, data, data_len);
+	else if (!strncmp(digest_list_path->dentry->d_name.name, "rpm-", 4))
+		ret = digest_list_parse_rpm(digest_cache, data, data_len);
 
 	return ret;
 }
diff --git a/security/integrity/digest_list_parsers/parsers.h b/security/integrity/digest_list_parsers/parsers.h
index e8fff2374d8..f86e58e9806 100644
--- a/security/integrity/digest_list_parsers/parsers.h
+++ b/security/integrity/digest_list_parsers/parsers.h
@@ -11,3 +11,5 @@
 
 int digest_list_parse_tlv(struct digest_cache *digest_cache, const u8 *data,
 			  size_t data_len);
+int digest_list_parse_rpm(struct digest_cache *digest_cache, const u8 *data,
+			  size_t data_len);
diff --git a/security/integrity/digest_list_parsers/rpm.c b/security/integrity/digest_list_parsers/rpm.c
new file mode 100644
index 00000000000..df2029d042f
--- /dev/null
+++ b/security/integrity/digest_list_parsers/rpm.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Parse an rpm digest list (RPM package header).
+ */
+
+#define pr_fmt(fmt) "RPM DIGEST LIST: "fmt
+#include <linux/module.h>
+
+#include "parsers.h"
+
+#define RPMTAG_FILEDIGESTS 1035
+#define RPMTAG_FILEDIGESTALGO 5011
+
+#define RPM_INT32_TYPE 4
+#define RPM_STRING_ARRAY_TYPE 8
+
+struct rpm_hdr {
+	u32 magic;
+	u32 reserved;
+	u32 tags;
+	u32 datasize;
+} __packed;
+
+struct rpm_entryinfo {
+	s32 tag;
+	u32 type;
+	s32 offset;
+	u32 count;
+} __packed;
+
+enum pgp_algos {
+	DIGEST_ALGO_MD5		=  1,
+	DIGEST_ALGO_SHA1	=  2,
+	DIGEST_ALGO_RMD160	=  3,
+	/* 4, 5, 6, and 7 are reserved. */
+	DIGEST_ALGO_SHA256	=  8,
+	DIGEST_ALGO_SHA384	=  9,
+	DIGEST_ALGO_SHA512	= 10,
+	DIGEST_ALGO_SHA224	= 11,
+};
+
+static const enum hash_algo pgp_algo_mapping[DIGEST_ALGO_SHA224 + 1] = {
+	[DIGEST_ALGO_MD5]	= HASH_ALGO_MD5,
+	[DIGEST_ALGO_SHA1]	= HASH_ALGO_SHA1,
+	[DIGEST_ALGO_RMD160]	= HASH_ALGO_RIPE_MD_160,
+	[4]			= HASH_ALGO__LAST,
+	[5]			= HASH_ALGO__LAST,
+	[6]			= HASH_ALGO__LAST,
+	[7]			= HASH_ALGO__LAST,
+	[DIGEST_ALGO_SHA256]	= HASH_ALGO_SHA256,
+	[DIGEST_ALGO_SHA384]	= HASH_ALGO_SHA384,
+	[DIGEST_ALGO_SHA512]	= HASH_ALGO_SHA512,
+	[DIGEST_ALGO_SHA224]	= HASH_ALGO_SHA224,
+};
+
+int digest_list_parse_rpm(struct digest_cache *digest_cache, const u8 *data,
+			  size_t data_len)
+{
+	const unsigned char rpm_header_magic[8] = {
+		0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+	};
+	const struct rpm_hdr *hdr;
+	const struct rpm_entryinfo *entry;
+	uint32_t tags, max_tags, datasize;
+	uint32_t digests_count, max_digests_count;
+	uint32_t digests_offset, algo_offset;
+	uint32_t digest_len, pkg_pgp_algo, i;
+	bool algo_offset_set = false, digests_offset_set = false;
+	enum hash_algo pkg_kernel_algo = HASH_ALGO_MD5;
+	u8 rpm_digest[SHA512_DIGEST_SIZE];
+	int ret;
+
+	if (data_len < sizeof(*hdr)) {
+		pr_debug("Not enough data for RPM header, current %ld, expected: %ld\n",
+			 data_len, sizeof(*hdr));
+		return -EINVAL;
+	}
+
+	if (memcmp(data, rpm_header_magic, sizeof(rpm_header_magic))) {
+		pr_debug("RPM header magic mismatch\n");
+		return -EINVAL;
+	}
+
+	hdr = (const struct rpm_hdr *)data;
+	data += sizeof(*hdr);
+	data_len -= sizeof(*hdr);
+
+	tags = __be32_to_cpu(hdr->tags);
+	max_tags = data_len / sizeof(*entry);
+
+	/* Finite termination on tags loop. */
+	if (tags > max_tags)
+		return -EINVAL;
+
+	datasize = __be32_to_cpu(hdr->datasize);
+	if (datasize != data_len - tags * sizeof(*entry))
+		return -EINVAL;
+
+	pr_debug("Scanning %d RPM header sections\n", tags);
+	for (i = 0; i < tags; i++) {
+		if (data_len < sizeof(*entry))
+			return -EINVAL;
+
+		entry = (const struct rpm_entryinfo *)data;
+		data += sizeof(*entry);
+		data_len -= sizeof(*entry);
+
+		switch (__be32_to_cpu(entry->tag)) {
+		case RPMTAG_FILEDIGESTS:
+			if (__be32_to_cpu(entry->type) != RPM_STRING_ARRAY_TYPE)
+				return -EINVAL;
+
+			digests_offset = __be32_to_cpu(entry->offset);
+			digests_count = __be32_to_cpu(entry->count);
+			digests_offset_set = true;
+
+			pr_debug("Found RPMTAG_FILEDIGESTS at offset %u, count: %u\n",
+				 digests_offset, digests_count);
+			break;
+		case RPMTAG_FILEDIGESTALGO:
+			if (__be32_to_cpu(entry->type) != RPM_INT32_TYPE)
+				return -EINVAL;
+
+			algo_offset = __be32_to_cpu(entry->offset);
+			algo_offset_set = true;
+
+			pr_debug("Found RPMTAG_FILEDIGESTALGO at offset %u\n",
+				 algo_offset);
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!digests_offset_set)
+		return -EINVAL;
+
+	if (algo_offset_set) {
+		if (algo_offset >= data_len)
+			return -EINVAL;
+
+		if (data_len - algo_offset < sizeof(uint32_t))
+			return -EINVAL;
+
+		pkg_pgp_algo = *(uint32_t *)&data[algo_offset];
+		pkg_pgp_algo = __be32_to_cpu(pkg_pgp_algo);
+		if (pkg_pgp_algo > DIGEST_ALGO_SHA224) {
+			pr_debug("Unknown PGP algo %d\n", pkg_pgp_algo);
+			return -EINVAL;
+		}
+
+		pkg_kernel_algo = pgp_algo_mapping[pkg_pgp_algo];
+		if (pkg_kernel_algo >= HASH_ALGO__LAST) {
+			pr_debug("Unknown mapping for PGP algo %d\n",
+				 pkg_pgp_algo);
+			return -EINVAL;
+		}
+
+		pr_debug("Found mapping for PGP algo %d: %s\n", pkg_pgp_algo,
+			 hash_algo_name[pkg_kernel_algo]);
+	}
+
+	digest_cache->algo = pkg_kernel_algo;
+	digest_len = hash_digest_size[pkg_kernel_algo];
+
+	if (digests_offset > data_len)
+		return -EINVAL;
+
+	/* Worst case, every digest is a \0. */
+	max_digests_count = data_len - digests_offset;
+
+	/* Finite termination on digests_count loop. */
+	if (digests_count > max_digests_count)
+		return -EINVAL;
+
+	ret = digest_cache_init_htable(digest_cache, digests_count);
+	if (ret < 0)
+		return ret;
+
+	ret = -ENOENT;
+
+	for (i = 0; i < digests_count; i++) {
+		if (digests_offset == data_len)
+			return -EINVAL;
+
+		if (!data[digests_offset]) {
+			digests_offset++;
+			continue;
+		}
+
+		if (data_len - digests_offset < digest_len * 2 + 1)
+			return -EINVAL;
+
+		ret = hex2bin(rpm_digest, (const char *)&data[digests_offset],
+			      digest_len);
+		if (ret < 0) {
+			pr_debug("Invalid hex format for digest %s\n",
+				 &data[digests_offset]);
+			ret = -EINVAL;
+			break;
+		}
+
+		ret = digest_cache_add(digest_cache, rpm_digest);
+		if (ret < 0)
+			return ret;
+
+		digests_offset += digest_len * 2 + 1;
+	}
+
+	return ret;
+}
-- 
2.34.1


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

* [RFC][PATCH v2 07/13] ima: Add digest_cache policy keyword
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (5 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 06/13] integrity/digest_cache: Parse rpm " Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 08/13] ima: Use digest cache for measurement Roberto Sassu
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Add the digest_cache policy keyword, to enable the use of the digest cache
for specific IMA actions and purpose. At the moment, the digest cache can
be used for measurement and appraisal of file content. They are reflected
respectively with the new flags DIGEST_CACHE_MEASURE and
DIGEST_CACHE_APPRAISE_CONTENT.

Depending on the IMA action of the parsed rule, the new flags are set in
the digest_cache_mask variable and passed back to process_measurement(), so
that the latter can determine whether or not it can use the digest cache.

The digest cache cannot be used for measurement on the default PCR. It
cannot also be used for appraisal together with the other appraisal methods
(imasig, sigv3, modsig).

Also, currently, the digest cache cannot be used to measure/appraise digest
lists. This functionality will be added in the future.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/ABI/testing/ima_policy  |  5 ++-
 security/integrity/ima/ima.h          |  6 ++-
 security/integrity/ima/ima_api.c      |  6 ++-
 security/integrity/ima/ima_appraise.c |  2 +-
 security/integrity/ima/ima_main.c     |  8 ++--
 security/integrity/ima/ima_policy.c   | 56 ++++++++++++++++++++++++++-
 6 files changed, 71 insertions(+), 12 deletions(-)

diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy
index 14d92c687ef..7792e65b35c 100644
--- a/Documentation/ABI/testing/ima_policy
+++ b/Documentation/ABI/testing/ima_policy
@@ -29,7 +29,7 @@ Description:
 				 [obj_user=] [obj_role=] [obj_type=]]
 			option:	[digest_type=] [template=] [permit_directio]
 				[appraise_type=] [appraise_flag=]
-				[appraise_algos=] [keyrings=]
+				[appraise_algos=] [keyrings=] [digest_cache=]
 		  base:
 			func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK]
 				[FIRMWARE_CHECK]
@@ -77,6 +77,9 @@ Description:
 			For example, "sha256,sha512" to only accept to appraise
 			files where the security.ima xattr was hashed with one
 			of these two algorithms.
+			digest_cache:= [content]
+			"content" means that the digest cache is used only
+			for file content measurement and/or appraisal.
 
 		  default policy:
 			# PROC_SUPER_MAGIC
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 3aef3d8fb57..f8f91d5c04a 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -260,7 +260,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode,
 		   const struct cred *cred, u32 secid, int mask,
 		   enum ima_hooks func, int *pcr,
 		   struct ima_template_desc **template_desc,
-		   const char *func_data, unsigned int *allowed_algos);
+		   const char *func_data, unsigned int *allowed_algos,
+		   u64 *digest_cache_mask);
 int ima_must_measure(struct inode *inode, int mask, enum ima_hooks func);
 int ima_collect_measurement(struct integrity_iint_cache *iint,
 			    struct file *file, void *buf, loff_t size,
@@ -291,7 +292,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode,
 		     const struct cred *cred, u32 secid, enum ima_hooks func,
 		     int mask, int flags, int *pcr,
 		     struct ima_template_desc **template_desc,
-		     const char *func_data, unsigned int *allowed_algos);
+		     const char *func_data, unsigned int *allowed_algos,
+		     u64 *digest_cache_mask);
 void ima_init_policy(void);
 void ima_update_policy(void);
 void ima_update_policy_flags(void);
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 452e80b541e..c591b093bb5 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -173,6 +173,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
  * @template_desc: pointer filled in if matched measure policy sets template=
  * @func_data: func specific data, may be NULL
  * @allowed_algos: allowlist of hash algorithms for the IMA xattr
+ * @digest_cache_mask: For which actions and purpose the digest cache is usable
  *
  * The policy is defined in terms of keypairs:
  *		subj=, obj=, type=, func=, mask=, fsmagic=
@@ -190,7 +191,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode,
 		   const struct cred *cred, u32 secid, int mask,
 		   enum ima_hooks func, int *pcr,
 		   struct ima_template_desc **template_desc,
-		   const char *func_data, unsigned int *allowed_algos)
+		   const char *func_data, unsigned int *allowed_algos,
+		   u64 *digest_cache_mask)
 {
 	int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE | IMA_HASH;
 
@@ -198,7 +200,7 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode,
 
 	return ima_match_policy(idmap, inode, cred, secid, func, mask,
 				flags, pcr, template_desc, func_data,
-				allowed_algos);
+				allowed_algos, digest_cache_mask);
 }
 
 static bool ima_get_verity_digest(struct integrity_iint_cache *iint,
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 491c1aca0b1..10dbafdae3d 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -81,7 +81,7 @@ int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode,
 	security_current_getsecid_subj(&secid);
 	return ima_match_policy(idmap, inode, current_cred(), secid,
 				func, mask, IMA_APPRAISE | IMA_HASH, NULL,
-				NULL, NULL, NULL);
+				NULL, NULL, NULL, NULL);
 }
 
 static int ima_fix_xattr(struct dentry *dentry,
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 81abdc8b233..4fdfc399fa6 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -231,7 +231,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
 	 */
 	action = ima_get_action(file_mnt_idmap(file), inode, cred, secid,
 				mask, func, &pcr, &template_desc, NULL,
-				&allowed_algos);
+				&allowed_algos, NULL);
 	violation_check = ((func == FILE_CHECK || func == MMAP_CHECK ||
 			    func == MMAP_CHECK_REQPROT) &&
 			   (ima_policy_flag & IMA_MEASURE));
@@ -473,11 +473,11 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot)
 	inode = file_inode(vma->vm_file);
 	action = ima_get_action(file_mnt_idmap(vma->vm_file), inode,
 				current_cred(), secid, MAY_EXEC, MMAP_CHECK,
-				&pcr, &template, NULL, NULL);
+				&pcr, &template, NULL, NULL, NULL);
 	action |= ima_get_action(file_mnt_idmap(vma->vm_file), inode,
 				 current_cred(), secid, MAY_EXEC,
 				 MMAP_CHECK_REQPROT, &pcr, &template, NULL,
-				 NULL);
+				 NULL, NULL);
 
 	/* Is the mmap'ed file in policy? */
 	if (!(action & (IMA_MEASURE | IMA_APPRAISE_SUBMASK)))
@@ -958,7 +958,7 @@ int process_buffer_measurement(struct mnt_idmap *idmap,
 		security_current_getsecid_subj(&secid);
 		action = ima_get_action(idmap, inode, current_cred(),
 					secid, 0, func, &pcr, &template,
-					func_data, NULL);
+					func_data, NULL, NULL);
 		if (!(action & IMA_MEASURE) && !digest)
 			return -ENOENT;
 	}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index b32c83d8a72..8583479112d 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -122,6 +122,7 @@ struct ima_rule_entry {
 	struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */
 	struct ima_rule_opt_list *label; /* Measure data grouped under this label */
 	struct ima_template_desc *template;
+	u64 digest_cache_mask;	/* For which actions and purpose the digest cache is usable */
 };
 
 /*
@@ -726,6 +727,7 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func)
  * @template_desc: the template that should be used for this rule
  * @func_data: func specific data, may be NULL
  * @allowed_algos: allowlist of hash algorithms for the IMA xattr
+ * @digest_cache_mask: For which actions and purpose the digest cache is usable
  *
  * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type)
  * conditions.
@@ -738,7 +740,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode,
 		     const struct cred *cred, u32 secid, enum ima_hooks func,
 		     int mask, int flags, int *pcr,
 		     struct ima_template_desc **template_desc,
-		     const char *func_data, unsigned int *allowed_algos)
+		     const char *func_data, unsigned int *allowed_algos,
+		     u64 *digest_cache_mask)
 {
 	struct ima_rule_entry *entry;
 	int action = 0, actmask = flags | (flags << 1);
@@ -783,6 +786,9 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode,
 		if (template_desc && entry->template)
 			*template_desc = entry->template;
 
+		if (digest_cache_mask)
+			*digest_cache_mask |= entry->digest_cache_mask;
+
 		if (!actmask)
 			break;
 	}
@@ -1073,7 +1079,7 @@ enum policy_opt {
 	Opt_digest_type,
 	Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos,
 	Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings,
-	Opt_label, Opt_err
+	Opt_label, Opt_digest_cache, Opt_err
 };
 
 static const match_table_t policy_tokens = {
@@ -1122,6 +1128,7 @@ static const match_table_t policy_tokens = {
 	{Opt_template, "template=%s"},
 	{Opt_keyrings, "keyrings=%s"},
 	{Opt_label, "label=%s"},
+	{Opt_digest_cache, "digest_cache=%s"},
 	{Opt_err, NULL}
 };
 
@@ -1886,6 +1893,26 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
 						 &(template_desc->num_fields));
 			entry->template = template_desc;
 			break;
+		case Opt_digest_cache:
+			ima_log_string(ab, "template", args[0].from);
+
+			result = -EINVAL;
+
+			if (!strcmp(args[0].from, "content")) {
+				switch (entry->action) {
+				case MEASURE:
+					entry->digest_cache_mask |= DIGEST_CACHE_MEASURE;
+					result = 0;
+					break;
+				case APPRAISE:
+					entry->digest_cache_mask |= DIGEST_CACHE_APPRAISE_CONTENT;
+					result = 0;
+					break;
+				default:
+					break;
+				}
+			}
+			break;
 		case Opt_err:
 			ima_log_string(ab, "UNKNOWN", p);
 			result = -EINVAL;
@@ -1912,6 +1939,28 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
 				     "verity rules should include d-ngv2");
 	}
 
+	/* New-style measurements with digest cache cannot be on default PCR. */
+	if (entry->action == MEASURE &&
+	    (entry->digest_cache_mask & DIGEST_CACHE_MEASURE)) {
+		if (!(entry->flags & IMA_PCR) ||
+		    entry->pcr == CONFIG_IMA_MEASURE_PCR_IDX)
+			result = -EINVAL;
+	}
+
+	/* Digest cache should not conflict with other appraisal methods. */
+	if (entry->action == APPRAISE &&
+	    (entry->digest_cache_mask & DIGEST_CACHE_APPRAISE_CONTENT)) {
+		if ((entry->flags & IMA_DIGSIG_REQUIRED) ||
+		    (entry->flags & IMA_VERITY_REQUIRED) ||
+		    (entry->flags & IMA_MODSIG_ALLOWED))
+			result = -EINVAL;
+	}
+
+	/* Digest cache cannot be used to measure/appraise digest lists. */
+	if ((entry->flags & IMA_FUNC) && entry->func == DIGEST_LIST_CHECK &&
+	    entry->digest_cache_mask)
+		result = -EINVAL;
+
 	audit_log_format(ab, "res=%d", !result);
 	audit_log_end(ab);
 	return result;
@@ -2278,6 +2327,9 @@ int ima_policy_show(struct seq_file *m, void *v)
 		seq_puts(m, "appraise_flag=check_blacklist ");
 	if (entry->flags & IMA_PERMIT_DIRECTIO)
 		seq_puts(m, "permit_directio ");
+	if ((entry->digest_cache_mask & DIGEST_CACHE_MEASURE) ||
+	    (entry->digest_cache_mask & DIGEST_CACHE_APPRAISE_CONTENT))
+		seq_puts(m, "digest_cache=content ");
 	rcu_read_unlock();
 	seq_puts(m, "\n");
 	return 0;
-- 
2.34.1


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

* [RFC][PATCH v2 08/13] ima: Use digest cache for measurement
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (6 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 07/13] ima: Add digest_cache policy keyword Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 09/13] ima: Use digest cache for appraisal Roberto Sassu
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

If a measure rule contains 'digest_cache=content', get the digest cache (if
available) associated to the file being measured.

AND the digest list mask from the IMA policy with the digest list mask of
the digest cache (set depending on the actions done on the digest lists),
to determine if the digest cache can be used for the measurement action.

If the digest cache is enabled, lookup the calculated digest of the file
being accessed and if found, pass the ANDed masks to
ima_store_measurement(). Otherwise, reset the mask to zero.

Finally, if the DIGEST_CACHE_MEASURE flag is set in the mask, mark the file
as measured for the supplied PCR (which cannot be the default one).

At the first digest list accessed, iterate over all digest lists in the
same directory, and measure them to make the PCR predictable. However,
don't parse those digest lists except the requested one, to avoid too much
memory pressure.

Skipping the measurement of cached digests causes less information to be
available to remote verifiers. In particular, they would know that a subset
or all files in the measured digest list could have been accessed, but they
won't know if and when.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 security/integrity/ima/ima.h      |  3 ++-
 security/integrity/ima/ima_api.c  | 16 +++++++++++++++-
 security/integrity/ima/ima_main.c | 28 ++++++++++++++++++++++++++--
 3 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index f8f91d5c04a..bb75cc3d2fd 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -270,7 +270,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file,
 			   const unsigned char *filename,
 			   struct evm_ima_xattr_data *xattr_value,
 			   int xattr_len, const struct modsig *modsig, int pcr,
-			   struct ima_template_desc *template_desc);
+			   struct ima_template_desc *template_desc,
+			   u64 digest_cache_mask);
 int process_buffer_measurement(struct mnt_idmap *idmap,
 			       struct inode *inode, const void *buf, int size,
 			       const char *eventname, enum ima_hooks func,
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index c591b093bb5..77b1ecff45a 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -339,7 +339,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint,
 			   struct file *file, const unsigned char *filename,
 			   struct evm_ima_xattr_data *xattr_value,
 			   int xattr_len, const struct modsig *modsig, int pcr,
-			   struct ima_template_desc *template_desc)
+			   struct ima_template_desc *template_desc,
+			   u64 digest_cache_mask)
 {
 	static const char op[] = "add_template_measure";
 	static const char audit_cause[] = "ENOMEM";
@@ -363,6 +364,19 @@ void ima_store_measurement(struct integrity_iint_cache *iint,
 	if (iint->measured_pcrs & (0x1 << pcr) && !modsig)
 		return;
 
+	/*
+	 * If the file digest was found in the digest cache, the digest cache
+	 * is enabled for measurement, and the digest list was measured, mark
+	 * the file as measured, so that it does not appear in the measurement
+	 * list (known digest), and the same action is not repeated at the next
+	 * access.
+	 */
+	if (digest_cache_mask & DIGEST_CACHE_MEASURE) {
+		iint->flags |= IMA_MEASURED;
+		iint->measured_pcrs |= (0x1 << pcr);
+		return;
+	}
+
 	result = ima_alloc_init_template(&event_data, &entry, template_desc);
 	if (result < 0) {
 		integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename,
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 4fdfc399fa6..54d006fc490 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -221,6 +221,8 @@ static int process_measurement(struct file *file, const struct cred *cred,
 	bool violation_check;
 	enum hash_algo hash_algo;
 	unsigned int allowed_algos = 0;
+	u64 digest_cache_mask = 0;
+	struct digest_cache *digest_cache = NULL;
 
 	if (!ima_policy_flag || !S_ISREG(inode->i_mode))
 		return 0;
@@ -231,7 +233,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
 	 */
 	action = ima_get_action(file_mnt_idmap(file), inode, cred, secid,
 				mask, func, &pcr, &template_desc, NULL,
-				&allowed_algos, NULL);
+				&allowed_algos, &digest_cache_mask);
 	violation_check = ((func == FILE_CHECK || func == MMAP_CHECK ||
 			    func == MMAP_CHECK_REQPROT) &&
 			   (ima_policy_flag & IMA_MEASURE));
@@ -263,6 +265,21 @@ static int process_measurement(struct file *file, const struct cred *cred,
 	if (!action)
 		goto out;
 
+	if (digest_cache_mask) {
+		/*
+		 * Prefetch the digest lists to measure them in a deterministic
+		 * way, and make the PCR predictable.
+		 */
+		if (digest_cache_mask & DIGEST_CACHE_MEASURE)
+			digest_cache_iter_dir(file_dentry(file));
+
+		digest_cache = digest_cache_get(file_dentry(file), iint);
+		if (digest_cache)
+			digest_cache_mask &= digest_cache->mask;
+		else
+			digest_cache_mask = 0;
+	}
+
 	mutex_lock(&iint->mutex);
 
 	if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags))
@@ -349,10 +366,17 @@ static int process_measurement(struct file *file, const struct cred *cred,
 	if (!pathbuf)	/* ima_rdwr_violation possibly pre-fetched */
 		pathname = ima_d_path(&file->f_path, &pathbuf, filename);
 
+	if (rc == 0 && digest_cache_mask) {
+		if (digest_cache_lookup(digest_cache, iint->ima_hash->digest,
+					iint->ima_hash->algo, pathname))
+			/* Reset the mask, the file digest was not found. */
+			digest_cache_mask = 0;
+	}
+
 	if (action & IMA_MEASURE)
 		ima_store_measurement(iint, file, pathname,
 				      xattr_value, xattr_len, modsig, pcr,
-				      template_desc);
+				      template_desc, digest_cache_mask);
 	if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
 		rc = ima_check_blacklist(iint, modsig, pcr);
 		if (rc != -EPERM) {
-- 
2.34.1


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

* [RFC][PATCH v2 09/13] ima: Use digest cache for appraisal
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (7 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 08/13] ima: Use digest cache for measurement Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 10/13] tools: Add tool to manage digest lists Roberto Sassu
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

If the digest of the accessed file is found in the digest cache, pass the
ANDed masks from the IMA policy and from the digest cache to
ima_appraise_measurement().

If the DIGEST_CACHE_APPRAISE_CONTENT flag is set in the mask, security.ima
is not available, and the modsig method is disabled, grant access in
read-only mode (except for new files).

Since xattrs were not verified with EVM, writes need to be prevented to
avoid the HMAC to be updated from an unverified one.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 security/integrity/ima/ima.h          |  6 ++++--
 security/integrity/ima/ima_appraise.c | 14 +++++++++++++-
 security/integrity/ima/ima_main.c     |  3 ++-
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index bb75cc3d2fd..06887f8f1bc 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -322,7 +322,8 @@ int ima_appraise_measurement(enum ima_hooks func,
 			     struct integrity_iint_cache *iint,
 			     struct file *file, const unsigned char *filename,
 			     struct evm_ima_xattr_data *xattr_value,
-			     int xattr_len, const struct modsig *modsig);
+			     int xattr_len, const struct modsig *modsig,
+			     u64 digest_cache_mask);
 int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode,
 		      int mask, enum ima_hooks func);
 void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file);
@@ -346,7 +347,8 @@ static inline int ima_appraise_measurement(enum ima_hooks func,
 					   const unsigned char *filename,
 					   struct evm_ima_xattr_data *xattr_value,
 					   int xattr_len,
-					   const struct modsig *modsig)
+					   const struct modsig *modsig,
+					   u8 digest_cache_mask)
 {
 	return INTEGRITY_UNKNOWN;
 }
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 10dbafdae3d..969a02802b9 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -479,7 +479,8 @@ int ima_appraise_measurement(enum ima_hooks func,
 			     struct integrity_iint_cache *iint,
 			     struct file *file, const unsigned char *filename,
 			     struct evm_ima_xattr_data *xattr_value,
-			     int xattr_len, const struct modsig *modsig)
+			     int xattr_len, const struct modsig *modsig,
+			     u64 digest_cache_mask)
 {
 	static const char op[] = "appraise_data";
 	const char *cause = "unknown";
@@ -514,6 +515,17 @@ int ima_appraise_measurement(enum ima_hooks func,
 		    (!(iint->flags & IMA_DIGSIG_REQUIRED) ||
 		     (inode->i_size == 0)))
 			status = INTEGRITY_PASS;
+		/*
+		 * Except for new files, use the digest cache to appraise the
+		 * file content and, at the same time, mark the file as
+		 * immutable to prevent file updates and transitioning from an
+		 * unverified HMAC to a valid HMAC.
+		 */
+		if (status != INTEGRITY_PASS &&
+		    (digest_cache_mask & DIGEST_CACHE_APPRAISE_CONTENT)) {
+			set_bit(IMA_DIGSIG, &iint->atomic_flags);
+			status = INTEGRITY_PASS;
+		}
 		goto out;
 	}
 
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 54d006fc490..b458ef62c4c 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -383,7 +383,8 @@ static int process_measurement(struct file *file, const struct cred *cred,
 			inode_lock(inode);
 			rc = ima_appraise_measurement(func, iint, file,
 						      pathname, xattr_value,
-						      xattr_len, modsig);
+						      xattr_len, modsig,
+						      digest_cache_mask);
 			inode_unlock(inode);
 		}
 		if (!rc)
-- 
2.34.1


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

* [RFC][PATCH v2 10/13] tools: Add tool to manage digest lists
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (8 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 09/13] ima: Use digest cache for appraisal Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 11/13] tools/digest-lists: Add tlv digest list generator and parser Roberto Sassu
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Add a tool to generate and manage the digest lists. Digest lists can be
generated from a directory, an individual file, or from a list.

Once generated, digest list content can be showed (digest algorithm and
value, file path). Also, the tool can add/remove the security.digest_list
xattr to/from each file in the generated digest lists.

To select the proper generator and parser, each digest list file name must
start with '<format>-'.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 MAINTAINERS                                |   1 +
 tools/Makefile                             |  16 +-
 tools/digest-lists/.gitignore              |   3 +
 tools/digest-lists/Makefile                |  48 +++
 tools/digest-lists/common.c                | 163 ++++++++++
 tools/digest-lists/common.h                |  90 ++++++
 tools/digest-lists/manage_digest_lists.c   | 342 +++++++++++++++++++++
 tools/digest-lists/manage_digest_lists.txt |  82 +++++
 8 files changed, 739 insertions(+), 6 deletions(-)
 create mode 100644 tools/digest-lists/.gitignore
 create mode 100644 tools/digest-lists/Makefile
 create mode 100644 tools/digest-lists/common.c
 create mode 100644 tools/digest-lists/common.h
 create mode 100644 tools/digest-lists/manage_digest_lists.c
 create mode 100644 tools/digest-lists/manage_digest_lists.txt

diff --git a/MAINTAINERS b/MAINTAINERS
index e5a325137e9..2bd85dcfd23 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10295,6 +10295,7 @@ S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
 F:	security/integrity/
 F:	security/integrity/ima/
+F:	tools/digest-lists/
 
 INTEL 810/815 FRAMEBUFFER DRIVER
 M:	Antonino Daplas <adaplas@gmail.com>
diff --git a/tools/Makefile b/tools/Makefile
index 37e9f680483..3789b5f292e 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -15,6 +15,7 @@ help:
 	@echo '  counter                - counter tools'
 	@echo '  cpupower               - a tool for all things x86 CPU power'
 	@echo '  debugging              - tools for debugging'
+	@echo '  digest-lists           - tools for managing digest lists'
 	@echo '  firewire               - the userspace part of nosy, an IEEE-1394 traffic sniffer'
 	@echo '  firmware               - Firmware tools'
 	@echo '  freefall               - laptop accelerometer program for disk protection'
@@ -69,7 +70,7 @@ acpi: FORCE
 cpupower: FORCE
 	$(call descend,power/$@)
 
-cgroup counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi pci firmware debugging tracing: FORCE
+cgroup counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi pci firmware debugging tracing digest-lists: FORCE
 	$(call descend,$@)
 
 bpf/%: FORCE
@@ -120,7 +121,8 @@ all: acpi cgroup counter cpupower gpio hv firewire \
 		perf selftests bootconfig spi turbostat usb \
 		virtio mm bpf x86_energy_perf_policy \
 		tmon freefall iio objtool kvm_stat wmi \
-		pci debugging tracing thermal thermometer thermal-engine
+		pci debugging tracing thermal thermometer thermal-engine \
+		digest-lists
 
 acpi_install:
 	$(call descend,power/$(@:_install=),install)
@@ -128,7 +130,7 @@ acpi_install:
 cpupower_install:
 	$(call descend,power/$(@:_install=),install)
 
-cgroup_install counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install pci_install debugging_install tracing_install:
+cgroup_install counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install pci_install debugging_install tracing_install digest-lists_install:
 	$(call descend,$(@:_install=),install)
 
 selftests_install:
@@ -161,7 +163,8 @@ install: acpi_install cgroup_install counter_install cpupower_install gpio_insta
 		virtio_install mm_install bpf_install x86_energy_perf_policy_install \
 		tmon_install freefall_install objtool_install kvm_stat_install \
 		wmi_install pci_install debugging_install intel-speed-select_install \
-		tracing_install thermometer_install thermal-engine_install
+		tracing_install thermometer_install thermal-engine_install \
+		digest-lists_install
 
 acpi_clean:
 	$(call descend,power/acpi,clean)
@@ -169,7 +172,7 @@ acpi_clean:
 cpupower_clean:
 	$(call descend,power/cpupower,clean)
 
-cgroup_clean counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean:
+cgroup_clean counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean digest-lists_clean:
 	$(call descend,$(@:_clean=),clean)
 
 libapi_clean:
@@ -214,6 +217,7 @@ clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firewire_cl
 		mm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
 		freefall_clean build_clean libbpf_clean libsubcmd_clean \
 		gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \
-		intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean
+		intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean \
+		digest-lists_clean
 
 .PHONY: FORCE
diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore
new file mode 100644
index 00000000000..1b8a7b9c205
--- /dev/null
+++ b/tools/digest-lists/.gitignore
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+manage_digest_lists
+manage_digest_lists.1
diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile
new file mode 100644
index 00000000000..05af3a91c06
--- /dev/null
+++ b/tools/digest-lists/Makefile
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../scripts/Makefile.include
+include ../scripts/utilities.mak
+BINDIR=usr/bin
+MANDIR=usr/share/man
+MAN1DIR=$(MANDIR)/man1
+CFLAGS=-ggdb -Wall
+
+PROGS=manage_digest_lists
+
+MAN1=manage_digest_lists.1
+
+A2X=a2x
+a2x_path := $(call get-executable,$(A2X))
+
+all: man $(PROGS)
+
+manage_digest_lists: manage_digest_lists.c common.c
+	$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) -lcrypto
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+  ifneq ($(V),1)
+     QUIET_A2X = @echo '  A2X     '$@;
+  endif
+endif
+
+%.1: %.txt
+ifeq ($(a2x_path),)
+	$(error "You need to install asciidoc for man pages")
+else
+	$(QUIET_A2X)$(A2X) --doctype manpage --format manpage $<
+endif
+
+clean:
+	rm -f $(MAN1) $(PROGS)
+
+man: $(MAN1)
+
+install-man: man
+	install -d -m 755 $(INSTALL_ROOT)/$(MAN1DIR)
+	install -m 644 $(MAN1) $(INSTALL_ROOT)/$(MAN1DIR)
+
+install-tools: $(PROGS)
+	install -d -m 755 $(INSTALL_ROOT)/$(BINDIR)
+	install -m 755 -p $(PROGS) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
+
+install: install-tools install-man
+.PHONY: all clean man install-tools install-man install
diff --git a/tools/digest-lists/common.c b/tools/digest-lists/common.c
new file mode 100644
index 00000000000..5378e677c09
--- /dev/null
+++ b/tools/digest-lists/common.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Common functions and data.
+ */
+
+#include <sys/mman.h>
+#include <sys/random.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hash_info.h>
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#include <asm/byteorder.h>
+
+#include "common.h"
+
+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,
+};
+
+int read_file(const char *path, size_t *len, unsigned char **data)
+{
+	struct stat st;
+	int rc = 0, fd;
+
+	if (stat(path, &st) == -1)
+		return -ENOENT;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -EACCES;
+
+	*len = st.st_size;
+
+	*data = mmap(NULL, *len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+	if (*data == MAP_FAILED)
+		rc = -ENOMEM;
+
+	close(fd);
+	return rc;
+}
+
+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, const char *path, enum hash_algo algo)
+{
+	unsigned char *data;
+	size_t len;
+	int ret;
+
+	ret = read_file(path, &len, &data);
+	if (ret < 0)
+		return ret;
+
+	ret = calc_digest(digest, data, len, algo);
+
+	munmap(data, len);
+	return ret;
+}
+
+ssize_t _write(int fd, void *buf, size_t buf_len)
+{
+	ssize_t len;
+	loff_t offset = 0;
+
+	while (offset < buf_len) {
+		len = write(fd, buf + offset, buf_len - offset);
+		if (len < 0)
+			return -errno;
+
+		offset += len;
+	}
+
+	return buf_len;
+}
diff --git a/tools/digest-lists/common.h b/tools/digest-lists/common.h
new file mode 100644
index 00000000000..d65168e2932
--- /dev/null
+++ b/tools/digest-lists/common.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Header of common.c
+ */
+
+#include <stdint.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hash_info.h>
+
+#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 ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+
+#define DIGEST_LIST_SIZE_MAX (64 * 1024 * 1024 - 1)
+
+/* In stripped ARM and x86-64 modules, ~ is surprisingly rare. */
+#define MODULE_SIG_STRING "~Module signature appended~\n"
+
+enum pkey_id_type {
+	PKEY_ID_PGP,		/* OpenPGP generated key ID */
+	PKEY_ID_X509,		/* X.509 arbitrary subjectKeyIdentifier */
+	PKEY_ID_PKCS7,		/* Signature in PKCS#7 message */
+};
+
+/*
+ * Module signature information block.
+ *
+ * The constituents of the signature section are, in order:
+ *
+ *	- Signer's name
+ *	- Key identifier
+ *	- Signature data
+ *	- Information block
+ */
+struct module_signature {
+	__u8	algo;		/* Public-key crypto algorithm [0] */
+	__u8	hash;		/* Digest algorithm [0] */
+	__u8	id_type;	/* Key identifier type [PKEY_ID_PKCS7] */
+	__u8	signer_len;	/* Length of signer's name [0] */
+	__u8	key_id_len;	/* Length of key identifier [0] */
+	__u8	__pad[3];
+	__be32	sig_len;	/* Length of signature data */
+};
+
+enum ops { OP_GEN, OP_SHOW, OP_ADD_XATTR, OP_RM_XATTR, OP__LAST };
+
+struct generator {
+	const char *name;
+	void *(*new)(int dirfd, char *input, enum hash_algo algo);
+	int (*add)(int dirfd, void *ptr, char *input);
+	void (*close)(void *ptr);
+};
+
+struct parser {
+	const char *name;
+	int (*parse)(const char *digest_list_path, enum ops op);
+};
+
+extern const char *ops_str[OP__LAST];
+extern const char *const hash_algo_name[HASH_ALGO__LAST];
+extern const int hash_digest_size[HASH_ALGO__LAST];
+
+int read_file(const char *path, size_t *len, unsigned char **data);
+int calc_digest(__u8 *digest, void *data, __u64 len, enum hash_algo algo);
+int calc_file_digest(__u8 *digest, const char *path, enum hash_algo algo);
+ssize_t _write(int fd, void *buf, size_t buf_len);
diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c
new file mode 100644
index 00000000000..bc425da5317
--- /dev/null
+++ b/tools/digest-lists/manage_digest_lists.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Implement a tool to manage digest lists..
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <linux/hash_info.h>
+#include <linux/xattr.h>
+#include <fts.h>
+
+#include "common.h"
+
+const char *ops_str[OP__LAST] = {
+	[OP_GEN] = "gen",
+	[OP_SHOW] = "show",
+	[OP_ADD_XATTR] = "add-xattr",
+	[OP_RM_XATTR] = "rm-xattr",
+};
+
+struct generator generators[] = {
+};
+
+struct parser parsers[] = {
+};
+
+static int generator_add(struct generator *generator, int dirfd,
+			 void *ptr, char *input)
+{
+	char *full_path = input;
+	int ret;
+
+	if (!generator->add)
+		return -ENOENT;
+
+	if (strncmp(input, "rpmdb", 5)) {
+		full_path = realpath(input, NULL);
+		if (!full_path) {
+			printf("Error generating full path of %s\n", full_path);
+			return -ENOMEM;
+		}
+	}
+
+	ret = generator->add(dirfd, ptr, full_path);
+
+	if (full_path != input)
+		free(full_path);
+
+	return ret;
+}
+
+static int gen_digest_list(char *digest_list_format, char *digest_list_dir,
+			   char *input, int input_is_list, __u8 algo)
+{
+	struct generator *generator;
+	void *ptr;
+	FTS *fts = NULL;
+	FTSENT *ftsent;
+	FILE *fp;
+	int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV);
+	char *paths[2] = { input, NULL };
+	char line[1024], *p;
+	int ret, i, dirfd;
+
+	for (i = 0; i < ARRAY_SIZE(generators); i++)
+		if (!strcmp(generators[i].name, digest_list_format))
+			break;
+
+	if (i == ARRAY_SIZE(generators)) {
+		printf("Cannot find generator for %s\n", digest_list_format);
+		return -ENOENT;
+	}
+
+	generator = &generators[i];
+
+	dirfd = open(digest_list_dir, O_RDONLY | O_DIRECTORY);
+	if (dirfd < 0) {
+		printf("Unable to open %s, ret: %d\n", digest_list_dir, -errno);
+		return -errno;
+	}
+
+	if (generator->new) {
+		ptr = generator->new(dirfd, input, algo);
+		if (!ptr) {
+			ret = -ENOMEM;
+			goto out;
+		}
+	}
+
+	if (input_is_list) {
+		fp = fopen(input, "r");
+		if (!fp) {
+			ret = -EACCES;
+			goto out_close;
+		}
+
+		while ((fgets(line, sizeof(line), fp))) {
+			p = strrchr(line, '\n');
+			*p = '\0';
+
+			ret = generator_add(generator, dirfd, ptr, line);
+			if (ret < 0) {
+				printf("Error generating entry for %s, ret: %d\n",
+				       line, ret);
+				fclose(fp);
+				goto out_close;
+			}
+		}
+
+		fclose(fp);
+		goto out_close;
+	} else if (!strncmp(input, "rpmdb", 5)) {
+		ret = generator_add(generator, dirfd, ptr, input);
+		if (ret < 0) {
+			printf("Error generating entry for %s, ret: %d\n",
+			       input, ret);
+			goto out_close;
+		}
+	}
+
+	fts = fts_open(paths, fts_flags, NULL);
+	if (!fts) {
+		printf("Unable to open %s\n", input);
+		ret = -EACCES;
+		goto out_close;
+	}
+
+	while ((ftsent = fts_read(fts)) != NULL) {
+		switch (ftsent->fts_info) {
+		case FTS_F:
+			ret = generator_add(generator, dirfd, ptr,
+					    ftsent->fts_path);
+			if (ret < 0) {
+				printf("Error generating entry for %s, ret: %d\n",
+				       ftsent->fts_path, ret);
+				goto out_fts_close;
+			}
+		default:
+			break;
+		}
+	}
+
+out_fts_close:
+	fts_close(fts);
+out_close:
+	if (generator->close)
+		generator->close(ptr);
+out:
+	close(dirfd);
+	return ret;
+}
+
+static struct parser *get_parser(const char *filename)
+{
+	const char *separator;
+	int i;
+
+	separator = strchr(filename, '-');
+	if (!separator)
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(parsers); i++)
+		if (!strncmp(parsers[i].name, filename, separator - filename))
+			break;
+
+	if (i == ARRAY_SIZE(parsers)) {
+		printf("Cannot find parser for %s\n", filename);
+		return NULL;
+	}
+
+	return &parsers[i];
+}
+
+static int parse_digest_list(char *digest_list_format, char *digest_list_path,
+			     enum ops op)
+{
+	struct parser *parser;
+	FTS *fts = NULL;
+	FTSENT *ftsent;
+	int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV);
+	char *paths[2] = { NULL, NULL };
+	char *full_path = NULL;
+	int ret;
+
+	full_path = realpath(digest_list_path, NULL);
+	if (!full_path)
+		return -ENOMEM;
+
+	paths[0] = full_path;
+
+	fts = fts_open(paths, fts_flags, NULL);
+	if (!fts) {
+		printf("Unable to open %s\n", digest_list_path);
+		free(full_path);
+		return -EACCES;
+	}
+
+	while ((ftsent = fts_read(fts)) != NULL) {
+		switch (ftsent->fts_info) {
+		case FTS_F:
+			parser = get_parser(ftsent->fts_name);
+			if (!parser)
+				continue;
+
+			ret = parser->parse(ftsent->fts_path, op);
+			if (ret < 0) {
+				printf("Error parsing entry %s, ret: %d\n",
+				       ftsent->fts_path, ret);
+				goto out_fts_close;
+			}
+
+			break;
+		default:
+			break;
+		}
+	}
+
+out_fts_close:
+	fts_close(fts);
+	free(full_path);
+	return ret;
+}
+
+static void usage(char *progname)
+{
+	printf("Usage: %s <options>\n", progname);
+	printf("Options:\n");
+	printf("\t-d <directory>: directory digest lists are written to\n"
+	       "\t-i <input>: input digest list for an operation"
+	       "\t-L: input is a list of files/directories\n"
+	       "\t-a <algo>: digest list algorithm\n"
+	       "\t-f <format>: digest list format\n"
+	       "\t-o <operation>: operation to perform\n"
+	       "\t\tgen: generate a digest list\n"
+	       "\t\tshow: show the content of a digest list\n"
+	       "\t\tadd-xattr: set the " XATTR_NAME_DIGEST_LIST " xattr to the digest list path\n"
+	       "\t\trm-xattr: remove the " XATTR_NAME_DIGEST_LIST " xattr\n"
+	       "\t-h: display help\n");
+}
+
+int main(int argc, char *argv[])
+{
+	char *digest_list_dir = NULL, *digest_list_format = NULL, *input = NULL;
+	enum hash_algo algo = HASH_ALGO_SHA256;
+	enum ops op = OP__LAST;
+	struct stat st;
+	int c, i;
+	int ret, input_is_list = 0;
+
+	while ((c = getopt(argc, argv, "d:i:La:f:o:h")) != -1) {
+		switch (c) {
+		case 'd':
+			digest_list_dir = optarg;
+			break;
+		case 'i':
+			input = optarg;
+			break;
+		case 'L':
+			input_is_list = 1;
+			break;
+		case 'a':
+			for (i = 0; i < HASH_ALGO__LAST; i++)
+				if (!strcmp(hash_algo_name[i], optarg))
+					break;
+			if (i == HASH_ALGO__LAST) {
+				printf("Invalid algo %s\n", optarg);
+				return -EINVAL;
+			}
+			algo = i;
+			break;
+		case 'f':
+			digest_list_format = optarg;
+			break;
+		case 'o':
+			for (op = 0; op < OP__LAST; op++)
+				if (!strcmp(ops_str[op], optarg))
+					break;
+			if (op == OP__LAST) {
+				printf("Invalid op %s\n", optarg);
+				return -EINVAL;
+			}
+			break;
+		case 'h':
+			usage(argv[0]);
+			return 0;
+		default:
+			printf("Invalid option %c\n", c);
+			return -EINVAL;
+		}
+	}
+
+	if (op == OP__LAST) {
+		printf("Operation not specified\n");
+		return -ENOENT;
+	}
+
+	switch (op) {
+	case OP_GEN:
+		if (!digest_list_format || !input || !digest_list_dir) {
+			printf("Missing format/input/digest list directory\n");
+			return -ENOENT;
+		}
+
+		if (stat(digest_list_dir, &st) == -1) {
+			ret = mkdir(digest_list_dir, 0755);
+			if (ret < 0) {
+				printf("Unable to create %s, ret: %d\n",
+				       digest_list_dir, -errno);
+				return -errno;
+			}
+		}
+
+		ret = gen_digest_list(digest_list_format, digest_list_dir,
+				      input, input_is_list, algo);
+		break;
+	case OP_SHOW:
+	case OP_ADD_XATTR:
+	case OP_RM_XATTR:
+		if (!input) {
+			printf("Missing input\n");
+			return -ENOENT;
+		}
+
+		ret = parse_digest_list(digest_list_format, input, op);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
diff --git a/tools/digest-lists/manage_digest_lists.txt b/tools/digest-lists/manage_digest_lists.txt
new file mode 100644
index 00000000000..62d655516e8
--- /dev/null
+++ b/tools/digest-lists/manage_digest_lists.txt
@@ -0,0 +1,82 @@
+manage_digest_lists(1)
+======================
+
+NAME
+----
+manage_digest_lists - manage digest lists lifecycle
+
+
+SYNOPSIS
+--------
+manage_digest_lists [options]
+
+
+DESCRIPTION
+------------
+manage_digest_lists can be used to manage the lifecycle of digest lists (e.g. generate, show).
+
+
+OPTIONS
+-------
+-d <directory>::
+	directory digest lists are written to
+
+-i <input>::
+	input digest list for an operation
+
+-L::
+	input is a list of files/directories
+
+-a <algo>::
+	digest list algorithm
+
+-f <format>::
+	digest list format
+
+-o <operation>::
+	operation to perform:::
+		gen::::
+			generate a digest list
+		show::::
+			show the content of a digest list
+		add-xattr::::
+			set the security.digest_list xattr to the digest list path
+		rm-xattr::::
+			remove the security.digest_list xattr
+
+-h::
+	display help
+
+
+EXAMPLES
+--------
+Generate digest lists from the RPM database:
+
+# manage_digest_lists -d /etc/digest_lists -i rpmdb -o gen -f rpm
+
+
+Generate digest lists for the kernel modules (for custom kernels):
+
+# manage_digest_lists -d /etc/digest_lists -i /lib/modules/`uname -r` -o gen -f tlv
+
+
+Show digest lists content in /etc/digest_lists
+
+# manage_digest_lists -i /etc/digest_lists -o show
+
+
+Add security.digest_list xattr for digest lists in /etc/digest_lists
+
+# manage_digest_lists -i /etc/digest_lists -o add-xattr
+
+
+AUTHOR
+------
+Written by Roberto Sassu, <roberto.sassu at huawei.com>.
+
+
+COPYING
+-------
+Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH. Free use of
+this software is granted under the terms of the GNU Public License 2.0
+(GPLv2).
-- 
2.34.1


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

* [RFC][PATCH v2 11/13] tools/digest-lists: Add tlv digest list generator and parser
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (9 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 10/13] tools: Add tool to manage digest lists Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 12/13] tools/digest-lists: Add rpm " Roberto Sassu
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Add a generator of tlv digest lists. It will store the digest
algorithm, the digest and path of each file provided as input.

Also add a parser of tlv digest lists. It will display the content (digest
algorithm and value, and file path), and will add/remove the
security.digest_list xattr to/from each file in the digest list.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 tools/digest-lists/.gitignore              |   2 +
 tools/digest-lists/Makefile                |  22 ++-
 tools/digest-lists/generators/generators.h |  16 ++
 tools/digest-lists/generators/tlv.c        | 168 ++++++++++++++++++
 tools/digest-lists/manage_digest_lists.c   |   5 +
 tools/digest-lists/parsers/parsers.h       |  14 ++
 tools/digest-lists/parsers/tlv.c           | 195 +++++++++++++++++++++
 tools/digest-lists/parsers/tlv_parser.h    |  38 ++++
 8 files changed, 458 insertions(+), 2 deletions(-)
 create mode 100644 tools/digest-lists/generators/generators.h
 create mode 100644 tools/digest-lists/generators/tlv.c
 create mode 100644 tools/digest-lists/parsers/parsers.h
 create mode 100644 tools/digest-lists/parsers/tlv.c
 create mode 100644 tools/digest-lists/parsers/tlv_parser.h

diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore
index 1b8a7b9c205..9a75ae766ff 100644
--- a/tools/digest-lists/.gitignore
+++ b/tools/digest-lists/.gitignore
@@ -1,3 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 manage_digest_lists
 manage_digest_lists.1
+libgen-tlv-list.so
+libparse-tlv-list.so
diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile
index 05af3a91c06..23f9fa3b588 100644
--- a/tools/digest-lists/Makefile
+++ b/tools/digest-lists/Makefile
@@ -1,13 +1,23 @@
 # SPDX-License-Identifier: GPL-2.0
 include ../scripts/Makefile.include
+include ../scripts/Makefile.arch
 include ../scripts/utilities.mak
+
 BINDIR=usr/bin
+ifeq ($(LP64), 1)
+  LIBDIR=usr/lib64
+else
+  LIBDIR=usr/lib
+endif
 MANDIR=usr/share/man
 MAN1DIR=$(MANDIR)/man1
 CFLAGS=-ggdb -Wall
 
 PROGS=manage_digest_lists
 
+GENERATORS=libgen-tlv-list.so
+PARSERS=libparse-tlv-list.so
+
 MAN1=manage_digest_lists.1
 
 A2X=a2x
@@ -15,9 +25,15 @@ a2x_path := $(call get-executable,$(A2X))
 
 all: man $(PROGS)
 
-manage_digest_lists: manage_digest_lists.c common.c
+manage_digest_lists: manage_digest_lists.c common.c $(GENERATORS) $(PARSERS)
 	$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) -lcrypto
 
+libgen-tlv-list.so: generators/tlv.c common.c
+	$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-tlv-list.so $^ -o $@
+
+libparse-tlv-list.so: parsers/tlv.c common.c ../../lib/tlv_parser.c
+	$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-tlv-list.so $^ -o $@ -I parsers
+
 ifneq ($(findstring $(MAKEFLAGS),s),s)
   ifneq ($(V),1)
      QUIET_A2X = @echo '  A2X     '$@;
@@ -32,7 +48,7 @@ else
 endif
 
 clean:
-	rm -f $(MAN1) $(PROGS)
+	rm -f $(MAN1) $(PROGS) $(GENERATORS) $(PARSERS)
 
 man: $(MAN1)
 
@@ -43,6 +59,8 @@ install-man: man
 install-tools: $(PROGS)
 	install -d -m 755 $(INSTALL_ROOT)/$(BINDIR)
 	install -m 755 -p $(PROGS) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
+	install -m 755 -p $(GENERATORS) "$(INSTALL_ROOT)/$(LIBDIR)/$(TARGET)"
+	install -m 755 -p $(PARSERS) "$(INSTALL_ROOT)/$(LIBDIR)/$(TARGET)"
 
 install: install-tools install-man
 .PHONY: all clean man install-tools install-man install
diff --git a/tools/digest-lists/generators/generators.h b/tools/digest-lists/generators/generators.h
new file mode 100644
index 00000000000..9830b791667
--- /dev/null
+++ b/tools/digest-lists/generators/generators.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Header for all digest list generators.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo);
+int tlv_list_gen_add(int dirfd, void *ptr, char *input);
+void tlv_list_gen_close(void *ptr);
diff --git a/tools/digest-lists/generators/tlv.c b/tools/digest-lists/generators/tlv.c
new file mode 100644
index 00000000000..cbc29a49f51
--- /dev/null
+++ b/tools/digest-lists/generators/tlv.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Generate tlv digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/hash_info.h>
+#include <asm/byteorder.h>
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#include "../../../include/uapi/linux/tlv_parser.h"
+#include "../../../include/uapi/linux/tlv_digest_list.h"
+#include "../common.h"
+
+struct tlv_struct {
+	__u8 *digest_list;
+	struct tlv_hdr *outer_hdr;
+	struct tlv_entry *outer_entry;
+	__u8 algo;
+	int fd;
+};
+
+static int new_digest_list(int dirfd, const char *input, struct tlv_struct *tlv)
+{
+	char filename[NAME_MAX + 1];
+	struct tlv_hdr *hdr;
+	const char *input_ptr;
+
+	input_ptr = strrchr(input, '/');
+	if (input_ptr)
+		input_ptr++;
+	else
+		input_ptr = input;
+
+	snprintf(filename, sizeof(filename), "tlv-%s", input_ptr);
+
+	tlv->fd = openat(dirfd, filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
+	if (tlv->fd < 0) {
+		printf("Unable to create %s\n", filename);
+		return -errno;
+	}
+
+	ftruncate(tlv->fd, DIGEST_LIST_SIZE_MAX);
+	tlv->digest_list = mmap(NULL, DIGEST_LIST_SIZE_MAX,
+				PROT_READ | PROT_WRITE, MAP_SHARED, tlv->fd, 0);
+
+	if (tlv->digest_list == MAP_FAILED) {
+		printf("Cannot allocate buffer\n");
+		close(tlv->fd);
+		return -ENOMEM;
+	}
+
+	hdr = (struct tlv_hdr *)tlv->digest_list;
+	memset(hdr, 0, sizeof(*hdr));
+
+	hdr->data_type = __cpu_to_be64(DIGEST_LIST_FILE);
+	hdr->num_fields = 0;
+	hdr->total_len = 0;
+	return 0;
+}
+
+static void write_entry(struct tlv_hdr *hdr, struct tlv_entry **entry,
+			__u16 field, __u8 *data, __u32 data_len,
+			bool update_data)
+{
+	__u16 num_fields;
+	__u64 total_len;
+	__u64 entry_len;
+
+	num_fields = __be64_to_cpu(hdr->num_fields);
+	total_len = __be64_to_cpu(hdr->total_len);
+
+	(*entry)->field = __cpu_to_be64(field);
+	(*entry)->length = __cpu_to_be64(data_len);
+
+	if (update_data)
+		memcpy((*entry)->data, data, data_len);
+
+	num_fields++;
+	entry_len = sizeof(*(*entry)) + data_len;
+	total_len += entry_len;
+
+	hdr->num_fields = __cpu_to_be64(num_fields);
+	hdr->total_len = __cpu_to_be64(total_len);
+	(*entry) = (struct tlv_entry *)((__u8 *)*entry + entry_len);
+}
+
+void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo)
+{
+	struct tlv_struct *tlv;
+	int ret;
+
+	tlv = malloc(sizeof(*tlv));
+	if (!tlv)
+		return NULL;
+
+	ret = new_digest_list(dirfd, input, tlv);
+	if (ret < 0) {
+		free(tlv);
+		return NULL;
+	}
+
+	tlv->outer_hdr = (struct tlv_hdr *)tlv->digest_list;
+	tlv->outer_entry = (struct tlv_entry *)(tlv->outer_hdr + 1);
+	tlv->algo = algo;
+
+	write_entry(tlv->outer_hdr, &tlv->outer_entry, DIGEST_LIST_ALGO,
+		    &tlv->algo, sizeof(tlv->algo), true);
+	return tlv;
+}
+
+int tlv_list_gen_add(int dirfd, void *ptr, char *input)
+{
+	struct tlv_struct *tlv = (struct tlv_struct *)ptr;
+	__u8 digest[SHA512_DIGEST_SIZE];
+	struct tlv_hdr *inner_hdr;
+	struct tlv_entry *inner_entry;
+	int ret;
+
+	ret = calc_file_digest(digest, input, tlv->algo);
+	if (ret < 0) {
+		printf("Cannot calculate digest of %s\n", input);
+		return ret;
+	}
+
+	inner_hdr = (struct tlv_hdr *)(tlv->outer_entry + 1);
+	inner_hdr->data_type = __cpu_to_be64(DIGEST_LIST_FILE);
+
+	inner_entry = (struct tlv_entry *)(inner_hdr + 1);
+
+	write_entry(inner_hdr, &inner_entry, ENTRY_DIGEST, digest,
+		    hash_digest_size[tlv->algo], true);
+	write_entry(inner_hdr, &inner_entry, ENTRY_PATH, (__u8 *)input,
+		    strlen(input) + 1, true);
+
+	write_entry(tlv->outer_hdr, &tlv->outer_entry, DIGEST_LIST_ENTRY, NULL,
+		    (__u8 *)inner_entry - (__u8 *)inner_hdr, false);
+	return 0;
+}
+
+void tlv_list_gen_close(void *ptr)
+{
+	struct tlv_struct *tlv = (struct tlv_struct *)ptr;
+
+	munmap(tlv->digest_list, DIGEST_LIST_SIZE_MAX);
+	ftruncate(tlv->fd, (__u8 *)tlv->outer_entry - (__u8 *)tlv->outer_hdr);
+	close(tlv->fd);
+	free(tlv);
+}
diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c
index bc425da5317..7caad681eee 100644
--- a/tools/digest-lists/manage_digest_lists.c
+++ b/tools/digest-lists/manage_digest_lists.c
@@ -20,6 +20,8 @@
 #include <fts.h>
 
 #include "common.h"
+#include "generators/generators.h"
+#include "parsers/parsers.h"
 
 const char *ops_str[OP__LAST] = {
 	[OP_GEN] = "gen",
@@ -29,9 +31,12 @@ const char *ops_str[OP__LAST] = {
 };
 
 struct generator generators[] = {
+	{ .name = "tlv", .new = tlv_list_gen_new, .add = tlv_list_gen_add,
+	  .close = tlv_list_gen_close },
 };
 
 struct parser parsers[] = {
+	{ .name = "tlv", .parse = tlv_list_parse },
 };
 
 static int generator_add(struct generator *generator, int dirfd,
diff --git a/tools/digest-lists/parsers/parsers.h b/tools/digest-lists/parsers/parsers.h
new file mode 100644
index 00000000000..708da7eac3b
--- /dev/null
+++ b/tools/digest-lists/parsers/parsers.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Header for all digest list parsers.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+int tlv_list_parse(const char *digest_list_path, enum ops op);
diff --git a/tools/digest-lists/parsers/tlv.c b/tools/digest-lists/parsers/tlv.c
new file mode 100644
index 00000000000..1c9909e80b9
--- /dev/null
+++ b/tools/digest-lists/parsers/tlv.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Parse tlv digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <limits.h>
+#include <sys/mman.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/hash_info.h>
+#include <asm/byteorder.h>
+#include <tlv_parser.h>
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#include "../../../include/uapi/linux/tlv_digest_list.h"
+#include "../common.h"
+
+struct tlv_parse_ctx {
+	const char *digest_list_path;
+	size_t digest_list_path_len;
+	enum hash_algo algo;
+	enum ops op;
+};
+
+const char *digest_list_types_str[] = {
+	FOR_EACH_DIGEST_LIST_TYPE(GENERATE_STRING)
+};
+
+const char *digest_list_fields_str[] = {
+	FOR_EACH_FIELD(GENERATE_STRING)
+};
+
+const char *entry_fields_str[] = {
+	FOR_EACH_ENTRY_FIELD(GENERATE_STRING)
+};
+
+static int parse_digest_list_algo(struct tlv_parse_ctx *ctx,
+				  enum digest_list_fields field,
+				  const __u8 *field_data, __u64 field_data_len)
+{
+	ctx->algo = *field_data;
+	return 0;
+}
+
+static int parse_entry_digest(struct tlv_parse_ctx *ctx,
+			      enum entry_fields field, const __u8 *field_data,
+			      __u64 field_data_len)
+{
+	int i;
+
+	if (ctx->op != OP_SHOW)
+		return 0;
+
+	printf("%s:", hash_algo_name[ctx->algo]);
+
+	for (i = 0; i < hash_digest_size[ctx->algo]; i++)
+		printf("%02x", field_data[i]);
+
+	return 0;
+}
+
+static int parse_entry_path(struct tlv_parse_ctx *ctx, enum entry_fields field,
+			    const __u8 *field_data, __u64 field_data_len)
+{
+	char *entry_path = (char *)field_data;
+	int ret;
+
+	switch (ctx->op) {
+	case OP_SHOW:
+		printf(" %s\n", entry_path);
+		ret = 0;
+		break;
+	case OP_ADD_XATTR:
+		ret = lsetxattr(entry_path, XATTR_NAME_DIGEST_LIST,
+				ctx->digest_list_path,
+				ctx->digest_list_path_len, 0);
+		if (ret < 0 && errno == ENODATA)
+			ret = 0;
+
+		if (ret < 0)
+			printf("Error setting %s on %s, %s\n",
+			       XATTR_NAME_DIGEST_LIST, entry_path,
+			       strerror(errno));
+		break;
+	case OP_RM_XATTR:
+		ret = lremovexattr(entry_path, XATTR_NAME_DIGEST_LIST);
+		if (ret < 0 && errno == ENODATA)
+			ret = 0;
+
+		if (ret < 0)
+			printf("Error removing %s from %s, %s\n",
+			       XATTR_NAME_DIGEST_LIST, entry_path,
+			       strerror(errno));
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int entry_callback(void *callback_data, __u64 field,
+			  const __u8 *field_data, __u64 field_data_len)
+{
+	struct tlv_parse_ctx *ctx = (struct tlv_parse_ctx *)callback_data;
+	int ret;
+
+	switch (field) {
+	case ENTRY_DIGEST:
+		ret = parse_entry_digest(ctx, field, field_data,
+					 field_data_len);
+		break;
+	case ENTRY_PATH:
+		ret = parse_entry_path(ctx, field, field_data, field_data_len);
+		break;
+	default:
+		pr_debug("Unhandled field %llu\n", field);
+		/* Just ignore non-relevant fields. */
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+static int parse_digest_list_entry(struct tlv_parse_ctx *ctx,
+				   enum digest_list_fields field,
+				   const __u8 *field_data, __u64 field_data_len)
+{
+	return tlv_parse(DIGEST_LIST_FILE, entry_callback, ctx, field_data,
+			 field_data_len, digest_list_types_str,
+			 DIGEST_LIST__LAST, entry_fields_str, ENTRY__LAST);
+}
+
+static int digest_list_callback(void *callback_data, __u64 field,
+				const __u8 *field_data, __u64 field_data_len)
+{
+	struct tlv_parse_ctx *ctx = (struct tlv_parse_ctx *)callback_data;
+	int ret;
+
+	switch (field) {
+	case DIGEST_LIST_ALGO:
+		ret = parse_digest_list_algo(ctx, field, field_data,
+					     field_data_len);
+		break;
+	case DIGEST_LIST_ENTRY:
+		ret = parse_digest_list_entry(ctx, field, field_data,
+					      field_data_len);
+		break;
+	default:
+		pr_debug("Unhandled field %llu\n", field);
+		/* Just ignore non-relevant fields. */
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+int tlv_list_parse(const char *digest_list_path, enum ops op)
+{
+	struct tlv_parse_ctx ctx = {
+		.op = op, .digest_list_path = digest_list_path,
+		.digest_list_path_len = strlen(digest_list_path)
+	};
+	unsigned char *data;
+	size_t data_len;
+	int ret;
+
+	ret = read_file(digest_list_path, &data_len, &data);
+	if (ret < 0)
+		return ret;
+
+	ret = tlv_parse(DIGEST_LIST_FILE, digest_list_callback, &ctx, data,
+			data_len, digest_list_types_str, DIGEST_LIST__LAST,
+			digest_list_fields_str, FIELD__LAST);
+
+	munmap(data, data_len);
+	return ret;
+}
diff --git a/tools/digest-lists/parsers/tlv_parser.h b/tools/digest-lists/parsers/tlv_parser.h
new file mode 100644
index 00000000000..3c9f54a97b3
--- /dev/null
+++ b/tools/digest-lists/parsers/tlv_parser.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Header file of TLV parser.
+ */
+
+#ifndef _TLV_PARSER_H
+#define _TLV_PARSER_H
+
+#include <stdio.h>
+#include <errno.h>
+#include <stddef.h>
+#include <asm/byteorder.h>
+#include <linux/tlv_parser.h>
+
+#ifdef TLV_DEBUG
+#define pr_debug(fmt, ...) printf(fmt, ##__VA_ARGS__)
+#else
+#define pr_debug(fmt, ...) { }
+#endif
+
+typedef int (*parse_callback)(void *, __u64, const __u8 *, __u64);
+
+int tlv_parse_hdr(const __u8 **data, size_t *data_len, __u64 *parsed_data_type,
+		  __u64 *parsed_num_fields, __u64 *parsed_total_len,
+		  const char **data_types, __u64 num_data_types);
+int tlv_parse_data(parse_callback callback, void *callback_data,
+		   __u64 parsed_num_fields, const __u8 *data, size_t data_len,
+		   const char **fields, __u64 num_fields);
+int tlv_parse(__u64 expected_data_type, parse_callback callback,
+	      void *callback_data, const __u8 *data, size_t data_len,
+	      const char **data_types, __u64 num_data_types,
+	      const char **fields, __u64 num_fields);
+
+#endif /* _TLV_PARSER_H */
-- 
2.34.1


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

* [RFC][PATCH v2 12/13] tools/digest-lists: Add rpm digest list generator and parser
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (10 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 11/13] tools/digest-lists: Add tlv digest list generator and parser Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-08-12 10:46 ` [RFC][PATCH v2 13/13] docs: Add documentation of the integrity digest cache Roberto Sassu
  2023-09-05 15:46 ` [RFC][PATCH v2 00/13] integrity: Introduce a " Roberto Sassu
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Add a generator to generate an rpm digest list from one or multiple RPM
package headers. The digest list contains the RPM magic string, the content
of the RPMTAG_IMMUTABLE section, and the user asymmetric key signature
(module-style) converted from the PGP signature in the RPMTAG_RSAHEADER
section.

This generator has as prerequisite gpg support for a new command
--conv-kernel, which converts the PGP format to a user asymmetric key
signature.

Also add a parser of rpm digest list, to show the content (digest algorithm
and value, and file path), and to add/remove the security.digest_list xattr
to/from each file in the RPM packages.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 tools/digest-lists/.gitignore              |   2 +
 tools/digest-lists/Makefile                |  10 +-
 tools/digest-lists/generators/generators.h |   2 +
 tools/digest-lists/generators/rpm.c        | 257 +++++++++++++++++++++
 tools/digest-lists/manage_digest_lists.c   |   2 +
 tools/digest-lists/parsers/parsers.h       |   2 +
 tools/digest-lists/parsers/rpm.c           | 169 ++++++++++++++
 7 files changed, 442 insertions(+), 2 deletions(-)
 create mode 100644 tools/digest-lists/generators/rpm.c
 create mode 100644 tools/digest-lists/parsers/rpm.c

diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore
index 9a75ae766ff..51ca25f3b50 100644
--- a/tools/digest-lists/.gitignore
+++ b/tools/digest-lists/.gitignore
@@ -3,3 +3,5 @@ manage_digest_lists
 manage_digest_lists.1
 libgen-tlv-list.so
 libparse-tlv-list.so
+libgen-rpm-list.so
+libparse-rpm-list.so
diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile
index 23f9fa3b588..2c8089affb8 100644
--- a/tools/digest-lists/Makefile
+++ b/tools/digest-lists/Makefile
@@ -15,8 +15,8 @@ CFLAGS=-ggdb -Wall
 
 PROGS=manage_digest_lists
 
-GENERATORS=libgen-tlv-list.so
-PARSERS=libparse-tlv-list.so
+GENERATORS=libgen-tlv-list.so libgen-rpm-list.so
+PARSERS=libparse-tlv-list.so libparse-rpm-list.so
 
 MAN1=manage_digest_lists.1
 
@@ -31,9 +31,15 @@ manage_digest_lists: manage_digest_lists.c common.c $(GENERATORS) $(PARSERS)
 libgen-tlv-list.so: generators/tlv.c common.c
 	$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-tlv-list.so $^ -o $@
 
+libgen-rpm-list.so: generators/rpm.c common.c
+	$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-rpm-list.so $^ -o $@ -lrpm -lrpmio
+
 libparse-tlv-list.so: parsers/tlv.c common.c ../../lib/tlv_parser.c
 	$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-tlv-list.so $^ -o $@ -I parsers
 
+libparse-rpm-list.so: parsers/rpm.c common.c
+	$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-rpm-list.so $^ -o $@ -I parsers -lrpm -lrpmio
+
 ifneq ($(findstring $(MAKEFLAGS),s),s)
   ifneq ($(V),1)
      QUIET_A2X = @echo '  A2X     '$@;
diff --git a/tools/digest-lists/generators/generators.h b/tools/digest-lists/generators/generators.h
index 9830b791667..ff3ed6ac8d4 100644
--- a/tools/digest-lists/generators/generators.h
+++ b/tools/digest-lists/generators/generators.h
@@ -14,3 +14,5 @@
 void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo);
 int tlv_list_gen_add(int dirfd, void *ptr, char *input);
 void tlv_list_gen_close(void *ptr);
+
+int rpm_list_gen_add(int dirfd, void *ptr, char *input);
diff --git a/tools/digest-lists/generators/rpm.c b/tools/digest-lists/generators/rpm.c
new file mode 100644
index 00000000000..29e7a6eb0ca
--- /dev/null
+++ b/tools/digest-lists/generators/rpm.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Generate rpm digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmmacro.h>
+#include <asm/byteorder.h>
+
+#include "../common.h"
+
+static int gen_filename(Header rpm, char *filename, int filename_len)
+{
+	char *_filename = headerFormat(rpm, "rpm-%{nvra}", NULL);
+
+	if (!_filename)
+		return -ENOMEM;
+
+	strncpy(filename, _filename, filename_len);
+	free(_filename);
+	return 0;
+}
+
+static int write_rpm_header(Header rpm, int dirfd, char *filename)
+{
+	rpmtd immutable;
+	ssize_t ret;
+	int fd;
+
+	fd = openat(dirfd, filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	if (fd < 0)
+		return -EACCES;
+
+	ret = _write(fd, (void *)rpm_header_magic, sizeof(rpm_header_magic));
+	if (ret != sizeof(rpm_header_magic)) {
+		ret = -EIO;
+		goto out;
+	}
+
+	immutable = rpmtdNew();
+	headerGet(rpm, RPMTAG_HEADERIMMUTABLE, immutable, 0);
+	ret = _write(fd, immutable->data, immutable->count);
+	if (ret != immutable->count) {
+		ret = -EIO;
+		goto out;
+	}
+
+	rpmtdFree(immutable);
+out:
+	close(fd);
+
+	if (ret < 0)
+		unlinkat(dirfd, filename, 0);
+
+	return ret;
+}
+
+static int write_rpm_header_signature(Header rpm, int dirfd, char *filename)
+{
+	char sig_to_convert[] = "/tmp/sig_to_convert_XXXXXX";
+	char uasym_sig[] = "/tmp/uasym_sig_XXXXXX";
+	struct module_signature modsig = { 0 };
+	rpmtd signature = rpmtdNew();
+	__u8 buf[1024];
+	struct stat st;
+	int ret, n_read, status, fd, fd_sig_to_convert, fd_uasym_sig;
+
+	fd_sig_to_convert = mkstemp(sig_to_convert);
+	if (fd_sig_to_convert == -1)
+		return -errno;
+
+	fd_uasym_sig = mkstemp(uasym_sig);
+	if (fd_uasym_sig == -1) {
+		ret = -errno;
+		goto out;
+	}
+
+	headerGet(rpm, RPMTAG_RSAHEADER, signature, 0);
+	if (!signature->count) {
+		printf("Warning: no RPM signature for %s\n", filename);
+		ret = 0;
+		goto out_get;
+	}
+
+	ret = _write(fd_sig_to_convert, signature->data, signature->count);
+	if (ret != signature->count)
+		goto out_get;
+
+	close(fd_sig_to_convert);
+	fd_sig_to_convert = -1;
+
+	if (fork() == 0)
+		return execlp("gpg", "gpg", "--no-keyring", "--conv-kernel",
+			      "-o", uasym_sig, sig_to_convert, NULL);
+
+	wait(&status);
+	if (WEXITSTATUS(status)) {
+		ret = WEXITSTATUS(status);
+		goto out_get;
+	}
+
+	if (stat(uasym_sig, &st) == -1)
+		goto out_get;
+
+	fd = openat(dirfd, filename, O_WRONLY | O_APPEND);
+	if (fd < 0) {
+		ret = -errno;
+		goto out_get;
+	}
+
+	modsig.id_type = PKEY_ID_PGP;
+	modsig.sig_len = st.st_size;
+	modsig.sig_len = __cpu_to_be32(modsig.sig_len);
+
+	while ((n_read = read(fd_uasym_sig, buf, sizeof(buf))) > 0) {
+		ret = _write(fd, buf, n_read);
+		if (ret != n_read)
+			goto out_fd;
+	}
+
+	ret = _write(fd, &modsig, sizeof(modsig));
+	if (ret != sizeof(modsig))
+		goto out_fd;
+
+	ret = _write(fd, MODULE_SIG_STRING, sizeof(MODULE_SIG_STRING) - 1);
+	if (ret != sizeof(MODULE_SIG_STRING) - 1)
+		goto out_fd;
+
+	ret = 0;
+out_fd:
+	close(fd);
+
+	if (ret < 0)
+		unlinkat(dirfd, filename, 0);
+out_get:
+	rpmtdFree(signature);
+out:
+	close(fd_sig_to_convert);
+	unlink(sig_to_convert);
+	close(fd_uasym_sig);
+	unlink(uasym_sig);
+
+	return ret;
+}
+
+static void write_rpm_digest_list(Header rpm, int dirfd, char *filename)
+{
+	int ret;
+
+	ret = write_rpm_header(rpm, dirfd, filename);
+	if (ret < 0) {
+		printf("Cannot dump RPM header of %s\n", filename);
+		return;
+	}
+
+	ret = write_rpm_header_signature(rpm, dirfd, filename);
+	if (ret < 0)
+		printf("Cannot add signature to %s\n", filename);
+}
+
+int rpm_list_gen_add(int dirfd, void *ptr, char *input)
+{
+	char filename[NAME_MAX + 1], *selection;
+	rpmts ts = NULL;
+	Header hdr;
+	FD_t fd;
+	rpmdbMatchIterator mi;
+	rpmVSFlags vsflags = 0;
+	int ret;
+
+	ts = rpmtsCreate();
+	if (!ts) {
+		rpmlog(RPMLOG_NOTICE, "rpmtsCreate() error..\n");
+		ret = -EACCES;
+		goto out;
+	}
+
+	ret = rpmReadConfigFiles(NULL, NULL);
+	if (ret != RPMRC_OK) {
+		rpmlog(RPMLOG_NOTICE, "Unable to read RPM configuration.\n");
+		ret = -EACCES;
+		goto out_ts;
+	}
+
+	if (strncmp(input, "rpmdb", 5)) {
+		vsflags |= _RPMVSF_NODIGESTS;
+		vsflags |= _RPMVSF_NOSIGNATURES;
+		rpmtsSetVSFlags(ts, vsflags);
+
+		fd = Fopen(input, "r.ufdio");
+		if (!fd || Ferror(fd)) {
+			rpmlog(RPMLOG_NOTICE,
+			       "Failed to open package file %s, %s\n", input,
+			       Fstrerror(fd));
+			ret = -EACCES;
+			goto out_rpm;
+		}
+
+		ret = rpmReadPackageFile(ts, fd, "rpm", &hdr);
+		Fclose(fd);
+
+		if (ret != RPMRC_OK) {
+			rpmlog(RPMLOG_NOTICE,
+			       "Could not read package file %s\n", input);
+			goto out_rpm;
+		}
+
+		gen_filename(hdr, filename, sizeof(filename));
+
+		write_rpm_digest_list(hdr, dirfd, filename);
+		headerFree(hdr);
+		goto out_rpm;
+	}
+
+	mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
+	while ((hdr = rpmdbNextIterator(mi)) != NULL) {
+		gen_filename(hdr, filename, sizeof(filename));
+
+		/* Skip rpm- */
+		if (strstr(filename + 4, "gpg-pubkey"))
+			continue;
+
+		selection = strchr(input, ':');
+		if (selection && !strstr(filename + 4, selection + 1))
+			continue;
+
+		write_rpm_digest_list(hdr, dirfd, filename);
+	}
+
+	rpmdbFreeIterator(mi);
+out_rpm:
+	rpmFreeRpmrc();
+	rpmFreeCrypto();
+	rpmFreeMacros(NULL);
+out_ts:
+	rpmtsFree(ts);
+out:
+	return ret;
+}
diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c
index 7caad681eee..3985bfcb827 100644
--- a/tools/digest-lists/manage_digest_lists.c
+++ b/tools/digest-lists/manage_digest_lists.c
@@ -33,10 +33,12 @@ const char *ops_str[OP__LAST] = {
 struct generator generators[] = {
 	{ .name = "tlv", .new = tlv_list_gen_new, .add = tlv_list_gen_add,
 	  .close = tlv_list_gen_close },
+	{ .name = "rpm", .add = rpm_list_gen_add },
 };
 
 struct parser parsers[] = {
 	{ .name = "tlv", .parse = tlv_list_parse },
+	{ .name = "rpm", .parse = rpm_list_gen_parse },
 };
 
 static int generator_add(struct generator *generator, int dirfd,
diff --git a/tools/digest-lists/parsers/parsers.h b/tools/digest-lists/parsers/parsers.h
index 708da7eac3b..ecefb2ec79b 100644
--- a/tools/digest-lists/parsers/parsers.h
+++ b/tools/digest-lists/parsers/parsers.h
@@ -12,3 +12,5 @@
 #include <errno.h>
 
 int tlv_list_parse(const char *digest_list_path, enum ops op);
+
+int rpm_list_gen_parse(const char *digest_list_path, enum ops op);
diff --git a/tools/digest-lists/parsers/rpm.c b/tools/digest-lists/parsers/rpm.c
new file mode 100644
index 00000000000..7dd063b64ac
--- /dev/null
+++ b/tools/digest-lists/parsers/rpm.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Parse rpm digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmmacro.h>
+#include <asm/byteorder.h>
+
+#include "../common.h"
+
+static const enum hash_algo pgp_hash_algorithms[PGPHASHALGO_SHA224 + 1] = {
+	[PGPHASHALGO_MD5]		= HASH_ALGO_MD5,
+	[PGPHASHALGO_SHA1]		= HASH_ALGO_SHA1,
+	[PGPHASHALGO_RIPEMD160]		= HASH_ALGO_RIPE_MD_160,
+	[PGPHASHALGO_SHA256]		= HASH_ALGO_SHA256,
+	[PGPHASHALGO_SHA384]		= HASH_ALGO_SHA384,
+	[PGPHASHALGO_SHA512]		= HASH_ALGO_SHA512,
+	[PGPHASHALGO_SHA224]		= HASH_ALGO_SHA224,
+};
+
+int rpm_list_gen_parse(const char *digest_list_path, enum ops op)
+{
+	rpmtd filedigestalgo, filedigests, basenames, dirnames, dirindexes;
+	rpmts ts = NULL;
+	Header hdr;
+	FD_t fd;
+	rpmVSFlags vsflags = 0;
+	char file_path[PATH_MAX];
+	enum hash_algo algo = HASH_ALGO_MD5;
+	const char *digest_str, *basename, *dirname;
+	__u32 dirindex, *pgp_algo_ptr;
+	size_t digest_list_path_len = strlen(digest_list_path);
+	int ret;
+
+	ts = rpmtsCreate();
+	if (!ts) {
+		rpmlog(RPMLOG_NOTICE, "rpmtsCreate() error..\n");
+		ret = -EACCES;
+		goto out;
+	}
+
+	ret = rpmReadConfigFiles(NULL, NULL);
+	if (ret != RPMRC_OK) {
+		rpmlog(RPMLOG_NOTICE, "Unable to read RPM configuration.\n");
+		ret = -EACCES;
+		goto out_ts;
+	}
+
+	vsflags |= _RPMVSF_NODIGESTS;
+	vsflags |= _RPMVSF_NOSIGNATURES;
+	rpmtsSetVSFlags(ts, vsflags);
+
+	fd = Fopen(digest_list_path, "r.ufdio");
+	if (!fd || Ferror(fd)) {
+		rpmlog(RPMLOG_NOTICE, "Failed to open package file %s, %s\n",
+		       digest_list_path, Fstrerror(fd));
+		ret = -EACCES;
+		goto out_rpm;
+	}
+
+	ret = rpmReadHeader(ts, fd, &hdr, NULL);
+	Fclose(fd);
+
+	if (ret != RPMRC_OK) {
+		rpmlog(RPMLOG_NOTICE, "Could not read package file %s\n",
+		       digest_list_path);
+		goto out_rpm;
+	}
+
+	filedigestalgo = rpmtdNew();
+	filedigests = rpmtdNew();
+	basenames = rpmtdNew();
+	dirnames = rpmtdNew();
+	dirindexes = rpmtdNew();
+
+	headerGet(hdr, RPMTAG_FILEDIGESTALGO, filedigestalgo, 0);
+	headerGet(hdr, RPMTAG_FILEDIGESTS, filedigests, 0);
+	headerGet(hdr, RPMTAG_BASENAMES, basenames, 0);
+	headerGet(hdr, RPMTAG_DIRNAMES, dirnames, 0);
+	headerGet(hdr, RPMTAG_DIRINDEXES, dirindexes, 0);
+
+	pgp_algo_ptr = rpmtdGetUint32(filedigestalgo);
+	if (pgp_algo_ptr && *pgp_algo_ptr <= PGPHASHALGO_SHA224)
+		algo = pgp_hash_algorithms[*pgp_algo_ptr];
+
+	while ((digest_str = rpmtdNextString(filedigests))) {
+		basename = rpmtdNextString(basenames);
+		dirindex = *rpmtdNextUint32(dirindexes);
+
+		rpmtdSetIndex(dirnames, dirindex);
+		dirname = rpmtdGetString(dirnames);
+
+		snprintf(file_path, sizeof(file_path), "%s%s", dirname,
+			 basename);
+
+		if (!strlen(digest_str))
+			continue;
+
+		switch (op) {
+		case OP_SHOW:
+			printf("%s:%s %s\n", hash_algo_name[algo], digest_str,
+			       file_path);
+			ret = 0;
+			break;
+		case OP_ADD_XATTR:
+			ret = lsetxattr(file_path, XATTR_NAME_DIGEST_LIST,
+					digest_list_path,
+					digest_list_path_len, 0);
+			if (ret < 0 && errno == ENODATA)
+				ret = 0;
+
+			if (ret < 0)
+				printf("Error setting %s on %s, %s\n",
+				       XATTR_NAME_DIGEST_LIST, file_path,
+				       strerror(errno));
+			break;
+		case OP_RM_XATTR:
+			ret = lremovexattr(file_path, XATTR_NAME_DIGEST_LIST);
+			if (ret < 0 && errno == ENODATA)
+				ret = 0;
+
+			if (ret < 0)
+				printf("Error removing %s from %s, %s\n",
+				       XATTR_NAME_DIGEST_LIST, file_path,
+				       strerror(errno));
+			break;
+		default:
+			ret = -EOPNOTSUPP;
+			break;
+		}
+
+		if (ret < 0)
+			break;
+	}
+
+	rpmtdFree(filedigestalgo);
+	rpmtdFree(filedigests);
+	rpmtdFree(basenames);
+	rpmtdFree(dirnames);
+	rpmtdFree(dirindexes);
+	headerFree(hdr);
+out_rpm:
+	rpmFreeRpmrc();
+	rpmFreeCrypto();
+	rpmFreeMacros(NULL);
+out_ts:
+	rpmtsFree(ts);
+out:
+	return ret;
+}
-- 
2.34.1


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

* [RFC][PATCH v2 13/13] docs: Add documentation of the integrity digest cache
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (11 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 12/13] tools/digest-lists: Add rpm " Roberto Sassu
@ 2023-08-12 10:46 ` Roberto Sassu
  2023-09-05 15:46 ` [RFC][PATCH v2 00/13] integrity: Introduce a " Roberto Sassu
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-08-12 10:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

From: Roberto Sassu <roberto.sassu@huawei.com>

Add the documentation of the integrity digest cache in
Documentation/security.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/security/index.rst              |   1 +
 .../security/integrity-digest-cache.rst       | 484 ++++++++++++++++++
 MAINTAINERS                                   |   1 +
 3 files changed, 486 insertions(+)
 create mode 100644 Documentation/security/integrity-digest-cache.rst

diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst
index 6ed8d2fa6f9..3316d50c839 100644
--- a/Documentation/security/index.rst
+++ b/Documentation/security/index.rst
@@ -18,3 +18,4 @@ Security Documentation
    digsig
    landlock
    secrets/index
+   integrity-digest-cache
diff --git a/Documentation/security/integrity-digest-cache.rst b/Documentation/security/integrity-digest-cache.rst
new file mode 100644
index 00000000000..371b3f84780
--- /dev/null
+++ b/Documentation/security/integrity-digest-cache.rst
@@ -0,0 +1,484 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================
+Integrity Digest Cache
+======================
+
+Introduction
+============
+
+The main goal of Integrity Measurement Architecture (IMA) is to perform a
+measurement of the file content and use it for remote attestation, to
+report a possibly compromised system, using the TPM as a root of trust. It
+can also prevent a system compromise from happening by checking the
+calculated file digest against a known-good reference value and by denying
+the current operation if there is a mismatch.
+
+
+Motivation
+==========
+
+This patch set aims to address two important shortcomings: predictability
+of the Platform Configuration Registers (PCRs), and the provisioning of
+reference values to compare the calculated file digest against.
+
+Remote attestation, according to Trusted Computing Group (TCG)
+specifications, is done by replicating the PCR extend operation in
+software with the digests in the event log (in this case the IMA
+measurement list), and by comparing the obtained value with the PCR value
+signed by the TPM with the quote operation.
+
+Due to how the extend operation is performed, if measurements are done in
+a different order, the final PCR value will be different. That means that
+if measurements are done in parallel, there is no way to predict what the
+final PCR value will be, making impossible to seal data to a PCR value. If
+the PCR value was predictable, a system could for example prove its
+integrity by unsealing and using its private key, without sending every
+time the full list of measurements.
+
+Provisioning reference values for file digests is also a difficult task.
+The solution so far was to add file signatures to RPM packages, and
+possibly to DEB packages, so that IMA can verify them. While this undoubtly
+works, it also requires Linux distribution vendors to support the feature
+by rebuilding all their packages, and eventually extending their PKI to
+perform the additional signatures. It could also require developers extra
+work to deal with the additional data.
+
+On the other hand, since often packages carry the file digests themselves,
+it won't be actually needed to add file signatures. If the kernel was able
+to extract the file digests by itself, all the tasks mentioned above for
+the Linux distribution vendors won't be needed too. All current and past
+Linux distributions can be easily retrofitted to enable IMA appraisal with
+the file digests from the packages.
+
+Narrowing down the scope of a package parser to only extract specific
+information makes it small enough to accurately verify that it cannot harm
+the kernel. An additional mitigation consists in verifying the signature of
+the package first, before attempting to extract the file digests.
+
+
+Solution
+========
+
+To avoid a PCR is extended in a non-deterministic way, the proposed
+solution is to replace individual file measurements with the measurement of
+a file (the digest list) containing a set of file digests. If the
+calculated digest of a file being measured/appraised matches one digest in
+the set, its measurement is skipped. If otherwise there is no match, the
+file digest is added to the measurement list.
+
+The resulting measurement list, which cannot be done on the default IMA PCR
+to avoid ambiguity with the default-style measurement, has the following
+meaning: none/some/all files represented with the measurement of the digest
+lists COULD have been accessed, without knowing IF and WHEN. Any other
+measurement (other than boot_aggregate) is of a file whose digest was not
+included in the digest list.
+
+File signatures have a coarser granularity, it is per-signing key and not
+per-package. A measurement list containing just the measurement of the
+signing keys and the files without/invalid signature (those with valid
+signature would be skipped) would be even less accurate.
+
+To ensure a rapid and smooth deployment of IMA appraisal, the kernel has
+been provided with the ability to extract file digests from the RPM
+package headers, and add them to the kernel memory on demand (only when a
+file from a given package is accessed). This ensures that the memory
+consumption for this new feature is directly proportional to the usage of
+the system.
+
+
+Scope
+=====
+
+The integrity digest cache enables IMA to extend a PCR (not the default
+one) in a deterministic fashion, and to appraise immutable files with file
+digests from the packages, when no other appraisal method is available. It
+does not yet support metadata verification with Extended Verification
+Module (EVM), for which a separate patch set will be provided.
+
+
+Design
+======
+
+The digest cache is a hash table of file digests, attached to the inode of
+the digest list from which file digests are extracted. It is accessible,
+when a given file is being measured/appraised, from the new xattr
+security.digest_list, containing the path of the digest list itself.
+
+If the calculated file digest is found in the digest cache, its measurement
+is avoided, or read-only access is granted if appraisal is in enforcing
+mode. Read-write access is prevented to avoid updating an unverified HMAC
+of file metadata.
+
+The digest cache can be used only if the following conditions are met:
+
+- The ``digest_cache=content`` keyword is added to the desired IMA policy
+  rules;
+- If the rule action is ``measure``, a PCR different from the default one
+  is specified;
+- If the rule action is ``appraise``, ``digest_cache=content`` and
+  ``appraise_type`` don't appear at the same time;
+- The same action for which the digest cache is used was done also on the
+  digest list;
+- The digest cache (currently) is not used for measurement/appraisal of
+  other digest lists.
+
+For performance reasons, the digest cache is attached to every inode using
+it, since multiple hooks can be invoked on it before the
+measurement/appraisal result is cached. A reference count indicates how
+many inodes use it, and only when it reaches zero, the digest cache can be
+freed (for example when inodes are evicted from memory).
+
+Two digest cache pointers have been added to the iint to distinguish for
+which purpose they should be used: dig_owner points to the digest cache
+created from the same inode the iint refers to, and should be used for
+measurement/appraisal of other inodes; dig_user points to the digest
+cache created from a different inode, and requested for
+measurement/appraisal. One digest cache pointer would be confusing, as
+for digest lists the digest cache was created from them, but IMA would
+try to use that digest cache for measurement/appraisal of itself.
+
+Finally, at the first digest list measurement, an iterator is executed to
+sequentially read (not parse) all the digest lists in the same directory,
+so that the PCR is extended in a deterministic fashion. The other
+concurrent users of the digest cache have to wait until the iterator
+finishes.
+
+
+API
+===
+
+Data Structures
+~~~~~~~~~~~~~~~
+
+.. kernel-doc:: security/integrity/digest_cache.h
+
+
+Functions
+~~~~~~~~~
+
+.. kernel-doc:: security/integrity/digest_cache.c
+
+``digest_cache_alloc()``, ``digest_cache_parse_digest_list()`` and
+``digest_cache_new()`` are internal functions used during the creation and
+initialization of the digest cache.
+
+``digest_cache_get()`` and ``digest_cache_free()`` are called by the user
+of the digest cache (e.g. IMA), to obtain and free a digest cache.
+
+``digest_cache_init_htable()``, ``digest_cache_add()`` and
+``digest_cache_lookup()`` are called by the digest list parsers to populate
+and search in a digest cache.
+
+
+Digest List Formats
+===================
+
+tlv
+~~~
+
+The Type-Length-Value (TLV) format was chosen for its extensibility.
+Additional fields can be added without breaking compatibility with old
+versions of the parser.
+
+The layout of a tlv digest list is the following::
+
+ [header: DIGEST_LIST_FILE, num fields, total len]
+ [field: DIGEST_LIST_ALGO, length, value]
+ [field: DIGEST_LIST_ENTRY#1, length, value (below)]
+  |- [header: DIGEST_LIST_FILE, num fields, total len]
+  |- [ENTRY#1_DIGEST, length, file digest]
+  |- [ENTRY#1_PATH, length, file path]
+ [field: DIGEST_LIST_ENTRY#N, length, value (below)]
+  |- [header: DIGEST_LIST_FILE, num fields, total len]
+  |- [ENTRY#N_DIGEST, length, file digest]
+  |- [ENTRY#N_PATH, length, file path]
+
+DIGEST_LIST_ALGO is a field to specify the algorithm of the file digest.
+DIGEST_LIST_ENTRY is a nested TLV structure with the following fields:
+ENTRY_DIGEST contains the file digest; ENTRY_PATH contains the file path.
+
+
+rpm
+~~~
+
+The rpm digest list is basically a subset of the RPM package header.
+Its format is::
+
+ [RPM magic number]
+ [RPMTAG_IMMUTABLE]
+
+RPMTAG_IMMUTABLE is a section of the full RPM header containing the part
+of the header that was signed, and whose signature is stored in the
+RPMTAG_RSAHEADER section.
+
+
+Appended Signature
+~~~~~~~~~~~~~~~~~~
+
+Digest lists can have a module-style appended signature, that can be used
+for appraisal with IMA. The signature type can be PKCS#7, as for kernel
+modules, or the new user asymmetric key signature.
+
+
+History
+=======
+
+The original name of this work was IMA Digest Lists, which was somehow
+considered too invasive. The code was moved to a separate component named
+DIGLIM (DIGest Lists Integrity Module), with the purpose of removing the
+complexity away of IMA, and also add the possibility of using it with other
+kernel components (e.g. Integrity Policy Enforcement, or IPE).
+
+Since it was originally proposed, in 2017, this work grew up a lot thanks
+to various comments/suggestions. It became integrally part of the openEuler
+distribution since end of 2020.
+
+There are significant differences between this and the previous versions.
+The most important one is moving from a centralized repository of file
+digests to a per-package repository. This significantly reduces the memory
+pressure, since digest lists are loaded into kernel memory only when they
+are actually needed. Also, file digests are automatically unloaded from
+kernel memory at the same time inodes are evicted from memory during
+reclamation.
+
+
+Performance
+===========
+
+The tests have been performed on a Fedora 38 virtual machine, with 8 cores
+(AMD EPYC-Rome), 4GB of RAM, TPM passthrough. The signing key is an ECDSA
+NIST P-384.
+
+IMA measurement policy: no cache
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ dont_measure fsmagic=0x01021994
+ measure func=BPRM_CHECK
+ measure func=MMAP_CHECK
+
+
+IMA measurement policy: cache
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ dont_measure fsmagic=0x01021994
+ measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11
+ measure func=BPRM_CHECK digest_cache=content pcr=11
+ measure func=MMAP_CHECK digest_cache=content pcr=11
+
+
+IMA Measurement Results
+~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+                               +-----------+-----------+-----------+
+                               | # measur. | boot time |   slab    |
+ +-----------------------------+-----------+-----------+-----------+
+ | measure (no cache)          |    389    |  12.682s  | 231453 KB |
+ +-----------------------------+-----------+-----------+-----------+
+ | measure (cache, no iter)    |    175    |  12.283s  | 234224 KB |
+ +-----------------------------+-----------+-----------+-----------+
+ | measure (cache, iter)       |    853    |  16.430s  | 238491 KB |
+ +-----------------------------+-----------+-----------+-----------+
+
+With the iterator enabled, all 852 packages are measured. Consequently, the
+boot time is longer. One possible optimization would be to exclude the
+packages that don't include measured files. By disabling the iterator, it
+can be seen that the packages actually used are 174 (one measurement is for
+boot_aggregate).
+
+
+IMA appraisal policy: no cache
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ dont_appraise fsmagic=0x01021994
+ appraise func=BPRM_CHECK appraise_type=imasig
+ appraise func=MMAP_CHECK appraise_type=imasig
+
+
+IMA appraisal policy: cache
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ dont_appraise fsmagic=0x01021994
+ appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig
+ appraise func=BPRM_CHECK digest_cache=content
+ appraise func=MMAP_CHECK digest_cache=content
+
+
+IMA Appraisal Results
+~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+                               +-----------+-----------+
+                               | boot time |   slab    |
+ +-----------------------------+-----------+-----------+
+ | appraise (no cache)         |  11.995s  | 231145 KB |
+ +-----------------------------+-----------+-----------+
+ | appraise (cache)            |  11.879s  | 233091 KB |
+ +-----------------------------+-----------+-----------+
+
+In this test, it can be seen that the performance of the two solutions are
+comparable, with the digest cache slightly ahead. The difference could be
+more substantial with more file appraised.
+
+
+How to Test
+===========
+
+First, it is necessary to copy the new kernel headers (tlv_parser.h,
+uasym_parser.h, tlv_digest_list.h) from usr/include/linux in the kernel
+source directory to /usr/include/linux.
+
+Then, gpg must be rebuilt with the additional patches to convert the PGP
+keys of the Linux distribution to the new user asymmetric key format:
+
+.. code-block:: bash
+
+ $ gpg --conv-kernel <path of PGP key> >> certs/uasym_keys.bin
+
+This embeds the converted keys in the kernel image. Then, the following
+kernel options must be enabled:
+
+.. code-block:: bash
+
+ CONFIG_INTEGRITY_DIGEST_CACHE=y
+ CONFIG_UASYM_KEYS_SIGS=y
+ CONFIG_UASYM_PRELOAD_PUBLIC_KEYS=y
+
+and the kernel must be rebuilt with the patches applied. After boot, it is
+necessary to build and install the digest list tool in tools/digest-lists,
+and to execute (as root):
+
+.. code-block:: bash
+
+ # manage_digest_lists -o gen -d /etc/digest_lists -i rpmdb -f rpm
+
+The new gpg must also be installed in the system, as it will be used to
+convert the PGP signatures of the RPM headers to the user asymmetric key
+format.
+
+It is recommended to create an additional digest list with the following
+files, by creating a file named ``list`` with the content:
+
+.. code-block:: bash
+
+ /usr/bin/manage_digest_lists
+ /usr/lib64/libgen-tlv-list.so
+ /usr/lib64/libgen-rpm-list.so
+ /usr/lib64/libparse-rpm-list.so
+ /usr/lib64/libparse-tlv-list.so
+
+Then, to create the digest list, it is sufficient to execute:
+
+.. code-block:: bash
+
+ # manage_digest_lists -i list -L -d /etc/digest_lists -o gen -f tlv
+
+If appraisal is enabled and in enforcing mode, it is necessary to sign the
+new digest list, with the sign-file tool in the scripts/ directory of the
+kernel sources:
+
+.. code-block:: bash
+
+ # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/tlv-list
+
+The final step is to add security.digest_list to each file with:
+
+.. code-block:: bash
+
+ # manage_digest_lists -i /etc/digest_lists -o add-xattr
+
+After that, it is possible to test the integrity digest cache with the
+following policy written to /etc/ima/ima-policy:
+
+.. code-block:: bash
+
+ dont_measure fsmagic=0x01021994
+ measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11
+ measure func=BPRM_CHECK digest_cache=content pcr=11
+ measure func=MMAP_CHECK digest_cache=content pcr=11
+ dont_appraise fsmagic=0x01021994
+ appraise func=BPRM_CHECK digest_cache=content
+ appraise func=MMAP_CHECK digest_cache=content
+ appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig
+
+Tmpfs is excluded for now, until memfd is properly handled.
+
+Before loading the policy, it is possible to enable dynamic debug to see
+which operations are done by the integrity digest cache:
+
+.. code-block:: bash
+
+ # echo "file tlv* +p" > /sys/kernel/debug/dynamic_debug/control
+ # echo "file rpm* +p" > /sys/kernel/debug/dynamic_debug/control
+ # echo "file digest* +p" > /sys/kernel/debug/dynamic_debug/control
+
+Alternatively, the same strings can be set as value of the dyndbg= option
+in the kernel command line.
+
+A preliminary test, before booting the system with the new policy, is to
+supply the policy to IMA in the current system with:
+
+.. code-block:: bash
+
+ # cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy
+
+If that worked, the system can be rebooted. Systemd will take care of
+loading the IMA policy at boot. The instructions have been tested on a
+Fedora 38 OS.
+
+After boot, it is possible to check the content of the measurement list:
+
+.. code-block:: bash
+
+ # cat /sys/kernel/security/ima/ascii_runtime_measurements
+
+If only the files shipped with Fedora 38 have been executed, the
+measurement list will contain only the digest lists, and not the individual
+files.
+
+Another test is to ensure that IMA prevents the execution of unknown files:
+
+.. code-block:: bash
+
+ # cp -a /bin/cat .
+ # ./cat
+
+That will work. But not on the modified binary:
+
+.. code-block:: bash
+
+ # echo 1 >> cat
+ # cat
+ -bash: ./cat: Permission denied
+
+Execution will be denied, and a new entry in the measurement list will
+appear (it would be probably ok to not add that entry, as access to the
+file was denied):
+
+.. code-block:: bash
+
+ 11 50b5a68bea0776a84eef6725f17ce474756e51c0 ima-ng sha256:15e1efee080fe54f5d7404af7e913de01671e745ce55215d89f3d6521d3884f0 /root/cat
+
+Finally, it is possible to test the shrinking of the digest cache, by
+forcing the kernel to evict inodes from memory:
+
+.. code-block:: bash
+
+ # echo 3 > /proc/sys/vm/drop_caches
+
+The kernel log should have messages like:
+
+.. code-block:: bash
+
+ [  313.032536] DIGEST CACHE: Remove digest sha256:102900208eef27b766380135906d431dba87edaa7ec6aa72e6ebd3dd67f3a97b from digest list /etc/digest_lists/rpm-libseccomp-2.5.3-4.fc38.x86_64
diff --git a/MAINTAINERS b/MAINTAINERS
index 2bd85dcfd23..af33db344ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10293,6 +10293,7 @@ M:	Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
 L:	linux-integrity@vger.kernel.org
 S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
+F:	Documentation/security/integrity-digest-cache.rst
 F:	security/integrity/
 F:	security/integrity/ima/
 F:	tools/digest-lists/
-- 
2.34.1


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

* Re: [RFC][PATCH v2 02/13] integrity: Introduce a digest cache
  2023-08-12 10:46 ` [RFC][PATCH v2 02/13] integrity: Introduce a digest cache Roberto Sassu
@ 2023-08-14 17:03   ` Jarkko Sakkinen
  2023-08-16  8:21     ` Roberto Sassu
  0 siblings, 1 reply; 21+ messages in thread
From: Jarkko Sakkinen @ 2023-08-14 17:03 UTC (permalink / raw)
  To: Roberto Sassu, corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

On Sat Aug 12, 2023 at 1:46 PM EEST, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
>
> Introduce the digest cache, a structure holding a hash table of digests,
> extracted from a digest list. Its pointer is stored in the iint of the

What is iint? I honestly don't know what it is. I first thought that it
was "int" typoed.

BR, Jarkko

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

* Re: [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search
  2023-08-12 10:46 ` [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search Roberto Sassu
@ 2023-08-14 17:13   ` Jarkko Sakkinen
  2023-08-16  8:35     ` Roberto Sassu
  0 siblings, 1 reply; 21+ messages in thread
From: Jarkko Sakkinen @ 2023-08-14 17:13 UTC (permalink / raw)
  To: Roberto Sassu, corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

On Sat Aug 12, 2023 at 1:46 PM EEST, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
>
> Add digest_cache_init_htable(), to size a hash table depending on the
> number of digests to be added to the cache.
>
> Add digest_cache_add() and digest_cache_lookup() to respectively add and
> lookup a digest in the digest cache.
>
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> ---
>  security/integrity/digest_cache.c | 131 ++++++++++++++++++++++++++++++
>  security/integrity/digest_cache.h |  24 ++++++
>  2 files changed, 155 insertions(+)
>
> diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
> index 4201c68171a..d14d84b804b 100644
> --- a/security/integrity/digest_cache.c
> +++ b/security/integrity/digest_cache.c
> @@ -315,3 +315,134 @@ struct digest_cache *digest_cache_get(struct dentry *dentry,
>  
>  	return iint->dig_user;
>  }
> +
> +/**
> + * digest_cache_init_htable - Allocate and initialize the hash table
> + * @digest_cache: Digest cache
> + * @num_digests: Number of digests to add to the digest cache
> + *
> + * This function allocates and initializes the hash table. Its size is
> + * determined by the number of digests to add to the digest cache, known
> + * at this point by the parser calling this function.
> + *
> + * Return: Zero on success, a negative value otherwise.
> + */
> +int digest_cache_init_htable(struct digest_cache *digest_cache,
> +			     u64 num_digests)
> +{
> +	int i;
> +
> +	if (!digest_cache)
> +		return 0;
> +
> +	digest_cache->num_slots = num_digests / DIGEST_CACHE_HTABLE_DEPTH;
> +	if (!digest_cache->num_slots)
> +		digest_cache->num_slots = 1;
> +
> +	digest_cache->slots = kmalloc_array(num_digests,
> +					    sizeof(*digest_cache->slots),
> +					    GFP_KERNEL);
> +	if (!digest_cache->slots)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < digest_cache->num_slots; i++)
> +		INIT_HLIST_HEAD(&digest_cache->slots[i]);
> +
> +	pr_debug("Initialized %d hash table slots for digest list %s\n",
> +		 digest_cache->num_slots, digest_cache->path_str);
> +	return 0;
> +}
> +
> +/**
> + * digest_cache_add - Add a new digest to the digest cache
> + * @digest_cache: Digest cache
> + * @digest: Digest to add
> + *
> + * This function, invoked by a digest list parser, adds a digest extracted
> + * from a digest list to the digest cache.
> + *
> + * Return: Zero on success, a negative value on error.

Nit: previous had a different phrasing "a negative value otherwise".

I would suggest "a POSIX error code otherwise" for both.

> + */
> +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest)
> +{
> +	struct digest_cache_entry *entry;
> +	unsigned int key;
> +	int digest_len;
> +
> +	if (!digest_cache)
> +		return 0;
> +
> +	digest_len = hash_digest_size[digest_cache->algo];
> +
> +	entry = kmalloc(sizeof(*entry) + digest_len, GFP_KERNEL);
> +	if (!entry)
> +		return -ENOMEM;
> +
> +	memcpy(entry->digest, digest, digest_len);
> +
> +	key = digest_cache_hash_key(digest, digest_cache->num_slots);
> +	hlist_add_head(&entry->hnext, &digest_cache->slots[key]);
> +	pr_debug("Add digest %s:%*phN from digest list %s\n",
> +		 hash_algo_name[digest_cache->algo], digest_len, digest,
> +		 digest_cache->path_str);
> +	return 0;
> +}
> +
> +/**
> + * digest_cache_lookup - Searches a digest in the digest cache
> + * @digest_cache: Digest cache
> + * @digest: Digest to search
> + * @algo: Algorithm of the digest to search
> + * @pathname: Path of the file whose digest is looked up
> + *
> + * This function, invoked by IMA or EVM, searches the calculated digest of
> + * a file or file metadata in the digest cache acquired with
> + * digest_cache_get().
> + *
> + * Return: Zero if the digest is found, a negative value if not.
> + */
> +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
> +			enum hash_algo algo, const char *pathname)
> +{
> +	struct digest_cache_entry *entry;
> +	unsigned int key;
> +	int digest_len;
> +	int search_depth = 0;
> +
> +	if (!digest_cache)
> +		return -ENOENT;
> +
> +	if (digest_cache->algo == HASH_ALGO__LAST) {
> +		pr_debug("Algorithm not set for digest list %s\n",
> +			 digest_cache->path_str);
> +		return -ENOENT;
> +	}
> +
> +	digest_len = hash_digest_size[digest_cache->algo];
> +
> +	if (algo != digest_cache->algo) {
> +		pr_debug("Algo mismatch for file %s, digest %s:%*phN in digest list %s (%s)\n",
> +			 pathname, hash_algo_name[algo], digest_len, digest,
> +			 digest_cache->path_str,
> +			 hash_algo_name[digest_cache->algo]);
> +		return -ENOENT;
> +	}
> +
> +	key = digest_cache_hash_key(digest, digest_cache->num_slots);
> +
> +	hlist_for_each_entry_rcu(entry, &digest_cache->slots[key], hnext) {
> +		if (!memcmp(entry->digest, digest, digest_len)) {
> +			pr_debug("Cache hit at depth %d for file %s, digest %s:%*phN in digest list %s\n",
> +				 search_depth, pathname, hash_algo_name[algo],
> +				 digest_len, digest, digest_cache->path_str);
> +			return 0;
> +		}
> +
> +		search_depth++;
> +	}
> +
> +	pr_debug("Cache miss for file %s, digest %s:%*phN in digest list %s\n",
> +		 pathname, hash_algo_name[algo], digest_len, digest,
> +		 digest_cache->path_str);
> +	return -ENOENT;
> +}
> diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
> index ff88e8593c6..01cd70f9850 100644
> --- a/security/integrity/digest_cache.h
> +++ b/security/integrity/digest_cache.h
> @@ -66,6 +66,11 @@ static inline unsigned int digest_cache_hash_key(u8 *digest,
>  void digest_cache_free(struct digest_cache *digest_cache);
>  struct digest_cache *digest_cache_get(struct dentry *dentry,
>  				      struct integrity_iint_cache *iint);
> +int digest_cache_init_htable(struct digest_cache *digest_cache,
> +			     u64 num_digests);
> +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest);
> +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
> +			enum hash_algo algo, const char *pathname);
>  #else
>  static inline void digest_cache_free(struct digest_cache *digest_cache)
>  {
> @@ -77,5 +82,24 @@ digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint)
>  	return NULL;
>  }
>  
> +static inline int digest_cache_init_htable(struct digest_cache *digest_cache,
> +					   u64 num_digests)
> +{
> +	return -EOPNOTSUPP;
> +}
> +
> +static inline int digest_cache_add(struct digest_cache *digest_cache,
> +				   u8 *digest)
> +{
> +	return -EOPNOTSUPP;
> +}
> +
> +static inline int digest_cache_lookup(struct digest_cache *digest_cache,
> +				      u8 *digest, enum hash_algo algo,
> +				      const char *pathname)
> +{
> +	return -ENOENT;
> +}
> +
>  #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
>  #endif /* _DIGEST_CACHE_H */
> -- 
> 2.34.1

Why all this complexity instead of using xarray?

https://docs.kernel.org/core-api/xarray.html

BR, Jarkko

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

* Re: [RFC][PATCH v2 02/13] integrity: Introduce a digest cache
  2023-08-14 17:03   ` Jarkko Sakkinen
@ 2023-08-16  8:21     ` Roberto Sassu
  2023-08-16 20:39       ` Jarkko Sakkinen
  0 siblings, 1 reply; 21+ messages in thread
From: Roberto Sassu @ 2023-08-16  8:21 UTC (permalink / raw)
  To: Jarkko Sakkinen, corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

On 8/14/2023 7:03 PM, Jarkko Sakkinen wrote:
> On Sat Aug 12, 2023 at 1:46 PM EEST, Roberto Sassu wrote:
>> From: Roberto Sassu <roberto.sassu@huawei.com>
>>
>> Introduce the digest cache, a structure holding a hash table of digests,
>> extracted from a digest list. Its pointer is stored in the iint of the
> 
> What is iint? I honestly don't know what it is. I first thought that it
> was "int" typoed.

Ops. It is the integrity_iint_cache structure, to retain the 
integrity-specific state of an inode. Will explain that in the next version.

Thanks

Roberto


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

* Re: [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search
  2023-08-14 17:13   ` Jarkko Sakkinen
@ 2023-08-16  8:35     ` Roberto Sassu
  2023-08-16 21:00       ` Jarkko Sakkinen
  0 siblings, 1 reply; 21+ messages in thread
From: Roberto Sassu @ 2023-08-16  8:35 UTC (permalink / raw)
  To: Jarkko Sakkinen, corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

On 8/14/2023 7:13 PM, Jarkko Sakkinen wrote:
> On Sat Aug 12, 2023 at 1:46 PM EEST, Roberto Sassu wrote:
>> From: Roberto Sassu <roberto.sassu@huawei.com>
>>
>> Add digest_cache_init_htable(), to size a hash table depending on the
>> number of digests to be added to the cache.
>>
>> Add digest_cache_add() and digest_cache_lookup() to respectively add and
>> lookup a digest in the digest cache.
>>
>> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
>> ---
>>   security/integrity/digest_cache.c | 131 ++++++++++++++++++++++++++++++
>>   security/integrity/digest_cache.h |  24 ++++++
>>   2 files changed, 155 insertions(+)
>>
>> diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
>> index 4201c68171a..d14d84b804b 100644
>> --- a/security/integrity/digest_cache.c
>> +++ b/security/integrity/digest_cache.c
>> @@ -315,3 +315,134 @@ struct digest_cache *digest_cache_get(struct dentry *dentry,
>>   
>>   	return iint->dig_user;
>>   }
>> +
>> +/**
>> + * digest_cache_init_htable - Allocate and initialize the hash table
>> + * @digest_cache: Digest cache
>> + * @num_digests: Number of digests to add to the digest cache
>> + *
>> + * This function allocates and initializes the hash table. Its size is
>> + * determined by the number of digests to add to the digest cache, known
>> + * at this point by the parser calling this function.
>> + *
>> + * Return: Zero on success, a negative value otherwise.
>> + */
>> +int digest_cache_init_htable(struct digest_cache *digest_cache,
>> +			     u64 num_digests)
>> +{
>> +	int i;
>> +
>> +	if (!digest_cache)
>> +		return 0;
>> +
>> +	digest_cache->num_slots = num_digests / DIGEST_CACHE_HTABLE_DEPTH;
>> +	if (!digest_cache->num_slots)
>> +		digest_cache->num_slots = 1;
>> +
>> +	digest_cache->slots = kmalloc_array(num_digests,
>> +					    sizeof(*digest_cache->slots),
>> +					    GFP_KERNEL);
>> +	if (!digest_cache->slots)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < digest_cache->num_slots; i++)
>> +		INIT_HLIST_HEAD(&digest_cache->slots[i]);
>> +
>> +	pr_debug("Initialized %d hash table slots for digest list %s\n",
>> +		 digest_cache->num_slots, digest_cache->path_str);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * digest_cache_add - Add a new digest to the digest cache
>> + * @digest_cache: Digest cache
>> + * @digest: Digest to add
>> + *
>> + * This function, invoked by a digest list parser, adds a digest extracted
>> + * from a digest list to the digest cache.
>> + *
>> + * Return: Zero on success, a negative value on error.
> 
> Nit: previous had a different phrasing "a negative value otherwise".
> 
> I would suggest "a POSIX error code otherwise" for both.

Ok.

>> + */
>> +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest)
>> +{
>> +	struct digest_cache_entry *entry;
>> +	unsigned int key;
>> +	int digest_len;
>> +
>> +	if (!digest_cache)
>> +		return 0;
>> +
>> +	digest_len = hash_digest_size[digest_cache->algo];
>> +
>> +	entry = kmalloc(sizeof(*entry) + digest_len, GFP_KERNEL);
>> +	if (!entry)
>> +		return -ENOMEM;
>> +
>> +	memcpy(entry->digest, digest, digest_len);
>> +
>> +	key = digest_cache_hash_key(digest, digest_cache->num_slots);
>> +	hlist_add_head(&entry->hnext, &digest_cache->slots[key]);
>> +	pr_debug("Add digest %s:%*phN from digest list %s\n",
>> +		 hash_algo_name[digest_cache->algo], digest_len, digest,
>> +		 digest_cache->path_str);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * digest_cache_lookup - Searches a digest in the digest cache
>> + * @digest_cache: Digest cache
>> + * @digest: Digest to search
>> + * @algo: Algorithm of the digest to search
>> + * @pathname: Path of the file whose digest is looked up
>> + *
>> + * This function, invoked by IMA or EVM, searches the calculated digest of
>> + * a file or file metadata in the digest cache acquired with
>> + * digest_cache_get().
>> + *
>> + * Return: Zero if the digest is found, a negative value if not.
>> + */
>> +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
>> +			enum hash_algo algo, const char *pathname)
>> +{
>> +	struct digest_cache_entry *entry;
>> +	unsigned int key;
>> +	int digest_len;
>> +	int search_depth = 0;
>> +
>> +	if (!digest_cache)
>> +		return -ENOENT;
>> +
>> +	if (digest_cache->algo == HASH_ALGO__LAST) {
>> +		pr_debug("Algorithm not set for digest list %s\n",
>> +			 digest_cache->path_str);
>> +		return -ENOENT;
>> +	}
>> +
>> +	digest_len = hash_digest_size[digest_cache->algo];
>> +
>> +	if (algo != digest_cache->algo) {
>> +		pr_debug("Algo mismatch for file %s, digest %s:%*phN in digest list %s (%s)\n",
>> +			 pathname, hash_algo_name[algo], digest_len, digest,
>> +			 digest_cache->path_str,
>> +			 hash_algo_name[digest_cache->algo]);
>> +		return -ENOENT;
>> +	}
>> +
>> +	key = digest_cache_hash_key(digest, digest_cache->num_slots);
>> +
>> +	hlist_for_each_entry_rcu(entry, &digest_cache->slots[key], hnext) {
>> +		if (!memcmp(entry->digest, digest, digest_len)) {
>> +			pr_debug("Cache hit at depth %d for file %s, digest %s:%*phN in digest list %s\n",
>> +				 search_depth, pathname, hash_algo_name[algo],
>> +				 digest_len, digest, digest_cache->path_str);
>> +			return 0;
>> +		}
>> +
>> +		search_depth++;
>> +	}
>> +
>> +	pr_debug("Cache miss for file %s, digest %s:%*phN in digest list %s\n",
>> +		 pathname, hash_algo_name[algo], digest_len, digest,
>> +		 digest_cache->path_str);
>> +	return -ENOENT;
>> +}
>> diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
>> index ff88e8593c6..01cd70f9850 100644
>> --- a/security/integrity/digest_cache.h
>> +++ b/security/integrity/digest_cache.h
>> @@ -66,6 +66,11 @@ static inline unsigned int digest_cache_hash_key(u8 *digest,
>>   void digest_cache_free(struct digest_cache *digest_cache);
>>   struct digest_cache *digest_cache_get(struct dentry *dentry,
>>   				      struct integrity_iint_cache *iint);
>> +int digest_cache_init_htable(struct digest_cache *digest_cache,
>> +			     u64 num_digests);
>> +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest);
>> +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
>> +			enum hash_algo algo, const char *pathname);
>>   #else
>>   static inline void digest_cache_free(struct digest_cache *digest_cache)
>>   {
>> @@ -77,5 +82,24 @@ digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint)
>>   	return NULL;
>>   }
>>   
>> +static inline int digest_cache_init_htable(struct digest_cache *digest_cache,
>> +					   u64 num_digests)
>> +{
>> +	return -EOPNOTSUPP;
>> +}
>> +
>> +static inline int digest_cache_add(struct digest_cache *digest_cache,
>> +				   u8 *digest)
>> +{
>> +	return -EOPNOTSUPP;
>> +}
>> +
>> +static inline int digest_cache_lookup(struct digest_cache *digest_cache,
>> +				      u8 *digest, enum hash_algo algo,
>> +				      const char *pathname)
>> +{
>> +	return -ENOENT;
>> +}
>> +
>>   #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
>>   #endif /* _DIGEST_CACHE_H */
>> -- 
>> 2.34.1
> 
> Why all this complexity instead of using xarray?
> 
> https://docs.kernel.org/core-api/xarray.html

Uhm, did I get correctly from the documentation that it isn't the 
optimal solution for hash tables?

Thanks

Roberto


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

* Re: [RFC][PATCH v2 02/13] integrity: Introduce a digest cache
  2023-08-16  8:21     ` Roberto Sassu
@ 2023-08-16 20:39       ` Jarkko Sakkinen
  0 siblings, 0 replies; 21+ messages in thread
From: Jarkko Sakkinen @ 2023-08-16 20:39 UTC (permalink / raw)
  To: Roberto Sassu, corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

On Wed Aug 16, 2023 at 11:21 AM EEST, Roberto Sassu wrote:
> On 8/14/2023 7:03 PM, Jarkko Sakkinen wrote:
> > On Sat Aug 12, 2023 at 1:46 PM EEST, Roberto Sassu wrote:
> >> From: Roberto Sassu <roberto.sassu@huawei.com>
> >>
> >> Introduce the digest cache, a structure holding a hash table of digests,
> >> extracted from a digest list. Its pointer is stored in the iint of the
> > 
> > What is iint? I honestly don't know what it is. I first thought that it
> > was "int" typoed.
>
> Ops. It is the integrity_iint_cache structure, to retain the 
> integrity-specific state of an inode. Will explain that in the next version.

OK, cool!

> Thanks
>
> Roberto

BR, Jarkko


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

* Re: [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search
  2023-08-16  8:35     ` Roberto Sassu
@ 2023-08-16 21:00       ` Jarkko Sakkinen
  0 siblings, 0 replies; 21+ messages in thread
From: Jarkko Sakkinen @ 2023-08-16 21:00 UTC (permalink / raw)
  To: Roberto Sassu, corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu

On Wed Aug 16, 2023 at 11:35 AM EEST, Roberto Sassu wrote:
> On 8/14/2023 7:13 PM, Jarkko Sakkinen wrote:
> > On Sat Aug 12, 2023 at 1:46 PM EEST, Roberto Sassu wrote:
> >> From: Roberto Sassu <roberto.sassu@huawei.com>
> >>
> >> Add digest_cache_init_htable(), to size a hash table depending on the
> >> number of digests to be added to the cache.
> >>
> >> Add digest_cache_add() and digest_cache_lookup() to respectively add and
> >> lookup a digest in the digest cache.
> >>
> >> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> >> ---
> >>   security/integrity/digest_cache.c | 131 ++++++++++++++++++++++++++++++
> >>   security/integrity/digest_cache.h |  24 ++++++
> >>   2 files changed, 155 insertions(+)
> >>
> >> diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
> >> index 4201c68171a..d14d84b804b 100644
> >> --- a/security/integrity/digest_cache.c
> >> +++ b/security/integrity/digest_cache.c
> >> @@ -315,3 +315,134 @@ struct digest_cache *digest_cache_get(struct dentry *dentry,
> >>   
> >>   	return iint->dig_user;
> >>   }
> >> +
> >> +/**
> >> + * digest_cache_init_htable - Allocate and initialize the hash table
> >> + * @digest_cache: Digest cache
> >> + * @num_digests: Number of digests to add to the digest cache
> >> + *
> >> + * This function allocates and initializes the hash table. Its size is
> >> + * determined by the number of digests to add to the digest cache, known
> >> + * at this point by the parser calling this function.
> >> + *
> >> + * Return: Zero on success, a negative value otherwise.
> >> + */
> >> +int digest_cache_init_htable(struct digest_cache *digest_cache,
> >> +			     u64 num_digests)
> >> +{
> >> +	int i;
> >> +
> >> +	if (!digest_cache)
> >> +		return 0;
> >> +
> >> +	digest_cache->num_slots = num_digests / DIGEST_CACHE_HTABLE_DEPTH;
> >> +	if (!digest_cache->num_slots)
> >> +		digest_cache->num_slots = 1;
> >> +
> >> +	digest_cache->slots = kmalloc_array(num_digests,
> >> +					    sizeof(*digest_cache->slots),
> >> +					    GFP_KERNEL);
> >> +	if (!digest_cache->slots)
> >> +		return -ENOMEM;
> >> +
> >> +	for (i = 0; i < digest_cache->num_slots; i++)
> >> +		INIT_HLIST_HEAD(&digest_cache->slots[i]);
> >> +
> >> +	pr_debug("Initialized %d hash table slots for digest list %s\n",
> >> +		 digest_cache->num_slots, digest_cache->path_str);
> >> +	return 0;
> >> +}
> >> +
> >> +/**
> >> + * digest_cache_add - Add a new digest to the digest cache
> >> + * @digest_cache: Digest cache
> >> + * @digest: Digest to add
> >> + *
> >> + * This function, invoked by a digest list parser, adds a digest extracted
> >> + * from a digest list to the digest cache.
> >> + *
> >> + * Return: Zero on success, a negative value on error.
> > 
> > Nit: previous had a different phrasing "a negative value otherwise".
> > 
> > I would suggest "a POSIX error code otherwise" for both.
>
> Ok.
>
> >> + */
> >> +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest)
> >> +{
> >> +	struct digest_cache_entry *entry;
> >> +	unsigned int key;
> >> +	int digest_len;
> >> +
> >> +	if (!digest_cache)
> >> +		return 0;
> >> +
> >> +	digest_len = hash_digest_size[digest_cache->algo];
> >> +
> >> +	entry = kmalloc(sizeof(*entry) + digest_len, GFP_KERNEL);
> >> +	if (!entry)
> >> +		return -ENOMEM;
> >> +
> >> +	memcpy(entry->digest, digest, digest_len);
> >> +
> >> +	key = digest_cache_hash_key(digest, digest_cache->num_slots);
> >> +	hlist_add_head(&entry->hnext, &digest_cache->slots[key]);
> >> +	pr_debug("Add digest %s:%*phN from digest list %s\n",
> >> +		 hash_algo_name[digest_cache->algo], digest_len, digest,
> >> +		 digest_cache->path_str);
> >> +	return 0;
> >> +}
> >> +
> >> +/**
> >> + * digest_cache_lookup - Searches a digest in the digest cache
> >> + * @digest_cache: Digest cache
> >> + * @digest: Digest to search
> >> + * @algo: Algorithm of the digest to search
> >> + * @pathname: Path of the file whose digest is looked up
> >> + *
> >> + * This function, invoked by IMA or EVM, searches the calculated digest of
> >> + * a file or file metadata in the digest cache acquired with
> >> + * digest_cache_get().
> >> + *
> >> + * Return: Zero if the digest is found, a negative value if not.
> >> + */
> >> +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
> >> +			enum hash_algo algo, const char *pathname)
> >> +{
> >> +	struct digest_cache_entry *entry;
> >> +	unsigned int key;
> >> +	int digest_len;
> >> +	int search_depth = 0;
> >> +
> >> +	if (!digest_cache)
> >> +		return -ENOENT;
> >> +
> >> +	if (digest_cache->algo == HASH_ALGO__LAST) {
> >> +		pr_debug("Algorithm not set for digest list %s\n",
> >> +			 digest_cache->path_str);
> >> +		return -ENOENT;
> >> +	}
> >> +
> >> +	digest_len = hash_digest_size[digest_cache->algo];
> >> +
> >> +	if (algo != digest_cache->algo) {
> >> +		pr_debug("Algo mismatch for file %s, digest %s:%*phN in digest list %s (%s)\n",
> >> +			 pathname, hash_algo_name[algo], digest_len, digest,
> >> +			 digest_cache->path_str,
> >> +			 hash_algo_name[digest_cache->algo]);
> >> +		return -ENOENT;
> >> +	}
> >> +
> >> +	key = digest_cache_hash_key(digest, digest_cache->num_slots);
> >> +
> >> +	hlist_for_each_entry_rcu(entry, &digest_cache->slots[key], hnext) {
> >> +		if (!memcmp(entry->digest, digest, digest_len)) {
> >> +			pr_debug("Cache hit at depth %d for file %s, digest %s:%*phN in digest list %s\n",
> >> +				 search_depth, pathname, hash_algo_name[algo],
> >> +				 digest_len, digest, digest_cache->path_str);
> >> +			return 0;
> >> +		}
> >> +
> >> +		search_depth++;
> >> +	}
> >> +
> >> +	pr_debug("Cache miss for file %s, digest %s:%*phN in digest list %s\n",
> >> +		 pathname, hash_algo_name[algo], digest_len, digest,
> >> +		 digest_cache->path_str);
> >> +	return -ENOENT;
> >> +}
> >> diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
> >> index ff88e8593c6..01cd70f9850 100644
> >> --- a/security/integrity/digest_cache.h
> >> +++ b/security/integrity/digest_cache.h
> >> @@ -66,6 +66,11 @@ static inline unsigned int digest_cache_hash_key(u8 *digest,
> >>   void digest_cache_free(struct digest_cache *digest_cache);
> >>   struct digest_cache *digest_cache_get(struct dentry *dentry,
> >>   				      struct integrity_iint_cache *iint);
> >> +int digest_cache_init_htable(struct digest_cache *digest_cache,
> >> +			     u64 num_digests);
> >> +int digest_cache_add(struct digest_cache *digest_cache, u8 *digest);
> >> +int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
> >> +			enum hash_algo algo, const char *pathname);
> >>   #else
> >>   static inline void digest_cache_free(struct digest_cache *digest_cache)
> >>   {
> >> @@ -77,5 +82,24 @@ digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint)
> >>   	return NULL;
> >>   }
> >>   
> >> +static inline int digest_cache_init_htable(struct digest_cache *digest_cache,
> >> +					   u64 num_digests)
> >> +{
> >> +	return -EOPNOTSUPP;
> >> +}
> >> +
> >> +static inline int digest_cache_add(struct digest_cache *digest_cache,
> >> +				   u8 *digest)
> >> +{
> >> +	return -EOPNOTSUPP;
> >> +}
> >> +
> >> +static inline int digest_cache_lookup(struct digest_cache *digest_cache,
> >> +				      u8 *digest, enum hash_algo algo,
> >> +				      const char *pathname)
> >> +{
> >> +	return -ENOENT;
> >> +}
> >> +
> >>   #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
> >>   #endif /* _DIGEST_CACHE_H */
> >> -- 
> >> 2.34.1
> > 
> > Why all this complexity instead of using xarray?
> > 
> > https://docs.kernel.org/core-api/xarray.html
>
> Uhm, did I get correctly from the documentation that it isn't the 
> optimal solution for hash tables?

I think you are correct with xarray that it is not a great fit here 
(I overlooked).

BR, Jarkko

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

* Re: [RFC][PATCH v2 00/13] integrity: Introduce a digest cache
  2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
                   ` (12 preceding siblings ...)
  2023-08-12 10:46 ` [RFC][PATCH v2 13/13] docs: Add documentation of the integrity digest cache Roberto Sassu
@ 2023-09-05 15:46 ` Roberto Sassu
  13 siblings, 0 replies; 21+ messages in thread
From: Roberto Sassu @ 2023-09-05 15:46 UTC (permalink / raw)
  To: corbet, zohar, dmitry.kasatkin, paul, jmorris, serge
  Cc: linux-kernel, linux-doc, linux-integrity, linux-security-module,
	bpf, jarkko, pbrobinson, zbyszek, hch, mjg59, pmatilai, jannh,
	Roberto Sassu, pvorel, jikos

On Sat, 2023-08-12 at 12:46 +0200, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
> 
> Introduction
> ============
> 
> The main goal of Integrity Measurement Architecture (IMA) is to perform a
> measurement of the file content and use it for remote attestation, to
> report a possibly compromised system, using the TPM as a root of trust. It
> can also prevent a system compromise from happening by checking the
> calculated file digest against a known-good reference value and by denying
> the current operation if there is a mismatch.

Small update, I build openSUSE Tumbleweed packages with the patches I
sent (I added an rpm plugin). After the following steps, openSUSE
Tumbleweed will:

- Provide a predictable measurement list and PCR (11)
- Enforce an IMA Appraisal policy for executable code (excluding tmpfs)
- Support the above after package installation and removal


1) Add the following repository to YaST:

https://download.opensuse.org/repositories/home:/roberto.sassu:/integrity-digest-cache/standard/


2) Install the necessary packages (in a testing VM):

# zypper in gpg2-2.3.8-316.2.x86_64 kernel-kvmsmall kernel-tools-digest-lists


3) Generate the initial set of digest lists from installed packages:

# manage_digest_lists -d /etc/digest_lists -o gen -i rpmdb -f rpm


4) Add security.digest_list xattr to every file, to point to the
respective digest list:

# manage_digest_lists -i /etc/digest_lists -o add-xattr


5) Create /etc/ima/ima-policy with the content:

dont_measure fsmagic=0x01021994
measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11
measure func=BPRM_CHECK digest_cache=content pcr=11
measure func=MMAP_CHECK digest_cache=content pcr=11
dont_appraise fsmagic=0x01021994
appraise func=BPRM_CHECK digest_cache=content
appraise func=MMAP_CHECK digest_cache=content
appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig


6) Add the following lines to /etc/apparmor.d/abstractions/base, to
   allow all processes to read /etc/digest_lists

@{etc_ro}/digest_lists/**      r,


7) Reboot


That's it. Only openSUSE-distributed files are allowed to run (plus the
ones I built). If there is any problem, please reboot with
ima_appraise=log in the kernel command line, and delete the IMA policy.

I hope to receive more reviews, and to make the necessary changes to
have this new feature accepted in the kernel. This is a requirement for
Linux distributions to pick the feature up.

Thanks

Roberto

> Motivation
> ==========
> 
> This patch set aims to address two important shortcomings: predictability
> of the Platform Configuration Registers (PCRs), and the provisioning of
> reference values to compare the calculated file digest against.
> 
> Remote attestation, according to Trusted Computing Group (TCG)
> specifications, is done by replicating the PCR extend operation in
> software with the digests in the event log (in this case the IMA
> measurement list), and by comparing the obtained value with the PCR value
> signed by the TPM with the quote operation.
> 
> Due to how the extend operation is performed, if measurements are done in
> a different order, the final PCR value will be different. That means that
> if measurements are done in parallel, there is no way to predict what the
> final PCR value will be, making impossible to seal data to a PCR value. If
> the PCR value was predictable, a system could for example prove its
> integrity by unsealing and using its private key, without sending every
> time the full list of measurements.
> 
> Provisioning reference values for file digests is also a difficult task.
> The solution so far was to add file signatures to RPM packages, and
> possibly to DEB packages, so that IMA can verify them. While this undoubtly
> works, it also requires Linux distribution vendors to support the feature
> by rebuilding all their packages, and eventually extending their PKI to
> perform the additional signatures. It could also require developers extra
> work to deal with the additional data.
> 
> On the other hand, since often packages carry the file digests themselves,
> it won't be actually needed to add file signatures. If the kernel was able
> to extract the file digests by itself, all the tasks mentioned above for
> the Linux distribution vendors won't be needed too. All current and past
> Linux distributions can be easily retrofitted to enable IMA appraisal with
> the file digests from the packages.
> 
> Narrowing down the scope of a package parser to only extract specific
> information makes it small enough to accurately verify that it cannot harm
> the kernel. An additional mitigation consists in verifying the signature of
> the package first, before attempting to extract the file digests.
> 
> 
> Solution
> ========
> 
> To avoid a PCR is extended in a non-deterministic way, the proposed
> solution is to replace individual file measurements with the measurement of
> a file (the digest list) containing a set of file digests. If the
> calculated digest of a file being measured/appraised matches one digest in
> the set, its measurement is skipped. If otherwise there is no match, the
> file digest is added to the measurement list.
> 
> The resulting measurement list, which cannot be done on the default IMA PCR
> to avoid ambiguity with the default-style measurement, has the following
> meaning: none/some/all files represented with the measurement of the digest
> lists COULD have been accessed, without knowing IF and WHEN. Any other
> measurement (other than boot_aggregate) is of a file whose digest was not
> included in the digest list.
> 
> File signatures have a coarser granularity, it is per-signing key and not
> per-package. A measurement list containing just the measurement of the
> signing keys and the files without/invalid signature (those with valid
> signature would be skipped) would be even less accurate.
> 
> To ensure a rapid and smooth deployment of IMA appraisal, the kernel has
> been provided with the ability to extract file digests from the RPM
> package headers, and add them to the kernel memory on demand (only when a
> file from a given package is accessed). This ensures that the memory
> consumption for this new feature is directly proportional to the usage of
> the system.
> 
> 
> Scope
> =====
> 
> The integrity digest cache enables IMA to extend a PCR (not the default
> one) in a deterministic fashion, and to appraise immutable files with file
> digests from the packages, when no other appraisal method is available. It
> does not yet support metadata verification with Extended Verification
> Module (EVM), for which a separate patch set will be provided.
> 
> 
> Design
> ======
> 
> The digest cache is a hash table of file digests, attached to the inode of
> the digest list from which file digests are extracted. It is accessible,
> when a given file is being measured/appraised, from the new xattr
> security.digest_list, containing the path of the digest list itself.
> 
> If the calculated file digest is found in the digest cache, its measurement
> is avoided, or read-only access is granted if appraisal is in enforcing
> mode. Read-write access is prevented to avoid updating an unverified HMAC
> of file metadata.
> 
> The digest cache can be used only if the following conditions are met:
> 
> - The 'digest_cache=content' keyword is added to the desired IMA policy
>   rules;
> - If the rule action is 'measure', a PCR different from the default one
>   is specified;
> - If the rule action is 'appraise', 'digest_cache=content' and
>   'appraise_type' don't appear at the same time;
> - The same action for which the digest cache is used was done also on the
>   digest list;
> - The digest cache is not (currently) used for measurement/appraisal of
>   other digest lists.
> 
> For performance reasons, the digest cache is attached to every inode using
> it, since multiple hooks can be invoked on it before the
> measurement/appraisal result is cached. A reference count indicates how
> many inodes use it, and only when it reaches zero, the digest cache can be
> freed (for example when inodes are evicted from memory).
> 
> Two digest cache pointers have been added to the iint to distinguish for
> which purpose they should be used: dig_owner points to the digest cache
> created from the same inode the iint refers to, and should be used for
> measurement/appraisal of other inodes; dig_user points to the digest
> cache created from a different inode, and requested for
> measurement/appraisal. One digest cache pointer would be confusing, as
> for digest lists the digest cache was created from them, but IMA would
> try to use that digest cache for measurement/appraisal of itself.
> 
> Finally, at the first digest list measurement, an iterator is executed to
> sequentially read (not parse) all the digest lists in the same directory,
> so that the PCR is extended in a deterministic fashion. The other
> concurrent users of the digest cache have to wait until the iterator
> finishes.
> 
> 
> API
> ===
> 
> digest_cache_alloc(), digest_cache_parse_digest_list() and
> digest_cache_new() are internal functions used during the creation and
> initialization of the digest cache.
> 
> digest_cache_get() and digest_cache_free() are called by the user of the
> digest cache (e.g. IMA), to obtain and free a digest cache.
> 
> digest_cache_init_htable(), digest_cache_add() and digest_cache_lookup()
> are called by the digest list parsers to populate and search in a digest
> cache.
> 
> 
> Digest List Formats
> ===================
> 
> tlv
> ~~~
> 
> The Type-Length-Value (TLV) format was chosen for its extensibility.
> Additional fields can be added without breaking compatibility with old
> versions of the parser.
> 
> The layout of a tlv digest list is the following:
> 
>  [header: DIGEST_LIST_FILE, num fields, total len]
>  [field: DIGEST_LIST_ALGO, length, value]
>  [field: DIGEST_LIST_ENTRY#1, length, value (below)]
>   |- [header: DIGEST_LIST_FILE, num fields, total len]
>   |- [ENTRY#1_DIGEST, length, file digest]
>   |- [ENTRY#1_PATH, length, file path]
>  [field: DIGEST_LIST_ENTRY#N, length, value (below)]
>   |- [header: DIGEST_LIST_FILE, num fields, total len]
>   |- [ENTRY#N_DIGEST, length, file digest]
>   |- [ENTRY#N_PATH, length, file path]
> 
> DIGEST_LIST_ALGO is a field to specify the algorithm of the file digest.
> DIGEST_LIST_ENTRY is a nested TLV structure with the following fields:
> ENTRY_DIGEST contains the file digest; ENTRY_PATH contains the file path.
> 
> 
> rpm
> ~~~
> 
> The rpm digest list is basically a subset of the RPM package header.
> Its format is:
> 
>  [RPM magic number]
>  [RPMTAG_IMMUTABLE]
> 
> RPMTAG_IMMUTABLE is a section of the full RPM header containing the part
> of the header that was signed, and whose signature is stored in the
> RPMTAG_RSAHEADER section.
> 
> 
> Appended Signature
> ~~~~~~~~~~~~~~~~~~
> 
> Digest lists can have a module-style appended signature, that can be used
> for appraisal with IMA. The signature type can be PKCS#7, as for kernel
> modules, or the new user asymmetric key signature.
> 
> 
> History
> =======
> 
> The original name of this work was IMA Digest Lists, which was somehow
> considered too invasive. The code was moved to a separate component named
> DIGLIM (DIGest Lists Integrity Module), with the purpose of removing the
> complexity away of IMA, and also add the possibility of using it with other
> kernel components (e.g. Integrity Policy Enforcement, or IPE).
> 
> Since it was originally proposed, in 2017, this work grew up a lot thanks
> to various comments/suggestions. It became integrally part of the openEuler
> distribution since end of 2020.
> 
> There are significant differences between this and the previous versions.
> The most important one is moving from a centralized repository of file
> digests to a per-package repository. This significantly reduces the memory
> pressure, since digest lists are loaded into kernel memory only when they
> are actually needed. Also, file digests are automatically unloaded from
> kernel memory at the same time inodes are evicted from memory during
> reclamation.
> 
> 
> Performance
> ===========
> 
> The tests have been performed on a Fedora 38 virtual machine, with 8 cores
> (AMD EPYC-Rome), 4GB of RAM, TPM passthrough. The signing key is an ECDSA
> NIST P-384.
> 
> IMA measurement policy: no cache
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
>  dont_measure fsmagic=0x01021994
>  measure func=BPRM_CHECK
>  measure func=MMAP_CHECK
> 
> 
> IMA measurement policy: cache
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
>  dont_measure fsmagic=0x01021994
>  measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11
>  measure func=BPRM_CHECK digest_cache=content pcr=11
>  measure func=MMAP_CHECK digest_cache=content pcr=11
> 
> 
> IMA Measurement Results
> ~~~~~~~~~~~~~~~~~~~~~~~
> 
>                                +-----------+-----------+-----------+
>                                | # measur. | boot time |   slab    |
>  +-----------------------------+-----------+-----------+-----------+
>  | measure (no cache)          |    389    |  12.682s  | 231453 KB |
>  +-----------------------------+-----------+-----------+-----------+
>  | measure (cache, no iter)    |    175    |  12.283s  | 234224 KB |
>  +-----------------------------+-----------+-----------+-----------+
>  | measure (cache, iter)       |    853    |  16.430s  | 238491 KB |
>  +-----------------------------+-----------+-----------+-----------+
> 
> With the iterator enabled, all 852 packages are measured. Consequently, the
> boot time is longer. One possible optimization would be to exclude the
> packages that don't include measured files. By disabling the iterator, it
> can be seen that the packages actually used are 174 (one measurement is for
> boot_aggregate).
> 
> 
> IMA appraisal policy: no cache
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
>  dont_appraise fsmagic=0x01021994
>  appraise func=BPRM_CHECK appraise_type=imasig
>  appraise func=MMAP_CHECK appraise_type=imasig
> 
> 
> IMA appraisal policy: cache
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
>  dont_appraise fsmagic=0x01021994
>  appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig
>  appraise func=BPRM_CHECK digest_cache=content
>  appraise func=MMAP_CHECK digest_cache=content
> 
> 
> IMA Appraisal Results
> ~~~~~~~~~~~~~~~~~~~~~
> 
>                                +-----------+-----------+
>                                | boot time |   slab    |
>  +-----------------------------+-----------+-----------+
>  | appraise (no cache)         |  11.995s  | 231145 KB |
>  +-----------------------------+-----------+-----------+
>  | appraise (cache)            |  11.879s  | 233091 KB |
>  +-----------------------------+-----------+-----------+
> 
> In this test, it can be seen that the performance of the two solutions are
> comparable, with the digest cache slightly ahead. The difference could be
> more substantial with more file appraised.
> 
> 
> How to Test
> ===========
> 
> First, it is necessary to copy the new kernel headers (tlv_parser.h,
> uasym_parser.h, tlv_digest_list.h) from usr/include/linux in the kernel
> source directory to /usr/include/linux.
> 
> Then, gpg must be rebuilt with the additional patches to convert the PGP
> keys of the Linux distribution to the new user asymmetric key format:
> 
>  $ gpg --conv-kernel <path of PGP key> >> certs/uasym_keys.bin
> 
> This embeds the converted keys in the kernel image. Then, the following
> kernel options must be enabled:
> 
>  CONFIG_INTEGRITY_DIGEST_CACHE=y
>  CONFIG_UASYM_KEYS_SIGS=y
>  CONFIG_UASYM_PRELOAD_PUBLIC_KEYS=y
> 
> and the kernel must be rebuilt with the patches applied. After boot, it is
> necessary to build and install the digest list tool in tools/digest-lists,
> and to execute (as root):
> 
>  # manage_digest_lists -o gen -d /etc/digest_lists -i rpmdb -f rpm
> 
> The new gpg must also be installed in the system, as it will be used to
> convert the PGP signatures of the RPM headers to the user asymmetric key
> format.
> 
> It is recommended to create an additional digest list with the following
> files, by creating a file named 'list' with the content:
> 
>  /usr/bin/manage_digest_lists
>  /usr/lib64/libgen-tlv-list.so
>  /usr/lib64/libgen-rpm-list.so
>  /usr/lib64/libparse-rpm-list.so
>  /usr/lib64/libparse-tlv-list.so
> 
> Then, to create the digest list, it is sufficient to execute:
> 
>  # manage_digest_lists -i list -L -d /etc/digest_lists -o gen -f tlv
> 
> If appraisal is enabled and in enforcing mode, it is necessary to sign the
> new digest list, with the sign-file tool in the scripts/ directory of the
> kernel sources:
> 
>  # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/tlv-list
> 
> The final step is to add security.digest_list to each file with:
> 
>  # manage_digest_lists -i /etc/digest_lists -o add-xattr
> 
> After that, it is possible to test the integrity digest cache with the
> following policy written to /etc/ima/ima-policy:
> 
>  dont_measure fsmagic=0x01021994
>  measure func=DIGEST_LIST_CHECK template=ima-modsig pcr=11
>  measure func=BPRM_CHECK digest_cache=content pcr=11
>  measure func=MMAP_CHECK digest_cache=content pcr=11
>  dont_appraise fsmagic=0x01021994
>  appraise func=BPRM_CHECK digest_cache=content
>  appraise func=MMAP_CHECK digest_cache=content
>  appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig
> 
> Tmpfs is excluded for now, until memfd is properly handled.
> 
> Before loading the policy, it is possible to enable dynamic debug to see
> which operations are done by the integrity digest cache:
> 
>  # echo "file tlv* +p" > /sys/kernel/debug/dynamic_debug/control
>  # echo "file rpm* +p" > /sys/kernel/debug/dynamic_debug/control
>  # echo "file digest* +p" > /sys/kernel/debug/dynamic_debug/control
> 
> Alternatively, the same strings can be set as value of the dyndbg= option
> in the kernel command line.
> 
> A preliminary test, before booting the system with the new policy, is to
> supply the policy to IMA in the current system with:
> 
>  # cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy
> 
> If that worked, the system can be rebooted. Systemd will take care of
> loading the IMA policy at boot. The instructions have been tested on a
> Fedora 38 OS.
> 
> After boot, it is possible to check the content of the measurement list:
> 
>  # cat /sys/kernel/security/ima/ascii_runtime_measurements
> 
> If only the files shipped with Fedora 38 have been executed, the
> measurement list will contain only the digest lists, and not the individual
> files.
> 
> Another test is to ensure that IMA prevents the execution of unknown files:
> 
>  # cp -a /bin/cat .
>  # ./cat
> 
> That will work. But not on the modified binary:
> 
>  # echo 1 >> cat
>  # cat
>  -bash: ./cat: Permission denied
> 
> Execution will be denied, and a new entry in the measurement list will
> appear (it would be probably ok to not add that entry, as access to the
> file was denied):
> 
>  11 50b5a68bea0776a84eef6725f17ce474756e51c0 ima-ng sha256:15e1efee080fe54f5d7404af7e913de01671e745ce55215d89f3d6521d3884f0 /root/cat
> 
> Finally, it is possible to test the shrinking of the digest cache, by
> forcing the kernel to evict inodes from memory:
> 
>  # echo 3 > /proc/sys/vm/drop_caches
> 
> The kernel log should have messages like:
> 
>  [  313.032536] DIGEST CACHE: Remove digest sha256:102900208eef27b766380135906d431dba87edaa7ec6aa72e6ebd3dd67f3a97b from digest list /etc/digest_lists/rpm-libseccomp-2.5.3-4.fc38.x86_64
> 
> 
> Patch set dependencies
> ======================
> 
> This patch set depends on:
> 
> https://lore.kernel.org/linux-integrity/20230720153247.3755856-2-roberto.sassu@huaweicloud.com/
> 
> which allows to appraise RPM package headers with the PGP keys of Linux
> distribution vendors.
> 
> 
> Patch set content
> =================
> 
> Patch 1 introduces a new hook to identify the loading of digest lists and
> consequently appraise them.
> 
> Patches 2-4 implement the digest cache, and an iterator to prefetch the
> digest lists to measure them in a deterministic way.
> 
> Patches 5-6 implement the currently supported digest list formats: tlv and
> rpm. The tlv format relies on the TLV parser defined in the patch set
> mentioned above.
> 
> Patches 7-9 enable the usage of the digest cache in IMA for measurement and
> appraisal.
> 
> Patches 10-12 add a tool to manage digest lists.
> 
> Patch 13 adds the documentation of the integrity digest cache.
> 
> 
> Changelog
> =========
> 
> v1:
> - Add documentation in Documentation/security/integrity-digest-cache.rst
> - Pass the mask of IMA actions to digest_cache_alloc()
> - Add a reference count to the digest cache
> - Remove the path parameter from digest_cache_get(), and rely on the
>   reference count to avoid the digest cache disappearing while being used
> - Rename the dentry_to_check parameter of digest_cache_get() to dentry
> - Rename digest_cache_get() to digest_cache_new() and add
>   digest_cache_get() to set the digest cache in the iint of the inode for
>   which the digest cache was requested
> - Add dig_owner and dig_user to the iint, to distinguish from which inode
>   the digest cache was created from, and which is using it; consequently it
>   makes the digest cache usable to measure/appraise other digest caches
>   (support not yet enabled)
> - Add dig_owner_mutex and dig_user_mutex to serialize accesses to dig_owner
>   and dig_user until they are initialized
> - Enforce strong synchronization and make the contenders wait until
>   dig_owner and dig_user are assigned to the iint the first time
> - Move checking IMA actions on the digest list earlier, and fail if no
>   action were performed (digest cache not usable)
> - Remove digest_cache_put(), not needed anymore with the introduction of
>   the reference count
> - Fail immediately in digest_cache_lookup() if the digest algorithm is
>   not set in the digest cache
> - Use 64 bit mask for IMA actions on the digest list instead of 8 bit
> - Return NULL in the inline version of digest_cache_get()
> - Use list_add_tail() instead of list_add() in the iterator
> - Copy the digest list path to a separate buffer in digest_cache_iter_dir()
> - Use digest list parsers verified with Frama-C
> - Explicitly disable (for now) the possibility in the IMA policy to use the
>   digest cache to measure/appraise other digest lists
> - Replace exit(<value>) with return <value> in manage_digest_lists.c
> 
> Roberto Sassu (13):
>   ima: Introduce hook DIGEST_LIST_CHECK
>   integrity: Introduce a digest cache
>   integrity/digest_cache: Add functions to populate and search
>   integrity/digest_cache: Prefetch digest lists in a directory
>   integrity/digest_cache: Parse tlv digest lists
>   integrity/digest_cache: Parse rpm digest lists
>   ima: Add digest_cache policy keyword
>   ima: Use digest cache for measurement
>   ima: Use digest cache for appraisal
>   tools: Add tool to manage digest lists
>   tools/digest-lists: Add tlv digest list generator and parser
>   tools/digest-lists: Add rpm digest list generator and parser
>   docs: Add documentation of the integrity digest cache
> 
>  Documentation/ABI/testing/ima_policy          |   6 +-
>  Documentation/security/index.rst              |   1 +
>  .../security/integrity-digest-cache.rst       | 484 ++++++++++++++++++
>  MAINTAINERS                                   |   2 +
>  include/linux/kernel_read_file.h              |   1 +
>  include/uapi/linux/tlv_digest_list.h          |  59 +++
>  include/uapi/linux/xattr.h                    |   3 +
>  security/integrity/Kconfig                    |  12 +
>  security/integrity/Makefile                   |   4 +
>  security/integrity/digest_cache.c             | 454 ++++++++++++++++
>  security/integrity/digest_cache.h             | 110 ++++
>  security/integrity/digest_cache_iter.c        | 160 ++++++
>  .../integrity/digest_list_parsers/parsers.h   |  15 +
>  security/integrity/digest_list_parsers/rpm.c  | 215 ++++++++
>  security/integrity/digest_list_parsers/tlv.c  | 188 +++++++
>  security/integrity/iint.c                     |  12 +
>  security/integrity/ima/ima.h                  |  16 +-
>  security/integrity/ima/ima_api.c              |  22 +-
>  security/integrity/ima/ima_appraise.c         |  16 +-
>  security/integrity/ima/ima_main.c             |  40 +-
>  security/integrity/ima/ima_policy.c           |  59 ++-
>  security/integrity/integrity.h                |   8 +
>  tools/Makefile                                |  16 +-
>  tools/digest-lists/.gitignore                 |   7 +
>  tools/digest-lists/Makefile                   |  72 +++
>  tools/digest-lists/common.c                   | 163 ++++++
>  tools/digest-lists/common.h                   |  90 ++++
>  tools/digest-lists/generators/generators.h    |  18 +
>  tools/digest-lists/generators/rpm.c           | 257 ++++++++++
>  tools/digest-lists/generators/tlv.c           | 168 ++++++
>  tools/digest-lists/manage_digest_lists.c      | 349 +++++++++++++
>  tools/digest-lists/manage_digest_lists.txt    |  82 +++
>  tools/digest-lists/parsers/parsers.h          |  16 +
>  tools/digest-lists/parsers/rpm.c              | 169 ++++++
>  tools/digest-lists/parsers/tlv.c              | 195 +++++++
>  tools/digest-lists/parsers/tlv_parser.h       |  38 ++
>  36 files changed, 3501 insertions(+), 26 deletions(-)
>  create mode 100644 Documentation/security/integrity-digest-cache.rst
>  create mode 100644 include/uapi/linux/tlv_digest_list.h
>  create mode 100644 security/integrity/digest_cache.c
>  create mode 100644 security/integrity/digest_cache.h
>  create mode 100644 security/integrity/digest_cache_iter.c
>  create mode 100644 security/integrity/digest_list_parsers/parsers.h
>  create mode 100644 security/integrity/digest_list_parsers/rpm.c
>  create mode 100644 security/integrity/digest_list_parsers/tlv.c
>  create mode 100644 tools/digest-lists/.gitignore
>  create mode 100644 tools/digest-lists/Makefile
>  create mode 100644 tools/digest-lists/common.c
>  create mode 100644 tools/digest-lists/common.h
>  create mode 100644 tools/digest-lists/generators/generators.h
>  create mode 100644 tools/digest-lists/generators/rpm.c
>  create mode 100644 tools/digest-lists/generators/tlv.c
>  create mode 100644 tools/digest-lists/manage_digest_lists.c
>  create mode 100644 tools/digest-lists/manage_digest_lists.txt
>  create mode 100644 tools/digest-lists/parsers/parsers.h
>  create mode 100644 tools/digest-lists/parsers/rpm.c
>  create mode 100644 tools/digest-lists/parsers/tlv.c
>  create mode 100644 tools/digest-lists/parsers/tlv_parser.h
> 


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

end of thread, other threads:[~2023-09-05 16:57 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-12 10:46 [RFC][PATCH v2 00/13] integrity: Introduce a digest cache Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 01/13] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 02/13] integrity: Introduce a digest cache Roberto Sassu
2023-08-14 17:03   ` Jarkko Sakkinen
2023-08-16  8:21     ` Roberto Sassu
2023-08-16 20:39       ` Jarkko Sakkinen
2023-08-12 10:46 ` [RFC][PATCH v2 03/13] integrity/digest_cache: Add functions to populate and search Roberto Sassu
2023-08-14 17:13   ` Jarkko Sakkinen
2023-08-16  8:35     ` Roberto Sassu
2023-08-16 21:00       ` Jarkko Sakkinen
2023-08-12 10:46 ` [RFC][PATCH v2 04/13] integrity/digest_cache: Prefetch digest lists in a directory Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 05/13] integrity/digest_cache: Parse tlv digest lists Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 06/13] integrity/digest_cache: Parse rpm " Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 07/13] ima: Add digest_cache policy keyword Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 08/13] ima: Use digest cache for measurement Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 09/13] ima: Use digest cache for appraisal Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 10/13] tools: Add tool to manage digest lists Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 11/13] tools/digest-lists: Add tlv digest list generator and parser Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 12/13] tools/digest-lists: Add rpm " Roberto Sassu
2023-08-12 10:46 ` [RFC][PATCH v2 13/13] docs: Add documentation of the integrity digest cache Roberto Sassu
2023-09-05 15:46 ` [RFC][PATCH v2 00/13] integrity: Introduce a " Roberto Sassu

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