[RFC][PATCH 6/9] diglim: RPM digest list generator

Roberto Sassu roberto.sassu at huawei.com
Wed Sep 15 16:31:42 UTC 2021


Introduce the generator of RPM digest lists, which takes the RPM header
from a package or the RPM DB. Optionally, it appends the RPM header
signature with the same format of kernel modules and ID PKEY_ID_PGP.

This type of digest list can be loaded through the user space parser
rpm_parser, which is introduced in a subsequent patch.

Signed-off-by: Roberto Sassu <roberto.sassu at huawei.com>
---
 MAINTAINERS            |   1 +
 tools/diglim/Makefile  |   5 +-
 tools/diglim/rpm_gen.c | 334 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 339 insertions(+), 1 deletion(-)
 create mode 100644 tools/diglim/rpm_gen.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b752790c06ea..04b252ebd7e1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5526,6 +5526,7 @@ F:	tools/diglim/Makefile
 F:	tools/diglim/common.c
 F:	tools/diglim/common.h
 F:	tools/diglim/compact_gen.c
+F:	tools/diglim/rpm_gen.c
 F:	tools/testing/selftests/diglim/
 
 DIOLAN U2C-12 I2C DRIVER
diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile
index 45efa554449d..332bcd93af78 100644
--- a/tools/diglim/Makefile
+++ b/tools/diglim/Makefile
@@ -3,7 +3,7 @@
 CC := $(CROSS_COMPILE)gcc
 CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb
 
-PROGS := compact_gen
+PROGS := compact_gen rpm_gen
 PROGS_EXTENDED := common.o
 
 all: $(PROGS)
@@ -16,3 +16,6 @@ common.o: common.c
 
 compact_gen: compact_gen.c $(PROGS_EXTENDED)
 	$(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lcrypto
+
+rpm_gen: rpm_gen.c $(PROGS_EXTENDED)
+	$(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lrpm -lrpmio
diff --git a/tools/diglim/rpm_gen.c b/tools/diglim/rpm_gen.c
new file mode 100644
index 000000000000..fbee65ea0394
--- /dev/null
+++ b/tools/diglim/rpm_gen.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu at huawei.com>
+ *
+ * Generate RPM digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <limits.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 <bits/endianness.h>
+
+#include "common.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#include <linux/byteorder/big_endian.h>
+#else
+#include <linux/byteorder/little_endian.h>
+#endif
+
+#include "../../usr/include/linux/diglim.h"
+
+const unsigned char rpm_header_magic[8] = {
+	0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+};
+
+/* In stripped ARM and x86-64 modules, ~ is surprisingly rare. */
+#define MODULE_SIG_STRING "~Module signature appended~\n"
+
+enum pkey_id_type {
+	PKEY_ID_PGP,		/* OpenPGP generated key ID */
+	PKEY_ID_X509,		/* X.509 arbitrary subjectKeyIdentifier */
+	PKEY_ID_PKCS7,		/* Signature in PKCS#7 message */
+};
+
+/*
+ * Module signature information block.
+ *
+ * The constituents of the signature section are, in order:
+ *
+ *	- Signer's name
+ *	- Key identifier
+ *	- Signature data
+ *	- Information block
+ */
+struct module_signature {
+	u8	algo;		/* Public-key crypto algorithm [0] */
+	u8	hash;		/* Digest algorithm [0] */
+	u8	id_type;	/* Key identifier type [PKEY_ID_PKCS7] */
+	u8	signer_len;	/* Length of signer's name [0] */
+	u8	key_id_len;	/* Length of key identifier [0] */
+	u8	__pad[3];
+	__be32	sig_len;	/* Length of signature data */
+};
+
+static int gen_filename_prefix(char *filename, int filename_len, int pos,
+			       const char *format, enum compact_types type)
+{
+	return snprintf(filename, filename_len, "%d-%s_list-%s-",
+			(pos >= 0) ? pos : 0, compact_types_str[type], format);
+}
+
+static void gen_filename(Header rpm, int pos, enum compact_types type,
+			 char *filename, int filename_len, char *output_format)
+{
+	rpmtd name = rpmtdNew(), version = rpmtdNew();
+	rpmtd release = rpmtdNew(), arch = rpmtdNew();
+	int prefix_len;
+
+	headerGet(rpm, RPMTAG_NAME, name, 0);
+	headerGet(rpm, RPMTAG_VERSION, version, 0);
+	headerGet(rpm, RPMTAG_RELEASE, release, 0);
+	headerGet(rpm, RPMTAG_ARCH, arch, 0);
+
+	prefix_len = gen_filename_prefix(filename, filename_len, pos,
+					 output_format, type);
+
+	snprintf(filename + prefix_len, filename_len - prefix_len,
+		 "%s-%s-%s.%s", rpmtdGetString(name), rpmtdGetString(version),
+		 rpmtdGetString(release), rpmtdGetString(arch));
+
+	rpmtdFree(name);
+	rpmtdFree(version);
+	rpmtdFree(release);
+	rpmtdFree(arch);
+}
+
+static int find_package(Header rpm, char *package)
+{
+	rpmtd name = rpmtdNew();
+	int found = 0;
+
+	headerGet(rpm, RPMTAG_NAME, name, 0);
+	if (!strncmp(rpmtdGetString(name), package, strlen(package)))
+		found = 1;
+
+	rpmtdFree(name);
+	return found;
+}
+
+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, 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)
+{
+	struct module_signature modsig = { 0 };
+	rpmtd signature = rpmtdNew();
+	int ret, fd;
+
+	headerGet(rpm, RPMTAG_RSAHEADER, signature, 0);
+	fd = openat(dirfd, filename, O_WRONLY | O_APPEND);
+	if (fd < 0) {
+		ret = -errno;
+		goto out;
+	}
+
+	modsig.id_type = PKEY_ID_PGP;
+	modsig.sig_len = signature->count;
+	modsig.sig_len = __cpu_to_be32(modsig.sig_len);
+
+	ret = write(fd, signature->data, signature->count);
+	if (ret != signature->count) {
+		ret = -EIO;
+		goto out_fd;
+	}
+
+	ret = write(fd, &modsig, sizeof(modsig));
+	if (ret != sizeof(modsig)) {
+		ret = -EIO;
+		goto out_fd;
+	}
+
+	ret = write(fd, MODULE_SIG_STRING, sizeof(MODULE_SIG_STRING) - 1);
+	if (ret != sizeof(MODULE_SIG_STRING) - 1) {
+		ret = -EIO;
+		goto out;
+	}
+
+	ret = 0;
+out_fd:
+	close(fd);
+out:
+	rpmtdFree(signature);
+
+	if (ret < 0)
+		unlinkat(dirfd, filename, 0);
+
+	return ret;
+}
+
+static void usage(char *progname)
+{
+	printf("Usage: %s <options>\n", progname);
+	printf("Options:\n");
+	printf("\t-d <output directory>: directory digest lists are written to\n"
+	       "\t-r <RPM path>: RPM package the digest list is generated from (all RPM packages in DB if not specified)\n"
+	       "\t-p <package>: selected RPM package in RPM DB\n"
+	       "\t-h: display help\n");
+}
+
+static void gen_rpm_digest_list(Header rpm, int dirfd, char *filename)
+{
+	int ret;
+
+	ret = write_rpm_header(rpm, dirfd, filename);
+	if (ret < 0) {
+		printf("Cannot generate %s digest list\n", filename);
+		return;
+	}
+
+	ret = write_rpm_header_signature(rpm, dirfd, filename);
+	if (ret < 0)
+		printf("Cannot add signature to %s digest list\n",
+		       filename);
+}
+
+int main(int argc, char *argv[])
+{
+	char filename[NAME_MAX + 1];
+	rpmts ts = NULL;
+	Header hdr;
+	FD_t fd;
+	rpmdbMatchIterator mi;
+	rpmVSFlags vsflags = 0;
+	char *input_package = NULL, *selected_package = NULL;
+	char *output_dir = NULL;
+	struct stat st;
+	int c;
+	int ret, dirfd;
+
+	while ((c = getopt(argc, argv, "d:r:p:h")) != -1) {
+		switch (c) {
+		case 'd':
+			output_dir = optarg;
+			break;
+		case 'r':
+			input_package = optarg;
+			break;
+		case 'p':
+			selected_package = optarg;
+			break;
+		case 'h':
+			usage(argv[0]);
+			exit(0);
+		default:
+			printf("Invalid option %c\n", c);
+			exit(1);
+		}
+	}
+
+	if (!output_dir) {
+		printf("Output directory not specified\n");
+		exit(1);
+	}
+
+	if (stat(output_dir, &st) == -1)
+		mkdir(output_dir, 0755);
+
+	dirfd = open(output_dir, O_RDONLY | O_DIRECTORY);
+	if (dirfd < 0) {
+		printf("Unable to open %s, ret: %d\n", output_dir, -errno);
+		ret = -errno;
+		goto out;
+	}
+
+	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;
+	}
+
+	if (input_package) {
+		vsflags |= _RPMVSF_NODIGESTS;
+		vsflags |= _RPMVSF_NOSIGNATURES;
+		rpmtsSetVSFlags(ts, vsflags);
+
+		fd = Fopen(input_package, "r.ufdio");
+		if ((!fd) || Ferror(fd)) {
+			rpmlog(RPMLOG_NOTICE,
+			       "Failed to open package file %s, %s\n",
+			       input_package, 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_package);
+			goto out_rpm;
+		}
+
+		gen_filename(hdr, 0, COMPACT_FILE, filename, sizeof(filename),
+			     "rpm");
+
+		gen_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, 0, COMPACT_FILE, filename, sizeof(filename),
+			     "rpm");
+
+		if (strstr(filename, "gpg-pubkey") != NULL)
+			continue;
+
+		if (selected_package && !find_package(hdr, selected_package))
+			continue;
+
+		gen_rpm_digest_list(hdr, dirfd, filename);
+	}
+
+	rpmdbFreeIterator(mi);
+out_rpm:
+	rpmFreeRpmrc();
+	rpmtsFree(ts);
+out:
+	close(dirfd);
+	return ret;
+}
-- 
2.25.1



More information about the Linux-security-module-archive mailing list