* [RFC][PATCH 00/12] integrity: Introduce a digest cache
@ 2023-07-21 16:33 Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 01/12] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, Roberto Sassu
From: Roberto Sassu <roberto.sassu@huawei.com>
A bit of history first. The original name of this work was IMA Digest
Lists, which was somehow considered too invasive. I then moved the code
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 the feedback of my colleagues and external reviewers. It became
integrally part of the openEuler distribution since end of 2020. The
upstreaming process has been difficult, also due to the fact too many
features were included in the proposals.
So, I decided to take a step back and find the minimum possible set of
features that would make this work meaningful. The really minimum set that
would make appraisal work is to parse the RPM header (after signature
verification) containing the reference digests and compare the calculated
file digest with them. That would be similar to reading the signature from
the xattr and verifying the digest.
But, maybe that was too minimal. It does not make sense to appraise and
parse again the same RPM header for another packaged files. And should not
be necessarily tied to appraisal, as it would be useful for measurement too
(to make a PCR predictable). Please read the Benefits section here:
https://lore.kernel.org/linux-integrity/20210914163401.864635-1-roberto.sassu@huawei.com/
So, this is how the integrity digest cache is born.
The main difference with the previous attempts is that there is not a
centralized place to store file/metadata digests, but they are stored in
the digest cache, attached to the file (the digest list) the digest cache
was created from.
The link with the files being measured/appraised is the new
security.digest_list xattr containing the full path of the digest list
they refer to. At the time there is a measurement/appraisal, the digest
cache is built by reading (after appraisal) and parsing the digest list
(a TLV format and the RPM package format are currently supported).
Extracted digests are added to a per-package hash table, sized depending on
the number of elements.
Lookup should be much faster, as we are not anymore searching in a hash
table with 80000 digests, but most of the time with 100 or less. Also,
there is no need for locking after creation, the digest cache does not
change depending on writes (it would not make sense, since digest lists are
signed). Another very important feature is that the digest cache is
reclaimable, i.e. it disappears if the inode is evicted from memory. In
that case, the digest cache need to be initialized again. The digest cache
does not disappear while IMA is using it, by acquiring and releasing a
reference to the path structure of the digest list.
What about the predictability of PCRs, that some folks are trying to
address with the Unified Kernel Image? The concept of digest lists is
quite simple: measure the digest list to represent the possible access of a
group of files, instead of recording individual file accesses. If digest
lists are measured in a deterministic way, the PCR remains predictable
despite files are accessed in a different order. We currently don't support
xattrs in the initial ram disk, but if we did and the initial ram disk uses
the same measured files as the root filesystem, the PCR would still remain
predictable.
Another important point of the design was to avoid any possible
interference with existing IMA measurement and appraisal behavior. This
has been achieved in the following way.
First, the digest cache needs to be explicitly enabled in the IMA policy
through the new policy keyword 'digest_cache=content'. Second, new-style
measurements cannot be done on the default IMA PCR, a policy writer must
specify a different PCR or the policy will be rejected. Also, the use of
the digest cache is incompatible with other appraisal methods, e.g. with
xattrs or a modsig. The policy writer cannot specify any of the other
methods if it includes the digest_cache directive. Finally, the digest
cache does not bypass EVM too. Files matched with the digest caches can
only be opened read-only, to prevent updating an unverified HMAC to a valid
one.
Another incorrect policy combination is forbidden. The use of the
digest_cache directive alone does not enable the new behavior. The action
for which the digest cache should be used should have also be done on the
digest list itself. Otherwise, one cannot explain why there are no
measurements (if the digest list was not measured for example). The same
applies for appraisal.
The last part I wanted to talk about is about the digest list parsers. This
was a long debate. In the original proposal, Matthew Garrett and Christoph
Hellwig said that adding parsers in the kernel is not scalable and not a
good idea in general. While I do agree with them, I'm also thinking what
benefits we get if we relax a bit this requirement. If we merge this patch
set and the dependency (user asymmetric keys and signatures) today, we are
immediately able to have a predictable PCR for measurement, and do
appraisal at least of executable code without additional support from the
Linux distributions. We would need a small rpm plugin to write/remove RPM
headers and their signature to/from the disk as soon as packages are
installed/removed.
Over the years, I have tried many alternatives to the kernel-based parsers.
In the very first version, we supported only one digest list format in the
kernel, and injected the digest list to each package at build time. While
it works, it is still one way to do that, others don't support it and it is
building infrastructure-dependent.
I have tried to do the conversion from the RPM format to the kernel format
in user space, with the idea that the process doing it could be isolated
against an untrusted root. I had a recent discussion with the security
folks and they don't seem excited about it.
I have fully implemented DIGLIM and PGP keys and signatures support in
eBPF. The idea itself of being able to add kernel functionality in a safe
way without touching the kernel is very nice, and I would have pursued it
more. However, after I found LSM policy bypass and other bugs and after my
patches were not accepted by the maintainers, I didn't really feel I could
rely on this subsystem. So, back to the original approach for now,
maintainers seem to be more incline to accept kernel code, if there is a
need for strong isolation. In the future, we might work on better
alternative.
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 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.
If you are curious to try on an existing system, you first need to build
gpg with the patches in the previous patch set, and convert the PGP keys of
your 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, enable the
following kernel options:
CONFIG_INTEGRITY_DIGEST_CACHE=y
CONFIG_UASYM_KEYS_SIGS=y
CONFIG_UASYM_PRELOAD_PUBLIC_KEYS=y
and rebuild the kernel with the patches applied. After boot, build and
install the digest list tool in tools/digest-lists, and execute:
$ manage_digest_lists -o gen -d /etc/digest_lists -i rpmdb -f rpm
You also need the new gpg when you execute this tool, to convert the PGP
signatures of the RPM headers to the user asymmetric key format.
You may want to add the following additional files in a digest list 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
and, execute:
$ manage_digest_lists -i list -L -d /etc/digest_lists -o gen -f tlv
You need to sign the created file:
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, create the following policy in /etc/ima/ima-policy:
dont_measure fsmagic=0x01021994
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
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
I'm excluding tmpfs for now, we need to deal with memfd.
Before loading the policy, you could enable the dynamic debug with:
$ 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
Or add the same strings with the dyndbg= option in the kernel command line.
Then, just cat the policy to IMA:
$ cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy
If that worked, you can reboot the system. Systemd will take care of
loading the IMA policy at boot. Everything works for me on Fedora 38.
You can check the content of the measurement list:
$ cat /sys/kernel/security/ima/ascii_runtime_measurements
You will see only the measurement of the digest lists, not of the other
files. If you try:
$ cp -a /bin/cat .
$ ./cat
That will work. But if you do:
$ echo 1 >> cat
$ cat
-bash: ./cat: Permission denied
Execution will be denied, and you will see a new entry in the measurement
list (honestly, it should not be there, as access to the file was denied):
11 50b5a68bea0776a84eef6725f17ce474756e51c0 ima-ng sha256:15e1efee080fe54f5d7404af7e913de01671e745ce55215d89f3d6521d3884f0 /root/cat
Finally, you could try to evict inodes from memory, to free the digest
cache:
$ echo 3 > /proc/sys/vm/drop_caches
You will see in the kernel logs 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
Roberto Sassu (12):
ima: Introduce hook DIGEST_LIST_CHECK
integrity: Introduce a digest cache
integrity/digest_cache: Add functions to populate and search
integrity/digest_cache: Iterate over digest lists in same dir
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
Documentation/ABI/testing/ima_policy | 6 +-
MAINTAINERS | 1 +
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 | 430 ++++++++++++++++++
security/integrity/digest_cache.h | 113 +++++
security/integrity/digest_cache_iter.c | 163 +++++++
.../integrity/digest_list_parsers/parsers.h | 15 +
security/integrity/digest_list_parsers/rpm.c | 174 +++++++
security/integrity/digest_list_parsers/tlv.c | 188 ++++++++
security/integrity/iint.c | 7 +
security/integrity/ima/ima.h | 17 +-
security/integrity/ima/ima_api.c | 22 +-
security/integrity/ima/ima_appraise.c | 16 +-
security/integrity/ima/ima_main.c | 39 +-
security/integrity/ima/ima_policy.c | 54 ++-
security/integrity/integrity.h | 5 +
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 ++
34 files changed, 2942 insertions(+), 27 deletions(-)
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] 14+ messages in thread
* [RFC][PATCH 01/12] ima: Introduce hook DIGEST_LIST_CHECK
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 02/12] integrity: Introduce a digest cache Roberto Sassu
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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] 14+ messages in thread
* [RFC][PATCH 02/12] integrity: Introduce a digest cache
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 01/12] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 03/12] integrity/digest_cache: Add functions to populate and search Roberto Sassu
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, Roberto Sassu
From: Roberto Sassu <roberto.sassu@huawei.com>
Introduce a cache of digests extracted from a (possibly signed) digest
list. The cached digests can be used by IMA to skip the measurement
of known files (not on the default PCR), or to grant access to a file if
appraisal is in enforcing mode. In the future, it can also be used to
verify the integrity of file metadata with EVM.
The digest cache is a structure holding a hash table of digests, extracted
from a digest list. It is accessible by reading the digest list path from
the new security.digest_list xattr of the file being measured/appraised,
and by retrieving the digest cache pointer from the integrity metadata
associated to the digest list file.
The digest cache is usable only if the action being performed on an
accessed file has been done also on the digest list itself. The
DIGEST_CACHE_MEASURE and DIGEST_CACHE_APPRAISE_CONTENT flags, if set,
enable the usage of the digest cache respectively for measuring and
appraising file content.
Introduce the first three methods, get, put and free. The first two are
called by IMA, when it processes a file and an action should be done on it.
The last is called by the integrity code at the time the inode is freed,
causing the digest cache also to be freed.
The digest cache is guaranteed to be available throughout the IMA
processing of the current file, by acquiring and releasing a reference to
the path structure of the digest list file (so that the latter cannot be
suddenly freed, e.g. by deleting the file).
Another important point is that, 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. The iint mutex is taken only
for assigning the digest cache pointer. Since the digest cache is not
modified afterwards, there is no need to lock after creation.
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 | 300 ++++++++++++++++++++++++++++++
security/integrity/digest_cache.h | 84 +++++++++
security/integrity/iint.c | 7 +
security/integrity/ima/ima.h | 1 -
security/integrity/integrity.h | 5 +
8 files changed, 412 insertions(+), 1 deletion(-)
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..66c2c4088e9
--- /dev/null
+++ b/security/integrity/digest_cache.c
@@ -0,0 +1,300 @@
+// 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
+ *
+ * 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)
+{
+ 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 = 0;
+ digest_cache->slots = NULL;
+ digest_cache->num_slots = 0;
+ 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;
+
+ 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("Free cache, algo: %s, digest list: %s",
+ 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_get - Get a digest cache
+ * @dentry_to_check: Dentry of the file being measured/appraised
+ * @digest_list_path: Path structure of the digest list
+ *
+ * This function retrieves the path of the digest list from the
+ * security.digest_list xattr of the file being measured/appraised. It then
+ * instantiates a new digest cache, and opens and parses the digest list.
+ *
+ * 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.
+ *
+ * The caller is responsible to invoke digest_cache_put(), to release
+ * the reference of the path structure associated to the digest list.
+ *
+ * Return: A new digest cache on success, NULL on error.
+ */
+struct digest_cache *digest_cache_get(struct dentry *dentry_to_check,
+ struct path *digest_list_path)
+{
+ struct integrity_iint_cache *digest_list_iint;
+ struct digest_cache *digest_cache = NULL;
+ char *path_str = NULL;
+ struct file *file;
+ void *data = NULL;
+ size_t data_len = 0;
+ struct inode *inode;
+ int ret;
+
+ ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry_to_check,
+ 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_to_check->d_name.name);
+ return digest_cache;
+ }
+
+ pr_debug("Found %s xattr in %s, digest list: %s\n",
+ XATTR_NAME_DIGEST_LIST, dentry_to_check->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->digest_cache) {
+ pr_debug("Cache for digest list %s exists\n", path_str);
+ digest_cache = digest_list_iint->digest_cache;
+ goto out_path;
+ }
+
+ 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_path;
+ }
+
+ /* 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;
+ }
+
+ data_len = ret;
+
+ digest_cache = digest_cache_alloc(path_str);
+ 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;
+ }
+
+ /*
+ * Add penalty only for concurrent add, otherwise don't take a lock.
+ * In the worst case, the lock contenders waste time to create a
+ * digest cache that is freed.
+ */
+ mutex_lock(&digest_list_iint->mutex);
+ /* Someone came before us. */
+ if (digest_list_iint->digest_cache) {
+ pr_debug("Cache for digest list %s exists\n",
+ digest_cache->path_str);
+ digest_cache_free(digest_cache);
+ digest_cache = digest_list_iint->digest_cache;
+ mutex_unlock(&digest_list_iint->mutex);
+ goto out_vfree;
+ }
+
+ digest_list_iint->digest_cache = digest_cache;
+ 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;
+ mutex_unlock(&digest_list_iint->mutex);
+
+ pr_debug("Get cache, algo: %s, digest list: %s, mask: %d\n",
+ 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_path:
+ if (!digest_cache)
+ path_put(digest_list_path);
+out:
+ kfree(path_str);
+ return digest_cache;
+}
+
+/**
+ * digest_cache_put - Release a digest cache
+ * @digest_cache: Digest cache
+ * @digest_list_path: Path structure of the digest list
+ *
+ * This function releases the path structure of the digest list.
+ */
+void digest_cache_put(struct digest_cache *digest_cache,
+ struct path *digest_list_path)
+{
+ if (!digest_cache)
+ return;
+
+ /* Pairs with kernel_path() in digest_cache_get(). */
+ path_put(digest_list_path);
+
+ pr_debug("Put cache, algo: %s, digest list: %s",
+ hash_algo_name[digest_cache->algo], digest_cache->path_str);
+}
diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
new file mode 100644
index 00000000000..fa4a716df65
--- /dev/null
+++ b/security/integrity/digest_cache.h
@@ -0,0 +1,84 @@
+/* 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 digest_cache - Digest cache
+ * @slots: Hash table slots
+ * @num_slots: Number of slots
+ * @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;
+ enum hash_algo algo;
+ char *path_str;
+ u8 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 path *digest_list_path);
+void digest_cache_put(struct digest_cache *digest_cache,
+ struct path *digest_list_path);
+#else
+static inline void digest_cache_free(struct digest_cache *digest_cache)
+{
+}
+
+static inline struct digest_cache *
+digest_cache_get(struct dentry *dentry, struct path *digest_list_path)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void digest_cache_put(struct digest_cache *digest_cache,
+ struct path *digest_list_path)
+{
+}
+
+#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
+#endif /* _DIGEST_CACHE_H */
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index a462df827de..9a35ae1fb85 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -80,6 +80,10 @@ 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->digest_cache);
+ iint->digest_cache = NULL;
+#endif
kmem_cache_free(iint_cache, iint);
}
@@ -165,6 +169,9 @@ 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->digest_cache = NULL;
+#endif
}
static int __init integrity_iintcache_init(void)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 3aef3d8fb57..859a94bcecb 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -452,5 +452,4 @@ static inline int ima_filter_rule_match(u32 secid, u32 field, u32 op,
#else
#define POLICY_FILE_FLAGS S_IWUSR
#endif /* CONFIG_IMA_READ_POLICY */
-
#endif /* __LINUX_IMA_H */
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 7167a6e99bd..bd3f9a27f0d 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,9 @@ 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 *digest_cache;
+#endif
};
/* rbtree tree calls to lookup, insert, delete
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 03/12] integrity/digest_cache: Add functions to populate and search
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 01/12] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 02/12] integrity: Introduce a digest cache Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 04/12] integrity/digest_cache: Iterate over digest lists in same dir Roberto Sassu
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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 | 124 ++++++++++++++++++++++++++++++
security/integrity/digest_cache.h | 24 ++++++
2 files changed, 148 insertions(+)
diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
index 66c2c4088e9..7537c7232db 100644
--- a/security/integrity/digest_cache.c
+++ b/security/integrity/digest_cache.c
@@ -298,3 +298,127 @@ void digest_cache_put(struct digest_cache *digest_cache,
pr_debug("Put cache, algo: %s, digest list: %s",
hash_algo_name[digest_cache->algo], digest_cache->path_str);
}
+
+/**
+ * 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
+ *
+ * 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;
+
+ 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 fa4a716df65..5e3997b2723 100644
--- a/security/integrity/digest_cache.h
+++ b/security/integrity/digest_cache.h
@@ -64,6 +64,11 @@ struct digest_cache *digest_cache_get(struct dentry *dentry,
struct path *digest_list_path);
void digest_cache_put(struct digest_cache *digest_cache,
struct path *digest_list_path);
+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)
{
@@ -80,5 +85,24 @@ static inline void digest_cache_put(struct digest_cache *digest_cache,
{
}
+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] 14+ messages in thread
* [RFC][PATCH 04/12] integrity/digest_cache: Iterate over digest lists in same dir
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (2 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 03/12] integrity/digest_cache: Add functions to populate and search Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 05/12] integrity/digest_cache: Parse tlv digest lists Roberto Sassu
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, Roberto Sassu
From: Roberto Sassu <roberto.sassu@huawei.com>
One advantage of the digest cache is the ability of skipping measurements
of cached digests. That would allow to accumulate integrity measurements
on a PCR in a predictable way, since only the digest lists would be
measured.
However, since digest lists are accessed on demand, when a file belonging
to that repo is measured/appraised, it could happen due to parallel
execution that also digest lists are measured not in the same order.
Thus, eliminate this possibility by iterating over the directory containing
the digest lists and by reading all of them, to trigger a measurement.
Read digest lists are not parsed, to avoid too much memory pressure.
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 | 163 +++++++++++++++++++++++++
3 files changed, 170 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 5e3997b2723..d8fd5ce47a7 100644
--- a/security/integrity/digest_cache.h
+++ b/security/integrity/digest_cache.h
@@ -69,6 +69,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)
{
@@ -104,5 +105,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..f9c4675d383
--- /dev/null
+++ b/security/integrity/digest_cache_iter.c
@@ -0,0 +1,163 @@
+// 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 a digest list iterator.
+ */
+
+#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 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(&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;
+ 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;
+
+ *ptr = '\0';
+ dir_file = filp_open(path_str, O_RDONLY, 0);
+ *ptr = '/';
+
+ if (IS_ERR(dir_file)) {
+ pr_debug("Cannot access parent directory of repo %s\n",
+ path_str);
+ goto out;
+ }
+
+ buf.head = &head;
+ iterate_dir(dir_file, &buf.ctx);
+ list_for_each_entry_safe(p, q, &head, list) {
+ pr_debug("Prereading digest list %s in %s\n", p->name,
+ path_str);
+
+ 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);
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 05/12] integrity/digest_cache: Parse tlv digest lists
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (3 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 04/12] integrity/digest_cache: Iterate over digest lists in same dir Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 06/12] integrity/digest_cache: Parse rpm " Roberto Sassu
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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 7537c7232db..a486dc1ff50 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
@@ -126,6 +127,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] 14+ messages in thread
* [RFC][PATCH 06/12] integrity/digest_cache: Parse rpm digest lists
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (4 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 05/12] integrity/digest_cache: Parse tlv digest lists Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 07/12] ima: Add digest_cache policy keyword Roberto Sassu
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, Roberto Sassu
From: Roberto Sassu <roberto.sassu@huawei.com>
Implement a simple parser of RPM headers, that extracts the digest 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 | 174 ++++++++++++++++++
4 files changed, 180 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 a486dc1ff50..3bf0e6d06bf 100644
--- a/security/integrity/digest_cache.c
+++ b/security/integrity/digest_cache.c
@@ -129,6 +129,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..a4c1d0350ec
--- /dev/null
+++ b/security/integrity/digest_list_parsers/rpm.c
@@ -0,0 +1,174 @@
+// 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 <linux/init.h>
+#include <linux/init_task.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
+
+#define RPMTAG_FILEDIGESTS 1035
+#define RPMTAG_FILEDIGESTALGO 5011
+
+#include "parsers.h"
+
+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 u8 *bufp = data, *bufendp = data + data_len;
+ const u8 *datap, *digests = NULL, *algo_buf = NULL;
+ struct rpm_hdr *hdr = (struct rpm_hdr *)bufp;
+ struct rpm_entryinfo *entry;
+ u32 tags = be32_to_cpu(hdr->tags);
+ u32 digests_count = 0;
+ enum hash_algo pkg_kernel_algo = HASH_ALGO_MD5;
+ enum pgp_algos pkg_pgp_algo;
+ u8 rpm_digest[SHA512_DIGEST_SIZE];
+ int ret, i, digest_len;
+
+ const unsigned char rpm_header_magic[8] = {
+ 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+ };
+
+ 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(bufp, rpm_header_magic, sizeof(rpm_header_magic))) {
+ pr_debug("RPM header magic mismatch\n");
+ return -EINVAL;
+ }
+
+ bufp += sizeof(*hdr);
+ datap = bufp + tags * sizeof(struct rpm_entryinfo);
+
+ pr_debug("Scanning %d RPM header sections\n", tags);
+
+ for (i = 0; i < tags && (bufp + sizeof(*entry)) <= bufendp;
+ i++, bufp += sizeof(*entry)) {
+ entry = (struct rpm_entryinfo *)bufp;
+
+ switch (be32_to_cpu(entry->tag)) {
+ case RPMTAG_FILEDIGESTS:
+ digests = datap + be32_to_cpu(entry->offset);
+ digests_count = be32_to_cpu(entry->count);
+ pr_debug("Found RPMTAG_FILEDIGESTS at offset %ld, count: %d\n",
+ digests - data, digests_count);
+ break;
+ case RPMTAG_FILEDIGESTALGO:
+ algo_buf = datap + be32_to_cpu(entry->offset);
+ pr_debug("Found RPMTAG_FILEDIGESTALGO at offset %ld\n",
+ algo_buf - data);
+ break;
+ }
+
+ if (digests && algo_buf)
+ break;
+ }
+
+ if (!digests)
+ return 0;
+
+ if (algo_buf && algo_buf + sizeof(u32) <= bufendp) {
+ pkg_pgp_algo = be32_to_cpu(*(u32 *)algo_buf);
+ 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];
+
+ ret = digest_cache_init_htable(digest_cache, digests_count);
+ if (ret < 0)
+ return ret;
+
+ ret = -ENOENT;
+
+ for (i = 0; i < digests_count && digests < bufendp; i++) {
+ if (!*digests) {
+ digests++;
+ continue;
+ }
+
+ if (digests + digest_len * 2 + 1 > bufendp) {
+ pr_debug("Read beyond end\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = hex2bin(rpm_digest, digests, digest_len);
+ if (ret < 0) {
+ pr_debug("Invalid hex format for digest %s\n", digests);
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = digest_cache_add(digest_cache, rpm_digest);
+ if (ret < 0)
+ return ret;
+
+ digests += digest_len * 2 + 1;
+ }
+
+ return ret;
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 07/12] ima: Add digest_cache policy keyword
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (5 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 06/12] integrity/digest_cache: Parse rpm " Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 08/12] ima: Use digest cache for measurement Roberto Sassu
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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).
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 | 51 +++++++++++++++++++++++++--
6 files changed, 66 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 859a94bcecb..bdf03525e15 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,
+ u8 *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,
+ u8 *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..bc25675264c 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,
+ u8 *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..701c4f21158 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;
+ u8 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,
+ u8 *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,23 @@ 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;
+ }
+
audit_log_format(ab, "res=%d", !result);
audit_log_end(ab);
return result;
@@ -2278,6 +2322,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] 14+ messages in thread
* [RFC][PATCH 08/12] ima: Use digest cache for measurement
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (6 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 07/12] ima: Add digest_cache policy keyword Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 09/12] ima: Use digest cache for appraisal Roberto Sassu
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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 | 27 +++++++++++++++++++++++++--
3 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index bdf03525e15..4f40e07954d 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,
+ unsigned int 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 bc25675264c..591d388158b 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,
+ unsigned int 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..7a5148ac3af 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -221,6 +221,9 @@ static int process_measurement(struct file *file, const struct cred *cred,
bool violation_check;
enum hash_algo hash_algo;
unsigned int allowed_algos = 0;
+ u8 digest_cache_mask = 0;
+ struct digest_cache *digest_cache = NULL;
+ struct path digest_list_path;
if (!ima_policy_flag || !S_ISREG(inode->i_mode))
return 0;
@@ -231,7 +234,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 +266,18 @@ static int process_measurement(struct file *file, const struct cred *cred,
if (!action)
goto out;
+ if (digest_cache_mask) {
+ if (digest_cache_mask & DIGEST_CACHE_MEASURE)
+ digest_cache_iter_dir(file_dentry(file));
+
+ digest_cache = digest_cache_get(file_dentry(file),
+ &digest_list_path);
+ 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 +364,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) {
@@ -391,6 +413,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
out:
if (pathbuf)
__putname(pathbuf);
+ digest_cache_put(digest_cache, &digest_list_path);
if (must_appraise) {
if (rc && (ima_appraise & IMA_APPRAISE_ENFORCE))
return -EACCES;
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 09/12] ima: Use digest cache for appraisal
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (7 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 08/12] ima: Use digest cache for measurement Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 10/12] tools: Add tool to manage digest lists Roberto Sassu
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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 4f40e07954d..385aaede15b 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,
+ u8 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..e6e0ac5e26a 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,
+ u8 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 7a5148ac3af..9f745b473bc 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -381,7 +381,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] 14+ messages in thread
* [RFC][PATCH 10/12] tools: Add tool to manage digest lists
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (8 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 09/12] ima: Use digest cache for appraisal Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 11/12] tools/digest-lists: Add tlv digest list generator and parser Roberto Sassu
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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 d3af1e179b0..6ee11828f2b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10294,6 +10294,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..9da62bd3570
--- /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);
+ exit(1);
+ }
+ 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);
+ exit(1);
+ }
+ break;
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ default:
+ printf("Invalid option %c\n", c);
+ exit(1);
+ }
+ }
+
+ if (op == OP__LAST) {
+ printf("Operation not specified\n");
+ exit(1);
+ }
+
+ switch (op) {
+ case OP_GEN:
+ if (!digest_list_format || !input || !digest_list_dir) {
+ printf("Missing format/input/digest list directory\n");
+ exit(1);
+ }
+
+ 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");
+ exit(1);
+ }
+
+ 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] 14+ messages in thread
* [RFC][PATCH 11/12] tools/digest-lists: Add tlv digest list generator and parser
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (9 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 10/12] tools: Add tool to manage digest lists Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 12/12] tools/digest-lists: Add rpm " Roberto Sassu
2023-08-03 16:20 ` [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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 9da62bd3570..db5680506a8 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] 14+ messages in thread
* [RFC][PATCH 12/12] tools/digest-lists: Add rpm digest list generator and parser
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (10 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 11/12] tools/digest-lists: Add tlv digest list generator and parser Roberto Sassu
@ 2023-07-21 16:33 ` Roberto Sassu
2023-08-03 16:20 ` [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-07-21 16:33 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, 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 db5680506a8..75ddb542062 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] 14+ messages in thread
* Re: [RFC][PATCH 00/12] integrity: Introduce a digest cache
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
` (11 preceding siblings ...)
2023-07-21 16:33 ` [RFC][PATCH 12/12] tools/digest-lists: Add rpm " Roberto Sassu
@ 2023-08-03 16:20 ` Roberto Sassu
12 siblings, 0 replies; 14+ messages in thread
From: Roberto Sassu @ 2023-08-03 16:20 UTC (permalink / raw)
To: zohar, dmitry.kasatkin, paul, jmorris, serge
Cc: linux-kernel, linux-integrity, linux-security-module, bpf, jarkko,
pbrobinson, zbyszek, hch, mjg59, Roberto Sassu, Panu Matilainen
On Fri, 2023-07-21 at 18:33 +0200, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
[...]
> The last part I wanted to talk about is about the digest list parsers. This
> was a long debate. In the original proposal, Matthew Garrett and Christoph
> Hellwig said that adding parsers in the kernel is not scalable and not a
> good idea in general. While I do agree with them, I'm also thinking what
> benefits we get if we relax a bit this requirement. If we merge this patch
I tried to mitigate the risk of adding unsafe code to the kernel by
verifying the parsers with a formal verification tool, Frama-C.
The verified code can be accessed here, and contains all the necessary
dependencies (so that the kernel is not involved):
https://github.com/robertosassu/rpm-formal
I added some assertions, to ensure that for any given input, the parser
does not try to reference memory outside the assigned memory area.
I also tried to enforce finite termination by making the number of
loops dependent on the passed data length.
The output I get is the following:
[eva:summary] ====== ANALYSIS SUMMARY ======
----------------------------------------------------------------------------
13 functions analyzed (out of 13): 100% coverage.
In these functions, 232 statements reached (out of 251): 92% coverage.
----------------------------------------------------------------------------
Some errors and warnings have been raised during the analysis:
by the Eva analyzer: 0 errors 2 warnings
by the Frama-C kernel: 0 errors 0 warnings
----------------------------------------------------------------------------
0 alarms generated by the analysis.
----------------------------------------------------------------------------
Evaluation of the logical properties reached by the analysis:
Assertions 5 valid 0 unknown 0 invalid 5 total
Preconditions 25 valid 0 unknown 0 invalid 25 total
100% of the logical properties reached have been proven.
----------------------------------------------------------------------------
The warnings are:
[eva] validate_tlv.c:353: Warning:
this partitioning parameter cannot be evaluated safely on all states
[eva] validate_tlv.c:381: Warning:
this partitioning parameter cannot be evaluated safely on all states
Not sure how I can make them go away. Anyway, the assertions are
successful.
I verified the parsers with both deterministic (random but valid) and
non-deterministic (random and possibly invalid) data. For deterministic
data, I also verified that bytes at a specific location have the
expected value.
Due to the increasing complexity, the analysis was not done on
arbitrary lengths and value ranges (it would probably require a
different type of analysis).
Thanks
Roberto
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2023-08-03 16:21 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-07-21 16:33 [RFC][PATCH 00/12] integrity: Introduce a digest cache Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 01/12] ima: Introduce hook DIGEST_LIST_CHECK Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 02/12] integrity: Introduce a digest cache Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 03/12] integrity/digest_cache: Add functions to populate and search Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 04/12] integrity/digest_cache: Iterate over digest lists in same dir Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 05/12] integrity/digest_cache: Parse tlv digest lists Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 06/12] integrity/digest_cache: Parse rpm " Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 07/12] ima: Add digest_cache policy keyword Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 08/12] ima: Use digest cache for measurement Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 09/12] ima: Use digest cache for appraisal Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 10/12] tools: Add tool to manage digest lists Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 11/12] tools/digest-lists: Add tlv digest list generator and parser Roberto Sassu
2023-07-21 16:33 ` [RFC][PATCH 12/12] tools/digest-lists: Add rpm " Roberto Sassu
2023-08-03 16:20 ` [RFC][PATCH 00/12] integrity: Introduce a digest cache 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).