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