From fa0b33ce1ff569ab55b46cdbcc47f2da6db3fb1a Mon Sep 17 00:00:00 2001 From: Roberto Sassu 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 +#include +#include +#include +#include "lib/rpmplugin.h" +#include +#include +#include +#include +#include +#include + +#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