From 4006d48a5492749cc09fdf42d640a8be59be32eb Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Thu, 12 Mar 2020 17:29:55 +0100 Subject: [PATCH] Generate digest lists --- build/files.c | 296 ++++++++++++++++++- build/parsePreamble.c | 2 +- macros.in | 1 + plugins/Makefile.am | 4 + plugins/digest_list.c | 611 ++++++++++++++++++++++++++++++++++++++++ rpmio/rpmio_internal.h | 31 ++ rpmio/rpmpgp_internal.c | 33 +-- 7 files changed, 938 insertions(+), 40 deletions(-) create mode 100644 plugins/digest_list.c diff --git a/build/files.c b/build/files.c index 5ae20c6..59c003a 100644 --- a/build/files.c +++ b/build/files.c @@ -50,6 +50,8 @@ #define DEBUG_LIB_PREFIX "/usr/lib/debug/" #define DEBUG_ID_DIR "/usr/lib/debug/.build-id" #define DEBUG_DWZ_DIR "/usr/lib/debug/.dwz" +#define DIGEST_LIST_DIR "/.digest_lists" +#define DEST_DIGEST_LIST_DIR "/etc/ima/digest_lists" #undef HASHTYPE #undef HTKEYTYPE @@ -129,6 +131,8 @@ typedef struct AttrRec_s { /* list of files */ static StringBuf check_fileList = NULL; +/* list of files per binary package */ +static StringBuf check_fileList_bin_pkg = NULL; typedef struct FileEntry_s { rpmfileAttrs attrFlags; @@ -193,6 +197,10 @@ typedef struct FileList_s { struct FileEntry_s cur; } * FileList; +static char *digest_list_dir; + +static int genDigestList(Header header, FileList fl, StringBuf fileList); + static void nullAttrRec(AttrRec ar) { memset(ar, 0, sizeof(*ar)); @@ -984,6 +992,132 @@ static int seenHardLink(FileRecords files, FileListRec flp, rpm_ino_t *fileid) return 0; } + +static void genDigestListInput(FileList fl, Package pkg, int isSrc) +{ + FileListRec flp; + char buf[BUFSIZ]; + char file_info[BUFSIZ]; + char file_digest[128 * 2 + 1]; + int i, gen_digest_lists = 1; + uint32_t defaultalgo = PGPHASHALGO_MD5, digestalgo; + Header h = pkg->header; /* just a shortcut */ + + /* + * See if non-md5 file digest algorithm is requested. If not + * specified, quietly assume md5. Otherwise check if supported type. + */ + digestalgo = rpmExpandNumeric(isSrc ? "%{_source_filedigest_algorithm}" : + "%{_binary_filedigest_algorithm}"); + if (digestalgo == 0) { + digestalgo = defaultalgo; + } + + if (rpmDigestLength(digestalgo) == 0) { + rpmlog(RPMLOG_WARNING, + _("Unknown file digest algorithm %u, falling back to MD5\n"), + digestalgo); + digestalgo = defaultalgo; + } + + /* Sort the big list */ + if (fl->files.recs) { + qsort(fl->files.recs, fl->files.used, + sizeof(*(fl->files.recs)), compareFileListRecs); + } + + /* Generate the header. */ + for (i = 0, flp = fl->files.recs; i < fl->files.used; i++, flp++) { + /* Merge duplicate entries. */ + while (i < (fl->files.used - 1) && + rstreq(flp->cpioPath, flp[1].cpioPath)) { + + /* Two entries for the same file found, merge the entries. */ + /* Note that an %exclude is a duplication of a file reference */ + + /* file flags */ + flp[1].flags |= flp->flags; + + if (!(flp[1].flags & RPMFILE_EXCLUDE)) + rpmlog(RPMLOG_WARNING, _("File listed twice: %s\n"), + flp->cpioPath); + + /* file mode */ + if (S_ISDIR(flp->fl_mode)) { + if ((flp[1].specdFlags & (SPECD_DIRMODE | SPECD_DEFDIRMODE)) < + (flp->specdFlags & (SPECD_DIRMODE | SPECD_DEFDIRMODE))) + flp[1].fl_mode = flp->fl_mode; + } else { + if ((flp[1].specdFlags & (SPECD_FILEMODE | SPECD_DEFFILEMODE)) < + (flp->specdFlags & (SPECD_FILEMODE | SPECD_DEFFILEMODE))) + flp[1].fl_mode = flp->fl_mode; + } + + /* uid */ + if ((flp[1].specdFlags & (SPECD_UID | SPECD_DEFUID)) < + (flp->specdFlags & (SPECD_UID | SPECD_DEFUID))) + { + flp[1].fl_uid = flp->fl_uid; + flp[1].uname = flp->uname; + } + + /* gid */ + if ((flp[1].specdFlags & (SPECD_GID | SPECD_DEFGID)) < + (flp->specdFlags & (SPECD_GID | SPECD_DEFGID))) + { + flp[1].fl_gid = flp->fl_gid; + flp[1].gname = flp->gname; + } + + /* verify flags */ + if ((flp[1].specdFlags & (SPECD_VERIFY | SPECD_DEFVERIFY)) < + (flp->specdFlags & (SPECD_VERIFY | SPECD_DEFVERIFY))) + flp[1].verifyFlags = flp->verifyFlags; + + /* XXX to-do: language */ + + flp++; i++; + } + + /* Skip files that were marked with %exclude. */ + if (flp->flags & RPMFILE_EXCLUDE) + { + argvAdd(&pkg->fileExcludeList, flp->cpioPath); + continue; + } + + buf[0] = '\0'; + if (S_ISREG(flp->fl_mode) && !(flp->flags & RPMFILE_GHOST)) + (void) rpmDoDigest(digestalgo, flp->diskPath, 1, + (unsigned char *)buf); + headerPutString(h, RPMTAG_FILEDIGESTS, buf); + snprintf(file_digest, sizeof(file_digest), "%s", buf); + + if (check_fileList_bin_pkg && S_ISREG(flp->fl_mode) && + !(flp->flags & RPMFILE_GHOST)) { + appendStringBuf(check_fileList_bin_pkg, "path="); + appendStringBuf(check_fileList_bin_pkg, flp->diskPath); + snprintf(file_info, sizeof(file_info), + "|digestalgopgp=%d|digest=%s|mode=%d" + "|uname=%s|gname=%s|caps=%s\n", + digestalgo, file_digest, flp->fl_mode, + rpmstrPoolStr(fl->pool, flp->uname), + rpmstrPoolStr(fl->pool, flp->gname), flp->caps && + strlen(flp->caps) ? flp->caps : ""); + appendStringBuf(check_fileList_bin_pkg, file_info); + } + + if (S_ISREG(flp->fl_mode) && + !strncmp(flp->cpioPath, DEST_DIGEST_LIST_DIR, + sizeof(DEST_DIGEST_LIST_DIR) - 1)) + gen_digest_lists = 0; + } + + if (gen_digest_lists && + genDigestList(pkg->header, fl, check_fileList_bin_pkg) > 0) + fl->processingFailed = 1; +} + /** * Add file entries to header. * @todo Should directories have %doc/%config attributes? (#14531) @@ -996,6 +1130,8 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc) { FileListRec flp; char buf[BUFSIZ]; + char file_info[BUFSIZ]; + char file_digest[128 * 2 + 1]; int i, npaths = 0; int fail_on_dupes = rpmExpandNumeric("%{?_duplicate_files_terminate_build}") > 0; uint32_t defaultalgo = RPM_HASH_MD5, digestalgo; @@ -1003,7 +1139,11 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc) Header h = pkg->header; /* just a shortcut */ time_t source_date_epoch = 0; char *srcdate = getenv("SOURCE_DATE_EPOCH"); + struct rpmtd_s oldfiledigests; + headerGet(h, RPMTAG_FILEDIGESTS, &oldfiledigests, HEADERGET_ALLOC); + headerDel(h, RPMTAG_FILEDIGESTS); + rpmtdInit(&oldfiledigests); /* Limit the maximum date to SOURCE_DATE_EPOCH if defined * similar to the tar --clamp-mtime option * https://reproducible-builds.org/specs/source-date-epoch/ @@ -1200,13 +1340,18 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc) if (fl->haveCaps) { headerPutString(h, RPMTAG_FILECAPS, flp->caps); } - + buf[0] = '\0'; - if (S_ISREG(flp->fl_mode) && !(flp->flags & RPMFILE_GHOST)) - (void) rpmDoDigest(digestalgo, flp->diskPath, 1, - (unsigned char *)buf); - headerPutString(h, RPMTAG_FILEDIGESTS, buf); - + if (strstr(flp->diskPath, DIGEST_LIST_DIR) || !oldfiledigests.count) { + if (S_ISREG(flp->fl_mode) && !(flp->flags & RPMFILE_GHOST)) + (void) rpmDoDigest(digestalgo, flp->diskPath, 1, + (unsigned char *)buf); + headerPutString(h, RPMTAG_FILEDIGESTS, buf); + } else { + headerPutString(h, RPMTAG_FILEDIGESTS, + rpmtdNextString(&oldfiledigests)); + } + buf[0] = '\0'; if (S_ISLNK(flp->fl_mode)) { ssize_t llen = readlink(flp->diskPath, buf, BUFSIZ-1); @@ -1247,6 +1392,7 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc) headerPutUint32(h, RPMTAG_FILEFLAGS, &(flp->flags) ,1); } + pkg->dpaths[npaths] = NULL; if (totalFileSize < UINT32_MAX) { @@ -1285,6 +1431,7 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc) /* Binary packages with dirNames cannot be installed by legacy rpm. */ (void) rpmlibNeedsFeature(pkg, "CompressedFileNames", "3.0.4-1"); } + rpmtdFreeData(&oldfiledigests); } static FileRecords FileRecordsFree(FileRecords files) @@ -1359,8 +1506,8 @@ static int validFilename(const char *fn) * @param statp file stat (possibly NULL) * @return RPMRC_OK on success */ -static rpmRC addFile(FileList fl, const char * diskPath, - struct stat * statp) +static rpmRC addFile_common(FileList fl, const char * diskPath, + struct stat * statp, int digest_list) { size_t plen = strlen(diskPath); char buf[plen + 1]; @@ -1371,6 +1518,10 @@ static rpmRC addFile(FileList fl, const char * diskPath, gid_t fileGid; const char *fileUname; const char *fileGname; + char realPath[PATH_MAX]; + int digest_list_prefix = 0; + struct stat st; + int exclude = 0; rpmRC rc = RPMRC_FAIL; /* assume failure */ /* Strip trailing slash. The special case of '/' path is handled below. */ @@ -1406,6 +1557,33 @@ static rpmRC addFile(FileList fl, const char * diskPath, if (*cpioPath == '\0') cpioPath = "/"; + snprintf(realPath, sizeof(realPath), "%s", diskPath); + rpmCleanPath(realPath); + + digest_list_prefix = (!strncmp(realPath, digest_list_dir, + strlen(digest_list_dir))); + + if ((!digest_list && digest_list_prefix) || + (digest_list && !digest_list_prefix)) { + rc = RPMRC_OK; + goto exit; + } + + if (digest_list) { + if (strncmp(cpioPath, DIGEST_LIST_DIR, sizeof(DIGEST_LIST_DIR) - 1)) { + rc = RPMRC_OK; + goto exit; + } + + cpioPath += sizeof(DIGEST_LIST_DIR) - 1; + + snprintf(realPath, sizeof(realPath), "%.*s%s", + (int)(strlen(digest_list_dir) - sizeof(DIGEST_LIST_DIR) + 1), + digest_list_dir, cpioPath); + if (!stat(realPath, &st)) + exclude = 1; + } + /* * Unless recursing, we dont have stat() info at hand. Handle the * various cases, preserving historical behavior wrt %dev(): @@ -1543,6 +1721,8 @@ static rpmRC addFile(FileList fl, const char * diskPath, } flp->flags = fl->cur.attrFlags; + if (exclude) + flp->flags |= RPMFILE_EXCLUDE; flp->specdFlags = fl->cur.specdFlags; flp->verifyFlags = fl->cur.verifyFlags; @@ -1563,6 +1743,32 @@ exit: return rc; } +/** + * Add a file to the package manifest. + * @param fl package file tree walk data + * @param diskPath path to file + * @param statp file stat (possibly NULL) + * @return RPMRC_OK on success + */ +static rpmRC addFile(FileList fl, const char * diskPath, + struct stat * statp) +{ + return addFile_common(fl, diskPath, statp, 0); +} + +/** + * Add a digest list to the package manifest. + * @param fl package file tree walk data + * @param diskPath path to digest list + * @param statp file stat (possibly NULL) + * @return RPMRC_OK on success + */ +static rpmRC addDigestList(FileList fl, const char * diskPath, + struct stat * statp) +{ + return addFile_common(fl, diskPath, statp, 1); +} + /** * Add directory (and all of its files) to the package manifest. * @param fl package file tree walk data @@ -2584,6 +2790,58 @@ static void addPackageFileList (struct FileList_s *fl, Package pkg, argvFree(fileNames); } +/** + * Generate digest lists list for current binary package. + * @header package header + * @fl file list + * @param fileList packaged file list + * @return -1 if skipped, 0 on OK, 1 on error + */ +static int genDigestList(Header header, FileList fl, StringBuf fileList) +{ + const char *errorString; + char *binFormat = rpmGetPath("%{_rpmfilename}", NULL); + char *binRpm = headerFormat(header, binFormat, &errorString); + static char * av_brp[] = { "%{?__brp_digest_list}", DIGEST_LIST_DIR + 1, NULL, NULL }; + StringBuf sb_stdout = NULL; + int rc = -1; + char * s = rpmExpand(av_brp[0], NULL); + + if (!(s && *s)) + goto exit; + + av_brp[2] = strchr(binRpm, '/') + 1; + rpmlog(RPMLOG_NOTICE, _("Generating digest list: %s\n"), s); + + rc = rpmfcExec(av_brp, fileList, &sb_stdout, 0, binRpm); + if (sb_stdout && getStringBuf(sb_stdout)) { + const char * t = getStringBuf(sb_stdout), *ptr; + char *digest_list_path; + + while((ptr = strchr(t, '\n'))) { + digest_list_path = strndup(t, ptr - t); + if (!digest_list_path) { + rc = -1; + goto exit; + } + FileEntryFree(&fl->cur); + resetPackageFilesDefaults(fl, fl->pkgFlags); + rc = addDigestList(fl, digest_list_path, NULL); + free(digest_list_path); + if (rc != RPMRC_OK) + break; + + t = ptr + 1; + } + } +exit: + free(binFormat); + free(binRpm); + free(s); + freeStringBuf(sb_stdout); + return rc; +} + static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, Package pkg, int didInstall, int test) { @@ -2597,6 +2855,10 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, if (readFilesManifest(spec, pkg, *fp)) return RPMRC_FAIL; } + + /* Init the buffer containing the list of packaged files */ + check_fileList_bin_pkg = newStringBuf(); + /* Init the file list structure */ memset(&fl, 0, sizeof(fl)); @@ -2652,12 +2914,17 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, if (checkHardLinks(&fl.files)) (void) rpmlibNeedsFeature(pkg, "PartialHardlinkSets", "4.0.4-1"); + genDigestListInput(&fl, pkg, 0); + if (fl.processingFailed) + goto exit; + genCpioListAndHeader(&fl, pkg, 0); exit: FileListFree(&fl); specialDirFree(specialDoc); specialDirFree(specialLic); + freeStringBuf(check_fileList_bin_pkg); return fl.processingFailed ? RPMRC_FAIL : RPMRC_OK; } @@ -3126,6 +3393,7 @@ static void addPackageDeps(Package from, Package to, enum rpmTag_e tag) rpmRC processBinaryFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, int didInstall, int test) { + struct stat st; Package pkg; rpmRC rc = RPMRC_OK; char *buildroot; @@ -3142,7 +3410,14 @@ rpmRC processBinaryFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, check_fileList = newStringBuf(); genSourceRpmName(spec); buildroot = rpmGenPath(spec->rootDir, spec->buildRoot, NULL); - + + digest_list_dir = rpmGenPath(buildroot, DIGEST_LIST_DIR, NULL); + if (!digest_list_dir) + goto exit; + + if (!stat(digest_list_dir, &st)) + rpmlog(RPMLOG_NOTICE, _("Ignoring files in: %s\n"), digest_list_dir); + if (rpmExpandNumeric("%{?_debuginfo_subpackages}")) { maindbg = findDebuginfoPackage(spec); if (maindbg) { @@ -3248,6 +3523,7 @@ exit: check_fileList = freeStringBuf(check_fileList); _free(buildroot); _free(uniquearch); - + _free(digest_list_dir); + return rc; } diff --git a/build/parsePreamble.c b/build/parsePreamble.c index 722d0ac..eb043a9 100644 --- a/build/parsePreamble.c +++ b/build/parsePreamble.c @@ -801,7 +801,7 @@ static rpmRC handlePreambleTag(rpmSpec spec, Package pkg, rpmTagVal tag, SINGLE_TOKEN_ONLY; if (tag == RPMTAG_RELEASE) { char *dist = rpmExpand("%{?dist}",NULL); - rasprintf(&field,"%s%s",field,dist); + rasprintf(&field,"%s%s",field,(dist && strstr(field, dist)) ? "" : dist); free(dist); } if (rpmCharCheck(spec, field, ALLOWED_CHARS_VERREL, NULL)) diff --git a/macros.in b/macros.in index 0f67b07..ae2ad1c 100644 --- a/macros.in +++ b/macros.in @@ -1135,6 +1135,7 @@ package or when debugging this package.\ %__transaction_prioreset %{__plugindir}/prioreset.so %__transaction_audit %{__plugindir}/audit.so %__transaction_dbus_announce %{__plugindir}/dbus_announce.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 822c7d2..161fe4c 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -69,3 +69,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 0000000..698c31e --- /dev/null +++ b/plugins/digest_list.c @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2020-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * 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 +#include +#include +#include +#include +#include +#include +#include "lib/rpmplugin.h" +#include +#include +#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" + +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 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; + struct stat st; + pid_t pid; + int ret = 0, fd; + + if (type == TR_REMOVED) + ima_path = DIGEST_LIST_DATA_DEL_PATH; + + if (stat(ima_path, &st) == -1) + return 0; + + /* 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 (stat(RPM_PARSER, &st) == -1) { + rpmlog(RPMLOG_DEBUG, "digest_list: %s not found, " + "not uploading digest list\n", RPM_PARSER); + return 0; + } + + 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 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_ima_xattr(rpmte te, char *path) +{ + rpmtd signature; + ssize_t written; + uint8_t sig[2048] = { 0 }; + pgpDigParams sigp = NULL; + struct signature_v2_hdr *sig_hdr = (struct signature_v2_hdr *)sig; + Header rpm = rpmteHeader(te); + FD_t fd; + int ret = 0, sig_size, sig_size_rounded; + + 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 write_digest_list_ima_xattr(rpmte te, char *path, char *path_sig) +{ + rpmtd signature; + uint8_t sig[2048] = { 0 }; + pgpDigParams sigp = NULL; + struct signature_v2_hdr *sig_hdr = (struct signature_v2_hdr *)sig; + Header rpm = rpmteHeader(te); + FD_t fd; + struct stat st; + int ret = 0, sig_size; + + 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)); + + if (stat(path_sig, &st) == -1) { + ret = -EACCES; + goto out; + } + + if (sizeof(sig_hdr) + st.st_size > sizeof(sig)) { + rpmlog(RPMLOG_ERR, "digest_list: signature in %s too big\n", + path); + ret = -E2BIG; + goto out; + } + + fd = Fopen(path_sig, "r.ufdio"); + if (fd < 0) { + rpmlog(RPMLOG_ERR, "digest_list: could not open '%s': %s\n", + path_sig, strerror(errno)); + ret = -EACCES; + goto out; + } + + 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); + ret = -EIO; + goto out; + } + + sig_hdr->sig_size = __cpu_to_be16(sig_size); + + rpmlog(RPMLOG_DEBUG, + "digest_list: read signature of %d bytes from '%s'\n", + sig_size, path_sig); + + 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); +out: + pgpDigParamsFree(sigp); + rpmtdFree(signature); + return ret; +} + +static int process_digest_list(rpmte te, int parser, int pre) +{ + char *path = NULL, *path_sig = NULL; + int digest_list_signed = 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; + + if (parser && !digest_list_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) + 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)); + + 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 { + 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; + } + + ret = lremovexattr(path, XATTR_NAME_IMA); + if (ret < 0) + rpmlog(RPMLOG_ERR, "digest_list: cannot remove " + "security.ima from '%s'\n", path); + else + rpmlog(RPMLOG_DEBUG, "digest_list: security.ima " + "successfully removed from '%s'\n", path); + } +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 (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, +}; diff --git a/rpmio/rpmio_internal.h b/rpmio/rpmio_internal.h index fb82b04..afb3a06 100644 --- a/rpmio/rpmio_internal.h +++ b/rpmio/rpmio_internal.h @@ -7,11 +7,42 @@ #include #include +#include #ifdef __cplusplus extern "C" { #endif +/** \ingroup rpmio + * Values parsed from OpenPGP signature/pubkey packet(s). + */ +struct pgpDigParams_s { + char * userid; + uint8_t * hash; + uint8_t tag; + const uint8_t * data; + + uint8_t key_flags; /*!< key usage flags */ + uint8_t version; /*!< version number. */ + uint32_t time; /*!< key/signature creation time. */ + uint8_t pubkey_algo; /*!< public key algorithm. */ + + uint8_t hash_algo; + uint8_t sigtype; + uint32_t hashlen; + uint8_t signhash16[2]; + pgpKeyID_t signid; + uint8_t saved; /*!< Various flags. `PGPDIG_SAVED_*` are never reset. + * `PGPDIG_SIG_HAS_*` are reset for each signature. */ +#define PGPDIG_SAVED_TIME (1 << 0) +#define PGPDIG_SAVED_ID (1 << 1) +#define PGPDIG_SIG_HAS_CREATION_TIME (1 << 2) +#define PGPDIG_SIG_HAS_KEY_FLAGS (1 << 3) + + pgpDigAlg alg; +}; + + void fdSetBundle(FD_t fd, rpmDigestBundle bundle); rpmDigestBundle fdGetBundle(FD_t fd, int create); diff --git a/rpmio/rpmpgp_internal.c b/rpmio/rpmpgp_internal.c index 19947be..efc206c 100644 --- a/rpmio/rpmpgp_internal.c +++ b/rpmio/rpmpgp_internal.c @@ -19,34 +19,6 @@ static int _print = 0; -/** \ingroup rpmio - * Values parsed from OpenPGP signature/pubkey packet(s). - */ -struct pgpDigParams_s { - char * userid; - uint8_t * hash; - uint8_t tag; - - uint8_t key_flags; /*!< key usage flags */ - uint8_t version; /*!< version number. */ - uint32_t time; /*!< key/signature creation time. */ - uint8_t pubkey_algo; /*!< public key algorithm. */ - - uint8_t hash_algo; - uint8_t sigtype; - uint32_t hashlen; - uint8_t signhash16[2]; - pgpKeyID_t signid; - uint8_t saved; /*!< Various flags. `PGPDIG_SAVED_*` are never reset. - * `PGPDIG_SIG_HAS_*` are reset for each signature. */ -#define PGPDIG_SAVED_TIME (1 << 0) -#define PGPDIG_SAVED_ID (1 << 1) -#define PGPDIG_SIG_HAS_CREATION_TIME (1 << 2) -#define PGPDIG_SIG_HAS_KEY_FLAGS (1 << 3) - - pgpDigAlg alg; -}; - /** \ingroup rpmio * Container for values parsed from an OpenPGP signature and public key. */ @@ -484,6 +456,7 @@ static int pgpPrtSig(pgpTag tag, const uint8_t *h, size_t hlen, } p = ((uint8_t *)v) + sizeof(*v); + _digp->data = p; rc = tag ? pgpPrtSigParams(tag, v->pubkey_algo, p, h, hlen, _digp) : 0; } break; case 4: @@ -545,7 +518,8 @@ static int pgpPrtSig(pgpTag tag, const uint8_t *h, size_t hlen, p += 2; if (p > hend) return 1; - + + _digp->data = p; rc = tag ? pgpPrtSigParams(tag, v->pubkey_algo, p, h, hlen, _digp) : 0; } break; default: @@ -636,6 +610,7 @@ static int pgpPrtKey(pgpTag tag, const uint8_t *h, size_t hlen, } p = ((uint8_t *)v) + sizeof(*v); + _digp->data = p; rc = pgpPrtPubkeyParams(v->pubkey_algo, p, h, hlen, _digp); } } break; -- 2.26.2