[RFC][PATCH v2 11/13] tools/digest-lists: Add tlv digest list generator and parser

From: Roberto Sassu
Date: Sat Aug 12 2023 - 06:50:48 EST


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

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@xxxxxxxxxx>
---
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@xxxxxxxxxx>
+ *
+ * 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@xxxxxxxxxx>
+ *
+ * Generate tlv digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/hash_info.h>
+#include <asm/byteorder.h>
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#include "../../../include/uapi/linux/tlv_parser.h"
+#include "../../../include/uapi/linux/tlv_digest_list.h"
+#include "../common.h"
+
+struct tlv_struct {
+ __u8 *digest_list;
+ struct tlv_hdr *outer_hdr;
+ struct tlv_entry *outer_entry;
+ __u8 algo;
+ int fd;
+};
+
+static int new_digest_list(int dirfd, const char *input, struct tlv_struct *tlv)
+{
+ char filename[NAME_MAX + 1];
+ struct tlv_hdr *hdr;
+ const char *input_ptr;
+
+ input_ptr = strrchr(input, '/');
+ if (input_ptr)
+ input_ptr++;
+ else
+ input_ptr = input;
+
+ snprintf(filename, sizeof(filename), "tlv-%s", input_ptr);
+
+ tlv->fd = openat(dirfd, filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (tlv->fd < 0) {
+ printf("Unable to create %s\n", filename);
+ return -errno;
+ }
+
+ ftruncate(tlv->fd, DIGEST_LIST_SIZE_MAX);
+ tlv->digest_list = mmap(NULL, DIGEST_LIST_SIZE_MAX,
+ PROT_READ | PROT_WRITE, MAP_SHARED, tlv->fd, 0);
+
+ if (tlv->digest_list == MAP_FAILED) {
+ printf("Cannot allocate buffer\n");
+ close(tlv->fd);
+ return -ENOMEM;
+ }
+
+ hdr = (struct tlv_hdr *)tlv->digest_list;
+ memset(hdr, 0, sizeof(*hdr));
+
+ hdr->data_type = __cpu_to_be64(DIGEST_LIST_FILE);
+ hdr->num_fields = 0;
+ hdr->total_len = 0;
+ return 0;
+}
+
+static void write_entry(struct tlv_hdr *hdr, struct tlv_entry **entry,
+ __u16 field, __u8 *data, __u32 data_len,
+ bool update_data)
+{
+ __u16 num_fields;
+ __u64 total_len;
+ __u64 entry_len;
+
+ num_fields = __be64_to_cpu(hdr->num_fields);
+ total_len = __be64_to_cpu(hdr->total_len);
+
+ (*entry)->field = __cpu_to_be64(field);
+ (*entry)->length = __cpu_to_be64(data_len);
+
+ if (update_data)
+ memcpy((*entry)->data, data, data_len);
+
+ num_fields++;
+ entry_len = sizeof(*(*entry)) + data_len;
+ total_len += entry_len;
+
+ hdr->num_fields = __cpu_to_be64(num_fields);
+ hdr->total_len = __cpu_to_be64(total_len);
+ (*entry) = (struct tlv_entry *)((__u8 *)*entry + entry_len);
+}
+
+void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo)
+{
+ struct tlv_struct *tlv;
+ int ret;
+
+ tlv = malloc(sizeof(*tlv));
+ if (!tlv)
+ return NULL;
+
+ ret = new_digest_list(dirfd, input, tlv);
+ if (ret < 0) {
+ free(tlv);
+ return NULL;
+ }
+
+ tlv->outer_hdr = (struct tlv_hdr *)tlv->digest_list;
+ tlv->outer_entry = (struct tlv_entry *)(tlv->outer_hdr + 1);
+ tlv->algo = algo;
+
+ write_entry(tlv->outer_hdr, &tlv->outer_entry, DIGEST_LIST_ALGO,
+ &tlv->algo, sizeof(tlv->algo), true);
+ return tlv;
+}
+
+int tlv_list_gen_add(int dirfd, void *ptr, char *input)
+{
+ struct tlv_struct *tlv = (struct tlv_struct *)ptr;
+ __u8 digest[SHA512_DIGEST_SIZE];
+ struct tlv_hdr *inner_hdr;
+ struct tlv_entry *inner_entry;
+ int ret;
+
+ ret = calc_file_digest(digest, input, tlv->algo);
+ if (ret < 0) {
+ printf("Cannot calculate digest of %s\n", input);
+ return ret;
+ }
+
+ inner_hdr = (struct tlv_hdr *)(tlv->outer_entry + 1);
+ inner_hdr->data_type = __cpu_to_be64(DIGEST_LIST_FILE);
+
+ inner_entry = (struct tlv_entry *)(inner_hdr + 1);
+
+ write_entry(inner_hdr, &inner_entry, ENTRY_DIGEST, digest,
+ hash_digest_size[tlv->algo], true);
+ write_entry(inner_hdr, &inner_entry, ENTRY_PATH, (__u8 *)input,
+ strlen(input) + 1, true);
+
+ write_entry(tlv->outer_hdr, &tlv->outer_entry, DIGEST_LIST_ENTRY, NULL,
+ (__u8 *)inner_entry - (__u8 *)inner_hdr, false);
+ return 0;
+}
+
+void tlv_list_gen_close(void *ptr)
+{
+ struct tlv_struct *tlv = (struct tlv_struct *)ptr;
+
+ munmap(tlv->digest_list, DIGEST_LIST_SIZE_MAX);
+ ftruncate(tlv->fd, (__u8 *)tlv->outer_entry - (__u8 *)tlv->outer_hdr);
+ close(tlv->fd);
+ free(tlv);
+}
diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c
index bc425da5317..7caad681eee 100644
--- a/tools/digest-lists/manage_digest_lists.c
+++ b/tools/digest-lists/manage_digest_lists.c
@@ -20,6 +20,8 @@
#include <fts.h>

#include "common.h"
+#include "generators/generators.h"
+#include "parsers/parsers.h"

const char *ops_str[OP__LAST] = {
[OP_GEN] = "gen",
@@ -29,9 +31,12 @@ const char *ops_str[OP__LAST] = {
};

struct generator generators[] = {
+ { .name = "tlv", .new = tlv_list_gen_new, .add = tlv_list_gen_add,
+ .close = tlv_list_gen_close },
};

struct parser parsers[] = {
+ { .name = "tlv", .parse = tlv_list_parse },
};

static int generator_add(struct generator *generator, int dirfd,
diff --git a/tools/digest-lists/parsers/parsers.h b/tools/digest-lists/parsers/parsers.h
new file mode 100644
index 00000000000..708da7eac3b
--- /dev/null
+++ b/tools/digest-lists/parsers/parsers.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * 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@xxxxxxxxxx>
+ *
+ * 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@xxxxxxxxxx>
+ *
+ * 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