[PATCH v6 08/15] digest_cache: Parse tlv digest lists

Roberto Sassu posted 15 patches 4 days, 4 hours ago
[PATCH v6 08/15] digest_cache: Parse tlv digest lists
Posted by Roberto Sassu 4 days, 4 hours ago
From: Roberto Sassu <roberto.sassu@huawei.com>

Add digest_list_parse_tlv(), to parse TLV-formatted (Type Length Value)
digest lists. Their structure is:

[field: DIGEST_LIST_ALGO, length, value]
[field: DIGEST_LIST_NUM_ENTRIES, length, value]
[field: DIGEST_LIST_ENTRY#1, length, value (below)]
 |- [DIGEST_LIST_ENTRY_DIGEST#1, length, file digest]
 |- [DIGEST_LIST_ENTRY_PATH#1, length, file path]
[field: DIGEST_LIST_ENTRY#N, length, value (below)]
 |- [DIGEST_LIST_ENTRY_DIGEST#N, length, file digest]
 |- [DIGEST_LIST_ENTRY_PATH#N, length, file path]

DIGEST_LIST_ALGO and DIGEST_LIST_NUM_ENTRIES must have a fixed length
respectively of sizeof(u16) and sizeof(u32).

The data of the DIGEST_LIST_ENTRY field are itself in TLV format, for which
the DIGEST_LIST_ENTRY_DIGEST and DIGEST_LIST_ENTRY_PATH fields are defined.

Currently defined fields are sufficient for measurement/appraisal of file
data. More fields will be introduced later for file metadata.

Introduce digest_list_callback() to handle the digest list fields,
DIGEST_LIST_ALGO, DIGEST_LIST_NUM_ENTRIES and DIGEST_LIST_ENTRY, and the
respective field parsers parse_digest_list_algo(),
parse_digest_list_num_entries() and parse_digest_list_entry().

Introduce digest_list_entry_callback(), to handle the DIGEST_LIST_ENTRY
fields, DIGEST_LIST_ENTRY_DIGEST and DIGEST_LIST_ENTRY_PATH, and the
respective field parsers parse_digest_list_entry_digest() and
parse_digest_list_entry_path().

The TLV parser itself is implemented in lib/tlv_parser.c.

Both the TLV parser and the tlv digest list parser have been formally
verified with Frama-C (https://frama-c.com/).

The analysis has been done on this file:

https://github.com/robertosassu/rpm-formal/blob/main/validate_tlv.c

Here is the result of the analysis:

[eva:summary] ====== ANALYSIS SUMMARY ======
---------------------------------------------------------------------------
12 functions analyzed (out of 12): 100% coverage.
In these functions, 177 statements reached (out of 191): 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    22 valid     0 unknown     0 invalid     22 total
100% of the logical properties reached have been proven.
---------------------------------------------------------------------------

The warnings are:

[eva] validate_tlv.c:256: Warning:
  this partitioning parameter cannot be evaluated safely on all states
[eva] validate_tlv.c:284: Warning:
  this partitioning parameter cannot be evaluated safely on all states

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 include/uapi/linux/tlv_digest_list.h          |  47 +++
 security/integrity/digest_cache/Kconfig       |   8 +
 security/integrity/digest_cache/Makefile      |   1 +
 security/integrity/digest_cache/parsers/tlv.c | 341 ++++++++++++++++++
 4 files changed, 397 insertions(+)
 create mode 100644 include/uapi/linux/tlv_digest_list.h
 create mode 100644 security/integrity/digest_cache/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 000000000000..f2031cd70e64
--- /dev/null
+++ b/include/uapi/linux/tlv_digest_list.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2017-2024 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_FIELD(DIGEST_LIST_FIELD) \
+	DIGEST_LIST_FIELD(DIGEST_LIST_ALGO) \
+	DIGEST_LIST_FIELD(DIGEST_LIST_NUM_ENTRIES) \
+	DIGEST_LIST_FIELD(DIGEST_LIST_ENTRY) \
+	DIGEST_LIST_FIELD(DIGEST_LIST_FIELD__LAST)
+
+#define FOR_EACH_DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_FIELD) \
+	DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_DIGEST) \
+	DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_PATH) \
+	DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_FIELD__LAST)
+
+#define GENERATE_ENUM(ENUM) ENUM,
+#define GENERATE_STRING(STRING) #STRING,
+
+/**
+ * enum digest_list_fields - Digest list fields
+ *
+ * Enumerates the digest list fields.
+ */
+enum digest_list_fields {
+	FOR_EACH_DIGEST_LIST_FIELD(GENERATE_ENUM)
+};
+
+/**
+ * enum digest_list_entry_fields - DIGEST_LIST_ENTRY fields
+ *
+ * Enumerates the DIGEST_LIST_ENTRY fields.
+ */
+enum digest_list_entry_fields {
+	FOR_EACH_DIGEST_LIST_ENTRY_FIELD(GENERATE_ENUM)
+};
+
+#endif /* _UAPI_LINUX_TLV_DIGEST_LIST_H */
diff --git a/security/integrity/digest_cache/Kconfig b/security/integrity/digest_cache/Kconfig
index 65c07110911b..972bcf8bb765 100644
--- a/security/integrity/digest_cache/Kconfig
+++ b/security/integrity/digest_cache/Kconfig
@@ -33,3 +33,11 @@ config DIGEST_CACHE_HTABLE_DEPTH
 	  A smaller number will increase the amount of hash table slots, and
 	  make the search faster. A bigger number will decrease the number of
 	  hash table slots, but make the search slower.
+
+config DIGEST_CACHE_TLV_PARSER
+	tristate "TLV digest list parser"
+	depends on INTEGRITY_DIGEST_CACHE
+	select TLV_PARSER
+	help
+	  Add support for parsing TLV-formatted (Type Length Value)
+	  digest list.
diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile
index d68cae690241..3b42b20d1bc0 100644
--- a/security/integrity/digest_cache/Makefile
+++ b/security/integrity/digest_cache/Makefile
@@ -3,6 +3,7 @@
 # Makefile for building the Integrity Digest Cache.
 
 obj-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o
+obj-$(CONFIG_DIGEST_CACHE_TLV_PARSER) += parsers/tlv.o
 
 digest_cache-y := main.o secfs.o htable.o parsers.o
 
diff --git a/security/integrity/digest_cache/parsers/tlv.c b/security/integrity/digest_cache/parsers/tlv.c
new file mode 100644
index 000000000000..31e407f0a43b
--- /dev/null
+++ b/security/integrity/digest_cache/parsers/tlv.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2024 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Parse a tlv digest list.
+ */
+
+#define pr_fmt(fmt) "digest_cache TLV PARSER: "fmt
+#include <linux/module.h>
+#include <linux/tlv_parser.h>
+#include <linux/digest_cache.h>
+#include <uapi/linux/tlv_digest_list.h>
+
+#define kenter(FMT, ...) \
+	pr_debug("==> %s(" FMT ")\n", __func__, ##__VA_ARGS__)
+#define kleave(FMT, ...) \
+	pr_debug("<== %s()" FMT "\n", __func__, ##__VA_ARGS__)
+
+static const char *digest_list_fields_str[] = {
+	FOR_EACH_DIGEST_LIST_FIELD(GENERATE_STRING)
+};
+
+static const char *digest_list_entry_fields_str[] = {
+	FOR_EACH_DIGEST_LIST_ENTRY_FIELD(GENERATE_STRING)
+};
+
+struct tlv_callback_data {
+	struct digest_cache *digest_cache;
+	enum hash_algo algo;
+};
+
+/**
+ * parse_digest_list_entry_digest - Parse DIGEST_LIST_ENTRY_DIGEST field
+ * @tlv_data: Callback data
+ * @field: Field identifier
+ * @field_data: Field data
+ * @field_data_len: Length of @field_data
+ *
+ * This function parses the DIGEST_LIST_ENTRY_DIGEST field (file digest).
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int parse_digest_list_entry_digest(struct tlv_callback_data *tlv_data,
+					  enum digest_list_entry_fields field,
+					  const __u8 *field_data,
+					  __u32 field_data_len)
+{
+	int ret;
+
+	kenter(",%u,%u", field, field_data_len);
+
+	if (tlv_data->algo == HASH_ALGO__LAST) {
+		pr_debug("Digest algo not set\n");
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	if (field_data_len != hash_digest_size[tlv_data->algo]) {
+		pr_debug("Unexpected data length %u, expected %d\n",
+			 field_data_len, hash_digest_size[tlv_data->algo]);
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	ret = digest_cache_htable_add(tlv_data->digest_cache,
+				      (__u8 *)field_data, tlv_data->algo);
+out:
+	kleave(" = %d", ret);
+	return ret;
+}
+
+/**
+ * parse_digest_list_entry_path - Parse DIGEST_LIST_ENTRY_PATH field
+ * @tlv_data: Callback data
+ * @field: Field identifier
+ * @field_data: Field data
+ * @field_data_len: Length of @field_data
+ *
+ * This function handles the DIGEST_LIST_ENTRY_PATH field (file path). It
+ * currently does not parse the data.
+ *
+ * Return: Zero.
+ */
+static int parse_digest_list_entry_path(struct tlv_callback_data *tlv_data,
+					enum digest_list_entry_fields field,
+					const __u8 *field_data,
+					__u32 field_data_len)
+{
+	kenter(",%u,%u", field, field_data_len);
+
+	kleave(" = 0");
+	return 0;
+}
+
+/**
+ * digest_list_entry_callback - DIGEST_LIST_ENTRY callback
+ * @callback_data: Callback data
+ * @field: Field identifier
+ * @field_data: Field data
+ * @field_data_len: Length of @field_data
+ *
+ * This callback handles the fields of DIGEST_LIST_ENTRY (nested) data, and
+ * calls the appropriate parser.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int digest_list_entry_callback(void *callback_data, __u16 field,
+				      const __u8 *field_data,
+				      __u32 field_data_len)
+{
+	struct tlv_callback_data *tlv_data;
+	int ret;
+
+	tlv_data = (struct tlv_callback_data *)callback_data;
+
+	switch (field) {
+	case DIGEST_LIST_ENTRY_DIGEST:
+		ret = parse_digest_list_entry_digest(tlv_data, field,
+						     field_data,
+						     field_data_len);
+		break;
+	case DIGEST_LIST_ENTRY_PATH:
+		ret = parse_digest_list_entry_path(tlv_data, field, field_data,
+						   field_data_len);
+		break;
+	default:
+		pr_debug("Unhandled field %s\n",
+			 digest_list_entry_fields_str[field]);
+		/* Just ignore non-relevant fields. */
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+/**
+ * parse_digest_list_algo - Parse DIGEST_LIST_ALGO field
+ * @tlv_data: Callback data
+ * @field: Field identifier
+ * @field_data: Field data
+ * @field_data_len: Length of @field_data
+ *
+ * This function parses the DIGEST_LIST_ALGO field (digest algorithm).
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int parse_digest_list_algo(struct tlv_callback_data *tlv_data,
+				  enum digest_list_fields field,
+				  const __u8 *field_data, __u32 field_data_len)
+{
+	__u16 algo;
+	int ret = 0;
+
+	kenter(",%u,%u", field, field_data_len);
+
+	if (field_data_len != sizeof(__u16)) {
+		pr_debug("Unexpected data length %u, expected %zu\n",
+			 field_data_len, sizeof(__u16));
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	algo = __be16_to_cpu(*(__u16 *)field_data);
+
+	if (algo >= HASH_ALGO__LAST) {
+		pr_debug("Unexpected digest algo %u\n", algo);
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	tlv_data->algo = algo;
+
+	pr_debug("Digest algo: %s\n", hash_algo_name[algo]);
+out:
+	kleave(" = %d", ret);
+	return ret;
+}
+
+/**
+ * parse_digest_list_num_entries - Parse DIGEST_LIST_NUM_ENTRIES field
+ * @tlv_data: Callback data
+ * @field: Field identifier
+ * @field_data: Field data
+ * @field_data_len: Length of @field_data
+ *
+ * This function parses the DIGEST_LIST_NUM_ENTRIES field (digest list entries).
+ * This field must appear after DIGEST_LIST_ALGO.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int parse_digest_list_num_entries(struct tlv_callback_data *tlv_data,
+					 enum digest_list_fields field,
+					 const __u8 *field_data,
+					 __u32 field_data_len)
+{
+	__u32 num_entries;
+	int ret;
+
+	kenter(",%u,%u", field, field_data_len);
+
+	if (field_data_len != sizeof(__u32)) {
+		pr_debug("Unexpected data length %u, expected %zu\n",
+			 field_data_len, sizeof(__u32));
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	if (tlv_data->algo == HASH_ALGO__LAST) {
+		pr_debug("Digest algo not yet initialized\n");
+		ret = -EBADMSG;
+		goto out;
+	}
+
+	num_entries = __be32_to_cpu(*(__u32 *)field_data);
+
+	ret = digest_cache_htable_init(tlv_data->digest_cache, num_entries,
+				       tlv_data->algo);
+out:
+	kleave(" = %d", ret);
+	return ret;
+}
+
+/**
+ * parse_digest_list_entry - Parse DIGEST_LIST_ENTRY field
+ * @tlv_data: Callback data
+ * @field: Field identifier
+ * @field_data: Field data
+ * @field_data_len: Length of @field_data
+ *
+ * This function parses the DIGEST_LIST_ENTRY field.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int parse_digest_list_entry(struct tlv_callback_data *tlv_data,
+				   enum digest_list_fields field,
+				   const __u8 *field_data, __u32 field_data_len)
+{
+	int ret;
+
+	kenter(",%u,%u", field, field_data_len);
+
+	ret = tlv_parse(digest_list_entry_callback, tlv_data, field_data,
+			field_data_len, digest_list_entry_fields_str,
+			DIGEST_LIST_ENTRY_FIELD__LAST);
+
+	kleave(" = %d", ret);
+	return ret;
+}
+
+/**
+ * digest_list_callback - Digest list callback
+ * @callback_data: Callback data
+ * @field: Field identifier
+ * @field_data: Field data
+ * @field_data_len: Length of @field_data
+ *
+ * This callback handles the digest list fields, and calls the appropriate
+ * parser.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int digest_list_callback(void *callback_data, __u16 field,
+				const __u8 *field_data, __u32 field_data_len)
+{
+	struct tlv_callback_data *tlv_data;
+	int ret;
+
+	tlv_data = (struct tlv_callback_data *)callback_data;
+
+	switch (field) {
+	case DIGEST_LIST_ALGO:
+		ret = parse_digest_list_algo(tlv_data, field, field_data,
+					     field_data_len);
+		break;
+	case DIGEST_LIST_NUM_ENTRIES:
+		ret = parse_digest_list_num_entries(tlv_data, field, field_data,
+						    field_data_len);
+		break;
+	case DIGEST_LIST_ENTRY:
+		ret = parse_digest_list_entry(tlv_data, 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;
+}
+
+/**
+ * digest_list_parse_tlv - Parse a tlv digest list
+ * @digest_cache: Digest cache
+ * @data: Data to parse
+ * @data_len: Length of @data
+ *
+ * This function parses a tlv digest list.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int digest_list_parse_tlv(struct digest_cache *digest_cache,
+				 const __u8 *data, size_t data_len)
+{
+	struct tlv_callback_data tlv_data = {
+		.digest_cache = digest_cache,
+		.algo = HASH_ALGO__LAST,
+	};
+
+	return tlv_parse(digest_list_callback, &tlv_data, data, data_len,
+			 digest_list_fields_str, DIGEST_LIST_FIELD__LAST);
+}
+
+static struct parser tlv_parser = {
+	.name = "tlv",
+	.owner = THIS_MODULE,
+	.func = digest_list_parse_tlv,
+};
+
+static int __init tlv_parser_init(void)
+{
+	return digest_cache_register_parser(&tlv_parser);
+}
+
+static void __exit tlv_parser_exit(void)
+{
+	digest_cache_unregister_parser(&tlv_parser);
+}
+
+module_init(tlv_parser_init);
+module_exit(tlv_parser_exit);
+
+MODULE_AUTHOR("Roberto Sassu");
+MODULE_DESCRIPTION("TLV digest list parser");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0.0");
-- 
2.47.0.118.gfd3785337b