rpm/Add-digest-list-plugin.patch

580 lines
15 KiB
Diff
Raw Normal View History

2020-07-14 09:04:38 +02:00
From fa0b33ce1ff569ab55b46cdbcc47f2da6db3fb1a Mon Sep 17 00:00:00 2001
From: Roberto Sassu <roberto.sassu@huawei.com>
Date: Wed, 26 Feb 2020 15:54:24 +0100
Subject: [PATCH 2/2] Add digest list plugin
---
macros.in | 1 +
plugins/Makefile.am | 4 +
plugins/digest_list.c | 534 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 539 insertions(+)
create mode 100644 plugins/digest_list.c
diff --git a/macros.in b/macros.in
index 402749362..8619c1323 100644
--- a/macros.in
+++ b/macros.in
@@ -1184,6 +1184,7 @@ package or when debugging this package.\
%__transaction_ima %{__plugindir}/ima.so
%__transaction_prioreset %{__plugindir}/prioreset.so
%__transaction_audit %{__plugindir}/audit.so
+%__transaction_digest_list %{__plugindir}/digest_list.so
#------------------------------------------------------------------------------
# Macros for further automated spec %setup and patch application
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index d4ef039ed..07aa3585b 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -48,3 +48,7 @@ audit_la_sources = audit.c
audit_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la @WITH_AUDIT_LIB@
plugins_LTLIBRARIES += audit.la
endif
+
+digest_list_la_sources = digest_list.c
+digest_list_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
+plugins_LTLIBRARIES += digest_list.la
diff --git a/plugins/digest_list.c b/plugins/digest_list.c
new file mode 100644
index 000000000..62aae06dd
--- /dev/null
+++ b/plugins/digest_list.c
@@ -0,0 +1,534 @@
+#include "system.h"
+#include "errno.h"
+
+#include <rpm/rpmlog.h>
+#include <rpm/rpmts.h>
+#include <rpm/header.h>
+#include <rpm/rpmfileutil.h>
+#include "lib/rpmplugin.h"
+#include <sys/stat.h>
+#include <openssl/sha.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <asm/byteorder.h>
+#include <sys/wait.h>
+
+#include "debug.h"
+
+#define IMA_DIR "/sys/kernel/security/ima"
+#define DIGEST_LIST_DATA_PATH IMA_DIR "/digest_list_data"
+#define DIGEST_LIST_DATA_DEL_PATH IMA_DIR "/digest_list_data_del"
+#define DIGEST_LIST_COUNT IMA_DIR "/digests_count"
+#define DIGEST_LIST_DEFAULT_PATH "/etc/ima/digest_lists"
+#define RPM_PARSER "/usr/libexec/rpm_parser"
+#define WRITE_RPM_PGP_SIG "/usr/bin/write_rpm_pgp_sig"
+
+#define DIGEST_LIST_OP_ADD 0
+#define DIGEST_LIST_OP_DEL 1
+
+enum hash_algo {
+ HASH_ALGO_MD4,
+ HASH_ALGO_MD5,
+ HASH_ALGO_SHA1,
+ HASH_ALGO_RIPE_MD_160,
+ HASH_ALGO_SHA256,
+ HASH_ALGO_SHA384,
+ HASH_ALGO_SHA512,
+ HASH_ALGO_SHA224,
+ HASH_ALGO_RIPE_MD_128,
+ HASH_ALGO_RIPE_MD_256,
+ HASH_ALGO_RIPE_MD_320,
+ HASH_ALGO_WP_256,
+ HASH_ALGO_WP_384,
+ HASH_ALGO_WP_512,
+ HASH_ALGO_TGR_128,
+ HASH_ALGO_TGR_160,
+ HASH_ALGO_TGR_192,
+ HASH_ALGO_SM3_256,
+ HASH_ALGO__LAST
+};
+
+enum pgp_hash_algo {
+ PGP_HASH_MD5 = 1,
+ PGP_HASH_SHA1 = 2,
+ PGP_HASH_RIPE_MD_160 = 3,
+ PGP_HASH_SHA256 = 8,
+ PGP_HASH_SHA384 = 9,
+ PGP_HASH_SHA512 = 10,
+ PGP_HASH_SHA224 = 11,
+ PGP_HASH__LAST
+};
+
+enum hash_algo pgp_algo_mapping[PGP_HASH__LAST] = {
+ [PGP_HASH_MD5] = HASH_ALGO_MD5,
+ [PGP_HASH_SHA1] = HASH_ALGO_SHA1,
+ [PGP_HASH_SHA224] = HASH_ALGO_SHA224,
+ [PGP_HASH_SHA256] = HASH_ALGO_SHA256,
+ [PGP_HASH_SHA384] = HASH_ALGO_SHA384,
+ [PGP_HASH_SHA512] = HASH_ALGO_SHA512,
+};
+
+/* from integrity.h */
+enum evm_ima_xattr_type {
+ IMA_XATTR_DIGEST = 0x01,
+ EVM_XATTR_HMAC,
+ EVM_IMA_XATTR_DIGSIG,
+ IMA_XATTR_DIGEST_NG,
+ EVM_XATTR_PORTABLE_DIGSIG,
+ EVM_IMA_XATTR_DIGEST_LIST,
+ IMA_XATTR_LAST
+};
+
+struct evm_ima_xattr_data {
+ uint8_t type;
+ uint8_t digest[SHA512_DIGEST_LENGTH + 1];
+} __attribute__((packed));
+
+struct signature_v2_hdr {
+ uint8_t type; /* xattr type */
+ uint8_t version; /* signature format version */
+ uint8_t hash_algo; /* Digest algorithm [enum hash_algo] */
+ __be32 keyid; /* IMA key identifier - not X509/PGP specific */
+ __be16 sig_size; /* signature size */
+ uint8_t sig[0]; /* signature payload */
+} __attribute__((packed));
+
+static int disable_plugin;
+
+static int upload_digest_list(char *path, int type, int digest_list_signed)
+{
+ size_t size;
+ char buf[21];
+ const char *ima_path = DIGEST_LIST_DATA_PATH;
+ pid_t pid;
+ int ret = 0, fd;
+
+ if (type == TR_REMOVED)
+ ima_path = DIGEST_LIST_DATA_DEL_PATH;
+
+ /* First determine if kernel interface can accept new digest lists */
+ fd = open(DIGEST_LIST_COUNT, O_RDONLY);
+ if (fd < 0) {
+ rpmlog(RPMLOG_ERR, "digest_list: could not open IMA interface "
+ "'%s': %s\n", DIGEST_LIST_COUNT, strerror(errno));
+ return -EACCES;
+ }
+
+ ret = read(fd, buf, sizeof(buf));
+ close(fd);
+
+ if (ret <= 0) {
+ rpmlog(RPMLOG_ERR, "digest_list: could not read from IMA "
+ "interface '%s': %s\n", DIGEST_LIST_COUNT,
+ strerror(errno));
+ return -EACCES;
+ }
+
+ /* Last character is newline */
+ buf[ret - 1] = '\0';
+
+ rpmlog(RPMLOG_DEBUG, "digest_list: digests count %s\n", buf);
+
+ if (*buf == '0') {
+ rpmlog(RPMLOG_DEBUG, "digest_list: not uploading '%s' to IMA "
+ "interface '%s'\n", path, ima_path);
+ return RPMRC_OK;
+ }
+
+ /* If the digest list is not signed, execute the RPM parser */
+ if (!digest_list_signed) {
+ if ((pid = fork()) == 0) {
+ execlp(RPM_PARSER, RPM_PARSER, (type == TR_ADDED) ?
+ "add" : "del", path, NULL);
+ _exit(EXIT_FAILURE);
+ }
+
+ waitpid(pid, &ret, 0);
+ if (ret != 0)
+ rpmlog(RPMLOG_ERR, "digest_list: %s returned %d\n",
+ RPM_PARSER, ret);
+ return 0;
+ }
+
+ fd = open(ima_path, O_WRONLY);
+ if (fd < 0) {
+ rpmlog(RPMLOG_ERR, "digest_list: could not open IMA interface "
+ "'%s': %s\n", ima_path, strerror(errno));
+ return -EACCES;
+ }
+
+ /* Write the path of the digest list to securityfs */
+ size = write(fd, path, strlen(path));
+ if (size != strlen(path)) {
+ rpmlog(RPMLOG_ERR, "digest_list: could not write '%s' to IMA "
+ "interface '%s': %s\n", path, ima_path, strerror(errno));
+ ret = -EIO;
+ goto out;
+ }
+
+ rpmlog(RPMLOG_DEBUG, "digest_list: written '%s' to '%s'\n", path,
+ ima_path);
+out:
+ close(fd);
+ return ret;
+}
+
+static int add_ima_xattr(const char *path, int algo,
+ const unsigned char *digest, int digest_len)
+{
+ struct evm_ima_xattr_data ima_xattr;
+ int ret;
+
+ ima_xattr.type = IMA_XATTR_DIGEST_NG;
+ ima_xattr.digest[0] = pgp_algo_mapping[algo];
+ memcpy(&ima_xattr.digest[1], digest, digest_len);
+
+ ret = lsetxattr(path, XATTR_NAME_IMA, (uint8_t *)&ima_xattr,
+ digest_len + 2, 0);
+ if (ret < 0)
+ rpmlog(RPMLOG_ERR, "digest_list: could not apply security.ima "
+ "on '%s': %s\n", path, strerror(errno));
+ else
+ rpmlog(RPMLOG_DEBUG, "digest_list: security.ima successfully "
+ "applied on '%s'\n", path);
+ return ret;
+}
+
+static int add_evm_digest_list_xattr(const char *path, int algo)
+{
+ struct signature_v2_hdr hdr;
+ int ret;
+
+ hdr.type = EVM_IMA_XATTR_DIGEST_LIST,
+ hdr.version = 2;
+ hdr.hash_algo = pgp_algo_mapping[algo];
+
+ ret = lsetxattr(path, XATTR_NAME_EVM, (uint8_t *)&hdr,
+ offsetof(struct signature_v2_hdr, keyid), 0);
+ if (ret < 0)
+ rpmlog(RPMLOG_ERR, "digest_list: could not apply security.evm "
+ "on '%s': %s\n", path, strerror(errno));
+ else
+ rpmlog(RPMLOG_DEBUG, "digest_list: security.evm successfully "
+ "applied on '%s'\n", path);
+ return ret;
+}
+
+static int add_evm_xattr(char *path, char *path_sig)
+{
+ unsigned char sig[2048];
+ size_t sig_len;
+ struct stat st;
+ int ret, fd;
+
+ if (stat(path_sig, &st) == -1)
+ return -EACCES;
+
+ if (st.st_size > sizeof(sig)) {
+ rpmlog(RPMLOG_ERR, "digest_list: signature in %s too big\n",
+ path);
+ return -ENOMEM;
+ }
+
+ fd = open(path_sig, O_RDONLY);
+ if (fd < 0) {
+ rpmlog(RPMLOG_ERR, "digest_list: could not open '%s': %s\n",
+ path_sig, strerror(errno));
+ return -EACCES;
+ }
+
+ sig_len = read(fd, sig, sizeof(sig));
+ if (sig_len != st.st_size) {
+ rpmlog(RPMLOG_ERR, "digest_list: could not read '%s': %s\n",
+ path_sig, strerror(errno));
+ ret = -EIO;
+ goto out;
+ }
+
+ rpmlog(RPMLOG_DEBUG, "digest_list: read signature of %ld bytes from "
+ "'%s'\n", sig_len, path_sig);
+
+ ret = lsetxattr(path, XATTR_NAME_EVM, sig, sig_len, 0);
+ if (ret < 0)
+ rpmlog(RPMLOG_ERR, "digest_list: could not apply security.evm "
+ "on '%s': %s\n", path, strerror(errno));
+ else
+ rpmlog(RPMLOG_DEBUG, "digest_list: security.evm successfully "
+ "applied on '%s'\n", path);
+out:
+ close(fd);
+ return ret;
+}
+
+static int write_rpm_digest_list(rpmte te, char *path)
+{
+ FD_t fd;
+ ssize_t written;
+ Header rpm = rpmteHeader(te);
+ rpmtd immutable;
+ int ret = 0;
+
+ immutable = rpmtdNew();
+ headerGet(rpm, RPMTAG_HEADERIMMUTABLE, immutable, 0);
+
+ fd = Fopen(path, "w.ufdio");
+ if (fd == NULL || Ferror(fd)) {
+ ret = -EACCES;
+ goto out;
+ }
+
+ written = Fwrite(rpm_header_magic, sizeof(uint8_t),
+ sizeof(rpm_header_magic), fd);
+
+ if (written != sizeof(rpm_header_magic)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ written = Fwrite(immutable->data, sizeof(uint8_t),
+ immutable->count, fd);
+ if (written != immutable->count || Ferror(fd))
+ ret = -EIO;
+out:
+ Fclose(fd);
+ rpmtdFree(immutable);
+ return ret;
+}
+
+static int write_rpm_digest_list_sig(rpmte te, char *rpm_path, char *sig_path)
+{
+ rpmtd signature;
+ ssize_t written;
+ Header rpm = rpmteHeader(te);
+ FD_t fd;
+ pid_t pid;
+ int ret = 0;
+
+ signature = rpmtdNew();
+ headerGet(rpm, RPMTAG_RSAHEADER, signature, 0);
+ if (!signature->count)
+ goto out;
+
+ fd = Fopen(sig_path, "w.ufdio");
+ if (fd == NULL || Ferror(fd)) {
+ ret = -EACCES;
+ goto out;
+ }
+
+ written = Fwrite(signature->data, sizeof(uint8_t),
+ signature->count, fd);
+ if (written != signature->count || Ferror(fd)) {
+ ret = -EIO;
+ Fclose(fd);
+ goto out_unlink;
+ }
+
+ Fclose(fd);
+
+ if ((pid = fork()) == 0) {
+ execlp(WRITE_RPM_PGP_SIG, WRITE_RPM_PGP_SIG,
+ rpm_path, sig_path, NULL);
+ _exit(EXIT_FAILURE);
+ }
+
+ waitpid(pid, &ret, 0);
+ if (ret != 0)
+ rpmlog(RPMLOG_ERR, "digest_list: %s returned %d\n",
+ WRITE_RPM_PGP_SIG, ret);
+out_unlink:
+ unlink(sig_path);
+out:
+ rpmtdFree(signature);
+ return ret;
+}
+
+static int process_digest_list(rpmte te, int parser)
+{
+ char *path = NULL, *path_sig = NULL;
+ int digest_list_signed = 0;
+ struct stat st;
+ ssize_t size;
+ rpmRC ret = RPMRC_OK;
+
+ path = malloc(PATH_MAX);
+ if (!path) {
+ ret = RPMRC_FAIL;
+ goto out;
+ }
+
+ path_sig = malloc(PATH_MAX);
+ if (!path_sig) {
+ ret = RPMRC_FAIL;
+ goto out;
+ }
+
+ if (parser)
+ snprintf(path_sig, PATH_MAX,
+ "%s.sig/0-parser_list-compact-libexec.sig",
+ DIGEST_LIST_DEFAULT_PATH);
+ else
+ snprintf(path_sig, PATH_MAX,
+ "%s.sig/0-metadata_list-compact-%s-%s-%s.%s.sig",
+ DIGEST_LIST_DEFAULT_PATH, rpmteN(te), rpmteV(te),
+ rpmteR(te), rpmteA(te));
+
+ if (!stat(path_sig, &st)) {
+ digest_list_signed = 1;
+ } else {
+ if (stat(WRITE_RPM_PGP_SIG, &st) == -1 ||
+ stat(RPM_PARSER, &st) == -1) {
+ rpmlog(RPMLOG_DEBUG, "digest_list: "
+ "digest-list-tools not installed\n");
+ goto out;
+ }
+ }
+
+ if (parser)
+ snprintf(path, PATH_MAX, "%s/0-parser_list-compact-libexec",
+ DIGEST_LIST_DEFAULT_PATH);
+ else
+ snprintf(path, PATH_MAX,
+ "%s/0-metadata_list-compact-%s-%s-%s.%s",
+ DIGEST_LIST_DEFAULT_PATH, rpmteN(te), rpmteV(te),
+ rpmteR(te), rpmteA(te));
+
+ if (stat(path, &st) == -1)
+ goto out;
+
+ if (!parser && !digest_list_signed) {
+ snprintf(path, PATH_MAX, "%s/0-metadata_list-rpm-%s-%s-%s.%s",
+ DIGEST_LIST_DEFAULT_PATH, rpmteN(te), rpmteV(te),
+ rpmteR(te), rpmteA(te));
+
+ /* RPM digest lists don't have security.evm */
+ size = lgetxattr(path, XATTR_NAME_IMA, NULL, 0);
+ } else {
+ size = lgetxattr(path, XATTR_NAME_EVM, NULL, 0);
+ }
+
+ /* Don't upload again if digest list was already processed */
+ if ((rpmteType(te) == TR_ADDED && size > 0) ||
+ (rpmteType(te) == TR_REMOVED && size < 0)) {
+ rpmlog(RPMLOG_DEBUG, "digest_list: '%s' already processed, "
+ "nothing to do\n", path);
+ goto out;
+ }
+
+ if (rpmteType(te) == TR_ADDED) {
+ if (!digest_list_signed) {
+ /* Write RPM header to the disk */
+ ret = write_rpm_digest_list(te, path);
+ if (ret < 0) {
+ ret = RPMRC_FAIL;
+ goto out;
+ }
+
+ /* Write RPM header sig to security.ima */
+ ret = write_rpm_digest_list_sig(te, path, path_sig);
+ if (ret < 0) {
+ ret = RPMRC_FAIL;
+ goto out;
+ }
+ } else {
+ add_evm_xattr(path, path_sig);
+ }
+ }
+
+ /* Upload digest list to securityfs */
+ upload_digest_list(path, rpmteType(te), digest_list_signed);
+
+ if (rpmteType(te) == TR_REMOVED) {
+ if (!digest_list_signed) {
+ unlink(path);
+ goto out;
+ }
+
+ ret = lremovexattr(path, XATTR_NAME_EVM);
+ if (ret < 0)
+ rpmlog(RPMLOG_ERR, "digest_list: cannot remove "
+ "security.evm from '%s'\n", path);
+ else
+ rpmlog(RPMLOG_DEBUG, "digest_list: security.evm "
+ "successfully removed from '%s'\n", path);
+ }
+out:
+ free(path);
+ free(path_sig);
+ return ret;
+}
+
+static rpmRC digest_list_psm_pre(rpmPlugin plugin, rpmte te)
+{
+ struct stat st;
+
+ if (disable_plugin)
+ return RPMRC_OK;
+
+ if (stat(DIGEST_LIST_DATA_PATH, &st) == -1) {
+ rpmlog(RPMLOG_DEBUG, "digest_list: IMA interface '%s' not "
+ "found, disabling plugin\n", DIGEST_LIST_DATA_PATH);
+ disable_plugin = 1;
+ return RPMRC_OK;
+ }
+
+ process_digest_list(te, 0);
+ if (!strcmp(rpmteN(te), "digest-list-tools"))
+ process_digest_list(te, 1);
+
+ return RPMRC_OK;
+}
+
+static rpmRC digest_list_psm_post(rpmPlugin plugin, rpmte te, int res)
+{
+ if (disable_plugin)
+ return RPMRC_OK;
+
+ if (res != RPMRC_OK)
+ return RPMRC_OK;
+
+ process_digest_list(te, 0);
+ if (!strcmp(rpmteN(te), "digest-list-tools"))
+ process_digest_list(te, 1);
+
+ return RPMRC_OK;
+}
+
+static rpmRC digest_list_fsm_file_prepare(rpmPlugin plugin, rpmfi fi,
+ const char *path,
+ const char *dest,
+ mode_t file_mode, rpmFsmOp op)
+{
+ const unsigned char *fdigest = NULL;
+ size_t len;
+ int algo;
+ rpmFileAction action = XFO_ACTION(op);
+
+ if (disable_plugin)
+ return RPMRC_OK;
+
+ /* Ignore skipped files and unowned directories */
+ if (XFA_SKIPPING(action) || (op & FAF_UNOWNED))
+ goto exit;
+
+ /* Ignore non-regular files */
+ if (!S_ISREG(file_mode))
+ goto exit;
+
+ fdigest = rpmfiFDigest(fi, &algo, &len);
+ if (!fdigest)
+ goto exit;
+
+ /* Assume that the hash algorithm used by evmctl and RPMs is the same */
+ add_ima_xattr(path, algo, fdigest, len);
+ if (strncmp(path, DIGEST_LIST_DEFAULT_PATH,
+ sizeof(DIGEST_LIST_DEFAULT_PATH) - 1))
+ add_evm_digest_list_xattr(path, algo);
+exit:
+ return RPMRC_OK;
+}
+
+struct rpmPluginHooks_s digest_list_hooks = {
+ .psm_pre = digest_list_psm_pre,
+ .psm_post = digest_list_psm_post,
+ .fsm_file_prepare = digest_list_fsm_file_prepare,
+};
--
2.27.GIT