From: Roberto Sassu <roberto.sassu@xxxxxxxxxx> 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@xxxxxxxxxx> --- 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@xxxxxxxxxx> + * + * 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