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");