Projects
Mega:24.09
rpm
_service:tar_scm:Add-digest-list-plugin.patch
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:Add-digest-list-plugin.patch of Package rpm
From 3b2fb7d5a40d25c3295e02eb3695a45189342369 Mon Sep 17 00:00:00 2001 From: zhoushuiqing <zhoushuiqing2@huawei.com> Date: Fri, 16 Jun 2023 11:21:37 +0800 Subject: [PATCH] Add-digest-list-plugin Signed-off-by: Huaxin Lu <luhuaxin1@huawei.com> --- plugins/digest_list.c | 680 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 680 insertions(+) create mode 100644 plugins/digest_list.c diff --git a/plugins/digest_list.c b/plugins/digest_list.c new file mode 100644 index 0000000..715b8d6 --- /dev/null +++ b/plugins/digest_list.c @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2020-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu <roberto.sassu@huawei.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: digest_list.c + * Plugin to load digest lists in the Linux kernel. + */ + +#include "system.h" +#include "errno.h" + +#include <fcntl.h> +#include <rpm/rpmlog.h> +#include <rpm/rpmts.h> +#include <rpm/header.h> +#include <rpm/rpmpgp.h> +#include "rpmio/rpmpgp_internal.h" +#include <rpm/rpmfileutil.h> +#include "lib/rpmplugin.h" +#include <netinet/in.h> +#include <sys/stat.h> +#include <openssl/sha.h> +#include <sys/xattr.h> +#include <sys/capability.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" + +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 +}; + +#define PGPHASHALGO__LAST PGPHASHALGO_SHA224 + 1 +enum hash_algo pgp_algo_mapping[PGPHASHALGO__LAST] = { + [PGPHASHALGO_MD5] = HASH_ALGO_MD5, + [PGPHASHALGO_SHA1] = HASH_ALGO_SHA1, + [PGPHASHALGO_SHA224] = HASH_ALGO_SHA224, + [PGPHASHALGO_SHA256] = HASH_ALGO_SHA256, + [PGPHASHALGO_SHA384] = HASH_ALGO_SHA384, + [PGPHASHALGO_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 digest_list_count_is_zero(void) +{ + int fd = 0, ret = 0; + char first = 0; + + 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, &first, 1); + if (ret <= 0) { + rpmlog(RPMLOG_ERR, "digest_list: could not read from IMA " + "interface '%s': %s\n", DIGEST_LIST_COUNT, + strerror(errno)); + close(fd); + return -EACCES; + } + + close(fd); + return (first == '0'); +} + +static int upload_digest_list(char *path, int type, int digest_list_signed) +{ + int ret = 0, fd = 0; + pid_t pid = 0; + size_t size = 0; + struct stat st; + const char *ima_path = NULL; + + ima_path = (type == TR_REMOVED) ? DIGEST_LIST_DATA_DEL_PATH : + DIGEST_LIST_DATA_PATH; + if (stat(ima_path, &st) == -1) { + rpmlog(RPMLOG_DEBUG, "digest_list: '%s' interface " + "not exist\n", ima_path); + return RPMRC_OK; + } + + /* First determine if kernel interface can accept new digest lists */ + if (digest_list_count_is_zero()) { + rpmlog(RPMLOG_DEBUG, "digest_list: the count is 0, not " + "upload '%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 (stat(RPM_PARSER, &st) == -1) { + rpmlog(RPMLOG_DEBUG, "digest_list: %s not found, " + "not uploading digest list\n", RPM_PARSER); + return RPMRC_OK; + } + + 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 RPMRC_OK; + } + + /* If the digest list is signed, write path to the IMA interface */ + fd = open(ima_path, O_WRONLY); + if (fd < 0) { + rpmlog(RPMLOG_ERR, "digest_list: rcould 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 write_rpm_digest_list(rpmte te, char *path) +{ + FD_t fd; + int ret = 0; + ssize_t written = 0; + Header rpm = rpmteHeader(te); + rpmtd 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_ima_xattr(rpmte te, char *path) +{ + FD_t fd; + ssize_t written = 0; + int ret = 0, sig_size = 0, sig_size_rounded = 0; + uint8_t sig[2048] = { 0 }; + pgpDigParams sigp = NULL; + struct signature_v2_hdr *sig_hdr = (struct signature_v2_hdr *)sig; + Header rpm = rpmteHeader(te); + rpmtd signature = rpmtdNew(); + + headerGet(rpm, RPMTAG_RSAHEADER, signature, 0); + ret = pgpPrtParams(signature->data, signature->count, + PGPTAG_SIGNATURE, &sigp); + if (ret) { + ret = -ENOENT; + goto out; + } + + fd = Fopen(path, "a.ufdio"); + if (fd == NULL || Ferror(fd)) { + ret = -EACCES; + goto out; + } + + written = Fwrite(sigp->hash, sizeof(uint8_t), + sigp->hashlen, fd); + if (written != sigp->hashlen || Ferror(fd)) { + ret = -EIO; + goto out; + } + + if (sigp->version == 4) { + /* V4 trailer is six octets long (rfc4880) */ + uint8_t trailer[6]; + uint32_t nb = sigp->hashlen; + nb = htonl(nb); + trailer[0] = sigp->version; + trailer[1] = 0xff; + memcpy(trailer+2, &nb, 4); + + written = Fwrite(trailer, sizeof(uint8_t), sizeof(trailer), fd); + if (written != sizeof(trailer) || Ferror(fd)) { + ret = -EIO; + goto out; + } + } + + Fclose(fd); + + sig_hdr->type = EVM_IMA_XATTR_DIGSIG; + sig_hdr->version = 2; + sig_hdr->hash_algo = pgp_algo_mapping[sigp->hash_algo]; + memcpy((void *)&sig_hdr->keyid, sigp->signid + sizeof(uint32_t), + sizeof(uint32_t)); + + sig_size = (pgpMpiBits(sigp->data) + 7) >> 3; + if (sizeof(sig_hdr) + sig_size > sizeof(sig)) { + rpmlog(RPMLOG_ERR, + "digest_list: signature in %s too big\n", path); + ret = -E2BIG; + goto out; + } + + sig_size_rounded = ((sig_size + 7) >> 3) * 8; + sig_hdr->sig_size = __cpu_to_be16(sig_size_rounded); + + memcpy(sig_hdr->sig + sig_size_rounded - sig_size, + (uint8_t *)sigp->data + 2, sig_size); + + ret = lsetxattr(path, XATTR_NAME_IMA, + sig, sizeof(*sig_hdr) + sig_size_rounded, 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); +out: + pgpDigParamsFree(sigp); + rpmtdFree(signature); + return ret; +} + +static int fill_pgp_signature_header(rpmte te, struct signature_v2_hdr *sig_hdr) +{ + int ret = 0; + pgpDigParams sigp = NULL; + Header rpm = rpmteHeader(te); + rpmtd signature = rpmtdNew(); + + headerGet(rpm, RPMTAG_RSAHEADER, signature, 0); + ret = pgpPrtParams(signature->data, signature->count, + PGPTAG_SIGNATURE, &sigp); + if (ret) { + ret = -ENOENT; + goto out; + } + + sig_hdr->type = EVM_IMA_XATTR_DIGSIG; + sig_hdr->version = 2; + sig_hdr->hash_algo = HASH_ALGO_SHA256; + memcpy((void *)&sig_hdr->keyid, sigp->signid + sizeof(uint32_t), + sizeof(uint32_t)); +out: + pgpDigParamsFree(sigp); + rpmtdFree(signature); + return ret; +} + +static int write_digest_list_ima_xattr(rpmte te, char *path, char *path_sig) +{ + FD_t fd; + struct stat st; + int ret = 0, sig_size, hdr_exist; + uint8_t sig[2048] = { 0 }; + struct signature_v2_hdr *sig_hdr = (struct signature_v2_hdr *)sig; + + if (stat(path_sig, &st) == -1) + return -EACCES; + + /* Check if the signature has already included a header */ + hdr_exist = st.st_size % 128 == 0 ? 0 : 1; + if (!hdr_exist) { + ret = fill_pgp_signature_header(te, sig_hdr); + if (ret < 0) + return ret; + } + + if (sizeof(sig_hdr) + st.st_size > sizeof(sig)) { + rpmlog(RPMLOG_ERR, "digest_list: signature in %s too big\n", + path); + return -E2BIG; + } + + fd = Fopen(path_sig, "r.ufdio"); + if (fd < 0) { + rpmlog(RPMLOG_ERR, "digest_list: could not open '%s': %s\n", + path_sig, strerror(errno)); + return -EACCES; + } + + sig_size = Fread(sig_hdr->sig, sizeof(uint8_t), st.st_size, fd); + if (sig_size != st.st_size || Ferror(fd)) { + rpmlog(RPMLOG_ERR, "digest_list: could not read '%s': %s\n", + path_sig, strerror(errno)); + Fclose(fd); + return -EIO; + } + + sig_hdr->sig_size = __cpu_to_be16(sig_size); + Fclose(fd); + rpmlog(RPMLOG_DEBUG, + "digest_list: read signature of %d bytes from '%s'\n", + sig_size, path_sig); + + /* The signature may include the header */ + if (hdr_exist) + ret = lsetxattr(path, XATTR_NAME_IMA, sig_hdr->sig, sig_size, 0); + else + ret = lsetxattr(path, XATTR_NAME_IMA, sig, sizeof(*sig_hdr) + sig_size, 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 check_append_signature(const char *path) +{ + const char *magic_str="~Module signature appended~"; + int magic_len = strlen(magic_str); + char buf[magic_len + 1]; + FILE *fp = NULL; + struct stat st; + int file_size = 0; + int ret = 0; + long offset = 0; + + if (stat(path, &st) == -1) + return 0; + + file_size = st.st_size; + + /* the character \0xa is append to MAGIC */ + offset = magic_len + 1; + if (file_size < offset) { + rpmlog(RPMLOG_ERR, "digest_list: not have sig, do nothing\n"); + return 0; + } + + fp = fopen(path, "rb+"); + if (!fp) { + rpmlog(RPMLOG_ERR, "digest_list: could not open '%s': %s\n", path, strerror(errno)); + return 0; + } + + ret = fseek(fp, (-offset), SEEK_END); + if (ret) { + rpmlog(RPMLOG_ERR, "digest_list: seek file fail with %s\n", strerror(errno)); + fclose(fp); + return 0; + } + + ret = fread(buf, 1, magic_len, fp); + if (ret == magic_len) { + if (strncmp(buf, magic_str, magic_len) == 0) { + fclose(fp); + return 1; + } + } + + fclose(fp); + return 0; +} + +static int process_digest_list(rpmte te, int parser, int pre) +{ + char *path = NULL, *path_sig = NULL; + int digest_list_signed = 0; + int digest_list_signed_append = 0; + struct stat st; + ssize_t size; + int type = rpmteType(te); + struct __user_cap_header_struct cap_header_data; + cap_user_header_t cap_header = &cap_header_data; + struct __user_cap_data_struct cap_data_data; + cap_user_data_t cap_data = &cap_data_data; + 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; + rpmlog(RPMLOG_DEBUG, "digest_list: digest_list_signed = 1\n"); + } else { + rpmlog(RPMLOG_DEBUG, "digest_list: digest_list_signed = 0\n"); + } + + if (parser && !digest_list_signed) { + rpmlog(RPMLOG_DEBUG, "digest_list: parser has to be signed!"); + 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) { + rpmlog(RPMLOG_DEBUG, "digest_list: failed to find digest list file path!"); + goto out; + } + + if (!digest_list_signed && check_append_signature(path)) { + digest_list_signed = 1; + digest_list_signed_append = 1; + } + + 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)); + + size = lgetxattr(path, XATTR_NAME_IMA, NULL, 0); + + if (type == TR_ADDED && !pre && size < 0) { + 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; + } + } + + /* don't call lsetxattr without CAP_SYS_ADMIN */ + cap_header->pid = getpid(); + cap_header->version = _LINUX_CAPABILITY_VERSION_1; + if (capget(cap_header, cap_data) < 0) { + ret = -ENOENT; + goto out; + } + if (!(cap_data->effective & CAP_TO_MASK(CAP_SYS_ADMIN))) { + ret = -EPERM; + goto out; + } + + if (!digest_list_signed) { + /* Write RPM header sig to security.ima */ + ret = write_rpm_digest_list_ima_xattr(te, path); + } else if (digest_list_signed_append) { + ret = RPMRC_OK; + } else { + ret = write_digest_list_ima_xattr(te, path, path_sig); + } + + if (ret < 0) { + ret = RPMRC_FAIL; + goto out; + } + } else if (type == TR_ADDED && pre) { + if (size < 0) + goto out; + + /* rpm is overwriting the digest list, remove from the kernel */ + type = TR_REMOVED; + } + + /* Upload digest list to securityfs */ + upload_digest_list(path, type, digest_list_signed); + + if (type == TR_REMOVED) { + if (!digest_list_signed) { + unlink(path); + goto out; + } + } +out: + free(path); + free(path_sig); + return ret; +} + +rpmte cur_te; +int digest_list_counter; + +static rpmRC digest_list_psm_pre(rpmPlugin plugin, rpmte te) +{ + Header rpm = rpmteHeader(te); + rpmtd dirnames, dirindexes; + int i = -1; + + digest_list_counter = 0; + + dirnames = rpmtdNew(); + headerGet(rpm, RPMTAG_DIRNAMES, dirnames, 0); + + while ((i = rpmtdNext(dirnames)) >= 0) { + char *dirname = (char *) rpmtdGetString(dirnames); + + if (!strncmp(dirname, DIGEST_LIST_DEFAULT_PATH, + sizeof(DIGEST_LIST_DEFAULT_PATH) - 1) && + dirname[sizeof(DIGEST_LIST_DEFAULT_PATH) - 1] == '/') + break; + } + + rpmtdFree(dirnames); + + if (i == -1) + return RPMRC_OK; + + dirindexes = rpmtdNew(); + headerGet(rpm, RPMTAG_DIRINDEXES, dirindexes, 0); + while (rpmtdNext(dirindexes) >= 0) + if (rpmtdGetNumber(dirindexes) == i) + digest_list_counter++; + + rpmtdFree(dirindexes); + + cur_te = te; + return RPMRC_OK; +} + +static rpmRC digest_list_file_common(rpmPlugin plugin, rpmfi fi, + const char* path, mode_t file_mode, + rpmFsmOp op, int pre, int res) +{ + rpmFileAction action = XFO_ACTION(op); + + if (!digest_list_counter) + return RPMRC_OK; + + if (!cur_te) + return RPMRC_OK; + + if (!pre && res != RPMRC_OK) + return res; + + if (!pre && rpmteType(cur_te) != TR_ADDED) + return RPMRC_OK; + + if (pre && action == FA_SKIP) + return RPMRC_OK; + + if (path == NULL || strncmp(path, DIGEST_LIST_DEFAULT_PATH, + sizeof(DIGEST_LIST_DEFAULT_PATH) - 1) || + path[sizeof(DIGEST_LIST_DEFAULT_PATH) - 1] != '/') + return RPMRC_OK; + + if (!pre && --digest_list_counter) + return RPMRC_OK; + + rpmlog(RPMLOG_DEBUG, "process ima digest, pre: %d, action: %d, teType: %d\n", + pre, action, rpmteType(cur_te)); + process_digest_list(cur_te, 0, pre); + if (!strcmp(rpmteN(cur_te), "digest-list-tools")) { + if (pre && rpmteType(cur_te) == TR_REMOVED) + return RPMRC_OK; + + rpmlog(RPMLOG_DEBUG, "process parser digest\n"); + process_digest_list(cur_te, 1, pre); + } + + return RPMRC_OK; +} + +static rpmRC digest_list_file_pre(rpmPlugin plugin, rpmfi fi, + const char* path, mode_t file_mode, + rpmFsmOp op) +{ + return digest_list_file_common(plugin, fi, path, file_mode, op, 1, 0); +} + +static rpmRC digest_list_file_post(rpmPlugin plugin, rpmfi fi, + const char* path, mode_t file_mode, + rpmFsmOp op, int res) +{ + return digest_list_file_common(plugin, fi, path, file_mode, op, 0, res); +} + +struct rpmPluginHooks_s digest_list_hooks = { + .psm_pre = digest_list_psm_pre, + .fsm_file_pre = digest_list_file_pre, + .fsm_file_post = digest_list_file_post, +}; -- 2.46.0
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2