diff --git a/0001-RPM-with-Copy-on-Write.patch b/0001-RPM-with-Copy-on-Write.patch new file mode 100644 index 0000000..151d2d8 --- /dev/null +++ b/0001-RPM-with-Copy-on-Write.patch @@ -0,0 +1,1344 @@ +From 7bd31ce85b2ed377f495c31fcea2422a07739e24 Mon Sep 17 00:00:00 2001 +From: Matthew Almond +Date: Fri, 8 Nov 2019 09:29:43 -0800 +Subject: [PATCH 01/30] RPM with Copy on Write + +This is part of https://fedoraproject.org/wiki/Changes/RPMCoW + +The majority of changes are in two new programs: + += rpm2extents + +Modeled as a 'stream processor'. It reads a regular .rpm file on stdin, +and produces a modified .rpm file on stdout. The lead, signature and +headers are preserved 1:1 to allow all the normal metadata inspection, +signature verification to work as expected. Only the 'payload' is +modified. + +The primary motivation for this tool is to re-organize the payload as a +sequence of raw file extents (hence the name). The files are organized +by their digest identity instead of path/filename. If any digest is +repeated, then the file is skipped/de-duped. Only regular files are +represented. All other entries like directories, symlinks, devices are +fully described in the headers and are omitted. + +The files are padded so they start on `sysconf(_SC_PAGESIZE)` boundries +to permit 'reflink' syscalls to work in the `reflink` plugin. + +At the end of the file is a footer with 3 sections: + +1. List of calculated digests of the input stream. This is used in + `librepo` because the file *written* is a derivative, and not the + same as the repo metadata describes. `rpm2extents` takes one or more + positional arguments that described which digest algorithms are + desired. This is often just `SHA256`. This program is only measuring + and recording the digest - it does not express an opinion on whether + the file is correct. Due to the API on most compression libraries + directly reading the source file, the whole file digest is measured + using a subprocess and pipes. I don't love it, but it works. +2. Sorted List of file content digests + offset pairs. This is used in + the plugin with a trivial binary search to locate the start of file + content. The size is not needed because it's part of normal headers. +3. (offset of 1., offset of 2., 8 byte MAGIC value) triple + += reflink plugin + +Looks for the 8 byte magic value at the end of the rpm file. If present +it alters the `RPMTAG_PAYLOADFORMAT` in memory to `clon`, and reads in +the digest-> offset table. + +`rpmPackageFilesInstall()` in `fsm.c` is +modified to alter the enumeration strategy from +`rpmfiNewArchiveReader()` to `rpmfilesIter()` if not `cpio`. This is +needed because there is no cpio to enumerate. In the same function, if +`rpmpluginsCallFsmFilePre()` returns `RPMRC_PLUGIN_CONTENTS` then +`fsmMkfile()` is skipped as it is assumed the plugin did the work. + +The majority of the work is in `reflink_fsm_file_pre()` - the per file +hook for RPM plugins. If the file enumerated in +`rpmPackageFilesInstall()` is a regular file, this function will look up +the offset in the digest->offset table and will try to reflink it, then +fall back to a regular copy. If reflinking does work: we will have +reflinked a whole number of pages, so we truncate the file to the +expected size. Therefore installing most files does involve two writes: +the reflink of the full size, then a fork/copy on write for the last +page worth. + +If the file passed to `reflink_fsm_file_pre()` is anything other than a +regular file, it return `RPMRC_OK` so the normal mechanics of +`rpmPackageFilesInstall()` are used. That handles directories, symlinks +and other non file types. + += New API for internal use + +1. `rpmReadPackageRaw()` is used within `rpm2extents` to read all the + headers without trying to validate signatures. This eliminates the + runtime dependency on rpmdb. +2. `rpmteFd()` exposes the Fd behind the rpmte, so plugins can interact + with the rpm itself. +3. `RPMRC_PLUGIN_CONTENTS` in `rpmRC_e` for use in + `rpmpluginsCallFsmFilePre()` specifically. +4. `pgpStringVal()` is used to help parse the command line in + `rpm2extents` - the positional arguments are strings, and this + converts the values back to the values in the table. + +Nothing has been removed, and none of the changes are intended to be +used externally, so I don't think a soname bump is warranted here. +--- + Makefile.am | 6 +- + lib/depends.c | 2 + + lib/fsm.c | 45 +++- + lib/package.c | 40 ++++ + lib/rpmlib.h | 9 + + lib/rpmplugins.c | 21 +- + lib/rpmte.c | 5 + + lib/rpmte.h | 2 + + lib/rpmtypes.h | 3 +- + macros.in | 1 + + plugins/Makefile.am | 4 + + plugins/reflink.c | 340 +++++++++++++++++++++++++++++ + rpm2extents.c | 519 ++++++++++++++++++++++++++++++++++++++++++++ + rpmio/rpmpgp.c | 10 + + rpmio/rpmpgp.h | 9 + + 15 files changed, 1004 insertions(+), 12 deletions(-) + create mode 100644 plugins/reflink.c + create mode 100644 rpm2extents.c + +diff --git a/Makefile.am b/Makefile.am +index e5c75d7b4..288668819 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -99,7 +99,7 @@ pkginclude_HEADERS += build/rpmfc.h + pkginclude_HEADERS += build/rpmspec.h + + +-bin_PROGRAMS = rpm rpm2cpio rpmbuild rpmdb rpmkeys rpmsign rpmspec ++bin_PROGRAMS = rpm rpm2cpio rpmbuild rpmdb rpmkeys rpmsign rpmspec rpm2extents + if WITH_ARCHIVE + bin_PROGRAMS += rpm2archive + endif +@@ -154,6 +154,10 @@ rpm2cpio_SOURCES = rpm2cpio.c debug.h system.h + rpm2cpio_LDADD = lib/librpm.la rpmio/librpmio.la + rpm2cpio_LDADD += @WITH_POPT_LIB@ + ++rpm2extents_SOURCES = rpm2extents.c debug.h system.h ++rpm2extents_LDADD = lib/librpm.la rpmio/librpmio.la ++rpm2extents_LDADD += @WITH_POPT_LIB@ ++ + rpm2archive_SOURCES = rpm2archive.c debug.h system.h + rpm2archive_LDADD = lib/librpm.la rpmio/librpmio.la + rpm2archive_LDADD += @WITH_POPT_LIB@ @WITH_ARCHIVE_LIB@ +diff --git a/lib/depends.c b/lib/depends.c +index 30234df3d..8998afcd3 100644 +--- a/lib/depends.c ++++ b/lib/depends.c +@@ -81,6 +81,8 @@ static rpmRC headerCheckPayloadFormat(Header h) { + */ + if (!payloadfmt) return rc; + ++ if (rstreq(payloadfmt, "clon")) return rc; ++ + if (!rstreq(payloadfmt, "cpio")) { + char *nevra = headerGetAsString(h, RPMTAG_NEVRA); + if (payloadfmt && rstreq(payloadfmt, "drpm")) { +diff --git a/lib/fsm.c b/lib/fsm.c +index 935a0a5c6..90193c749 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -8,6 +8,7 @@ + + #include + #include ++#include + #if WITH_CAP + #include + #endif +@@ -19,6 +20,7 @@ + + #include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */ + #include "lib/fsm.h" ++#include "lib/rpmlib.h" + #include "lib/rpmte_internal.h" /* XXX rpmfs */ + #include "lib/rpmplugins.h" /* rpm plugins hooks */ + #include "lib/rpmug.h" +@@ -52,6 +54,7 @@ struct filedata_s { + int stage; + int setmeta; + int skip; ++ int plugin_contents; + rpmFileAction action; + const char *suffix; + char *fpath; +@@ -891,6 +894,14 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); + struct filedata_s *firstlink = NULL; + ++ Header h = rpmteHeader(te); ++ const char *payloadfmt = headerGetString(h, RPMTAG_PAYLOADFORMAT); ++ bool cpio = true; ++ ++ if (payloadfmt && rstreq(payloadfmt, "clon")) { ++ cpio = false; ++ } ++ + /* transaction id used for temporary path suffix while installing */ + rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts)); + +@@ -911,12 +922,23 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Remap file perms, owner, and group. */ + rc = rpmfiStat(fi, 1, &fp->sb); + +- setFileState(fs, fx); + fsmDebug(fp->fpath, fp->action, &fp->sb); + + /* Run fsm file pre hook for all plugins */ + rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, + fp->sb.st_mode, fp->action); ++ fp->plugin_contents = false; ++ switch (rc) { ++ case RPMRC_OK: ++ setFileState(fs, fx); ++ break; ++ case RPMRC_PLUGIN_CONTENTS: ++ fp->plugin_contents = true; ++ // reduce reads on cpio to this value. Could be zero if ++ // this is from a hard link. ++ rc = RPMRC_OK; ++ break; ++ } + fp->stage = FILE_PRE; + } + fi = rpmfiFree(fi); +@@ -924,10 +946,14 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + if (rc) + goto exit; + +- fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); +- if (fi == NULL) { +- rc = RPMERR_BAD_MAGIC; +- goto exit; ++ if (cpio) { ++ fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); ++ if (fi == NULL) { ++ rc = RPMERR_BAD_MAGIC; ++ goto exit; ++ } ++ } else { ++ fi = rpmfilesIter(files, RPMFI_ITER_FWD); + } + + /* Detect and create directories not explicitly in package. */ +@@ -969,8 +995,12 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- rc = fsmMkfile(fi, fp, files, psm, nodigest, +- &firstlink, &firstlinkfile); ++ if(fp->plugin_contents) { ++ rc = RPMRC_OK; ++ }else { ++ rc = fsmMkfile(fi, fp, files, psm, nodigest, ++ &firstlink, &firstlinkfile); ++ } + } + } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -1078,6 +1108,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST)); + + exit: ++ h = headerFree(h); + fi = rpmfiFree(fi); + Fclose(payload); + free(tid); +diff --git a/lib/package.c b/lib/package.c +index 281275029..90bd0d8a7 100644 +--- a/lib/package.c ++++ b/lib/package.c +@@ -404,5 +404,45 @@ exit: + return rc; + } + ++rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp) ++{ ++ char *msg = NULL; ++ hdrblob sigblob = hdrblobCreate(); ++ hdrblob blob = hdrblobCreate(); ++ Header h = NULL; ++ Header sigh = NULL; ++ ++ rpmRC rc = rpmLeadRead(fd, &msg); ++ if (rc != RPMRC_OK) ++ goto exit; ++ ++ rc = hdrblobRead(fd, 1, 0, RPMTAG_HEADERSIGNATURES, sigblob, &msg); ++ if (rc != RPMRC_OK) ++ goto exit; ++ ++ rc = hdrblobRead(fd, 1, 1, RPMTAG_HEADERIMMUTABLE, blob, &msg); ++ if (rc != RPMRC_OK) ++ goto exit; ++ ++ rc = hdrblobImport(sigblob, 0, &sigh, &msg); ++ if (rc) ++ goto exit; + ++ rc = hdrblobImport(blob, 0, &h, &msg); ++ if (rc) ++ goto exit; + ++ *sigp = headerLink(sigh); ++ *hdrp = headerLink(h); ++ ++exit: ++ if (rc != RPMRC_OK && msg) ++ rpmlog(RPMLOG_ERR, "%s: %s\n", Fdescr(fd), msg); ++ hdrblobFree(sigblob); ++ hdrblobFree(blob); ++ headerFree(sigh); ++ headerFree(h); ++ free(msg); ++ ++ return rc; ++} +diff --git a/lib/rpmlib.h b/lib/rpmlib.h +index 0879d04e5..a09ba0daf 100644 +--- a/lib/rpmlib.h ++++ b/lib/rpmlib.h +@@ -155,6 +155,15 @@ rpmRC rpmReadHeader(rpmts ts, FD_t fd, Header *hdrp, char ** msg); + rpmRC rpmReadPackageFile(rpmts ts, FD_t fd, + const char * fn, Header * hdrp); + ++/** \ingroup header ++ * Return package signature, header from file handle, no verification. ++ * @param fd file handle ++ * @param[out] sigp address of header (or NULL) ++ * @param[out] hdrp address of header (or NULL) ++ * @return RPMRC_OK on success ++ */ ++rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp); ++ + /** \ingroup rpmtrans + * Install source package. + * @param ts transaction set +diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c +index 62d75c4cf..c5084d398 100644 +--- a/lib/rpmplugins.c ++++ b/lib/rpmplugins.c +@@ -356,13 +356,28 @@ rpmRC rpmpluginsCallFsmFilePre(rpmPlugins plugins, rpmfi fi, const char *path, + plugin_fsm_file_pre_func hookFunc; + int i; + rpmRC rc = RPMRC_OK; ++ rpmRC hook_rc; + + for (i = 0; i < plugins->count; i++) { + rpmPlugin plugin = plugins->plugins[i]; + RPMPLUGINS_SET_HOOK_FUNC(fsm_file_pre); +- if (hookFunc && hookFunc(plugin, fi, path, file_mode, op) == RPMRC_FAIL) { +- rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name); +- rc = RPMRC_FAIL; ++ if (hookFunc) { ++ hook_rc = hookFunc(plugin, fi, path, file_mode, op); ++ if (hook_rc == RPMRC_FAIL) { ++ rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name); ++ rc = RPMRC_FAIL; ++ } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) { ++ if (rc == RPMRC_PLUGIN_CONTENTS) { ++ /* ++ Another plugin already said it'd handle contents. It's undefined how ++ these would combine, so treat this as a failure condition. ++ */ ++ rc = RPMRC_FAIL; ++ } else { ++ /* Plugin will handle content */ ++ rc = RPMRC_PLUGIN_CONTENTS; ++ } ++ } + } + } + +diff --git a/lib/rpmte.c b/lib/rpmte.c +index 3663604e7..d43dc41ad 100644 +--- a/lib/rpmte.c ++++ b/lib/rpmte.c +@@ -423,6 +423,11 @@ FD_t rpmteSetFd(rpmte te, FD_t fd) + return NULL; + } + ++FD_t rpmteFd(rpmte te) ++{ ++ return (te != NULL ? te->fd : NULL); ++} ++ + fnpyKey rpmteKey(rpmte te) + { + return (te != NULL ? te->key : NULL); +diff --git a/lib/rpmte.h b/lib/rpmte.h +index 81acf7a19..6fc0a9f91 100644 +--- a/lib/rpmte.h ++++ b/lib/rpmte.h +@@ -209,6 +209,8 @@ const char * rpmteNEVR(rpmte te); + */ + const char * rpmteNEVRA(rpmte te); + ++FD_t rpmteFd(rpmte te); ++ + /** \ingroup rpmte + * Retrieve key from transaction element. + * @param te transaction element +diff --git a/lib/rpmtypes.h b/lib/rpmtypes.h +index e8e69b506..af2611e9e 100644 +--- a/lib/rpmtypes.h ++++ b/lib/rpmtypes.h +@@ -106,7 +106,8 @@ typedef enum rpmRC_e { + RPMRC_NOTFOUND = 1, /*!< Generic not found code. */ + RPMRC_FAIL = 2, /*!< Generic failure code. */ + RPMRC_NOTTRUSTED = 3, /*!< Signature is OK, but key is not trusted. */ +- RPMRC_NOKEY = 4 /*!< Public key is unavailable. */ ++ RPMRC_NOKEY = 4, /*!< Public key is unavailable. */ ++ RPMRC_PLUGIN_CONTENTS = 5 /*!< fsm_file_pre plugin is handling content */ + } rpmRC; + + #ifdef __cplusplus +diff --git a/macros.in b/macros.in +index e90cefa9a..363252b0f 100644 +--- a/macros.in ++++ b/macros.in +@@ -1143,6 +1143,7 @@ package or when debugging this package.\ + + # Transaction plugin macros + %__plugindir %{_libdir}/rpm-plugins ++%__transaction_reflink %{__plugindir}/reflink.so + %__transaction_systemd_inhibit %{__plugindir}/systemd_inhibit.so + %__transaction_selinux %{__plugindir}/selinux.so + %__transaction_syslog %{__plugindir}/syslog.so +diff --git a/plugins/Makefile.am b/plugins/Makefile.am +index 3a929d0ce..ad0d3bce7 100644 +--- a/plugins/Makefile.am ++++ b/plugins/Makefile.am +@@ -42,6 +42,10 @@ prioreset_la_SOURCES = prioreset.c + prioreset_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la + plugins_LTLIBRARIES += prioreset.la + ++reflink_la_SOURCES = reflink.c ++reflink_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la ++plugins_LTLIBRARIES += reflink.la ++ + syslog_la_SOURCES = syslog.c + syslog_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la + plugins_LTLIBRARIES += syslog.la +diff --git a/plugins/reflink.c b/plugins/reflink.c +new file mode 100644 +index 000000000..d7f19acd9 +--- /dev/null ++++ b/plugins/reflink.c +@@ -0,0 +1,340 @@ ++#include "system.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#if defined(__linux__) ++#include /* For FICLONE */ ++#endif ++ ++#include ++#include "lib/rpmlib.h" ++#include "lib/rpmplugin.h" ++#include "lib/rpmte_internal.h" ++#include ++#include "rpmio/rpmio_internal.h" ++ ++ ++#include "debug.h" ++ ++#include ++ ++/* use hash table to remember inode -> ix (for rpmfilesFN(ix)) lookups */ ++#undef HASHTYPE ++#undef HTKEYTYPE ++#undef HTDATATYPE ++#define HASHTYPE inodeIndexHash ++#define HTKEYTYPE rpm_ino_t ++#define HTDATATYPE int ++#include "lib/rpmhash.H" ++#include "lib/rpmhash.C" ++ ++/* ++We use this in find to indicate a key wasn't found. This is an unrecoverable ++error, but we can at least show a decent error. 0 is never a valid offset ++because it's the offset of the start of the file. ++*/ ++#define NOT_FOUND 0 ++ ++#define BUFFER_SIZE (1024 * 128) ++ ++/* magic value at end of file (64 bits) that indicates this is a transcoded rpm */ ++#define MAGIC 3472329499408095051 ++ ++struct reflink_state_s { ++ /* Stuff that's used across rpms */ ++ long fundamental_block_size; ++ char *buffer; ++ ++ /* stuff that's used/updated per psm */ ++ uint32_t keys, keysize; ++ ++ // table for current rpm, keys * (keysize + sizeof(rpm_loff_t)) ++ unsigned char *table; ++ FD_t fd; ++ rpmfiles files; ++ inodeIndexHash inodeIndexes; ++}; ++ ++typedef struct reflink_state_s * reflink_state; ++ ++static int inodeCmp(rpm_ino_t a, rpm_ino_t b) ++{ ++ return (a != b); ++} ++ ++static unsigned int inodeId(rpm_ino_t a) ++{ ++ /* rpm_ino_t is uint32_t so maps safely to unsigned int */ ++ return (unsigned int)a; ++} ++ ++static rpmRC reflink_init(rpmPlugin plugin, rpmts ts) { ++ reflink_state state = rcalloc(1, sizeof(struct reflink_state_s)); ++ ++ /* ++ IOCTL-FICLONERANGE(2): ...Disk filesystems generally require the offset and ++ length arguments to be aligned to the fundamental block size. ++ ++ The value of "fundamental block size" is directly related to the system's ++ page size, so we should use that. ++ */ ++ state->fundamental_block_size = sysconf(_SC_PAGESIZE); ++ state->buffer = rcalloc(1, BUFFER_SIZE); ++ rpmPluginSetData(plugin, state); ++ ++ return RPMRC_OK; ++} ++ ++static void reflink_cleanup(rpmPlugin plugin) { ++ reflink_state state = rpmPluginGetData(plugin); ++ free(state->buffer); ++ free(state); ++} ++ ++static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) { ++ reflink_state state = rpmPluginGetData(plugin); ++ state->fd = rpmteFd(te); ++ if (state->fd == 0) { ++ rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n")); ++ return RPMRC_OK; ++ } ++ rpm_loff_t current = Ftell(state->fd); ++ uint64_t magic; ++ if (Fseek(state->fd, -(sizeof(magic)), SEEK_END) < 0) { ++ rpmlog(RPMLOG_ERR, _("reflink: failed to seek for magic\n")); ++ if (Fseek(state->fd, current, SEEK_SET) < 0) { ++ /* yes this gets a bit repetitive */ ++ rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); ++ } ++ return RPMRC_FAIL; ++ } ++ size_t len = sizeof(magic); ++ if (Fread(&magic, len, 1, state->fd) != len) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read magic\n")); ++ if (Fseek(state->fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); ++ } ++ return RPMRC_FAIL; ++ } ++ if (magic != MAGIC) { ++ rpmlog(RPMLOG_DEBUG, _("reflink: not transcoded\n")); ++ if (Fseek(state->fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); ++ return RPMRC_FAIL; ++ } ++ return RPMRC_OK; ++ } ++ rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n")); ++ Header h = rpmteHeader(te); ++ ++ /* replace/add header that main fsm.c can read */ ++ headerDel(h, RPMTAG_PAYLOADFORMAT); ++ headerPutString(h, RPMTAG_PAYLOADFORMAT, "clon"); ++ headerFree(h); ++ state->files = rpmteFiles(te); ++ /* tail of file contains offset_table, offset_checksums ++ then magic ++ */ ++ if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(magic)), SEEK_END) < 0) { ++ rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"), state->fd); ++ return RPMRC_FAIL; ++ } ++ rpm_loff_t table_start; ++ len = sizeof(table_start); ++ if (Fread(&table_start, len, 1, state->fd) != len) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read table_start\n")); ++ return RPMRC_FAIL; ++ } ++ if (Fseek(state->fd, table_start, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to seek to table_start\n")); ++ return RPMRC_FAIL; ++ } ++ len = sizeof(state->keys); ++ if (Fread(&state->keys, len, 1, state->fd) != len) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read number of keys\n")); ++ return RPMRC_FAIL; ++ } ++ len = sizeof(state->keysize); ++ if (Fread(&state->keysize, len, 1, state->fd) != len) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read keysize\n")); ++ return RPMRC_FAIL; ++ } ++ rpmlog(RPMLOG_DEBUG, _("reflink: table_start=0x%lx, keys=%d, keysize=%d\n"), table_start, state->keys, state->keysize); ++ // now get digest table if there is a reason to have one. ++ if (state->keys == 0 || state->keysize == 0) { ++ // no files (or no digests(!)) ++ state->table = NULL; ++ } else { ++ int table_size = state->keys * (state->keysize + sizeof(rpm_loff_t)); ++ state->table = rcalloc(1, table_size); ++ if (Fread(state->table, table_size, 1, state->fd) != table_size) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read table\n")); ++ return RPMRC_FAIL; ++ } ++ state->inodeIndexes = inodeIndexHashCreate(state->keys, inodeId, inodeCmp, NULL, NULL); ++ } ++ ++ // seek back to original location ++ // might not be needed if we seek to offset immediately ++ if (Fseek(state->fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); ++ return RPMRC_FAIL; ++ } ++ return RPMRC_OK; ++} ++ ++static rpmRC reflink_psm_post(rpmPlugin plugin, rpmte te, int res) ++{ ++ reflink_state state = rpmPluginGetData(plugin); ++ state->files = rpmfilesFree(state->files); ++ if (state->table) { ++ free(state->table); ++ state->table = NULL; ++ } ++ if (state->inodeIndexes) { ++ inodeIndexHashFree(state->inodeIndexes); ++ state->inodeIndexes = NULL; ++ } ++ return RPMRC_OK; ++} ++ ++ ++// have a prototype, warnings system ++rpm_loff_t find(const unsigned char *digest, reflink_state state); ++ ++rpm_loff_t find(const unsigned char *digest, reflink_state state) { ++# if defined(__GNUC__) ++ /* GCC nested function because bsearch's comparison function can't access ++ state-keysize otherwise ++ */ ++ int cmpdigest(const void *k1, const void *k2) { ++ rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2); ++ return memcmp(k1, k2, state->keysize); ++ } ++# endif ++ rpmlog(RPMLOG_DEBUG, _("reflink: bsearch(key=%p, base=%p, nmemb=%d, size=%lu)\n"), digest, state->table, state->keys, state->keysize + sizeof(rpm_loff_t)); ++ char *entry = bsearch(digest, state->table, state->keys, state->keysize + sizeof(rpm_loff_t), cmpdigest); ++ if (entry == NULL) { ++ return NOT_FOUND; ++ } ++ rpm_loff_t offset = *(rpm_loff_t *)(entry + state->keysize); ++ return offset; ++} ++ ++static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path, mode_t file_mode, rpmFsmOp op) ++{ ++ struct file_clone_range fcr; ++ rpm_loff_t size; ++ int dst, rc; ++ int *hlix; ++ ++ reflink_state state = rpmPluginGetData(plugin); ++ if (state->table == NULL) { ++ // no table means rpm is not in reflink format, so leave. Now. ++ return RPMRC_OK; ++ } ++ if (op == FA_TOUCH) { ++ // we're not overwriting an existing file ++ return RPMRC_OK; ++ } ++ fcr.dest_offset = 0; ++ if (S_ISREG(file_mode) && !(rpmfiFFlags(fi) & RPMFILE_GHOST)) { ++ rpm_ino_t inode = rpmfiFInode(fi); ++ /* check for hard link entry in table. GetEntry overwrites hlix with the address of the first match */ ++ if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hlix, NULL, NULL)) { ++ // entry is in table, use hard link ++ char *fn = rpmfilesFN(state->files, hlix[0]); ++ if (link(fn, path) != 0) { ++ rpmlog(RPMLOG_ERR, _("reflink: Unable to hard link %s -> %s due to %s\n"), fn, path, strerror(errno)); ++ free(fn); ++ return RPMRC_FAIL; ++ } ++ free(fn); ++ return RPMRC_PLUGIN_CONTENTS; ++ } ++ /* if we didn't hard link, then we'll track this inode as being created soon */ ++ if (rpmfiFNlink(fi) > 1) { ++ /* minor optimization: only store files with more than one link */ ++ inodeIndexHashAddEntry(state->inodeIndexes, inode, rpmfiFX(fi)); ++ } ++ /* derived from wfd_open in fsm.c */ ++ mode_t old_umask = umask(0577); ++ dst = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR); ++ umask(old_umask); ++ if (dst == -1) { ++ rpmlog(RPMLOG_ERR, _("reflink: Unable to open %s for writing due to %s, flags = %x\n"), path, strerror(errno), rpmfiFFlags(fi)); ++ return RPMRC_FAIL; ++ } ++ size = rpmfiFSize(fi); ++ if (size > 0) { ++ /* round src_length down to fundamental_block_size multiple */ ++ fcr.src_length = size / state->fundamental_block_size * state->fundamental_block_size; ++ if ((size % state->fundamental_block_size) > 0) { ++ /* round up to next fundamental_block_size. We expect the data in the rpm to be similarly padded */ ++ fcr.src_length += state->fundamental_block_size; ++ } ++ fcr.src_fd = Fileno(state->fd); ++ if (fcr.src_fd == -1) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, _("reflink: src fd lookup failed\n")); ++ return RPMRC_FAIL; ++ } ++ fcr.src_offset = find(rpmfiFDigest(fi, NULL, NULL), state); ++ if (fcr.src_offset == NOT_FOUND) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, _("reflink: digest not found\n")); ++ return RPMRC_FAIL; ++ } ++ rpmlog(RPMLOG_DEBUG, _("reflink: Reflinking %lu bytes at %lu to %s orig size=%lu, file=%ld\n"), fcr.src_length, fcr.src_offset, path, size, fcr.src_fd); ++ rc = ioctl(dst, FICLONERANGE, &fcr); ++ if (rc) { ++ rpmlog(RPMLOG_WARNING, _("reflink: falling back to copying bits for %s due to %d, %d = %s\n"), path, rc, errno, strerror(errno)); ++ if (Fseek(state->fd, fcr.src_offset, SEEK_SET) < 0) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, _("reflink: unable to seek on copying bits\n")); ++ return RPMRC_FAIL; ++ } ++ rpm_loff_t left = size; ++ size_t len, read, written; ++ while (left) { ++ len = (left > BUFFER_SIZE ? BUFFER_SIZE : left); ++ read = Fread(state->buffer, len, 1, state->fd); ++ if (read != len) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, _("reflink: short read on copying bits\n")); ++ return RPMRC_FAIL; ++ } ++ written = write(dst, state->buffer, len); ++ if (read != written) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, _("reflink: short write on copying bits\n")); ++ return RPMRC_FAIL; ++ } ++ left -= len; ++ } ++ } else { ++ /* reflink worked, so truncate */ ++ rc = ftruncate(dst, size); ++ if (rc) { ++ rpmlog(RPMLOG_ERR, _("reflink: Unable to truncate %s to %ld due to %s\n"), path, size, strerror(errno)); ++ return RPMRC_FAIL; ++ } ++ } ++ } ++ close(dst); ++ return RPMRC_PLUGIN_CONTENTS; ++ } ++ return RPMRC_OK; ++} ++ ++struct rpmPluginHooks_s reflink_hooks = { ++ .init = reflink_init, ++ .cleanup = reflink_cleanup, ++ .psm_pre = reflink_psm_pre, ++ .psm_post = reflink_psm_post, ++ .fsm_file_pre = reflink_fsm_file_pre, ++}; +diff --git a/rpm2extents.c b/rpm2extents.c +new file mode 100644 +index 000000000..5662b86a6 +--- /dev/null ++++ b/rpm2extents.c +@@ -0,0 +1,519 @@ ++/* rpm2extents: convert payload to inline extents */ ++ ++#include "system.h" ++ ++#include /* rpmReadPackageFile .. */ ++#include ++#include ++#include ++#include ++ ++#include ++#include "lib/rpmlead.h" ++#include "lib/signature.h" ++#include "lib/header_internal.h" ++#include "rpmio/rpmio_internal.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++ ++/* hash of void * (pointers) to file digests to offsets within output. ++ The length of the key depends on what the FILEDIGESTALGO is. ++ */ ++#undef HASHTYPE ++#undef HTKEYTYPE ++#undef HTDATATYPE ++#define HASHTYPE digestSet ++#define HTKEYTYPE const unsigned char * ++#include "lib/rpmhash.H" ++#include "lib/rpmhash.C" ++ ++/* magic value at end of file (64 bits) that indicates this is a transcoded rpm */ ++#define MAGIC 3472329499408095051 ++ ++struct digestoffset { ++ const unsigned char * digest; ++ rpm_loff_t pos; ++}; ++ ++rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit); ++ ++rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit) ++{ ++ return (unit - (pos % unit)) % unit; ++} ++ ++static int digestor( ++ FD_t fdi, ++ FD_t fdo, ++ FD_t validationo, ++ uint8_t algos[], ++ uint32_t algos_len ++) ++{ ++ ssize_t fdilength; ++ const char *filedigest, *algo_name; ++ size_t filedigest_len, len; ++ uint32_t algo_name_len, algo_digest_len; ++ int algo; ++ rpmRC rc = RPMRC_FAIL; ++ ++ for (algo = 0; algo < algos_len; algo++) ++ { ++ fdInitDigest(fdi, algos[algo], 0); ++ } ++ fdilength = ufdCopy(fdi, fdo); ++ if (fdilength == -1) ++ { ++ fprintf(stderr, _("digest cat failed\n")); ++ goto exit; ++ } ++ ++ len = sizeof(fdilength); ++ if (Fwrite(&fdilength, len, 1, validationo) != len) ++ { ++ fprintf(stderr, _("Unable to write input length %zd\n"), fdilength); ++ goto exit; ++ } ++ len = sizeof(algos_len); ++ if (Fwrite(&algos_len, len, 1, validationo) != len) ++ { ++ fprintf(stderr, _("Unable to write number of validation digests\n")); ++ goto exit; ++ } ++ for (algo = 0; algo < algos_len; algo++) ++ { ++ fdFiniDigest(fdi, algos[algo], (void **)&filedigest, &filedigest_len, 0); ++ ++ algo_name = pgpValString(PGPVAL_HASHALGO, algos[algo]); ++ algo_name_len = (uint32_t)strlen(algo_name); ++ algo_digest_len = (uint32_t)filedigest_len; ++ ++ len = sizeof(algo_name_len); ++ if (Fwrite(&algo_name_len, len, 1, validationo) != len) ++ { ++ fprintf( ++ stderr, ++ _("Unable to write validation algo name length\n") ++ ); ++ goto exit; ++ } ++ len = sizeof(algo_digest_len); ++ if (Fwrite(&algo_digest_len, len, 1, validationo) != len) ++ { ++ fprintf( ++ stderr, ++ _("Unable to write number of bytes for validation digest\n") ++ ); ++ goto exit; ++ } ++ if (Fwrite(algo_name, algo_name_len, 1, validationo) != algo_name_len) ++ { ++ fprintf(stderr, _("Unable to write validation algo name\n")); ++ goto exit; ++ } ++ if ( ++ Fwrite( ++ filedigest, ++ algo_digest_len, ++ 1, ++ validationo ++ ) != algo_digest_len ++ ) ++ { ++ fprintf( ++ stderr, ++ _("Unable to write validation digest value %u, %zu\n"), ++ algo_digest_len, ++ filedigest_len ++ ); ++ goto exit; ++ } ++ } ++ rc = RPMRC_OK; ++exit: ++ return rc; ++} ++ ++static rpmRC process_package(FD_t fdi, FD_t validationi) ++{ ++ uint32_t diglen; ++ /* GNU C extension: can use diglen from outer context */ ++ int digestSetCmp(const unsigned char * a, const unsigned char * b) ++ { ++ return memcmp(a, b, diglen); ++ } ++ ++ unsigned int digestSetHash(const unsigned char * digest) ++ { ++ /* assumes sizeof(unsigned int) < diglen */ ++ return *(unsigned int *)digest; ++ } ++ ++ int digestoffsetCmp(const void * a, const void * b) ++ { ++ return digestSetCmp( ++ ((struct digestoffset *)a)->digest, ++ ((struct digestoffset *)b)->digest ++ ); ++ } ++ ++ FD_t fdo; ++ FD_t gzdi; ++ Header h, sigh; ++ long fundamental_block_size = sysconf(_SC_PAGESIZE); ++ rpmRC rc = RPMRC_OK; ++ rpm_mode_t mode; ++ char *rpmio_flags = NULL, *zeros; ++ const unsigned char *digest; ++ rpm_loff_t pos, size, pad, validation_pos; ++ uint32_t offset_ix = 0; ++ size_t len; ++ int next = 0; ++ ++ fdo = fdDup(STDOUT_FILENO); ++ ++ if (rpmReadPackageRaw(fdi, &sigh, &h)) ++ { ++ fprintf(stderr, _("Error reading package\n")); ++ exit(EXIT_FAILURE); ++ } ++ ++ if (rpmLeadWrite(fdo, h)) ++ { ++ fprintf( ++ stderr, ++ _("Unable to write package lead: %s\n"), ++ Fstrerror(fdo) ++ ); ++ exit(EXIT_FAILURE); ++ } ++ ++ if (rpmWriteSignature(fdo, sigh)) ++ { ++ fprintf(stderr, _("Unable to write signature: %s\n"), Fstrerror(fdo)); ++ exit(EXIT_FAILURE); ++ } ++ ++ if (headerWrite(fdo, h, HEADER_MAGIC_YES)) ++ { ++ fprintf(stderr, _("Unable to write headers: %s\n"), Fstrerror(fdo)); ++ exit(EXIT_FAILURE); ++ } ++ ++ /* Retrieve payload size and compression type. */ ++ { const char *compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR); ++ rpmio_flags = rstrscat(NULL, "r.", compr ? compr : "gzip", NULL); ++ } ++ ++ gzdi = Fdopen(fdi, rpmio_flags); /* XXX gzdi == fdi */ ++ free(rpmio_flags); ++ ++ if (gzdi == NULL) ++ { ++ fprintf(stderr, _("cannot re-open payload: %s\n"), Fstrerror(gzdi)); ++ exit(EXIT_FAILURE); ++ } ++ ++ rpmfiles files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER); ++ rpmfi fi = rpmfiNewArchiveReader( ++ gzdi, ++ files, ++ RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST ++ ); ++ ++ /* this is encoded in the file format, so needs to be fixed size (for ++ now?) ++ */ ++ diglen = (uint32_t)rpmDigestLength(rpmfiDigestAlgo(fi)); ++ digestSet ds = digestSetCreate( ++ rpmfiFC(fi), ++ digestSetHash, ++ digestSetCmp, ++ NULL ++ ); ++ struct digestoffset offsets[rpmfiFC(fi)]; ++ pos = RPMLEAD_SIZE + headerSizeof(sigh, HEADER_MAGIC_YES); ++ ++ /* main headers are aligned to 8 byte boundry */ ++ pos += pad_to(pos, 8); ++ pos += headerSizeof(h, HEADER_MAGIC_YES); ++ ++ zeros = xcalloc(fundamental_block_size, 1); ++ ++ while (next >= 0) ++ { ++ next = rpmfiNext(fi); ++ if (next == RPMERR_ITER_END) ++ { ++ rc = RPMRC_OK; ++ break; ++ } ++ mode = rpmfiFMode(fi); ++ if (!S_ISREG(mode) || !rpmfiArchiveHasContent(fi)) ++ { ++ /* not a regular file, or the archive doesn't contain any content for ++ this entry ++ */ ++ continue; ++ } ++ digest = rpmfiFDigest(fi, NULL, NULL); ++ if (digestSetGetEntry(ds, digest, NULL)) ++ { ++ /* This specific digest has already been included, so skip it */ ++ continue; ++ } ++ pad = pad_to(pos, fundamental_block_size); ++ if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) ++ { ++ fprintf(stderr, _("Unable to write padding\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ /* round up to next fundamental_block_size */ ++ pos += pad; ++ digestSetAddEntry(ds, digest); ++ offsets[offset_ix].digest = digest; ++ offsets[offset_ix].pos = pos; ++ offset_ix++; ++ size = rpmfiFSize(fi); ++ rc = rpmfiArchiveReadToFile(fi, fdo, 0); ++ if (rc != RPMRC_OK) ++ { ++ fprintf(stderr, _("rpmfiArchiveReadToFile failed with %d\n"), rc); ++ goto exit; ++ } ++ pos += size; ++ } ++ Fclose(gzdi); /* XXX gzdi == fdi */ ++ ++ qsort( ++ offsets, ++ (size_t)offset_ix, ++ sizeof(struct digestoffset), ++ digestoffsetCmp ++ ); ++ ++ len = sizeof(offset_ix); ++ if (Fwrite(&offset_ix, len, 1, fdo) != len) ++ { ++ fprintf(stderr, _("Unable to write length of table\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ len = sizeof(diglen); ++ if (Fwrite(&diglen, len, 1, fdo) != len) ++ { ++ fprintf(stderr, _("Unable to write length of digest\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ len = sizeof(rpm_loff_t); ++ for (int x = 0; x < offset_ix; x++) ++ { ++ if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) ++ { ++ fprintf(stderr, _("Unable to write digest\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) ++ { ++ fprintf(stderr, _("Unable to write offset\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ } ++ validation_pos = ( ++ pos + sizeof(offset_ix) + sizeof(diglen) + ++ offset_ix * (diglen + sizeof(rpm_loff_t)) ++ ); ++ ++ ssize_t validation_len = ufdCopy(validationi, fdo); ++ if (validation_len == -1) ++ { ++ fprintf(stderr, _("digest table ufdCopy failed\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ /* add more padding so the last file can be cloned. It doesn't matter that ++ the table and validation etc are in this space. In fact, it's pretty ++ efficient if it is ++ */ ++ ++ pad = pad_to( ++ ( ++ validation_pos + validation_len + 2 * sizeof(rpm_loff_t) + ++ sizeof(uint64_t) ++ ), ++ fundamental_block_size ++ ); ++ if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) ++ { ++ fprintf(stderr, _("Unable to write final padding\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ zeros = _free(zeros); ++ if (Fwrite(&pos, len, 1, fdo) != len) ++ { ++ fprintf(stderr, _("Unable to write offset of digest table\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ if (Fwrite(&validation_pos, len, 1, fdo) != len) ++ { ++ fprintf(stderr, _("Unable to write offset of validation table\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ uint64_t magic = MAGIC; ++ len = sizeof(magic); ++ if (Fwrite(&magic, len, 1, fdo) != len) ++ { ++ fprintf(stderr, _("Unable to write magic\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ ++exit: ++ rpmfilesFree(files); ++ rpmfiFree(fi); ++ headerFree(h); ++ return rc; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ rpmRC rc; ++ int cprc = 0; ++ uint8_t algos[argc - 1]; ++ int mainpipefd[2]; ++ int metapipefd[2]; ++ pid_t cpid, w; ++ int wstatus; ++ ++ xsetprogname(argv[0]); /* Portability call -- see system.h */ ++ rpmReadConfigFiles(NULL, NULL); ++ ++ if (argc > 1 && (rstreq(argv[1], "-h") || rstreq(argv[1], "--help"))) ++ { ++ fprintf(stderr, _("Usage: %s [DIGESTALGO]...\n"), argv[0]); ++ exit(EXIT_FAILURE); ++ } ++ ++ if (argc == 1) ++ { ++ fprintf( ++ stderr, ++ _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n") ++ ); ++ exit(EXIT_FAILURE); ++ } ++ ++ for (int x = 0; x < (argc - 1); x++) ++ { ++ if (pgpStringVal(PGPVAL_HASHALGO, argv[x + 1], &algos[x]) != 0) ++ { ++ fprintf( ++ stderr, ++ _("Unable to resolve '%s' as a digest algorithm, exiting\n"), ++ argv[x + 1] ++ ); ++ exit(EXIT_FAILURE); ++ } ++ } ++ ++ ++ if (pipe(mainpipefd) == -1) ++ { ++ fprintf(stderr, _("Main pipe failure\n")); ++ exit(EXIT_FAILURE); ++ } ++ if (pipe(metapipefd) == -1) ++ { ++ fprintf(stderr, _("Meta pipe failure\n")); ++ exit(EXIT_FAILURE); ++ } ++ cpid = fork(); ++ if (cpid == 0) ++ { ++ /* child: digestor */ ++ close(mainpipefd[0]); ++ close(metapipefd[0]); ++ FD_t fdi = fdDup(STDIN_FILENO); ++ FD_t fdo = fdDup(mainpipefd[1]); ++ FD_t validationo = fdDup(metapipefd[1]); ++ rc = digestor(fdi, fdo, validationo, algos, argc - 1); ++ Fclose(validationo); ++ Fclose(fdo); ++ Fclose(fdi); ++ } else { ++ /* parent: main program */ ++ close(mainpipefd[1]); ++ close(metapipefd[1]); ++ FD_t fdi = fdDup(mainpipefd[0]); ++ FD_t validationi = fdDup(metapipefd[0]); ++ rc = process_package(fdi, validationi); ++ Fclose(validationi); ++ /* fdi is normally closed through the stacked file gzdi in the function. */ ++ /* wait for child process (digestor for stdin) to complete. */ ++ if (rc != RPMRC_OK) ++ { ++ if (kill(cpid, SIGTERM) != 0) ++ { ++ fprintf( ++ stderr, ++ _("Failed to kill digest process when main process failed: %s\n"), ++ strerror(errno) ++ ); ++ } ++ } ++ w = waitpid(cpid, &wstatus, 0); ++ if (w == -1) ++ { ++ fprintf(stderr, _("waitpid failed\n")); ++ cprc = EXIT_FAILURE; ++ } else if (WIFEXITED(wstatus)) ++ { ++ cprc = WEXITSTATUS(wstatus); ++ if (cprc != 0) ++ { ++ fprintf( ++ stderr, ++ _("Digest process non-zero exit code %d\n"), ++ cprc ++ ); ++ } ++ } else if (WIFSIGNALED(wstatus)) ++ { ++ fprintf( ++ stderr, ++ _("Digest process was terminated with a signal: %d\n"), ++ WTERMSIG(wstatus) ++ ); ++ cprc = EXIT_FAILURE; ++ } else ++ { ++ /* don't think this can happen, but covering all bases */ ++ fprintf(stderr, _("Unhandled circumstance in waitpid\n")); ++ cprc = EXIT_FAILURE; ++ } ++ if (cprc != EXIT_SUCCESS) ++ { ++ rc = RPMRC_FAIL; ++ } ++ } ++ if (rc != RPMRC_OK) ++ { ++ /* translate rpmRC into generic failure return code. */ ++ return EXIT_FAILURE; ++ } ++ return EXIT_SUCCESS; ++} +diff --git a/rpmio/rpmpgp.c b/rpmio/rpmpgp.c +index 015c15a5c..7b972b4a6 100644 +--- a/rpmio/rpmpgp.c ++++ b/rpmio/rpmpgp.c +@@ -283,6 +283,16 @@ int pgpValTok(pgpValTbl vs, const char * s, const char * se) + return vs->val; + } + ++int pgpStringVal(pgpValType type, const char *str, uint8_t *val) ++{ ++ pgpValTbl tbl = pgpValTable(type); ++ if (tbl == NULL) return -1; ++ int v = pgpValTok(tbl, str, str + strlen(str)); ++ if (v == -1) return -1; ++ *val = (uint8_t)v; ++ return 0; ++} ++ + /** \ingroup rpmpgp + * Decode length from 1, 2, or 5 octet body length encoding, used in + * new format packet headers and V4 signature subpackets. +diff --git a/rpmio/rpmpgp.h b/rpmio/rpmpgp.h +index c53e29b01..2b57318ba 100644 +--- a/rpmio/rpmpgp.h ++++ b/rpmio/rpmpgp.h +@@ -973,6 +973,15 @@ typedef rpmFlags rpmDigestFlags; + */ + const char * pgpValString(pgpValType type, uint8_t val); + ++/** \ingroup rpmpgp ++ * Return OpenPGP value for a string. ++ * @param type type of value ++ * @param str string to lookup ++ * @param[out] val byte value associated with string ++ * @return 0 on success else -1 ++ */ ++int pgpStringVal(pgpValType type, const char *str, uint8_t *val); ++ + /** \ingroup rpmpgp + * Return (native-endian) integer from big-endian representation. + * @param s pointer to big-endian integer +-- +2.35.1 + diff --git a/0002-Remove-use-of-bool-type-for-consistency.patch b/0002-Remove-use-of-bool-type-for-consistency.patch new file mode 100644 index 0000000..5867127 --- /dev/null +++ b/0002-Remove-use-of-bool-type-for-consistency.patch @@ -0,0 +1,53 @@ +From 845b5c3882b1eecb31d712b61a4e91fe0eb70712 Mon Sep 17 00:00:00 2001 +From: Matthew Almond +Date: Sun, 31 Jan 2021 12:30:33 -0800 +Subject: [PATCH 02/30] Remove use of bool type for consistency + +--- + lib/fsm.c | 9 ++++----- + 1 file changed, 4 insertions(+), 5 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 90193c749..feda3750c 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -8,7 +8,6 @@ + + #include + #include +-#include + #if WITH_CAP + #include + #endif +@@ -896,10 +895,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + Header h = rpmteHeader(te); + const char *payloadfmt = headerGetString(h, RPMTAG_PAYLOADFORMAT); +- bool cpio = true; ++ int cpio = 1; + + if (payloadfmt && rstreq(payloadfmt, "clon")) { +- cpio = false; ++ cpio = 0; + } + + /* transaction id used for temporary path suffix while installing */ +@@ -927,13 +926,13 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Run fsm file pre hook for all plugins */ + rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, + fp->sb.st_mode, fp->action); +- fp->plugin_contents = false; ++ fp->plugin_contents = 0; + switch (rc) { + case RPMRC_OK: + setFileState(fs, fx); + break; + case RPMRC_PLUGIN_CONTENTS: +- fp->plugin_contents = true; ++ fp->plugin_contents = 1; + // reduce reads on cpio to this value. Could be zero if + // this is from a hard link. + rc = RPMRC_OK; +-- +2.35.1 + diff --git a/0003-Match-formatting-style-of-existing-code.patch b/0003-Match-formatting-style-of-existing-code.patch new file mode 100644 index 0000000..aae1035 --- /dev/null +++ b/0003-Match-formatting-style-of-existing-code.patch @@ -0,0 +1,1249 @@ +From aa31421bfe835dadd29da3aa46ee446038f80d02 Mon Sep 17 00:00:00 2001 +From: Matthew Almond +Date: Sun, 31 Jan 2021 13:51:16 -0800 +Subject: [PATCH 03/30] Match formatting/style of existing code + +The existing code contains some variability in formatting. I'm not sure +if { is meant to be on the end of the line, or on a new line, but I've +standardized on the former. + +The indentation is intended to match the existing convention: 4 column +indent, but 8 column wide tab characters. This is easy to follow/use in +vim, but is surprisingly difficult to get right in vscode. I am doing +this reformat here and now, and future changes will be after this. + +I'm keen to fold the patches together, but for now, I'm trying to keep +the history of #1470 linear so everyone can follow along. +--- + lib/rpmplugins.c | 6 +- + plugins/reflink.c | 407 ++++++++++++++++++--------------- + rpm2extents.c | 562 ++++++++++++++++++++-------------------------- + 3 files changed, 462 insertions(+), 513 deletions(-) + +diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c +index c5084d398..3da3097af 100644 +--- a/lib/rpmplugins.c ++++ b/lib/rpmplugins.c +@@ -368,9 +368,9 @@ rpmRC rpmpluginsCallFsmFilePre(rpmPlugins plugins, rpmfi fi, const char *path, + rc = RPMRC_FAIL; + } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) { + if (rc == RPMRC_PLUGIN_CONTENTS) { +- /* +- Another plugin already said it'd handle contents. It's undefined how +- these would combine, so treat this as a failure condition. ++ /* Another plugin already said it'd handle contents. It's ++ * undefined how these would combine, so treat this as a ++ * failure condition. + */ + rc = RPMRC_FAIL; + } else { +diff --git a/plugins/reflink.c b/plugins/reflink.c +index d7f19acd9..9eaa87094 100644 +--- a/plugins/reflink.c ++++ b/plugins/reflink.c +@@ -32,31 +32,32 @@ + #include "lib/rpmhash.H" + #include "lib/rpmhash.C" + +-/* +-We use this in find to indicate a key wasn't found. This is an unrecoverable +-error, but we can at least show a decent error. 0 is never a valid offset +-because it's the offset of the start of the file. +-*/ ++/* We use this in find to indicate a key wasn't found. This is an ++ * unrecoverable error, but we can at least show a decent error. 0 is never a ++ * valid offset because it's the offset of the start of the file. ++ */ + #define NOT_FOUND 0 + + #define BUFFER_SIZE (1024 * 128) + +-/* magic value at end of file (64 bits) that indicates this is a transcoded rpm */ ++/* magic value at end of file (64 bits) that indicates this is a transcoded ++ * rpm. ++ */ + #define MAGIC 3472329499408095051 + + struct reflink_state_s { +- /* Stuff that's used across rpms */ +- long fundamental_block_size; +- char *buffer; ++ /* Stuff that's used across rpms */ ++ long fundamental_block_size; ++ char *buffer; + +- /* stuff that's used/updated per psm */ +- uint32_t keys, keysize; ++ /* stuff that's used/updated per psm */ ++ uint32_t keys, keysize; + +- // table for current rpm, keys * (keysize + sizeof(rpm_loff_t)) +- unsigned char *table; +- FD_t fd; +- rpmfiles files; +- inodeIndexHash inodeIndexes; ++ /* table for current rpm, keys * (keysize + sizeof(rpm_loff_t)) */ ++ unsigned char *table; ++ FD_t fd; ++ rpmfiles files; ++ inodeIndexHash inodeIndexes; + }; + + typedef struct reflink_state_s * reflink_state; +@@ -73,60 +74,62 @@ static unsigned int inodeId(rpm_ino_t a) + } + + static rpmRC reflink_init(rpmPlugin plugin, rpmts ts) { +- reflink_state state = rcalloc(1, sizeof(struct reflink_state_s)); ++ reflink_state state = rcalloc(1, sizeof(struct reflink_state_s)); + +- /* +- IOCTL-FICLONERANGE(2): ...Disk filesystems generally require the offset and +- length arguments to be aligned to the fundamental block size. ++ /* IOCTL-FICLONERANGE(2): ...Disk filesystems generally require the offset ++ * and length arguments to be aligned to the fundamental block size. ++ * ++ * The value of "fundamental block size" is directly related to the ++ * system's page size, so we should use that. ++ */ ++ state->fundamental_block_size = sysconf(_SC_PAGESIZE); ++ state->buffer = rcalloc(1, BUFFER_SIZE); ++ rpmPluginSetData(plugin, state); + +- The value of "fundamental block size" is directly related to the system's +- page size, so we should use that. +- */ +- state->fundamental_block_size = sysconf(_SC_PAGESIZE); +- state->buffer = rcalloc(1, BUFFER_SIZE); +- rpmPluginSetData(plugin, state); +- +- return RPMRC_OK; ++ return RPMRC_OK; + } + + static void reflink_cleanup(rpmPlugin plugin) { +- reflink_state state = rpmPluginGetData(plugin); +- free(state->buffer); +- free(state); ++ reflink_state state = rpmPluginGetData(plugin); ++ free(state->buffer); ++ free(state); + } + + static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) { + reflink_state state = rpmPluginGetData(plugin); + state->fd = rpmteFd(te); + if (state->fd == 0) { +- rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n")); +- return RPMRC_OK; ++ rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n")); ++ return RPMRC_OK; + } + rpm_loff_t current = Ftell(state->fd); + uint64_t magic; + if (Fseek(state->fd, -(sizeof(magic)), SEEK_END) < 0) { +- rpmlog(RPMLOG_ERR, _("reflink: failed to seek for magic\n")); +- if (Fseek(state->fd, current, SEEK_SET) < 0) { +- /* yes this gets a bit repetitive */ +- rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); +- } +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, _("reflink: failed to seek for magic\n")); ++ if (Fseek(state->fd, current, SEEK_SET) < 0) { ++ /* yes this gets a bit repetitive */ ++ rpmlog(RPMLOG_ERR, ++ _("reflink: unable to seek back to original location\n")); ++ } ++ return RPMRC_FAIL; + } + size_t len = sizeof(magic); + if (Fread(&magic, len, 1, state->fd) != len) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to read magic\n")); +- if (Fseek(state->fd, current, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); +- } +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read magic\n")); ++ if (Fseek(state->fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, ++ _("reflink: unable to seek back to original location\n")); ++ } ++ return RPMRC_FAIL; + } + if (magic != MAGIC) { +- rpmlog(RPMLOG_DEBUG, _("reflink: not transcoded\n")); +- if (Fseek(state->fd, current, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); +- return RPMRC_FAIL; +- } +- return RPMRC_OK; ++ rpmlog(RPMLOG_DEBUG, _("reflink: not transcoded\n")); ++ if (Fseek(state->fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, ++ _("reflink: unable to seek back to original location\n")); ++ return RPMRC_FAIL; ++ } ++ return RPMRC_OK; + } + rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n")); + Header h = rpmteHeader(te); +@@ -136,53 +139,60 @@ static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) { + headerPutString(h, RPMTAG_PAYLOADFORMAT, "clon"); + headerFree(h); + state->files = rpmteFiles(te); +- /* tail of file contains offset_table, offset_checksums +- then magic +- */ ++ /* tail of file contains offset_table, offset_checksums then magic */ + if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(magic)), SEEK_END) < 0) { +- rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"), state->fd); +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"), ++ state->fd); ++ return RPMRC_FAIL; + } + rpm_loff_t table_start; + len = sizeof(table_start); + if (Fread(&table_start, len, 1, state->fd) != len) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to read table_start\n")); +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read table_start\n")); ++ return RPMRC_FAIL; + } + if (Fseek(state->fd, table_start, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to seek to table_start\n")); +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, _("reflink: unable to seek to table_start\n")); ++ return RPMRC_FAIL; + } + len = sizeof(state->keys); + if (Fread(&state->keys, len, 1, state->fd) != len) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to read number of keys\n")); +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read number of keys\n")); ++ return RPMRC_FAIL; + } + len = sizeof(state->keysize); + if (Fread(&state->keysize, len, 1, state->fd) != len) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to read keysize\n")); +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read keysize\n")); ++ return RPMRC_FAIL; + } +- rpmlog(RPMLOG_DEBUG, _("reflink: table_start=0x%lx, keys=%d, keysize=%d\n"), table_start, state->keys, state->keysize); +- // now get digest table if there is a reason to have one. ++ rpmlog( ++ RPMLOG_DEBUG, ++ _("reflink: table_start=0x%lx, keys=%d, keysize=%d\n"), ++ table_start, state->keys, state->keysize ++ ); ++ /* now get digest table if there is a reason to have one. */ + if (state->keys == 0 || state->keysize == 0) { +- // no files (or no digests(!)) +- state->table = NULL; ++ /* no files (or no digests(!)) */ ++ state->table = NULL; + } else { +- int table_size = state->keys * (state->keysize + sizeof(rpm_loff_t)); +- state->table = rcalloc(1, table_size); +- if (Fread(state->table, table_size, 1, state->fd) != table_size) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to read table\n")); +- return RPMRC_FAIL; +- } +- state->inodeIndexes = inodeIndexHashCreate(state->keys, inodeId, inodeCmp, NULL, NULL); ++ int table_size = state->keys * (state->keysize + sizeof(rpm_loff_t)); ++ state->table = rcalloc(1, table_size); ++ if (Fread(state->table, table_size, 1, state->fd) != table_size) { ++ rpmlog(RPMLOG_ERR, _("reflink: unable to read table\n")); ++ return RPMRC_FAIL; ++ } ++ state->inodeIndexes = inodeIndexHashCreate( ++ state->keys, inodeId, inodeCmp, NULL, NULL ++ ); + } + +- // seek back to original location +- // might not be needed if we seek to offset immediately ++ /* Seek back to original location. ++ * Might not be needed if we seek to offset immediately ++ */ + if (Fseek(state->fd, current, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to seek back to original location\n")); +- return RPMRC_FAIL; ++ rpmlog(RPMLOG_ERR, ++ _("reflink: unable to seek back to original location\n")); ++ return RPMRC_FAIL; + } + return RPMRC_OK; + } +@@ -192,40 +202,45 @@ static rpmRC reflink_psm_post(rpmPlugin plugin, rpmte te, int res) + reflink_state state = rpmPluginGetData(plugin); + state->files = rpmfilesFree(state->files); + if (state->table) { +- free(state->table); +- state->table = NULL; ++ free(state->table); ++ state->table = NULL; + } + if (state->inodeIndexes) { +- inodeIndexHashFree(state->inodeIndexes); +- state->inodeIndexes = NULL; ++ inodeIndexHashFree(state->inodeIndexes); ++ state->inodeIndexes = NULL; + } + return RPMRC_OK; + } + + +-// have a prototype, warnings system ++/* have a prototype, warnings system */ + rpm_loff_t find(const unsigned char *digest, reflink_state state); + + rpm_loff_t find(const unsigned char *digest, reflink_state state) { + # if defined(__GNUC__) +- /* GCC nested function because bsearch's comparison function can't access +- state-keysize otherwise +- */ +- int cmpdigest(const void *k1, const void *k2) { +- rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2); +- return memcmp(k1, k2, state->keysize); +- } ++ /* GCC nested function because bsearch's comparison function can't access ++ * state-keysize otherwise ++ */ ++ int cmpdigest(const void *k1, const void *k2) { ++ rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2); ++ return memcmp(k1, k2, state->keysize); ++ } + # endif +- rpmlog(RPMLOG_DEBUG, _("reflink: bsearch(key=%p, base=%p, nmemb=%d, size=%lu)\n"), digest, state->table, state->keys, state->keysize + sizeof(rpm_loff_t)); +- char *entry = bsearch(digest, state->table, state->keys, state->keysize + sizeof(rpm_loff_t), cmpdigest); +- if (entry == NULL) { +- return NOT_FOUND; +- } +- rpm_loff_t offset = *(rpm_loff_t *)(entry + state->keysize); +- return offset; ++ rpmlog(RPMLOG_DEBUG, ++ _("reflink: bsearch(key=%p, base=%p, nmemb=%d, size=%lu)\n"), ++ digest, state->table, state->keys, ++ state->keysize + sizeof(rpm_loff_t)); ++ char *entry = bsearch(digest, state->table, state->keys, ++ state->keysize + sizeof(rpm_loff_t), cmpdigest); ++ if (entry == NULL) { ++ return NOT_FOUND; ++ } ++ rpm_loff_t offset = *(rpm_loff_t *)(entry + state->keysize); ++ return offset; + } + +-static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path, mode_t file_mode, rpmFsmOp op) ++static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path, ++ mode_t file_mode, rpmFsmOp op) + { + struct file_clone_range fcr; + rpm_loff_t size; +@@ -234,99 +249,119 @@ static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path, + + reflink_state state = rpmPluginGetData(plugin); + if (state->table == NULL) { +- // no table means rpm is not in reflink format, so leave. Now. +- return RPMRC_OK; ++ /* no table means rpm is not in reflink format, so leave. Now. */ ++ return RPMRC_OK; + } + if (op == FA_TOUCH) { +- // we're not overwriting an existing file +- return RPMRC_OK; ++ /* we're not overwriting an existing file. */ ++ return RPMRC_OK; + } + fcr.dest_offset = 0; + if (S_ISREG(file_mode) && !(rpmfiFFlags(fi) & RPMFILE_GHOST)) { +- rpm_ino_t inode = rpmfiFInode(fi); +- /* check for hard link entry in table. GetEntry overwrites hlix with the address of the first match */ +- if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hlix, NULL, NULL)) { +- // entry is in table, use hard link +- char *fn = rpmfilesFN(state->files, hlix[0]); +- if (link(fn, path) != 0) { +- rpmlog(RPMLOG_ERR, _("reflink: Unable to hard link %s -> %s due to %s\n"), fn, path, strerror(errno)); +- free(fn); +- return RPMRC_FAIL; +- } +- free(fn); +- return RPMRC_PLUGIN_CONTENTS; +- } +- /* if we didn't hard link, then we'll track this inode as being created soon */ +- if (rpmfiFNlink(fi) > 1) { +- /* minor optimization: only store files with more than one link */ +- inodeIndexHashAddEntry(state->inodeIndexes, inode, rpmfiFX(fi)); +- } +- /* derived from wfd_open in fsm.c */ +- mode_t old_umask = umask(0577); +- dst = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR); +- umask(old_umask); +- if (dst == -1) { +- rpmlog(RPMLOG_ERR, _("reflink: Unable to open %s for writing due to %s, flags = %x\n"), path, strerror(errno), rpmfiFFlags(fi)); +- return RPMRC_FAIL; +- } +- size = rpmfiFSize(fi); +- if (size > 0) { +- /* round src_length down to fundamental_block_size multiple */ +- fcr.src_length = size / state->fundamental_block_size * state->fundamental_block_size; +- if ((size % state->fundamental_block_size) > 0) { +- /* round up to next fundamental_block_size. We expect the data in the rpm to be similarly padded */ +- fcr.src_length += state->fundamental_block_size; +- } +- fcr.src_fd = Fileno(state->fd); +- if (fcr.src_fd == -1) { +- close(dst); +- rpmlog(RPMLOG_ERR, _("reflink: src fd lookup failed\n")); +- return RPMRC_FAIL; +- } +- fcr.src_offset = find(rpmfiFDigest(fi, NULL, NULL), state); +- if (fcr.src_offset == NOT_FOUND) { +- close(dst); +- rpmlog(RPMLOG_ERR, _("reflink: digest not found\n")); +- return RPMRC_FAIL; +- } +- rpmlog(RPMLOG_DEBUG, _("reflink: Reflinking %lu bytes at %lu to %s orig size=%lu, file=%ld\n"), fcr.src_length, fcr.src_offset, path, size, fcr.src_fd); +- rc = ioctl(dst, FICLONERANGE, &fcr); +- if (rc) { +- rpmlog(RPMLOG_WARNING, _("reflink: falling back to copying bits for %s due to %d, %d = %s\n"), path, rc, errno, strerror(errno)); +- if (Fseek(state->fd, fcr.src_offset, SEEK_SET) < 0) { +- close(dst); +- rpmlog(RPMLOG_ERR, _("reflink: unable to seek on copying bits\n")); +- return RPMRC_FAIL; +- } +- rpm_loff_t left = size; +- size_t len, read, written; +- while (left) { +- len = (left > BUFFER_SIZE ? BUFFER_SIZE : left); +- read = Fread(state->buffer, len, 1, state->fd); +- if (read != len) { +- close(dst); +- rpmlog(RPMLOG_ERR, _("reflink: short read on copying bits\n")); +- return RPMRC_FAIL; +- } +- written = write(dst, state->buffer, len); +- if (read != written) { +- close(dst); +- rpmlog(RPMLOG_ERR, _("reflink: short write on copying bits\n")); +- return RPMRC_FAIL; +- } +- left -= len; +- } +- } else { +- /* reflink worked, so truncate */ +- rc = ftruncate(dst, size); +- if (rc) { +- rpmlog(RPMLOG_ERR, _("reflink: Unable to truncate %s to %ld due to %s\n"), path, size, strerror(errno)); +- return RPMRC_FAIL; +- } +- } +- } +- close(dst); +- return RPMRC_PLUGIN_CONTENTS; ++ rpm_ino_t inode = rpmfiFInode(fi); ++ /* check for hard link entry in table. GetEntry overwrites hlix with ++ * the address of the first match. ++ */ ++ if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hlix, NULL, ++ NULL)) { ++ /* entry is in table, use hard link */ ++ char *fn = rpmfilesFN(state->files, hlix[0]); ++ if (link(fn, path) != 0) { ++ rpmlog(RPMLOG_ERR, ++ _("reflink: Unable to hard link %s -> %s due to %s\n"), ++ fn, path, strerror(errno)); ++ free(fn); ++ return RPMRC_FAIL; ++ } ++ free(fn); ++ return RPMRC_PLUGIN_CONTENTS; ++ } ++ /* if we didn't hard link, then we'll track this inode as being ++ * created soon ++ */ ++ if (rpmfiFNlink(fi) > 1) { ++ /* minor optimization: only store files with more than one link */ ++ inodeIndexHashAddEntry(state->inodeIndexes, inode, rpmfiFX(fi)); ++ } ++ /* derived from wfd_open in fsm.c */ ++ mode_t old_umask = umask(0577); ++ dst = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR); ++ umask(old_umask); ++ if (dst == -1) { ++ rpmlog(RPMLOG_ERR, ++ _("reflink: Unable to open %s for writing due to %s, flags = %x\n"), ++ path, strerror(errno), rpmfiFFlags(fi)); ++ return RPMRC_FAIL; ++ } ++ size = rpmfiFSize(fi); ++ if (size > 0) { ++ /* round src_length down to fundamental_block_size multiple */ ++ fcr.src_length = size / state->fundamental_block_size * state->fundamental_block_size; ++ if ((size % state->fundamental_block_size) > 0) { ++ /* round up to next fundamental_block_size. We expect the data ++ * in the rpm to be similarly padded. ++ */ ++ fcr.src_length += state->fundamental_block_size; ++ } ++ fcr.src_fd = Fileno(state->fd); ++ if (fcr.src_fd == -1) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, _("reflink: src fd lookup failed\n")); ++ return RPMRC_FAIL; ++ } ++ fcr.src_offset = find(rpmfiFDigest(fi, NULL, NULL), state); ++ if (fcr.src_offset == NOT_FOUND) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, _("reflink: digest not found\n")); ++ return RPMRC_FAIL; ++ } ++ rpmlog(RPMLOG_DEBUG, ++ _("reflink: Reflinking %lu bytes at %lu to %s orig size=%lu, file=%ld\n"), ++ fcr.src_length, fcr.src_offset, path, size, fcr.src_fd); ++ rc = ioctl(dst, FICLONERANGE, &fcr); ++ if (rc) { ++ rpmlog(RPMLOG_WARNING, ++ _("reflink: falling back to copying bits for %s due to %d, %d = %s\n"), ++ path, rc, errno, strerror(errno)); ++ if (Fseek(state->fd, fcr.src_offset, SEEK_SET) < 0) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, ++ _("reflink: unable to seek on copying bits\n")); ++ return RPMRC_FAIL; ++ } ++ rpm_loff_t left = size; ++ size_t len, read, written; ++ while (left) { ++ len = (left > BUFFER_SIZE ? BUFFER_SIZE : left); ++ read = Fread(state->buffer, len, 1, state->fd); ++ if (read != len) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, ++ _("reflink: short read on copying bits\n")); ++ return RPMRC_FAIL; ++ } ++ written = write(dst, state->buffer, len); ++ if (read != written) { ++ close(dst); ++ rpmlog(RPMLOG_ERR, ++ _("reflink: short write on copying bits\n")); ++ return RPMRC_FAIL; ++ } ++ left -= len; ++ } ++ } else { ++ /* reflink worked, so truncate */ ++ rc = ftruncate(dst, size); ++ if (rc) { ++ rpmlog(RPMLOG_ERR, ++ _("reflink: Unable to truncate %s to %ld due to %s\n"), ++ path, size, strerror(errno)); ++ return RPMRC_FAIL; ++ } ++ } ++ } ++ close(dst); ++ return RPMRC_PLUGIN_CONTENTS; + } + return RPMRC_OK; + } +diff --git a/rpm2extents.c b/rpm2extents.c +index 5662b86a6..c111be0a2 100644 +--- a/rpm2extents.c ++++ b/rpm2extents.c +@@ -24,7 +24,7 @@ + #include "debug.h" + + /* hash of void * (pointers) to file digests to offsets within output. +- The length of the key depends on what the FILEDIGESTALGO is. ++ * The length of the key depends on what the FILEDIGESTALGO is. + */ + #undef HASHTYPE + #undef HTKEYTYPE +@@ -34,7 +34,9 @@ + #include "lib/rpmhash.H" + #include "lib/rpmhash.C" + +-/* magic value at end of file (64 bits) that indicates this is a transcoded rpm */ ++/* magic value at end of file (64 bits) that indicates this is a transcoded ++ * rpm. ++ */ + #define MAGIC 3472329499408095051 + + struct digestoffset { +@@ -64,77 +66,54 @@ static int digestor( + int algo; + rpmRC rc = RPMRC_FAIL; + +- for (algo = 0; algo < algos_len; algo++) +- { +- fdInitDigest(fdi, algos[algo], 0); ++ for (algo = 0; algo < algos_len; algo++) { ++ fdInitDigest(fdi, algos[algo], 0); + } + fdilength = ufdCopy(fdi, fdo); +- if (fdilength == -1) +- { +- fprintf(stderr, _("digest cat failed\n")); +- goto exit; ++ if (fdilength == -1) { ++ fprintf(stderr, _("digest cat failed\n")); ++ goto exit; + } + + len = sizeof(fdilength); +- if (Fwrite(&fdilength, len, 1, validationo) != len) +- { +- fprintf(stderr, _("Unable to write input length %zd\n"), fdilength); +- goto exit; ++ if (Fwrite(&fdilength, len, 1, validationo) != len) { ++ fprintf(stderr, _("Unable to write input length %zd\n"), fdilength); ++ goto exit; + } + len = sizeof(algos_len); +- if (Fwrite(&algos_len, len, 1, validationo) != len) +- { +- fprintf(stderr, _("Unable to write number of validation digests\n")); +- goto exit; ++ if (Fwrite(&algos_len, len, 1, validationo) != len) { ++ fprintf(stderr, _("Unable to write number of validation digests\n")); ++ goto exit; + } +- for (algo = 0; algo < algos_len; algo++) +- { +- fdFiniDigest(fdi, algos[algo], (void **)&filedigest, &filedigest_len, 0); +- +- algo_name = pgpValString(PGPVAL_HASHALGO, algos[algo]); +- algo_name_len = (uint32_t)strlen(algo_name); +- algo_digest_len = (uint32_t)filedigest_len; +- +- len = sizeof(algo_name_len); +- if (Fwrite(&algo_name_len, len, 1, validationo) != len) +- { +- fprintf( +- stderr, +- _("Unable to write validation algo name length\n") +- ); +- goto exit; +- } +- len = sizeof(algo_digest_len); +- if (Fwrite(&algo_digest_len, len, 1, validationo) != len) +- { +- fprintf( +- stderr, +- _("Unable to write number of bytes for validation digest\n") +- ); +- goto exit; +- } +- if (Fwrite(algo_name, algo_name_len, 1, validationo) != algo_name_len) +- { +- fprintf(stderr, _("Unable to write validation algo name\n")); +- goto exit; +- } +- if ( +- Fwrite( +- filedigest, +- algo_digest_len, +- 1, +- validationo +- ) != algo_digest_len +- ) +- { +- fprintf( +- stderr, +- _("Unable to write validation digest value %u, %zu\n"), +- algo_digest_len, +- filedigest_len +- ); +- goto exit; +- } ++ for (algo = 0; algo < algos_len; algo++) { ++ fdFiniDigest(fdi, algos[algo], (void **)&filedigest, &filedigest_len, 0); ++ ++ algo_name = pgpValString(PGPVAL_HASHALGO, algos[algo]); ++ algo_name_len = (uint32_t)strlen(algo_name); ++ algo_digest_len = (uint32_t)filedigest_len; ++ ++ len = sizeof(algo_name_len); ++ if (Fwrite(&algo_name_len, len, 1, validationo) != len) { ++ fprintf(stderr, ++ _("Unable to write validation algo name length\n")); ++ goto exit; ++ } ++ len = sizeof(algo_digest_len); ++ if (Fwrite(&algo_digest_len, len, 1, validationo) != len) { ++ fprintf(stderr, ++ _("Unable to write number of bytes for validation digest\n")); ++ goto exit; ++ } ++ if (Fwrite(algo_name, algo_name_len, 1, validationo) != algo_name_len) { ++ fprintf(stderr, _("Unable to write validation algo name\n")); ++ goto exit; ++ } ++ if (Fwrite(filedigest, algo_digest_len, 1, validationo ) != algo_digest_len) { ++ fprintf(stderr, ++ _("Unable to write validation digest value %u, %zu\n"), ++ algo_digest_len, filedigest_len); ++ goto exit; ++ } + } + rc = RPMRC_OK; + exit: +@@ -145,23 +124,20 @@ static rpmRC process_package(FD_t fdi, FD_t validationi) + { + uint32_t diglen; + /* GNU C extension: can use diglen from outer context */ +- int digestSetCmp(const unsigned char * a, const unsigned char * b) +- { +- return memcmp(a, b, diglen); ++ int digestSetCmp(const unsigned char * a, const unsigned char * b) { ++ return memcmp(a, b, diglen); + } + +- unsigned int digestSetHash(const unsigned char * digest) +- { ++ unsigned int digestSetHash(const unsigned char * digest) { + /* assumes sizeof(unsigned int) < diglen */ + return *(unsigned int *)digest; + } + +- int digestoffsetCmp(const void * a, const void * b) +- { +- return digestSetCmp( +- ((struct digestoffset *)a)->digest, +- ((struct digestoffset *)b)->digest +- ); ++ int digestoffsetCmp(const void * a, const void * b) { ++ return digestSetCmp( ++ ((struct digestoffset *)a)->digest, ++ ((struct digestoffset *)b)->digest ++ ); + } + + FD_t fdo; +@@ -179,65 +155,52 @@ static rpmRC process_package(FD_t fdi, FD_t validationi) + + fdo = fdDup(STDOUT_FILENO); + +- if (rpmReadPackageRaw(fdi, &sigh, &h)) +- { +- fprintf(stderr, _("Error reading package\n")); +- exit(EXIT_FAILURE); ++ if (rpmReadPackageRaw(fdi, &sigh, &h)) { ++ fprintf(stderr, _("Error reading package\n")); ++ exit(EXIT_FAILURE); + } + + if (rpmLeadWrite(fdo, h)) + { +- fprintf( +- stderr, +- _("Unable to write package lead: %s\n"), +- Fstrerror(fdo) +- ); +- exit(EXIT_FAILURE); ++ fprintf(stderr, _("Unable to write package lead: %s\n"), ++ Fstrerror(fdo)); ++ exit(EXIT_FAILURE); + } + +- if (rpmWriteSignature(fdo, sigh)) +- { +- fprintf(stderr, _("Unable to write signature: %s\n"), Fstrerror(fdo)); +- exit(EXIT_FAILURE); ++ if (rpmWriteSignature(fdo, sigh)) { ++ fprintf(stderr, _("Unable to write signature: %s\n"), Fstrerror(fdo)); ++ exit(EXIT_FAILURE); + } + +- if (headerWrite(fdo, h, HEADER_MAGIC_YES)) +- { +- fprintf(stderr, _("Unable to write headers: %s\n"), Fstrerror(fdo)); +- exit(EXIT_FAILURE); ++ if (headerWrite(fdo, h, HEADER_MAGIC_YES)) { ++ fprintf(stderr, _("Unable to write headers: %s\n"), Fstrerror(fdo)); ++ exit(EXIT_FAILURE); + } + + /* Retrieve payload size and compression type. */ +- { const char *compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR); +- rpmio_flags = rstrscat(NULL, "r.", compr ? compr : "gzip", NULL); ++ { ++ const char *compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR); ++ rpmio_flags = rstrscat(NULL, "r.", compr ? compr : "gzip", NULL); + } + + gzdi = Fdopen(fdi, rpmio_flags); /* XXX gzdi == fdi */ + free(rpmio_flags); + +- if (gzdi == NULL) +- { +- fprintf(stderr, _("cannot re-open payload: %s\n"), Fstrerror(gzdi)); +- exit(EXIT_FAILURE); ++ if (gzdi == NULL) { ++ fprintf(stderr, _("cannot re-open payload: %s\n"), Fstrerror(gzdi)); ++ exit(EXIT_FAILURE); + } + + rpmfiles files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER); +- rpmfi fi = rpmfiNewArchiveReader( +- gzdi, +- files, +- RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST +- ); ++ rpmfi fi = rpmfiNewArchiveReader(gzdi, files, ++ RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST); + + /* this is encoded in the file format, so needs to be fixed size (for +- now?) +- */ ++ * now?) ++ */ + diglen = (uint32_t)rpmDigestLength(rpmfiDigestAlgo(fi)); +- digestSet ds = digestSetCreate( +- rpmfiFC(fi), +- digestSetHash, +- digestSetCmp, +- NULL +- ); ++ digestSet ds = digestSetCreate(rpmfiFC(fi), digestSetHash, digestSetCmp, ++ NULL); + struct digestoffset offsets[rpmfiFC(fi)]; + pos = RPMLEAD_SIZE + headerSizeof(sigh, HEADER_MAGIC_YES); + +@@ -247,139 +210,114 @@ static rpmRC process_package(FD_t fdi, FD_t validationi) + + zeros = xcalloc(fundamental_block_size, 1); + +- while (next >= 0) +- { +- next = rpmfiNext(fi); +- if (next == RPMERR_ITER_END) +- { +- rc = RPMRC_OK; +- break; +- } +- mode = rpmfiFMode(fi); +- if (!S_ISREG(mode) || !rpmfiArchiveHasContent(fi)) +- { +- /* not a regular file, or the archive doesn't contain any content for +- this entry +- */ +- continue; +- } +- digest = rpmfiFDigest(fi, NULL, NULL); +- if (digestSetGetEntry(ds, digest, NULL)) +- { +- /* This specific digest has already been included, so skip it */ +- continue; +- } +- pad = pad_to(pos, fundamental_block_size); +- if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) +- { +- fprintf(stderr, _("Unable to write padding\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- /* round up to next fundamental_block_size */ +- pos += pad; +- digestSetAddEntry(ds, digest); +- offsets[offset_ix].digest = digest; +- offsets[offset_ix].pos = pos; +- offset_ix++; +- size = rpmfiFSize(fi); +- rc = rpmfiArchiveReadToFile(fi, fdo, 0); +- if (rc != RPMRC_OK) +- { +- fprintf(stderr, _("rpmfiArchiveReadToFile failed with %d\n"), rc); +- goto exit; +- } +- pos += size; ++ while (next >= 0) { ++ next = rpmfiNext(fi); ++ if (next == RPMERR_ITER_END) { ++ rc = RPMRC_OK; ++ break; ++ } ++ mode = rpmfiFMode(fi); ++ if (!S_ISREG(mode) || !rpmfiArchiveHasContent(fi)) { ++ /* not a regular file, or the archive doesn't contain any content ++ * for this entry. ++ */ ++ continue; ++ } ++ digest = rpmfiFDigest(fi, NULL, NULL); ++ if (digestSetGetEntry(ds, digest, NULL)) { ++ /* This specific digest has already been included, so skip it. */ ++ continue; ++ } ++ pad = pad_to(pos, fundamental_block_size); ++ if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) { ++ fprintf(stderr, _("Unable to write padding\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ /* round up to next fundamental_block_size */ ++ pos += pad; ++ digestSetAddEntry(ds, digest); ++ offsets[offset_ix].digest = digest; ++ offsets[offset_ix].pos = pos; ++ offset_ix++; ++ size = rpmfiFSize(fi); ++ rc = rpmfiArchiveReadToFile(fi, fdo, 0); ++ if (rc != RPMRC_OK) { ++ fprintf(stderr, _("rpmfiArchiveReadToFile failed with %d\n"), rc); ++ goto exit; ++ } ++ pos += size; + } + Fclose(gzdi); /* XXX gzdi == fdi */ + +- qsort( +- offsets, +- (size_t)offset_ix, +- sizeof(struct digestoffset), +- digestoffsetCmp +- ); ++ qsort(offsets, (size_t)offset_ix, sizeof(struct digestoffset), ++ digestoffsetCmp); + + len = sizeof(offset_ix); +- if (Fwrite(&offset_ix, len, 1, fdo) != len) +- { +- fprintf(stderr, _("Unable to write length of table\n")); +- rc = RPMRC_FAIL; +- goto exit; ++ if (Fwrite(&offset_ix, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write length of table\n")); ++ rc = RPMRC_FAIL; ++ goto exit; + } + len = sizeof(diglen); +- if (Fwrite(&diglen, len, 1, fdo) != len) +- { +- fprintf(stderr, _("Unable to write length of digest\n")); +- rc = RPMRC_FAIL; +- goto exit; ++ if (Fwrite(&diglen, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write length of digest\n")); ++ rc = RPMRC_FAIL; ++ goto exit; + } + len = sizeof(rpm_loff_t); +- for (int x = 0; x < offset_ix; x++) +- { +- if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) +- { +- fprintf(stderr, _("Unable to write digest\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) +- { +- fprintf(stderr, _("Unable to write offset\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } ++ for (int x = 0; x < offset_ix; x++) { ++ if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) { ++ fprintf(stderr, _("Unable to write digest\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write offset\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } + } + validation_pos = ( +- pos + sizeof(offset_ix) + sizeof(diglen) + +- offset_ix * (diglen + sizeof(rpm_loff_t)) ++ pos + sizeof(offset_ix) + sizeof(diglen) + ++ offset_ix * (diglen + sizeof(rpm_loff_t)) + ); + + ssize_t validation_len = ufdCopy(validationi, fdo); +- if (validation_len == -1) +- { +- fprintf(stderr, _("digest table ufdCopy failed\n")); +- rc = RPMRC_FAIL; +- goto exit; ++ if (validation_len == -1) { ++ fprintf(stderr, _("digest table ufdCopy failed\n")); ++ rc = RPMRC_FAIL; ++ goto exit; + } + /* add more padding so the last file can be cloned. It doesn't matter that +- the table and validation etc are in this space. In fact, it's pretty +- efficient if it is ++ * the table and validation etc are in this space. In fact, it's pretty ++ * efficient if it is. + */ + +- pad = pad_to( +- ( +- validation_pos + validation_len + 2 * sizeof(rpm_loff_t) + +- sizeof(uint64_t) +- ), +- fundamental_block_size +- ); +- if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) +- { +- fprintf(stderr, _("Unable to write final padding\n")); +- rc = RPMRC_FAIL; +- goto exit; ++ pad = pad_to((validation_pos + validation_len + 2 * sizeof(rpm_loff_t) + ++ sizeof(uint64_t)), fundamental_block_size); ++ if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) { ++ fprintf(stderr, _("Unable to write final padding\n")); ++ rc = RPMRC_FAIL; ++ goto exit; + } + zeros = _free(zeros); +- if (Fwrite(&pos, len, 1, fdo) != len) +- { +- fprintf(stderr, _("Unable to write offset of digest table\n")); +- rc = RPMRC_FAIL; +- goto exit; ++ if (Fwrite(&pos, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write offset of digest table\n")); ++ rc = RPMRC_FAIL; ++ goto exit; + } +- if (Fwrite(&validation_pos, len, 1, fdo) != len) +- { +- fprintf(stderr, _("Unable to write offset of validation table\n")); +- rc = RPMRC_FAIL; +- goto exit; ++ if (Fwrite(&validation_pos, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write offset of validation table\n")); ++ rc = RPMRC_FAIL; ++ goto exit; + } + uint64_t magic = MAGIC; + len = sizeof(magic); +- if (Fwrite(&magic, len, 1, fdo) != len) +- { +- fprintf(stderr, _("Unable to write magic\n")); +- rc = RPMRC_FAIL; +- goto exit; ++ if (Fwrite(&magic, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write magic\n")); ++ rc = RPMRC_FAIL; ++ goto exit; + } + + exit: +@@ -389,8 +327,7 @@ exit: + return rc; + } + +-int main(int argc, char *argv[]) +-{ ++int main(int argc, char *argv[]) { + rpmRC rc; + int cprc = 0; + uint8_t algos[argc - 1]; +@@ -402,118 +339,95 @@ int main(int argc, char *argv[]) + xsetprogname(argv[0]); /* Portability call -- see system.h */ + rpmReadConfigFiles(NULL, NULL); + +- if (argc > 1 && (rstreq(argv[1], "-h") || rstreq(argv[1], "--help"))) +- { +- fprintf(stderr, _("Usage: %s [DIGESTALGO]...\n"), argv[0]); +- exit(EXIT_FAILURE); ++ if (argc > 1 && (rstreq(argv[1], "-h") || rstreq(argv[1], "--help"))) { ++ fprintf(stderr, _("Usage: %s [DIGESTALGO]...\n"), argv[0]); ++ exit(EXIT_FAILURE); + } + +- if (argc == 1) +- { +- fprintf( +- stderr, +- _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n") +- ); +- exit(EXIT_FAILURE); ++ if (argc == 1) { ++ fprintf(stderr, ++ _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n")); ++ exit(EXIT_FAILURE); + } + +- for (int x = 0; x < (argc - 1); x++) +- { +- if (pgpStringVal(PGPVAL_HASHALGO, argv[x + 1], &algos[x]) != 0) +- { +- fprintf( +- stderr, +- _("Unable to resolve '%s' as a digest algorithm, exiting\n"), +- argv[x + 1] +- ); +- exit(EXIT_FAILURE); +- } ++ for (int x = 0; x < (argc - 1); x++) { ++ if (pgpStringVal(PGPVAL_HASHALGO, argv[x + 1], &algos[x]) != 0) ++ { ++ fprintf(stderr, ++ _("Unable to resolve '%s' as a digest algorithm, exiting\n"), ++ argv[x + 1]); ++ exit(EXIT_FAILURE); ++ } + } + + +- if (pipe(mainpipefd) == -1) +- { +- fprintf(stderr, _("Main pipe failure\n")); +- exit(EXIT_FAILURE); ++ if (pipe(mainpipefd) == -1) { ++ fprintf(stderr, _("Main pipe failure\n")); ++ exit(EXIT_FAILURE); + } +- if (pipe(metapipefd) == -1) +- { +- fprintf(stderr, _("Meta pipe failure\n")); +- exit(EXIT_FAILURE); ++ if (pipe(metapipefd) == -1) { ++ fprintf(stderr, _("Meta pipe failure\n")); ++ exit(EXIT_FAILURE); + } + cpid = fork(); +- if (cpid == 0) +- { +- /* child: digestor */ +- close(mainpipefd[0]); +- close(metapipefd[0]); +- FD_t fdi = fdDup(STDIN_FILENO); +- FD_t fdo = fdDup(mainpipefd[1]); +- FD_t validationo = fdDup(metapipefd[1]); +- rc = digestor(fdi, fdo, validationo, algos, argc - 1); +- Fclose(validationo); +- Fclose(fdo); +- Fclose(fdi); ++ if (cpid == 0) { ++ /* child: digestor */ ++ close(mainpipefd[0]); ++ close(metapipefd[0]); ++ FD_t fdi = fdDup(STDIN_FILENO); ++ FD_t fdo = fdDup(mainpipefd[1]); ++ FD_t validationo = fdDup(metapipefd[1]); ++ rc = digestor(fdi, fdo, validationo, algos, argc - 1); ++ Fclose(validationo); ++ Fclose(fdo); ++ Fclose(fdi); + } else { +- /* parent: main program */ +- close(mainpipefd[1]); +- close(metapipefd[1]); +- FD_t fdi = fdDup(mainpipefd[0]); +- FD_t validationi = fdDup(metapipefd[0]); +- rc = process_package(fdi, validationi); +- Fclose(validationi); +- /* fdi is normally closed through the stacked file gzdi in the function. */ +- /* wait for child process (digestor for stdin) to complete. */ +- if (rc != RPMRC_OK) +- { +- if (kill(cpid, SIGTERM) != 0) +- { +- fprintf( +- stderr, +- _("Failed to kill digest process when main process failed: %s\n"), +- strerror(errno) +- ); +- } +- } +- w = waitpid(cpid, &wstatus, 0); +- if (w == -1) +- { +- fprintf(stderr, _("waitpid failed\n")); +- cprc = EXIT_FAILURE; +- } else if (WIFEXITED(wstatus)) +- { +- cprc = WEXITSTATUS(wstatus); +- if (cprc != 0) +- { +- fprintf( +- stderr, +- _("Digest process non-zero exit code %d\n"), +- cprc +- ); +- } +- } else if (WIFSIGNALED(wstatus)) +- { +- fprintf( +- stderr, +- _("Digest process was terminated with a signal: %d\n"), +- WTERMSIG(wstatus) +- ); +- cprc = EXIT_FAILURE; +- } else +- { +- /* don't think this can happen, but covering all bases */ +- fprintf(stderr, _("Unhandled circumstance in waitpid\n")); +- cprc = EXIT_FAILURE; +- } +- if (cprc != EXIT_SUCCESS) +- { +- rc = RPMRC_FAIL; +- } ++ /* parent: main program */ ++ close(mainpipefd[1]); ++ close(metapipefd[1]); ++ FD_t fdi = fdDup(mainpipefd[0]); ++ FD_t validationi = fdDup(metapipefd[0]); ++ rc = process_package(fdi, validationi); ++ Fclose(validationi); ++ /* fdi is normally closed through the stacked file gzdi in the ++ * function. ++ * Wait for child process (digestor for stdin) to complete. ++ */ ++ if (rc != RPMRC_OK) { ++ if (kill(cpid, SIGTERM) != 0) { ++ fprintf(stderr, ++ _("Failed to kill digest process when main process failed: %s\n"), ++ strerror(errno)); ++ } ++ } ++ w = waitpid(cpid, &wstatus, 0); ++ if (w == -1) { ++ fprintf(stderr, _("waitpid failed\n")); ++ cprc = EXIT_FAILURE; ++ } else if (WIFEXITED(wstatus)) { ++ cprc = WEXITSTATUS(wstatus); ++ if (cprc != 0) { ++ fprintf(stderr, ++ _("Digest process non-zero exit code %d\n"), ++ cprc); ++ } ++ } else if (WIFSIGNALED(wstatus)) { ++ fprintf(stderr, ++ _("Digest process was terminated with a signal: %d\n"), ++ WTERMSIG(wstatus)); ++ cprc = EXIT_FAILURE; ++ } else { ++ /* Don't think this can happen, but covering all bases */ ++ fprintf(stderr, _("Unhandled circumstance in waitpid\n")); ++ cprc = EXIT_FAILURE; ++ } ++ if (cprc != EXIT_SUCCESS) { ++ rc = RPMRC_FAIL; ++ } + } +- if (rc != RPMRC_OK) +- { +- /* translate rpmRC into generic failure return code. */ +- return EXIT_FAILURE; ++ if (rc != RPMRC_OK) { ++ /* translate rpmRC into generic failure return code. */ ++ return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } +-- +2.35.1 + diff --git a/0004-Fix-printf-formatting-in-reflink.c.patch b/0004-Fix-printf-formatting-in-reflink.c.patch new file mode 100644 index 0000000..c33f3d5 --- /dev/null +++ b/0004-Fix-printf-formatting-in-reflink.c.patch @@ -0,0 +1,27 @@ +From 15127592f8cc3221129f61b79319d88c7727bec3 Mon Sep 17 00:00:00 2001 +From: Matthew Almond +Date: Sun, 31 Jan 2021 15:24:25 -0800 +Subject: [PATCH 04/30] Fix printf formatting in reflink.c + +There were some mismatches on field "sizes". This should eliminate the +error messages. +--- + plugins/reflink.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/plugins/reflink.c b/plugins/reflink.c +index 9eaa87094..513887604 100644 +--- a/plugins/reflink.c ++++ b/plugins/reflink.c +@@ -316,7 +316,7 @@ static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path, + return RPMRC_FAIL; + } + rpmlog(RPMLOG_DEBUG, +- _("reflink: Reflinking %lu bytes at %lu to %s orig size=%lu, file=%ld\n"), ++ _("reflink: Reflinking %llu bytes at %llu to %s orig size=%ld, file=%lld\n"), + fcr.src_length, fcr.src_offset, path, size, fcr.src_fd); + rc = ioctl(dst, FICLONERANGE, &fcr); + if (rc) { +-- +2.35.1 + diff --git a/0005-tests-rpm2extents-Add-basic-tests-for-rpm2extents.patch b/0005-tests-rpm2extents-Add-basic-tests-for-rpm2extents.patch new file mode 100644 index 0000000..ff335d7 --- /dev/null +++ b/0005-tests-rpm2extents-Add-basic-tests-for-rpm2extents.patch @@ -0,0 +1,73 @@ +From fd4060dcfbe4127fb0d19f1878d0d8b9f34c7b9a Mon Sep 17 00:00:00 2001 +From: chantra +Date: Fri, 28 Jan 2022 08:33:16 -0800 +Subject: [PATCH 05/30] [tests][rpm2extents] Add basic tests for rpm2extents + +--- + tests/Makefile.am | 1 + + tests/rpm2extents.at | 31 +++++++++++++++++++++++++++++++ + tests/rpmtests.at | 1 + + 3 files changed, 33 insertions(+) + create mode 100644 tests/rpm2extents.at + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index f78e17c3e..fc8a24a5e 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -36,6 +36,7 @@ TESTSUITE_AT += rpmio.at + TESTSUITE_AT += rpmio.at + TESTSUITE_AT += rpmorder.at + TESTSUITE_AT += rpmvfylevel.at ++TESTSUITE_AT += rpm2extents.at + EXTRA_DIST += $(TESTSUITE_AT) + + ## testsuite data +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +new file mode 100644 +index 000000000..f943b9af4 +--- /dev/null ++++ b/tests/rpm2extents.at +@@ -0,0 +1,31 @@ ++# rpm2extents.at: Some very basic checks ++# ++# Copyright (C) 2022 Manu Bretelle ++# ++# 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; either version 2 of the License, or ++# (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++AT_BANNER([rpm2extents tests]) ++ ++# ------------------------------ ++ ++# check that transcoder write magic at the end ++AT_SETUP([rpm2extents magic]) ++AT_KEYWORDS([rpm2extents]) ++AT_CHECK([runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 | tail -c8], ++[0], ++[KWTSH100], ++[ignore]) ++AT_CLEANUP ++ +diff --git a/tests/rpmtests.at b/tests/rpmtests.at +index a1adab8e0..205fed6a3 100644 +--- a/tests/rpmtests.at ++++ b/tests/rpmtests.at +@@ -21,3 +21,4 @@ m4_include([rpmreplace.at]) + m4_include([rpmconfig.at]) + m4_include([rpmconfig2.at]) + m4_include([rpmconfig3.at]) ++m4_include([rpm2extents.at]) +-- +2.35.1 + diff --git a/0006-rpm2extents-verify-package-signature-during-transcod.patch b/0006-rpm2extents-verify-package-signature-during-transcod.patch new file mode 100644 index 0000000..c2eb42c --- /dev/null +++ b/0006-rpm2extents-verify-package-signature-during-transcod.patch @@ -0,0 +1,431 @@ +From ea1177fcef609519f0c2377ebee236001d2a8fae Mon Sep 17 00:00:00 2001 +From: chantra +Date: Fri, 28 Jan 2022 08:31:39 -0800 +Subject: [PATCH 06/30] [rpm2extents] verify package signature during + transcoding + +--- + lib/rpmchecksig.c | 30 ++++++ + lib/rpmcli.h | 9 ++ + rpm2extents.c | 233 +++++++++++++++++++++++++++++++++++++++++----- + 3 files changed, 248 insertions(+), 24 deletions(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index 40a3ab83f..1a6b95323 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -304,3 +304,33 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv) + rpmKeyringFree(keyring); + return res; + } ++ ++int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi) ++{ ++ int res = 0; ++ rpmKeyring keyring = rpmtsGetKeyring(ts, 1); ++ rpmVSFlags vsflags = rpmtsVfyFlags(ts); ++ int vfylevel = rpmtsVfyLevel(ts); ++ ++ vsflags |= rpmcliVSFlags; ++ if (rpmcliVfyLevelMask) { ++ vfylevel &= ~rpmcliVfyLevelMask; ++ rpmtsSetVfyLevel(ts, vfylevel); ++ } ++ ++ FD_t fd = fdDup(Fileno(fdi)); ++ if (fd == NULL || Ferror(fd)) { ++ rpmlog(RPMLOG_ERR, _("fdDup failed: %s\n"), Fstrerror(fd)); ++ res++; ++ } else if (rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, "stdin")) { ++ res++; ++ } ++ ++ lseek(Fileno(fd), SEEK_SET, 0); ++ Fclose(fd); ++ rpmsqPoll(); ++ ++ rpmKeyringFree(keyring); ++ return res; ++} ++ +diff --git a/lib/rpmcli.h b/lib/rpmcli.h +index 906fe9951..52443e459 100644 +--- a/lib/rpmcli.h ++++ b/lib/rpmcli.h +@@ -411,6 +411,15 @@ int rpmcliImportPubkeys(rpmts ts, ARGV_const_t argv); + */ + int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv); + ++ ++/** \ingroup rpmcli ++ * Verify package signatures ++ * @param ts transaction set ++ * @param fd a file descriptor to verify ++ * @return 0 on success ++ */ ++int rpmcliVerifySignaturesFD(rpmts ts, FD_t fd); ++ + #ifdef __cplusplus + } + #endif +diff --git a/rpm2extents.c b/rpm2extents.c +index c111be0a2..d8e582676 100644 +--- a/rpm2extents.c ++++ b/rpm2extents.c +@@ -2,7 +2,9 @@ + + #include "system.h" + ++#include + #include /* rpmReadPackageFile .. */ ++#include + #include + #include + #include +@@ -10,6 +12,7 @@ + + #include + #include "lib/rpmlead.h" ++#include "lib/rpmts.h" + #include "lib/signature.h" + #include "lib/header_internal.h" + #include "rpmio/rpmio_internal.h" +@@ -51,6 +54,16 @@ rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit) + return (unit - (pos % unit)) % unit; + } + ++static struct poptOption optionsTable[] = { ++ { NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0, ++ N_("Common options for all rpm modes and executables:"), NULL }, ++ ++ POPT_AUTOALIAS ++ POPT_AUTOHELP ++ POPT_TABLEEND ++}; ++ ++ + static int digestor( + FD_t fdi, + FD_t fdo, +@@ -120,7 +133,19 @@ exit: + return rc; + } + +-static rpmRC process_package(FD_t fdi, FD_t validationi) ++static rpmRC validator(FD_t fdi){ ++ rpmts ts = rpmtsCreate(); ++ rpmtsSetRootDir(ts, rpmcliRootDir); ++ /* rpmlog prints NOTICE to stdout */ ++ // rpmlogSetFile(stderr); ++ if(rpmcliVerifySignaturesFD(ts, fdi)){ ++ fprintf(stderr, _("Error validating package\n")); ++ return RPMRC_FAIL; ++ } ++ return RPMRC_OK; ++} ++ ++static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + { + uint32_t diglen; + /* GNU C extension: can use diglen from outer context */ +@@ -148,7 +173,7 @@ static rpmRC process_package(FD_t fdi, FD_t validationi) + rpm_mode_t mode; + char *rpmio_flags = NULL, *zeros; + const unsigned char *digest; +- rpm_loff_t pos, size, pad, validation_pos; ++ rpm_loff_t pos, size, pad, digest_pos, validation_pos; + uint32_t offset_ix = 0; + size_t len; + int next = 0; +@@ -278,17 +303,26 @@ static rpmRC process_package(FD_t fdi, FD_t validationi) + goto exit; + } + } +- validation_pos = ( ++ digest_pos = ( + pos + sizeof(offset_ix) + sizeof(diglen) + + offset_ix * (diglen + sizeof(rpm_loff_t)) + ); + ++ ssize_t digest_len = ufdCopy(digestori, fdo); ++ if (digest_len == -1) { ++ fprintf(stderr, _("digest table ufdCopy failed\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ ++ validation_pos = digest_pos + digest_len; + ssize_t validation_len = ufdCopy(validationi, fdo); + if (validation_len == -1) { +- fprintf(stderr, _("digest table ufdCopy failed\n")); ++ fprintf(stderr, _("validation output ufdCopy failed\n")); + rc = RPMRC_FAIL; + goto exit; + } ++ + /* add more padding so the last file can be cloned. It doesn't matter that + * the table and validation etc are in this space. In fact, it's pretty + * efficient if it is. +@@ -307,11 +341,16 @@ static rpmRC process_package(FD_t fdi, FD_t validationi) + rc = RPMRC_FAIL; + goto exit; + } +- if (Fwrite(&validation_pos, len, 1, fdo) != len) { ++ if (Fwrite(&digest_pos, len, 1, fdo) != len) { + fprintf(stderr, _("Unable to write offset of validation table\n")); + rc = RPMRC_FAIL; + goto exit; + } ++ if (Fwrite(&validation_pos, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write offset of validation output\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } + uint64_t magic = MAGIC; + len = sizeof(magic); + if (Fwrite(&magic, len, 1, fdo) != len) { +@@ -327,10 +366,156 @@ exit: + return rc; + } + ++static off_t ufdTee(FD_t sfd, FD_t *fds, int len) ++{ ++ char buf[BUFSIZ]; ++ ssize_t rdbytes, wrbytes; ++ off_t total = 0; ++ ++ while (1) { ++ rdbytes = Fread(buf, sizeof(buf[0]), sizeof(buf), sfd); ++ ++ if (rdbytes > 0) { ++ for(int i=0; i < len; i++) { ++ wrbytes = Fwrite(buf, sizeof(buf[0]), rdbytes, fds[i]); ++ if (wrbytes != rdbytes) { ++ fprintf(stderr, "Error wriing to FD %d: %s\n", i, Fstrerror(fds[i])); ++ total = -1; ++ break; ++ } ++ } ++ if(total == -1){ ++ break; ++ } ++ total += wrbytes; ++ } else { ++ if (rdbytes < 0) ++ total = -1; ++ break; ++ } ++ } ++ ++ return total; ++} ++ ++static int teeRpm(FD_t fdi, FD_t digestori) { ++ rpmRC rc; ++ off_t offt = -1; ++ int processorpipefd[2]; ++ int validatorpipefd[2]; ++ int rpmsignpipefd[2]; ++ pid_t cpids[2], w; ++ int wstatus; ++ FD_t fds[2]; ++ ++ if (pipe(processorpipefd) == -1) { ++ fprintf(stderr, _("Processor pipe failure\n")); ++ return RPMRC_FAIL; ++ } ++ ++ if (pipe(validatorpipefd) == -1) { ++ fprintf(stderr, _("Validator pipe failure\n")); ++ return RPMRC_FAIL; ++ } ++ ++ if (pipe(rpmsignpipefd) == -1) { ++ fprintf(stderr, _("Validator pipe failure\n")); ++ return RPMRC_FAIL; ++ } ++ ++ cpids[0] = fork(); ++ if (cpids[0] == 0) { ++ /* child: validator */ ++ close(processorpipefd[0]); ++ close(processorpipefd[1]); ++ close(validatorpipefd[1]); ++ close(rpmsignpipefd[0]); ++ FD_t fdi = fdDup(validatorpipefd[0]); ++ // redirect STDOUT to the pipe ++ close(STDOUT_FILENO); ++ FD_t fdo = fdDup(rpmsignpipefd[1]); ++ close(rpmsignpipefd[1]); ++ rc = validator(fdi); ++ if(rc != RPMRC_OK) { ++ fprintf(stderr, _("Validator failed\n")); ++ } ++ Fclose(fdi); ++ Fclose(fdo); ++ if (rc != RPMRC_OK) { ++ exit(EXIT_FAILURE); ++ } ++ exit(EXIT_SUCCESS); ++ } else { ++ /* parent: main program */ ++ cpids[1] = fork(); ++ if (cpids[1] == 0) { ++ /* child: process_package */ ++ close(validatorpipefd[0]); ++ close(validatorpipefd[1]); ++ close(processorpipefd[1]); ++ close(rpmsignpipefd[1]); ++ FD_t fdi = fdDup(processorpipefd[0]); ++ close(processorpipefd[0]); ++ FD_t validatori = fdDup(rpmsignpipefd[0]); ++ close(rpmsignpipefd[0]); ++ ++ rc = process_package(fdi, digestori, validatori); ++ if(rc != RPMRC_OK) { ++ fprintf(stderr, _("Validator failed\n")); ++ } ++ Fclose(digestori); ++ Fclose(validatori); ++ /* fdi is normally closed through the stacked file gzdi in the ++ * function ++ */ ++ ++ if (rc != RPMRC_OK) { ++ exit(EXIT_FAILURE); ++ } ++ exit(EXIT_SUCCESS); ++ ++ ++ } else { ++ /* Actual parent. Read from fdi and write to both processes */ ++ close(processorpipefd[0]); ++ close(validatorpipefd[0]); ++ fds[0] = fdDup(processorpipefd[1]); ++ fds[1] = fdDup(validatorpipefd[1]); ++ close(validatorpipefd[1]); ++ close(processorpipefd[1]); ++ close(rpmsignpipefd[0]); ++ close(rpmsignpipefd[1]); ++ ++ offt = ufdTee(fdi, fds, 2); ++ if(offt == -1){ ++ fprintf(stderr, _("Failed to tee RPM\n")); ++ rc = RPMRC_FAIL; ++ } ++ Fclose(fds[0]); ++ Fclose(fds[1]); ++ w = waitpid(cpids[0], &wstatus, 0); ++ if (w == -1) { ++ fprintf(stderr, _("waitpid cpids[0] failed\n")); ++ rc = RPMRC_FAIL; ++ } ++ w = waitpid(cpids[1], &wstatus, 0); ++ if (w == -1) { ++ fprintf(stderr, _("waitpid cpids[1] failed\n")); ++ rc = RPMRC_FAIL; ++ } ++ } ++ } ++ ++ return rc; ++} ++ + int main(int argc, char *argv[]) { + rpmRC rc; + int cprc = 0; +- uint8_t algos[argc - 1]; ++ poptContext optCon = NULL; ++ const char **args = NULL; ++ int nb_algos = 0; ++ + int mainpipefd[2]; + int metapipefd[2]; + pid_t cpid, w; +@@ -338,29 +523,30 @@ int main(int argc, char *argv[]) { + + xsetprogname(argv[0]); /* Portability call -- see system.h */ + rpmReadConfigFiles(NULL, NULL); ++ optCon = rpmcliInit(argc, argv, optionsTable); ++ poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); + +- if (argc > 1 && (rstreq(argv[1], "-h") || rstreq(argv[1], "--help"))) { +- fprintf(stderr, _("Usage: %s [DIGESTALGO]...\n"), argv[0]); +- exit(EXIT_FAILURE); +- } +- +- if (argc == 1) { ++ if (poptPeekArg(optCon) == NULL) { + fprintf(stderr, + _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n")); ++ poptPrintUsage(optCon, stderr, 0); + exit(EXIT_FAILURE); + } + +- for (int x = 0; x < (argc - 1); x++) { +- if (pgpStringVal(PGPVAL_HASHALGO, argv[x + 1], &algos[x]) != 0) ++ args = poptGetArgs(optCon); ++ ++ for (nb_algos=0; args[nb_algos]; nb_algos++); ++ uint8_t algos[nb_algos]; ++ for (int x = 0; x < nb_algos; x++) { ++ if (pgpStringVal(PGPVAL_HASHALGO, args[x], &algos[x]) != 0) + { + fprintf(stderr, + _("Unable to resolve '%s' as a digest algorithm, exiting\n"), +- argv[x + 1]); ++ args[x]); + exit(EXIT_FAILURE); + } + } + +- + if (pipe(mainpipefd) == -1) { + fprintf(stderr, _("Main pipe failure\n")); + exit(EXIT_FAILURE); +@@ -369,6 +555,7 @@ int main(int argc, char *argv[]) { + fprintf(stderr, _("Meta pipe failure\n")); + exit(EXIT_FAILURE); + } ++ + cpid = fork(); + if (cpid == 0) { + /* child: digestor */ +@@ -377,7 +564,7 @@ int main(int argc, char *argv[]) { + FD_t fdi = fdDup(STDIN_FILENO); + FD_t fdo = fdDup(mainpipefd[1]); + FD_t validationo = fdDup(metapipefd[1]); +- rc = digestor(fdi, fdo, validationo, algos, argc - 1); ++ rc = digestor(fdi, fdo, validationo, algos, nb_algos); + Fclose(validationo); + Fclose(fdo); + Fclose(fdi); +@@ -386,12 +573,10 @@ int main(int argc, char *argv[]) { + close(mainpipefd[1]); + close(metapipefd[1]); + FD_t fdi = fdDup(mainpipefd[0]); +- FD_t validationi = fdDup(metapipefd[0]); +- rc = process_package(fdi, validationi); +- Fclose(validationi); +- /* fdi is normally closed through the stacked file gzdi in the +- * function. +- * Wait for child process (digestor for stdin) to complete. ++ FD_t digestori = fdDup(metapipefd[0]); ++ rc = teeRpm(fdi, digestori); ++ Fclose(digestori); ++ /* Wait for child process (digestor for stdin) to complete. + */ + if (rc != RPMRC_OK) { + if (kill(cpid, SIGTERM) != 0) { +@@ -402,7 +587,7 @@ int main(int argc, char *argv[]) { + } + w = waitpid(cpid, &wstatus, 0); + if (w == -1) { +- fprintf(stderr, _("waitpid failed\n")); ++ fprintf(stderr, _("waitpid %d failed %s\n"), cpid, strerror(errno)); + cprc = EXIT_FAILURE; + } else if (WIFEXITED(wstatus)) { + cprc = WEXITSTATUS(wstatus); +-- +2.35.1 + diff --git a/0007-rpm2extents-write-RC-and-output-to-transcodedfile-me.patch b/0007-rpm2extents-write-RC-and-output-to-transcodedfile-me.patch new file mode 100644 index 0000000..07c84fd --- /dev/null +++ b/0007-rpm2extents-write-RC-and-output-to-transcodedfile-me.patch @@ -0,0 +1,282 @@ +From a4755a5ed793ca439bb23b804ba7a8ab080ff110 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Fri, 28 Jan 2022 19:19:45 -0800 +Subject: [PATCH 07/30] [rpm2extents] write RC and output to transcodedfile + metadata + +create a new rpmcliVerifySignaturesFD function which takes a custom callback +that allows collecting a textual output of the validation, similar to what +rpmsign command would give. +--- + lib/rpmchecksig.c | 67 +++++++++++++++++++++++++++++++++++-------- + lib/rpmcli.h | 5 ++-- + rpm2extents.c | 73 +++++++++++++++++++++++++++++++---------------- + 3 files changed, 106 insertions(+), 39 deletions(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index 1a6b95323..fcdbb424f 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -260,6 +260,29 @@ exit: + return rc; + } + ++static int rpmpkgVerifySigsFD(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, ++ FD_t fd, rpmsinfoCb cb, void *cbdata) ++{ ++ char *msg = NULL; ++ int rc; ++ struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring); ++ ++ rc = rpmpkgRead(vs, fd, NULL, NULL, &msg); ++ ++ if (rc) ++ goto exit; ++ ++ rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, cb, cbdata); ++ ++exit: ++ if (rc && msg) ++ rpmlog(RPMLOG_ERR, "%s\n", msg); ++ rpmvsFree(vs); ++ free(msg); ++ return rc; ++} ++ ++ + /* Wrapper around rpmkVerifySigs to preserve API */ + int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn) + { +@@ -305,12 +328,38 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv) + return res; + } + +-int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi) ++struct vfydatafd_s { ++ size_t len; ++ char msg[BUFSIZ]; ++}; ++ ++ ++static int vfyFDCb(struct rpmsinfo_s *sinfo, void *cbdata) + { +- int res = 0; ++ struct vfydatafd_s *vd = cbdata; ++ char *vmsg, *msg; ++ size_t n; ++ size_t remainder = BUFSIZ - vd->len; ++ ++ vmsg = rpmsinfoMsg(sinfo); ++ rasprintf(&msg, " %s\n", vmsg); ++ n = rstrlcpy(vd->msg + vd->len, msg, remainder); ++ free(vmsg); ++ free(msg); ++ if(n <= remainder){ ++ vd->len += n; ++ } ++ return 1; ++} ++ ++ ++int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi, char **msg) ++{ ++ rpmRC rc = RPMRC_FAIL; + rpmKeyring keyring = rpmtsGetKeyring(ts, 1); + rpmVSFlags vsflags = rpmtsVfyFlags(ts); + int vfylevel = rpmtsVfyLevel(ts); ++ struct vfydatafd_s vd = {.len = 0}; + + vsflags |= rpmcliVSFlags; + if (rpmcliVfyLevelMask) { +@@ -318,19 +367,13 @@ int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi) + rpmtsSetVfyLevel(ts, vfylevel); + } + +- FD_t fd = fdDup(Fileno(fdi)); +- if (fd == NULL || Ferror(fd)) { +- rpmlog(RPMLOG_ERR, _("fdDup failed: %s\n"), Fstrerror(fd)); +- res++; +- } else if (rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, "stdin")) { +- res++; ++ if (!rpmpkgVerifySigsFD(keyring, vfylevel, vsflags, fdi, vfyFDCb, &vd)) { ++ rc = RPMRC_OK; + } +- +- lseek(Fileno(fd), SEEK_SET, 0); +- Fclose(fd); ++ *msg = strdup(vd.msg); + rpmsqPoll(); + + rpmKeyringFree(keyring); +- return res; ++ return rc; + } + +diff --git a/lib/rpmcli.h b/lib/rpmcli.h +index 52443e459..7ff48b37a 100644 +--- a/lib/rpmcli.h ++++ b/lib/rpmcli.h +@@ -413,12 +413,13 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv); + + + /** \ingroup rpmcli +- * Verify package signatures ++ * Verify package signatures. + * @param ts transaction set + * @param fd a file descriptor to verify ++ * @param msg a string containing textual information about the verification, similar to rpmcliVerifySignatures output. + * @return 0 on success + */ +-int rpmcliVerifySignaturesFD(rpmts ts, FD_t fd); ++int rpmcliVerifySignaturesFD(rpmts ts, FD_t fd, char **msg); + + #ifdef __cplusplus + } +diff --git a/rpm2extents.c b/rpm2extents.c +index d8e582676..065a00306 100644 +--- a/rpm2extents.c ++++ b/rpm2extents.c +@@ -133,16 +133,38 @@ exit: + return rc; + } + +-static rpmRC validator(FD_t fdi){ ++static rpmRC validator(FD_t fdi, FD_t fdo){ ++ int rc; ++ char *msg = NULL; + rpmts ts = rpmtsCreate(); ++ size_t len; ++ + rpmtsSetRootDir(ts, rpmcliRootDir); +- /* rpmlog prints NOTICE to stdout */ +- // rpmlogSetFile(stderr); +- if(rpmcliVerifySignaturesFD(ts, fdi)){ ++ rc = rpmcliVerifySignaturesFD(ts, fdi, &msg); ++ if(rc){ + fprintf(stderr, _("Error validating package\n")); +- return RPMRC_FAIL; + } +- return RPMRC_OK; ++ len = sizeof(rc); ++ if (Fwrite(&rc, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write validator RC code %d\n"), rc); ++ goto exit; ++ } ++ size_t validator_len = msg ? strlen(msg) : 0; ++ len = sizeof(validator_len); ++ if (Fwrite(&validator_len, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write validator output length code %zd\n"), validator_len); ++ goto exit; ++ } ++ if (Fwrite(msg, validator_len, 1, fdo) != validator_len) { ++ fprintf(stderr, _("Unable to write validator output %s\n"), msg); ++ goto exit; ++ } ++ ++exit: ++ if(msg) { ++ free(msg); ++ } ++ return rc ? RPMRC_FAIL : RPMRC_OK; + } + + static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) +@@ -173,7 +195,7 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + rpm_mode_t mode; + char *rpmio_flags = NULL, *zeros; + const unsigned char *digest; +- rpm_loff_t pos, size, pad, digest_pos, validation_pos; ++ rpm_loff_t pos, size, pad, digest_pos, validation_pos, digest_table_pos; + uint32_t offset_ix = 0; + size_t len; + int next = 0; +@@ -278,6 +300,16 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + qsort(offsets, (size_t)offset_ix, sizeof(struct digestoffset), + digestoffsetCmp); + ++ validation_pos = pos; ++ ssize_t validation_len = ufdCopy(validationi, fdo); ++ if (validation_len == -1) { ++ fprintf(stderr, _("validation output ufdCopy failed\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ ++ digest_table_pos = validation_pos + validation_len; ++ + len = sizeof(offset_ix); + if (Fwrite(&offset_ix, len, 1, fdo) != len) { + fprintf(stderr, _("Unable to write length of table\n")); +@@ -304,7 +336,7 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + } + } + digest_pos = ( +- pos + sizeof(offset_ix) + sizeof(diglen) + ++ digest_table_pos + sizeof(offset_ix) + sizeof(diglen) + + offset_ix * (diglen + sizeof(rpm_loff_t)) + ); + +@@ -315,14 +347,6 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + goto exit; + } + +- validation_pos = digest_pos + digest_len; +- ssize_t validation_len = ufdCopy(validationi, fdo); +- if (validation_len == -1) { +- fprintf(stderr, _("validation output ufdCopy failed\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- + /* add more padding so the last file can be cloned. It doesn't matter that + * the table and validation etc are in this space. In fact, it's pretty + * efficient if it is. +@@ -336,18 +360,18 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + goto exit; + } + zeros = _free(zeros); +- if (Fwrite(&pos, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write offset of digest table\n")); ++ if (Fwrite(&validation_pos, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write offset of validation output\n")); + rc = RPMRC_FAIL; + goto exit; + } +- if (Fwrite(&digest_pos, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write offset of validation table\n")); ++ if (Fwrite(&digest_table_pos, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write offset of digest table\n")); + rc = RPMRC_FAIL; + goto exit; + } +- if (Fwrite(&validation_pos, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write offset of validation output\n")); ++ if (Fwrite(&digest_pos, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write offset of validation table\n")); + rc = RPMRC_FAIL; + goto exit; + } +@@ -431,11 +455,9 @@ static int teeRpm(FD_t fdi, FD_t digestori) { + close(validatorpipefd[1]); + close(rpmsignpipefd[0]); + FD_t fdi = fdDup(validatorpipefd[0]); +- // redirect STDOUT to the pipe +- close(STDOUT_FILENO); + FD_t fdo = fdDup(rpmsignpipefd[1]); + close(rpmsignpipefd[1]); +- rc = validator(fdi); ++ rc = validator(fdi, fdo); + if(rc != RPMRC_OK) { + fprintf(stderr, _("Validator failed\n")); + } +@@ -486,6 +508,7 @@ static int teeRpm(FD_t fdi, FD_t digestori) { + close(rpmsignpipefd[0]); + close(rpmsignpipefd[1]); + ++ rc = RPMRC_OK; + offt = ufdTee(fdi, fds, 2); + if(offt == -1){ + fprintf(stderr, _("Failed to tee RPM\n")); +-- +2.35.1 + diff --git a/0008-rpm2extents-Add-script-to-troubleshoot-transcoded-fi.patch b/0008-rpm2extents-Add-script-to-troubleshoot-transcoded-fi.patch new file mode 100644 index 0000000..ddbf31d --- /dev/null +++ b/0008-rpm2extents-Add-script-to-troubleshoot-transcoded-fi.patch @@ -0,0 +1,116 @@ +From c705a6287f8c7fb5e37dad0ac87257731a41fa69 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Sat, 29 Jan 2022 07:00:27 +0000 +Subject: [PATCH 08/30] [rpm2extents] Add script to troubleshoot transcoded + file content This script is essentially dumping the metadata written at the + end of the transcoded files, it will also be used as part of the end-to-end + tests. + +--- + scripts/rpm2extents_dump | 94 ++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 94 insertions(+) + create mode 100755 scripts/rpm2extents_dump + +diff --git a/scripts/rpm2extents_dump b/scripts/rpm2extents_dump +new file mode 100755 +index 000000000..596a59a49 +--- /dev/null ++++ b/scripts/rpm2extents_dump +@@ -0,0 +1,94 @@ ++#!/usr/bin/env python3 ++ ++import argparse ++import binascii ++import os ++import struct ++import sys ++ ++MAGIC_SIZE = 8 ++MAGIC_STR = b'KWTSH100' ++ ++POS_SIZE = 8 ++ ++def keep_position(func): ++ def wrapper(*args, **kwargs): ++ curr = args[0].tell() ++ res = func(*args, **kwargs) ++ f.seek(curr, os.SEEK_SET) ++ return res ++ return wrapper ++ ++def read_validation_digest(f, validation_offset): ++ digests = [] ++ # validation ++ f.seek(validation_offset, os.SEEK_SET) ++ val_content_len, val_digests_num = struct.unpack('=QI', f.read(8+4)) ++ for i in range(val_digests_num): ++ algo_name_len, digest_len = struct.unpack('=II', f.read(8)) ++ algo_name, digest = struct.unpack(f'{algo_name_len}s{digest_len}s', f.read(algo_name_len+digest_len)) ++ digests.append((algo_name, binascii.hexlify(digest))) ++ return digests ++ ++ ++def read_digests_table(f, digest_offset): ++ digests = [] ++ # validation ++ f.seek(digest_offset, os.SEEK_SET) ++ table_len, digest_len = struct.unpack('=II', f.read(8)) ++ ++ for i in range(table_len): ++ digest, pos = struct.unpack(f'{digest_len}sQ', f.read(digest_len + 8)) ++ digests.append((pos, binascii.hexlify(digest))) ++ return digests ++ ++def read_signature_output(f, signature_offset): ++ f.seek(signature_offset, os.SEEK_SET) ++ signature_rc, signature_output_len = struct.unpack('=IQ', f.read(12)) ++ return signature_rc, f.read(signature_output_len) ++ ++@keep_position ++def parse_file(f): ++ digests = [] ++ pos_table_offset = f.seek(-8 - 3*POS_SIZE, os.SEEK_END) ++ signature_offset, digest_offset, validation_offset = struct.unpack('=QQQ', f.read(3*POS_SIZE)) ++ ++ validation_digests = read_validation_digest(f, validation_offset) ++ digests_table = read_digests_table(f, digest_offset) ++ signature_ouput = read_signature_output(f, signature_offset) ++ ++ return validation_digests, digests_table, signature_ouput ++ ++@keep_position ++def is_transcoded(f): ++ f.seek(-MAGIC_SIZE, os.SEEK_END) ++ magic = f.read(MAGIC_SIZE) ++ return magic == MAGIC_STR ++ ++def arg_parse(): ++ parser = argparse.ArgumentParser() ++ parser.add_argument('--dump-signature', action='store_true') ++ parser.add_argument('--dump-file-digest-table', action='store_true') ++ parser.add_argument('--dump-digests', action='store_true') ++ parser.add_argument('file') ++ ++ return parser.parse_args() ++ ++if __name__ == '__main__': ++ args = arg_parse() ++ f = open(args.file, 'rb') ++ if not is_transcoded(f): ++ sys.exit(1) ++ ++ validation_digests, digests_table, signature_output = parse_file(f) ++ if(args.dump_file_digest_table): ++ for digest in digests_table: ++ print(f"FileDigest {hex(digest[0])}: {digest[1]}") ++ ++ if(args.dump_digests): ++ for validation_digest in validation_digests: ++ print(f"HeaderDigest {validation_digest[0]} {validation_digest[1]}") ++ ++ if(args.dump_signature): ++ print(f"RPMSignOutput RC {signature_output[0]}\nRPMSignOutput Content {signature_output[1].decode()}") ++ +-- +2.35.1 + diff --git a/0009-rpm2extents-Add-test-verifying-RC-code-and-signature.patch b/0009-rpm2extents-Add-test-verifying-RC-code-and-signature.patch new file mode 100644 index 0000000..a0e9757 --- /dev/null +++ b/0009-rpm2extents-Add-test-verifying-RC-code-and-signature.patch @@ -0,0 +1,70 @@ +From 44b86112136e4804eb606636cbcb4ae847cad773 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Sat, 29 Jan 2022 07:05:18 +0000 +Subject: [PATCH 09/30] [rpm2extents] Add test verifying RC code and signature + validation text + +When transcoding an RPM, the RPM signatures are being validated on the fly and the result from the signature validation is being written at the tail of the transcoded file. +This test check that we get the expected results and is based on the rpmsigdig.at test. +--- + Makefile.am | 2 +- + tests/rpm2extents.at | 33 +++++++++++++++++++++++++++++++++ + 2 files changed, 34 insertions(+), 1 deletion(-) + +diff --git a/Makefile.am b/Makefile.am +index 288668819..96542c8c8 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -185,7 +185,7 @@ bin_PROGRAMS += rpmgraph + rpmgraph_SOURCES = tools/rpmgraph.c + rpmgraph_LDADD = lib/librpm.la rpmio/librpmio.la @WITH_POPT_LIB@ + +-dist_bin_SCRIPTS = scripts/gendiff ++dist_bin_SCRIPTS = scripts/gendiff scripts/rpm2extents_dump + + rpmconfig_DATA = rpmrc + rpmrc: $(top_srcdir)/rpmrc.in +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +index f943b9af4..baea987e4 100644 +--- a/tests/rpm2extents.at ++++ b/tests/rpm2extents.at +@@ -29,3 +29,36 @@ AT_CHECK([runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rp + [ignore]) + AT_CLEANUP + ++# Check that tailer writes checksig return code and content. ++# ++AT_SETUP([rpm2extents signature]) ++AT_KEYWORDS([rpm2extents digest signature]) ++AT_CHECK([ ++RPMDB_INIT ++ ++runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > /tmp/hello-2.0-1.x86_64-signed.rpm 2> /dev/null ++rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm ++runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub ++runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > /tmp/hello-2.0-1.x86_64-signed.rpm ++rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm ++], ++[0], ++[RPMSignOutput RC 2 ++RPMSignOutput Content Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY ++ Header SHA256 digest: OK ++ Header SHA1 digest: OK ++ Payload SHA256 digest: OK ++ V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY ++ MD5 digest: OK ++ ++RPMSignOutput RC 0 ++RPMSignOutput Content Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK ++ Header SHA256 digest: OK ++ Header SHA1 digest: OK ++ Payload SHA256 digest: OK ++ V4 RSA/SHA256 Signature, key ID 1964c5fc: OK ++ MD5 digest: OK ++ ++], ++[]) ++AT_CLEANUP +-- +2.35.1 + diff --git a/0010-rpm2extents-Make-rpmkeys-support-reading-metadata-fr.patch b/0010-rpm2extents-Make-rpmkeys-support-reading-metadata-fr.patch new file mode 100644 index 0000000..ed7e994 --- /dev/null +++ b/0010-rpm2extents-Make-rpmkeys-support-reading-metadata-fr.patch @@ -0,0 +1,204 @@ +From 7da1e826ccb08fdd404524146736b3f12a473e31 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Mon, 31 Jan 2022 14:42:25 -0800 +Subject: [PATCH 10/30] [rpm2extents] Make rpmkeys support reading metadata + from transcoded footer + +If the file is seekable and is a transcoded file, read signature validation from transcoded file tail metadata +--- + lib/rpmchecksig.c | 112 ++++++++++++++++++++++++++++++++++++++++++- + tests/rpm2extents.at | 34 ++++++++++++- + 2 files changed, 144 insertions(+), 2 deletions(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index fcdbb424f..6164d012c 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -24,6 +24,11 @@ + + #include "debug.h" + ++/* magic value at end of file (64 bits) that indicates this is a transcoded ++ * rpm. ++ */ ++#define MAGIC 3472329499408095051 ++ + static int doImport(rpmts ts, const char *fn, char *buf, ssize_t blen) + { + char const * const pgpmark = "-----BEGIN PGP "; +@@ -220,6 +225,107 @@ exit: + return rc; + } + ++static rpmRC isTranscodedRpm(FD_t fd) { ++ rpmRC rc = RPMRC_NOTFOUND; ++ rpm_loff_t current; ++ uint64_t magic; ++ size_t len; ++ ++ // If the file is not seekable, we cannot detect whether or not it is transcoded. ++ if(Fseek(fd, 0, SEEK_CUR) < 0) { ++ return RPMRC_FAIL; ++ } ++ current = Ftell(fd); ++ ++ if(Fseek(fd, -(sizeof(magic)), SEEK_END) < 0) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: failed to seek for magic\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ len = sizeof(magic); ++ if (Fread(&magic, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read magic\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ if (magic != MAGIC) { ++ rpmlog(RPMLOG_DEBUG, _("isTranscodedRpm: not transcoded\n")); ++ rc = RPMRC_NOTFOUND; ++ goto exit; ++ } ++ rc = RPMRC_OK; ++exit: ++ if (Fseek(fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to seek back to original location\n")); ++ } ++ return rc; ++} ++ ++static int rpmpkgVerifySigsTranscoded(FD_t fd){ ++ rpm_loff_t current; ++ uint64_t magic; ++ rpm_loff_t offset; ++ int32_t rc; ++ size_t len; ++ uint64_t content_len; ++ char *content = NULL; ++ ++ current = Ftell(fd); ++ ++ if(Fseek(fd, -(sizeof(magic) + 3 * sizeof(offset) ), SEEK_END) < 0) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: failed to seek for offset\n")); ++ rc = -1; ++ goto exit; ++ } ++ ++ len = sizeof(offset); ++ if (Fread(&offset, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read Signature Verification offset\n")); ++ rc = -1; ++ goto exit; ++ } ++ ++ if(Fseek(fd, offset, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to seek signature verification offset\n")); ++ rc = -1; ++ goto exit; ++ } ++ len = sizeof(rc); ++ if (Fread(&rc, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read Signature Verification RC\n")); ++ rc = -1; ++ goto exit; ++ } ++ ++ len = sizeof(content_len); ++ if (Fread(&content_len, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read signature content length\n")); ++ goto exit; ++ } ++ ++ content = malloc(content_len + 1); ++ if(content == NULL) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to allocate memory to read signature content\n")); ++ goto exit; ++ } ++ content[content_len] = 0; ++ if (Fread(content, content_len, 1, fd) != content_len) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read signature content\n")); ++ goto exit; ++ } ++ ++ rpmlog(RPMLOG_NOTICE, "%s", content); ++exit: ++ if(content){ ++ free(content); ++ } ++ if (Fseek(fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: unable to seek back to original location\n")); ++ } ++ return rc; ++ ++} ++ + static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, + FD_t fd, const char *fn) + { +@@ -229,10 +335,14 @@ static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, + .verbose = rpmIsVerbose(), + }; + int rc; +- struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring); + + rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd.verbose ? "\n" : ""); + ++ if(isTranscodedRpm(fd) == RPMRC_OK){ ++ return rpmpkgVerifySigsTranscoded(fd); ++ } ++ struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring); ++ + rc = rpmpkgRead(vs, fd, NULL, NULL, &msg); + + if (rc) +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +index baea987e4..18accfc75 100644 +--- a/tests/rpm2extents.at ++++ b/tests/rpm2extents.at +@@ -29,7 +29,7 @@ AT_CHECK([runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rp + [ignore]) + AT_CLEANUP + +-# Check that tailer writes checksig return code and content. ++# Check that transcoder writes checksig return code and content. + # + AT_SETUP([rpm2extents signature]) + AT_KEYWORDS([rpm2extents digest signature]) +@@ -62,3 +62,35 @@ RPMSignOutput Content Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK + ], + []) + AT_CLEANUP ++ ++AT_SETUP([rpm2extents signature verification]) ++AT_KEYWORDS([rpm2extents digest signature]) ++AT_CHECK([ ++RPMDB_INIT ++ ++runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/hello-2.0-1.x86_64-signed.rpm 2> /dev/null ++runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $? ++runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub ++runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/hello-2.0-1.x86_64-signed.rpm ++runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $? ++], ++[0], ++[/tmp/hello-2.0-1.x86_64-signed.rpm: ++ Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY ++ Header SHA256 digest: OK ++ Header SHA1 digest: OK ++ Payload SHA256 digest: OK ++ V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY ++ MD5 digest: OK ++1 ++/tmp/hello-2.0-1.x86_64-signed.rpm: ++ Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK ++ Header SHA256 digest: OK ++ Header SHA1 digest: OK ++ Payload SHA256 digest: OK ++ V4 RSA/SHA256 Signature, key ID 1964c5fc: OK ++ MD5 digest: OK ++0 ++], ++[]) ++AT_CLEANUP +-- +2.35.1 + diff --git a/0011-rpm2extents-Perform-digest-computation-within-the-va.patch b/0011-rpm2extents-Perform-digest-computation-within-the-va.patch new file mode 100644 index 0000000..ea98bed --- /dev/null +++ b/0011-rpm2extents-Perform-digest-computation-within-the-va.patch @@ -0,0 +1,389 @@ +From 86776bf17f1644c76fdf8b87042645cf77bd3873 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Wed, 2 Feb 2022 13:34:28 -0800 +Subject: [PATCH 11/30] [rpm2extents] Perform digest computation within the + validator + +The validator calls `rpmcliVerifySignaturesFD` which under the hood +performs `Fread`. Digests are computed/updated for each `Fread`. + +This diffs takes advantage of that by initializing the digest before +calling `rpmcliVerifySignaturesFD`. Once `rpmcliVerifySignaturesFD` as +returned and the file has been read, the digests are available. + +This saves us from spawning a `digestor` process, as well as performing +an extra file read within it. +--- + rpm2extents.c | 234 +++++++++++++++++++++++--------------------------- + 1 file changed, 106 insertions(+), 128 deletions(-) + +diff --git a/rpm2extents.c b/rpm2extents.c +index 065a00306..e316a2834 100644 +--- a/rpm2extents.c ++++ b/rpm2extents.c +@@ -64,38 +64,37 @@ static struct poptOption optionsTable[] = { + }; + + +-static int digestor( ++static void FDDigestInit(FD_t fdi, uint8_t algos[], uint32_t algos_len){ ++ int algo; ++ ++ for (algo = 0; algo < algos_len; algo++) { ++ fdInitDigest(fdi, algos[algo], 0); ++ } ++} ++ ++static int FDWriteDigests( + FD_t fdi, + FD_t fdo, +- FD_t validationo, + uint8_t algos[], +- uint32_t algos_len +-) ++ uint32_t algos_len) + { +- ssize_t fdilength; + const char *filedigest, *algo_name; + size_t filedigest_len, len; + uint32_t algo_name_len, algo_digest_len; + int algo; + rpmRC rc = RPMRC_FAIL; + +- for (algo = 0; algo < algos_len; algo++) { +- fdInitDigest(fdi, algos[algo], 0); +- } +- fdilength = ufdCopy(fdi, fdo); +- if (fdilength == -1) { +- fprintf(stderr, _("digest cat failed\n")); +- goto exit; +- } ++ ssize_t fdilength = fdOp(fdi, FDSTAT_READ)->bytes; + + len = sizeof(fdilength); +- if (Fwrite(&fdilength, len, 1, validationo) != len) { ++ if (Fwrite(&fdilength, len, 1, fdo) != len) { + fprintf(stderr, _("Unable to write input length %zd\n"), fdilength); + goto exit; + } + len = sizeof(algos_len); +- if (Fwrite(&algos_len, len, 1, validationo) != len) { +- fprintf(stderr, _("Unable to write number of validation digests\n")); ++ if (Fwrite(&algos_len, len, 1, fdo) != len) { ++ algo_digest_len = (uint32_t)filedigest_len; ++ fprintf(stderr, _("Unable to write number of digests\n")); + goto exit; + } + for (algo = 0; algo < algos_len; algo++) { +@@ -106,24 +105,24 @@ static int digestor( + algo_digest_len = (uint32_t)filedigest_len; + + len = sizeof(algo_name_len); +- if (Fwrite(&algo_name_len, len, 1, validationo) != len) { ++ if (Fwrite(&algo_name_len, len, 1, fdo) != len) { + fprintf(stderr, +- _("Unable to write validation algo name length\n")); ++ _("Unable to write digest algo name length\n")); + goto exit; + } + len = sizeof(algo_digest_len); +- if (Fwrite(&algo_digest_len, len, 1, validationo) != len) { ++ if (Fwrite(&algo_digest_len, len, 1, fdo) != len) { + fprintf(stderr, +- _("Unable to write number of bytes for validation digest\n")); ++ _("Unable to write number of bytes for digest\n")); + goto exit; + } +- if (Fwrite(algo_name, algo_name_len, 1, validationo) != algo_name_len) { +- fprintf(stderr, _("Unable to write validation algo name\n")); ++ if (Fwrite(algo_name, algo_name_len, 1, fdo) != algo_name_len) { ++ fprintf(stderr, _("Unable to write digest algo name\n")); + goto exit; + } +- if (Fwrite(filedigest, algo_digest_len, 1, validationo ) != algo_digest_len) { ++ if (Fwrite(filedigest, algo_digest_len, 1, fdo ) != algo_digest_len) { + fprintf(stderr, +- _("Unable to write validation digest value %u, %zu\n"), ++ _("Unable to write digest value %u, %zu\n"), + algo_digest_len, filedigest_len); + goto exit; + } +@@ -133,38 +132,66 @@ exit: + return rc; + } + +-static rpmRC validator(FD_t fdi, FD_t fdo){ +- int rc; +- char *msg = NULL; +- rpmts ts = rpmtsCreate(); ++static rpmRC FDWriteSignaturesValidation(FD_t fdo, int rpmvsrc, char *msg) { + size_t len; ++ rpmRC rc = RPMRC_FAIL; + +- rpmtsSetRootDir(ts, rpmcliRootDir); +- rc = rpmcliVerifySignaturesFD(ts, fdi, &msg); +- if(rc){ +- fprintf(stderr, _("Error validating package\n")); ++ if(rpmvsrc){ ++ fprintf(stderr, _("Error verifying package signatures\n")); + } +- len = sizeof(rc); +- if (Fwrite(&rc, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write validator RC code %d\n"), rc); ++ ++ len = sizeof(rpmvsrc); ++ if (Fwrite(&rpmvsrc, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write signature verification RC code %d\n"), rpmvsrc); ++ goto exit; ++ } ++ size_t content_len = msg ? strlen(msg) : 0; ++ len = sizeof(content_len); ++ if (Fwrite(&content_len, len, 1, fdo) != len) { ++ fprintf(stderr, _("Unable to write signature verification output length %zd\n"), content_len); + goto exit; + } +- size_t validator_len = msg ? strlen(msg) : 0; +- len = sizeof(validator_len); +- if (Fwrite(&validator_len, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write validator output length code %zd\n"), validator_len); ++ if (Fwrite(msg, content_len, 1, fdo) != content_len) { ++ fprintf(stderr, _("Unable to write signature verification output %s\n"), msg); + goto exit; + } +- if (Fwrite(msg, validator_len, 1, fdo) != validator_len) { +- fprintf(stderr, _("Unable to write validator output %s\n"), msg); ++ ++ rc = RPMRC_OK; ++exit: ++ ++ return rc; ++} ++ ++static rpmRC validator(FD_t fdi, FD_t digesto, FD_t sigo, ++ uint8_t algos[], ++ uint32_t algos_len){ ++ int rpmvsrc; ++ rpmRC rc = RPMRC_FAIL; ++ char *msg = NULL; ++ rpmts ts = rpmtsCreate(); ++ ++ rpmtsSetRootDir(ts, rpmcliRootDir); ++ ++ FDDigestInit(fdi, algos, algos_len); ++ ++ rpmvsrc = rpmcliVerifySignaturesFD(ts, fdi, &msg); ++ ++ // Write result of digest computation ++ if(FDWriteDigests(fdi, digesto, algos, algos_len) != RPMRC_OK) { ++ fprintf(stderr, _("Failed to write digests")); + goto exit; + } + ++ // Write result of signature validation. ++ if(FDWriteSignaturesValidation(sigo, rpmvsrc, msg)) { ++ goto exit; ++ } ++ rc = RPMRC_OK; + exit: + if(msg) { + free(msg); + } +- return rc ? RPMRC_FAIL : RPMRC_OK; ++ return rc; + } + + static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) +@@ -422,12 +449,16 @@ static off_t ufdTee(FD_t sfd, FD_t *fds, int len) + return total; + } + +-static int teeRpm(FD_t fdi, FD_t digestori) { +- rpmRC rc; ++static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) { ++ rpmRC rc = RPMRC_FAIL; + off_t offt = -1; ++ // tee-ed stdin + int processorpipefd[2]; + int validatorpipefd[2]; +- int rpmsignpipefd[2]; ++ // metadata ++ int meta_digestpipefd[2]; ++ int meta_rpmsignpipefd[2]; ++ + pid_t cpids[2], w; + int wstatus; + FD_t fds[2]; +@@ -442,8 +473,13 @@ static int teeRpm(FD_t fdi, FD_t digestori) { + return RPMRC_FAIL; + } + +- if (pipe(rpmsignpipefd) == -1) { +- fprintf(stderr, _("Validator pipe failure\n")); ++ if (pipe(meta_digestpipefd) == -1) { ++ fprintf(stderr, _("Meta digest pipe failure\n")); ++ return RPMRC_FAIL; ++ } ++ ++ if (pipe(meta_rpmsignpipefd) == -1) { ++ fprintf(stderr, _("Meta rpm signature pipe failure\n")); + return RPMRC_FAIL; + } + +@@ -453,16 +489,20 @@ static int teeRpm(FD_t fdi, FD_t digestori) { + close(processorpipefd[0]); + close(processorpipefd[1]); + close(validatorpipefd[1]); +- close(rpmsignpipefd[0]); ++ close(meta_digestpipefd[0]); ++ close(meta_rpmsignpipefd[0]); + FD_t fdi = fdDup(validatorpipefd[0]); +- FD_t fdo = fdDup(rpmsignpipefd[1]); +- close(rpmsignpipefd[1]); +- rc = validator(fdi, fdo); ++ FD_t digesto = fdDup(meta_digestpipefd[1]); ++ FD_t sigo = fdDup(meta_rpmsignpipefd[1]); ++ close(meta_digestpipefd[1]); ++ close(meta_rpmsignpipefd[1]); ++ rc = validator(fdi, digesto, sigo, algos, algos_len); + if(rc != RPMRC_OK) { + fprintf(stderr, _("Validator failed\n")); + } + Fclose(fdi); +- Fclose(fdo); ++ Fclose(digesto); ++ Fclose(sigo); + if (rc != RPMRC_OK) { + exit(EXIT_FAILURE); + } +@@ -475,18 +515,21 @@ static int teeRpm(FD_t fdi, FD_t digestori) { + close(validatorpipefd[0]); + close(validatorpipefd[1]); + close(processorpipefd[1]); +- close(rpmsignpipefd[1]); ++ close(meta_digestpipefd[1]); ++ close(meta_rpmsignpipefd[1]); + FD_t fdi = fdDup(processorpipefd[0]); + close(processorpipefd[0]); +- FD_t validatori = fdDup(rpmsignpipefd[0]); +- close(rpmsignpipefd[0]); ++ FD_t sigi = fdDup(meta_rpmsignpipefd[0]); ++ close(meta_rpmsignpipefd[0]); ++ FD_t digestori = fdDup(meta_digestpipefd[0]); ++ close(meta_digestpipefd[0]); + +- rc = process_package(fdi, digestori, validatori); ++ rc = process_package(fdi, digestori, sigi); + if(rc != RPMRC_OK) { + fprintf(stderr, _("Validator failed\n")); + } + Fclose(digestori); +- Fclose(validatori); ++ Fclose(sigi); + /* fdi is normally closed through the stacked file gzdi in the + * function + */ +@@ -505,8 +548,10 @@ static int teeRpm(FD_t fdi, FD_t digestori) { + fds[1] = fdDup(validatorpipefd[1]); + close(validatorpipefd[1]); + close(processorpipefd[1]); +- close(rpmsignpipefd[0]); +- close(rpmsignpipefd[1]); ++ close(meta_digestpipefd[0]); ++ close(meta_digestpipefd[1]); ++ close(meta_rpmsignpipefd[0]); ++ close(meta_rpmsignpipefd[1]); + + rc = RPMRC_OK; + offt = ufdTee(fdi, fds, 2); +@@ -534,16 +579,10 @@ static int teeRpm(FD_t fdi, FD_t digestori) { + + int main(int argc, char *argv[]) { + rpmRC rc; +- int cprc = 0; + poptContext optCon = NULL; + const char **args = NULL; + int nb_algos = 0; + +- int mainpipefd[2]; +- int metapipefd[2]; +- pid_t cpid, w; +- int wstatus; +- + xsetprogname(argv[0]); /* Portability call -- see system.h */ + rpmReadConfigFiles(NULL, NULL); + optCon = rpmcliInit(argc, argv, optionsTable); +@@ -570,69 +609,8 @@ int main(int argc, char *argv[]) { + } + } + +- if (pipe(mainpipefd) == -1) { +- fprintf(stderr, _("Main pipe failure\n")); +- exit(EXIT_FAILURE); +- } +- if (pipe(metapipefd) == -1) { +- fprintf(stderr, _("Meta pipe failure\n")); +- exit(EXIT_FAILURE); +- } +- +- cpid = fork(); +- if (cpid == 0) { +- /* child: digestor */ +- close(mainpipefd[0]); +- close(metapipefd[0]); +- FD_t fdi = fdDup(STDIN_FILENO); +- FD_t fdo = fdDup(mainpipefd[1]); +- FD_t validationo = fdDup(metapipefd[1]); +- rc = digestor(fdi, fdo, validationo, algos, nb_algos); +- Fclose(validationo); +- Fclose(fdo); +- Fclose(fdi); +- } else { +- /* parent: main program */ +- close(mainpipefd[1]); +- close(metapipefd[1]); +- FD_t fdi = fdDup(mainpipefd[0]); +- FD_t digestori = fdDup(metapipefd[0]); +- rc = teeRpm(fdi, digestori); +- Fclose(digestori); +- /* Wait for child process (digestor for stdin) to complete. +- */ +- if (rc != RPMRC_OK) { +- if (kill(cpid, SIGTERM) != 0) { +- fprintf(stderr, +- _("Failed to kill digest process when main process failed: %s\n"), +- strerror(errno)); +- } +- } +- w = waitpid(cpid, &wstatus, 0); +- if (w == -1) { +- fprintf(stderr, _("waitpid %d failed %s\n"), cpid, strerror(errno)); +- cprc = EXIT_FAILURE; +- } else if (WIFEXITED(wstatus)) { +- cprc = WEXITSTATUS(wstatus); +- if (cprc != 0) { +- fprintf(stderr, +- _("Digest process non-zero exit code %d\n"), +- cprc); +- } +- } else if (WIFSIGNALED(wstatus)) { +- fprintf(stderr, +- _("Digest process was terminated with a signal: %d\n"), +- WTERMSIG(wstatus)); +- cprc = EXIT_FAILURE; +- } else { +- /* Don't think this can happen, but covering all bases */ +- fprintf(stderr, _("Unhandled circumstance in waitpid\n")); +- cprc = EXIT_FAILURE; +- } +- if (cprc != EXIT_SUCCESS) { +- rc = RPMRC_FAIL; +- } +- } ++ FD_t fdi = fdDup(STDIN_FILENO); ++ rc = teeRpm(fdi, algos, nb_algos); + if (rc != RPMRC_OK) { + /* translate rpmRC into generic failure return code. */ + return EXIT_FAILURE; +-- +2.35.1 + diff --git a/0012-rpmextents-Create-an-internal-library-to-make-rpmext.patch b/0012-rpmextents-Create-an-internal-library-to-make-rpmext.patch new file mode 100644 index 0000000..e7b7804 --- /dev/null +++ b/0012-rpmextents-Create-an-internal-library-to-make-rpmext.patch @@ -0,0 +1,299 @@ +From ecab80b80e3917d3acf0f909c9cc84691a207fc0 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Thu, 3 Feb 2022 21:09:05 -0800 +Subject: [PATCH 12/30] [rpmextents] Create an internal library to make + rpmextents file manipulation easier and less duplicated + +--- + lib/Makefile.am | 3 ++- + lib/rpmchecksig.c | 42 +--------------------------------- + lib/rpmextents.c | 46 +++++++++++++++++++++++++++++++++++++ + lib/rpmextents_internal.h | 22 ++++++++++++++++++ + plugins/reflink.c | 48 +++++++++++++-------------------------- + rpm2extents.c | 8 ++----- + 6 files changed, 89 insertions(+), 80 deletions(-) + create mode 100644 lib/rpmextents.c + create mode 100644 lib/rpmextents_internal.h + +diff --git a/lib/Makefile.am b/lib/Makefile.am +index 5a1b6ca9b..2f1b3597f 100644 +--- a/lib/Makefile.am ++++ b/lib/Makefile.am +@@ -40,7 +40,8 @@ librpm_la_SOURCES = \ + rpmscript.h rpmscript.c \ + rpmchroot.c rpmchroot.h \ + rpmplugins.c rpmplugins.h rpmplugin.h rpmug.c rpmug.h \ +- rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h ++ rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h \ ++ rpmextents.c rpmextents_internal.h + + librpm_la_LDFLAGS = -version-info $(rpm_version_info) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index 6164d012c..dc1726a18 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -20,15 +20,11 @@ + #include "rpmio/rpmio_internal.h" /* fdSetBundle() */ + #include "lib/rpmlead.h" + #include "lib/header_internal.h" ++#include "lib/rpmextents_internal.h" + #include "lib/rpmvs.h" + + #include "debug.h" + +-/* magic value at end of file (64 bits) that indicates this is a transcoded +- * rpm. +- */ +-#define MAGIC 3472329499408095051 +- + static int doImport(rpmts ts, const char *fn, char *buf, ssize_t blen) + { + char const * const pgpmark = "-----BEGIN PGP "; +@@ -225,42 +221,6 @@ exit: + return rc; + } + +-static rpmRC isTranscodedRpm(FD_t fd) { +- rpmRC rc = RPMRC_NOTFOUND; +- rpm_loff_t current; +- uint64_t magic; +- size_t len; +- +- // If the file is not seekable, we cannot detect whether or not it is transcoded. +- if(Fseek(fd, 0, SEEK_CUR) < 0) { +- return RPMRC_FAIL; +- } +- current = Ftell(fd); +- +- if(Fseek(fd, -(sizeof(magic)), SEEK_END) < 0) { +- rpmlog(RPMLOG_ERR, _("isTranscodedRpm: failed to seek for magic\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- len = sizeof(magic); +- if (Fread(&magic, len, 1, fd) != len) { +- rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read magic\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- if (magic != MAGIC) { +- rpmlog(RPMLOG_DEBUG, _("isTranscodedRpm: not transcoded\n")); +- rc = RPMRC_NOTFOUND; +- goto exit; +- } +- rc = RPMRC_OK; +-exit: +- if (Fseek(fd, current, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to seek back to original location\n")); +- } +- return rc; +-} +- + static int rpmpkgVerifySigsTranscoded(FD_t fd){ + rpm_loff_t current; + uint64_t magic; +diff --git a/lib/rpmextents.c b/lib/rpmextents.c +new file mode 100644 +index 000000000..015277751 +--- /dev/null ++++ b/lib/rpmextents.c +@@ -0,0 +1,46 @@ ++ ++#include "system.h" ++ ++#include ++#include ++ ++#include "lib/rpmextents_internal.h" ++ ++rpmRC isTranscodedRpm(FD_t fd) { ++ rpmRC rc = RPMRC_NOTFOUND; ++ rpm_loff_t current; ++ extents_magic_t magic; ++ size_t len; ++ ++ // If the file is not seekable, we cannot detect whether or not it is transcoded. ++ if(Fseek(fd, 0, SEEK_CUR) < 0) { ++ return RPMRC_FAIL; ++ } ++ current = Ftell(fd); ++ ++ if(Fseek(fd, -(sizeof(magic)), SEEK_END) < 0) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: failed to seek for magic\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ len = sizeof(magic); ++ if (Fread(&magic, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read magic\n")); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ if (magic != EXTENTS_MAGIC) { ++ rpmlog(RPMLOG_DEBUG, _("isTranscodedRpm: not transcoded\n")); ++ rc = RPMRC_NOTFOUND; ++ goto exit; ++ } ++ rc = RPMRC_OK; ++exit: ++ if (Fseek(fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to seek back to original location\n")); ++ rc = RPMRC_FAIL; ++ } ++ return rc; ++} ++ ++ +diff --git a/lib/rpmextents_internal.h b/lib/rpmextents_internal.h +new file mode 100644 +index 000000000..57cecfc31 +--- /dev/null ++++ b/lib/rpmextents_internal.h +@@ -0,0 +1,22 @@ ++#ifndef _RPMEXTENTS_INTERNAL_H ++#define _RPMEXTENTS_INTERNAL_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#include ++ ++/* magic value at end of file (64 bits) that indicates this is a transcoded ++ * rpm. ++ */ ++#define EXTENTS_MAGIC 3472329499408095051 ++ ++typedef uint64_t extents_magic_t; ++ ++rpmRC isTranscodedRpm(FD_t fd); ++ ++#ifdef __cplusplus ++} ++#endif ++#endif +diff --git a/plugins/reflink.c b/plugins/reflink.c +index 513887604..ec575f55e 100644 +--- a/plugins/reflink.c ++++ b/plugins/reflink.c +@@ -13,6 +13,7 @@ + #include + #include "lib/rpmlib.h" + #include "lib/rpmplugin.h" ++#include "lib/rpmextents_internal.h" + #include "lib/rpmte_internal.h" + #include + #include "rpmio/rpmio_internal.h" +@@ -40,11 +41,6 @@ + + #define BUFFER_SIZE (1024 * 128) + +-/* magic value at end of file (64 bits) that indicates this is a transcoded +- * rpm. +- */ +-#define MAGIC 3472329499408095051 +- + struct reflink_state_s { + /* Stuff that's used across rpms */ + long fundamental_block_size; +@@ -96,40 +92,28 @@ static void reflink_cleanup(rpmPlugin plugin) { + } + + static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) { ++ rpmRC rc; ++ size_t len; ++ + reflink_state state = rpmPluginGetData(plugin); + state->fd = rpmteFd(te); + if (state->fd == 0) { + rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n")); + return RPMRC_OK; + } ++ + rpm_loff_t current = Ftell(state->fd); +- uint64_t magic; +- if (Fseek(state->fd, -(sizeof(magic)), SEEK_END) < 0) { +- rpmlog(RPMLOG_ERR, _("reflink: failed to seek for magic\n")); +- if (Fseek(state->fd, current, SEEK_SET) < 0) { +- /* yes this gets a bit repetitive */ +- rpmlog(RPMLOG_ERR, +- _("reflink: unable to seek back to original location\n")); +- } +- return RPMRC_FAIL; +- } +- size_t len = sizeof(magic); +- if (Fread(&magic, len, 1, state->fd) != len) { +- rpmlog(RPMLOG_ERR, _("reflink: unable to read magic\n")); +- if (Fseek(state->fd, current, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, +- _("reflink: unable to seek back to original location\n")); +- } +- return RPMRC_FAIL; +- } +- if (magic != MAGIC) { +- rpmlog(RPMLOG_DEBUG, _("reflink: not transcoded\n")); +- if (Fseek(state->fd, current, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, +- _("reflink: unable to seek back to original location\n")); ++ rc = isTranscodedRpm(state->fd); ++ ++ switch(rc){ ++ // Fail to parse the file, fail the plugin. ++ case RPMRC_FAIL: + return RPMRC_FAIL; +- } +- return RPMRC_OK; ++ // This is not a transcoded file, do nothing. ++ case RPMRC_NOTFOUND: ++ return RPMRC_OK; ++ default: ++ break; + } + rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n")); + Header h = rpmteHeader(te); +@@ -140,7 +124,7 @@ static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) { + headerFree(h); + state->files = rpmteFiles(te); + /* tail of file contains offset_table, offset_checksums then magic */ +- if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(magic)), SEEK_END) < 0) { ++ if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(extents_magic_t)), SEEK_END) < 0) { + rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"), + state->fd); + return RPMRC_FAIL; +diff --git a/rpm2extents.c b/rpm2extents.c +index e316a2834..a326e3857 100644 +--- a/rpm2extents.c ++++ b/rpm2extents.c +@@ -15,6 +15,7 @@ + #include "lib/rpmts.h" + #include "lib/signature.h" + #include "lib/header_internal.h" ++#include "lib/rpmextents_internal.h" + #include "rpmio/rpmio_internal.h" + + #include +@@ -37,11 +38,6 @@ + #include "lib/rpmhash.H" + #include "lib/rpmhash.C" + +-/* magic value at end of file (64 bits) that indicates this is a transcoded +- * rpm. +- */ +-#define MAGIC 3472329499408095051 +- + struct digestoffset { + const unsigned char * digest; + rpm_loff_t pos; +@@ -402,7 +398,7 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + rc = RPMRC_FAIL; + goto exit; + } +- uint64_t magic = MAGIC; ++ extents_magic_t magic = EXTENTS_MAGIC; + len = sizeof(magic); + if (Fwrite(&magic, len, 1, fdo) != len) { + fprintf(stderr, _("Unable to write magic\n")); +-- +2.35.1 + diff --git a/0013-plugin-add-plugin_fsm_file_install_func-plugin-hook.patch b/0013-plugin-add-plugin_fsm_file_install_func-plugin-hook.patch new file mode 100644 index 0000000..56bb554 --- /dev/null +++ b/0013-plugin-add-plugin_fsm_file_install_func-plugin-hook.patch @@ -0,0 +1,109 @@ +From 5c97d7f83f56015d6a37934cee4e55ed8d747890 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 8 Feb 2022 16:57:25 -0800 +Subject: [PATCH 13/30] [plugin] add `plugin_fsm_file_install_func` plugin hook + +This hook is to be called when installing individual files from the RPM. +--- + lib/rpmplugin.h | 5 +++++ + lib/rpmplugins.c | 37 +++++++++++++++++++++++++++++++++++++ + lib/rpmplugins.h | 15 +++++++++++++++ + 3 files changed, 57 insertions(+) + +diff --git a/lib/rpmplugin.h b/lib/rpmplugin.h +index fd81aec8d..877db81f3 100644 +--- a/lib/rpmplugin.h ++++ b/lib/rpmplugin.h +@@ -60,6 +60,10 @@ typedef rpmRC (*plugin_fsm_file_prepare_func)(rpmPlugin plugin, rpmfi fi, + const char* path, + const char *dest, + mode_t file_mode, rpmFsmOp op); ++typedef rpmRC (*plugin_fsm_file_install_func)(rpmPlugin plugin, rpmfi fi, ++ const char* path, ++ mode_t file_mode, rpmFsmOp op); ++ + + typedef struct rpmPluginHooks_s * rpmPluginHooks; + struct rpmPluginHooks_s { +@@ -80,6 +84,7 @@ struct rpmPluginHooks_s { + plugin_fsm_file_pre_func fsm_file_pre; + plugin_fsm_file_post_func fsm_file_post; + plugin_fsm_file_prepare_func fsm_file_prepare; ++ plugin_fsm_file_install_func fsm_file_install; + }; + + #ifdef __cplusplus +diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c +index 3da3097af..850a025a0 100644 +--- a/lib/rpmplugins.c ++++ b/lib/rpmplugins.c +@@ -421,3 +421,40 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi, + + return rc; + } ++ ++rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi, ++ const char *path, mode_t file_mode, ++ rpmFsmOp op) ++{ ++ plugin_fsm_file_install_func hookFunc; ++ int i; ++ rpmRC rc = RPMRC_OK; ++ rpmRC hook_rc; ++ ++ for (i = 0; i < plugins->count; i++) { ++ rpmPlugin plugin = plugins->plugins[i]; ++ RPMPLUGINS_SET_HOOK_FUNC(fsm_file_install); ++ if (hookFunc) { ++ hook_rc = hookFunc(plugin, fi, path, file_mode, op); ++ if (hook_rc == RPMRC_FAIL) { ++ rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_install failed\n", plugin->name); ++ rc = RPMRC_FAIL; ++ } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) { ++ if (rc == RPMRC_PLUGIN_CONTENTS) { ++ /* Another plugin already said it'd handle contents. It's ++ * undefined how these would combine, so treat this as a ++ * failure condition. ++ */ ++ rc = RPMRC_FAIL; ++ } else { ++ /* Plugin will handle content */ ++ rc = RPMRC_PLUGIN_CONTENTS; ++ } ++ } ++ } ++ } ++ ++ return rc; ++} ++ ++ +diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h +index 39762c376..5365cf698 100644 +--- a/lib/rpmplugins.h ++++ b/lib/rpmplugins.h +@@ -167,6 +167,21 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi, + const char *path, const char *dest, + mode_t mode, rpmFsmOp op); + ++/** \ingroup rpmplugins ++ * Call the fsm file install plugin hook ++ * @param plugins plugins structure ++ * @param fi file info iterator (or NULL) ++ * @param path file object path ++ * @param file_mode file object mode ++ * @param op file operation + associated flags ++ * @return RPMRC_OK on success, RPMRC_FAIL otherwise ++ */ ++RPM_GNUC_INTERNAL ++rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi, ++ const char* path, mode_t file_mode, ++ rpmFsmOp op); ++ ++ + #ifdef __cplusplus + } + #endif +-- +2.35.1 + diff --git a/0014-fsm-Call-new-rpmpluginsCallFsmFileInstall-in-rpmPack.patch b/0014-fsm-Call-new-rpmpluginsCallFsmFileInstall-in-rpmPack.patch new file mode 100644 index 0000000..737368d --- /dev/null +++ b/0014-fsm-Call-new-rpmpluginsCallFsmFileInstall-in-rpmPack.patch @@ -0,0 +1,79 @@ +From ad46eb4132cbd2c4ee23686a1c52f2fc57afffc5 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 8 Feb 2022 17:06:55 -0800 +Subject: [PATCH 14/30] [fsm] Call new `rpmpluginsCallFsmFileInstall` in + `rpmPackageFilesInstall` + +Call `rpmpluginsCallFsmFileInstall` for every files to be installed by +`rpmPackageFilesInstall`, this allows for plugin such as reflink to +write (reflink) a file and make sure the parent directories have already +been created. +If this was done in `rpmpluginsCallFsmFilePre`, the directories may be +missing, which makes file installation fail. +--- + lib/fsm.c | 29 +++++++++-------------------- + 1 file changed, 9 insertions(+), 20 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index feda3750c..711f553d3 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -53,7 +53,6 @@ struct filedata_s { + int stage; + int setmeta; + int skip; +- int plugin_contents; + rpmFileAction action; + const char *suffix; + char *fpath; +@@ -921,23 +920,12 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Remap file perms, owner, and group. */ + rc = rpmfiStat(fi, 1, &fp->sb); + ++ setFileState(fs, fx); + fsmDebug(fp->fpath, fp->action, &fp->sb); + + /* Run fsm file pre hook for all plugins */ + rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, + fp->sb.st_mode, fp->action); +- fp->plugin_contents = 0; +- switch (rc) { +- case RPMRC_OK: +- setFileState(fs, fx); +- break; +- case RPMRC_PLUGIN_CONTENTS: +- fp->plugin_contents = 1; +- // reduce reads on cpio to this value. Could be zero if +- // this is from a hard link. +- rc = RPMRC_OK; +- break; +- } + fp->stage = FILE_PRE; + } + fi = rpmfiFree(fi); +@@ -992,14 +980,15 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + if (fp->action == FA_TOUCH) + continue; + +- if (S_ISREG(fp->sb.st_mode)) { ++ rpmRC plugin_rc = rpmpluginsCallFsmFileInstall(plugins, fi, fp->fpath, fp->sb.st_mode, fp->action); ++ if(!(plugin_rc == RPMRC_PLUGIN_CONTENTS || plugin_rc == RPMRC_OK)){ ++ rc = plugin_rc; ++ } else if(plugin_rc == RPMRC_PLUGIN_CONTENTS){ ++ rc = RPMRC_OK; ++ } else if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- if(fp->plugin_contents) { +- rc = RPMRC_OK; +- }else { +- rc = fsmMkfile(fi, fp, files, psm, nodigest, +- &firstlink, &firstlinkfile); +- } ++ rc = fsmMkfile(fi, fp, files, psm, nodigest, ++ &firstlink, &firstlinkfile); + } + } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +-- +2.35.1 + diff --git a/0015-reflink-use-reflink_fsm_file_install-hook-instead-of.patch b/0015-reflink-use-reflink_fsm_file_install-hook-instead-of.patch new file mode 100644 index 0000000..8183898 --- /dev/null +++ b/0015-reflink-use-reflink_fsm_file_install-hook-instead-of.patch @@ -0,0 +1,33 @@ +From b2fc576828af873a1993bdaa2fcb7c860b94df3e Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 8 Feb 2022 17:10:23 -0800 +Subject: [PATCH 15/30] [reflink] use reflink_fsm_file_install hook instead of + reflink_fsm_file_pre + +--- + plugins/reflink.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/plugins/reflink.c b/plugins/reflink.c +index ec575f55e..7dda06d8e 100644 +--- a/plugins/reflink.c ++++ b/plugins/reflink.c +@@ -223,7 +223,7 @@ rpm_loff_t find(const unsigned char *digest, reflink_state state) { + return offset; + } + +-static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path, ++static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* path, + mode_t file_mode, rpmFsmOp op) + { + struct file_clone_range fcr; +@@ -355,5 +355,5 @@ struct rpmPluginHooks_s reflink_hooks = { + .cleanup = reflink_cleanup, + .psm_pre = reflink_psm_pre, + .psm_post = reflink_psm_post, +- .fsm_file_pre = reflink_fsm_file_pre, ++ .fsm_file_install = reflink_fsm_file_install, + }; +-- +2.35.1 + diff --git a/0015-rpmsign-RPMSIGN_FLAG_IMA-is-already-set.patch b/0015-rpmsign-RPMSIGN_FLAG_IMA-is-already-set.patch new file mode 100644 index 0000000..e6dae81 --- /dev/null +++ b/0015-rpmsign-RPMSIGN_FLAG_IMA-is-already-set.patch @@ -0,0 +1,28 @@ +From 7784da14fe57df919df9dfdad30e436ffe6d3e28 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 20 Apr 2020 11:22:15 -0400 +Subject: [PATCH 15/33] rpmsign: RPMSIGN_FLAG_IMA is already set + +There is no need to set RPMSIGN_FLAG_IMA since it was already set to +get here. + +Signed-off-by: Jes Sorensen +--- + rpmsign.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/rpmsign.c b/rpmsign.c +index a74948ba8..e1d207da5 100644 +--- a/rpmsign.c ++++ b/rpmsign.c +@@ -130,7 +130,6 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + free(fileSigningKeyPassword); + } + +- sargs->signflags |= RPMSIGN_FLAG_IMA; + free(key); + } + #endif +-- +2.27.0 + diff --git a/0016-Add-basic-autoconf-and-framework-for-fsverity-suppor.patch b/0016-Add-basic-autoconf-and-framework-for-fsverity-suppor.patch new file mode 100644 index 0000000..89845cb --- /dev/null +++ b/0016-Add-basic-autoconf-and-framework-for-fsverity-suppor.patch @@ -0,0 +1,136 @@ +From f525681b4f66026578bc728b864bfea3d814c29e Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Fri, 27 Mar 2020 18:31:36 -0400 +Subject: [PATCH 16/33] Add basic autoconf and framework for fsverity support + +Use the same signing key argument as is used for IMA file signing. + +Signed-off-by: Jes Sorensen +--- + configure.ac | 19 +++++++++++++++++++ + rpmsign.c | 20 ++++++++++++++------ + sign/Makefile.am | 5 +++++ + sign/rpmsign.h | 1 + + 4 files changed, 39 insertions(+), 6 deletions(-) + +diff --git a/configure.ac b/configure.ac +index 3c102d5eb..cc7144440 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -919,6 +919,25 @@ fi + AM_CONDITIONAL(WITH_IMAEVM,[test "$with_imaevm" = yes]) + AC_SUBST(WITH_IMAEVM_LIB) + ++# fsverity ++AC_ARG_WITH([fsverity], [AS_HELP_STRING([--with-fsverity],[build with fsverity support])],[],[with_fsverity=no]) ++if test "$with_fsverity" = yes ; then ++ AC_MSG_CHECKING([libfsverity]) ++ AC_COMPILE_IFELSE( ++ [AC_LANG_PROGRAM( ++ [[#include ]], ++ [[return libfsverity_sign_digest(NULL, NULL, NULL, NULL);]] ++ )], ++ [AC_MSG_RESULT(yes) ++ AC_DEFINE(WITH_FSVERITY, 1, [Build with fsverity support?]) ++ WITH_FSVERITY_LIB="-lfsverity" ++ ], ++ [AC_MSG_ERROR([--with-fsverity given, libfsverity or libfsverity.h missing])] ++ ) ++fi ++AM_CONDITIONAL(WITH_FSVERITY,[test "$with_fsverity" = yes]) ++AC_SUBST(WITH_FSVERITY_LIB) ++ + # libcap + WITH_CAP_LIB= + AC_ARG_WITH(cap, [AS_HELP_STRING([--with-cap],[build with capability support])], +diff --git a/rpmsign.c b/rpmsign.c +index e1d207da5..8861c2c59 100644 +--- a/rpmsign.c ++++ b/rpmsign.c +@@ -18,7 +18,7 @@ enum modes { + + static int mode = MODE_NONE; + +-#ifdef WITH_IMAEVM ++#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) + static int fskpass = 0; + static char * fileSigningKey = NULL; + #endif +@@ -39,6 +39,13 @@ static struct poptOption signOptsTable[] = { + { "signfiles", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), + &sargs.signflags, RPMSIGN_FLAG_IMA, + N_("sign package(s) files"), NULL}, ++#endif ++#ifdef WITH_FSVERITY ++ { "signverity", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), ++ &sargs.signflags, RPMSIGN_FLAG_FSVERITY, ++ N_("generate fsverity signatures for package(s) files"), NULL}, ++#endif ++#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) + { "fskpath", '\0', POPT_ARG_STRING, &fileSigningKey, 0, + N_("use file signing key "), + N_("") }, +@@ -59,7 +66,7 @@ static struct poptOption optionsTable[] = { + POPT_TABLEEND + }; + +-#ifdef WITH_IMAEVM ++#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) + static char *get_fskpass(void) + { + struct termios flags, tmp_flags; +@@ -106,12 +113,12 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + goto exit; + } + +-#ifdef WITH_IMAEVM ++#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) + if (fileSigningKey) { + rpmPushMacro(NULL, "_file_signing_key", NULL, fileSigningKey, RMIL_GLOBAL); + } + +- if (sargs->signflags & RPMSIGN_FLAG_IMA) { ++ if (sargs->signflags & (RPMSIGN_FLAG_IMA | RPMSIGN_FLAG_FSVERITY)) { + char *fileSigningKeyPassword = NULL; + char *key = rpmExpand("%{?_file_signing_key}", NULL); + if (rstreq(key, "")) { +@@ -165,8 +172,9 @@ int main(int argc, char *argv[]) + argerror(_("no arguments given")); + } + +-#ifdef WITH_IMAEVM +- if (fileSigningKey && !(sargs.signflags & RPMSIGN_FLAG_IMA)) { ++#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) ++ if (fileSigningKey && ++ !(sargs.signflags & (RPMSIGN_FLAG_IMA | RPMSIGN_FLAG_FSVERITY))) { + argerror(_("--fskpath may only be specified when signing files")); + } + #endif +diff --git a/sign/Makefile.am b/sign/Makefile.am +index db774de0e..8d372915a 100644 +--- a/sign/Makefile.am ++++ b/sign/Makefile.am +@@ -24,3 +24,8 @@ if WITH_IMAEVM + librpmsign_la_SOURCES += rpmsignfiles.c rpmsignfiles.h + librpmsign_la_LIBADD += @WITH_IMAEVM_LIB@ + endif ++ ++if WITH_FSVERITY ++librpmsign_la_SOURCES += rpmsignverity.c rpmsignverity.h ++librpmsign_la_LIBADD += @WITH_FSVERITY_LIB@ ++endif +diff --git a/sign/rpmsign.h b/sign/rpmsign.h +index 7a770d879..2b8a10a1a 100644 +--- a/sign/rpmsign.h ++++ b/sign/rpmsign.h +@@ -17,6 +17,7 @@ enum rpmSignFlags_e { + RPMSIGN_FLAG_NONE = 0, + RPMSIGN_FLAG_IMA = (1 << 0), + RPMSIGN_FLAG_RPMV3 = (1 << 1), ++ RPMSIGN_FLAG_FSVERITY = (1 << 2), + }; + typedef rpmFlags rpmSignFlags; + +-- +2.27.0 + diff --git a/0016-test-new-runroot_plugins-function-to-run-command-in-.patch b/0016-test-new-runroot_plugins-function-to-run-command-in-.patch new file mode 100644 index 0000000..801de12 --- /dev/null +++ b/0016-test-new-runroot_plugins-function-to-run-command-in-.patch @@ -0,0 +1,33 @@ +From e04b5d20a6d8c64dba7416edba8e435145a5d7d3 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 8 Feb 2022 17:12:09 -0800 +Subject: [PATCH 16/30] [test] new runroot_plugins function to run command in + fakeroot without having the plugins disabled + +--- + tests/atlocal.in | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/tests/atlocal.in b/tests/atlocal.in +index c3189d327..c18637362 100644 +--- a/tests/atlocal.in ++++ b/tests/atlocal.in +@@ -82,6 +82,15 @@ function runroot() + ) + } + ++function runroot_plugins() ++{ ++ setup_env ++ (unset RPM_CONFIGDIR RPM_POPTEXEC_PATH; cd ${RPMTEST} && \ ++ MAGIC="/magic/magic" FAKECHROOT_BASE="${RPMTEST}" fakechroot "$@" --define "_buildhost testhost" --define "_topdir /build" --nouserns ++ ) ++} ++ ++ + function runroot_other() + { + setup_env +-- +2.35.1 + diff --git a/0017-rpmsign-Add-helper-to-indicate-file-signing-enabled.patch b/0017-rpmsign-Add-helper-to-indicate-file-signing-enabled.patch new file mode 100644 index 0000000..ae64146 --- /dev/null +++ b/0017-rpmsign-Add-helper-to-indicate-file-signing-enabled.patch @@ -0,0 +1,51 @@ +From dbb4f464d177e2c3bfa13b1b2bb511fa6fde40d9 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Wed, 27 May 2020 16:49:03 -0400 +Subject: [PATCH 17/33] rpmsign: Add helper to indicate file signing enabled + +Helper function returning true if either IMA or VERITY signatures are +to be applied. This simplifies the code and makes it easier to read. + +Signed-off-by: Jes Sorensen +--- + rpmsign.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/rpmsign.c b/rpmsign.c +index 8861c2c59..94cbf1d1a 100644 +--- a/rpmsign.c ++++ b/rpmsign.c +@@ -67,6 +67,11 @@ static struct poptOption optionsTable[] = { + }; + + #if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) ++static int flags_sign_files(int flags) ++{ ++ return (flags & (RPMSIGN_FLAG_IMA | RPMSIGN_FLAG_FSVERITY) ? 1 : 0); ++} ++ + static char *get_fskpass(void) + { + struct termios flags, tmp_flags; +@@ -118,7 +123,7 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + rpmPushMacro(NULL, "_file_signing_key", NULL, fileSigningKey, RMIL_GLOBAL); + } + +- if (sargs->signflags & (RPMSIGN_FLAG_IMA | RPMSIGN_FLAG_FSVERITY)) { ++ if (flags_sign_files(sargs->signflags)) { + char *fileSigningKeyPassword = NULL; + char *key = rpmExpand("%{?_file_signing_key}", NULL); + if (rstreq(key, "")) { +@@ -173,8 +178,7 @@ int main(int argc, char *argv[]) + } + + #if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) +- if (fileSigningKey && +- !(sargs.signflags & (RPMSIGN_FLAG_IMA | RPMSIGN_FLAG_FSVERITY))) { ++ if (fileSigningKey && !(flags_sign_files(sargs.signflags))) { + argerror(_("--fskpath may only be specified when signing files")); + } + #endif +-- +2.27.0 + diff --git a/0017-test-Add-test-installing-an-RPM-with-reflink-plugin.patch b/0017-test-Add-test-installing-an-RPM-with-reflink-plugin.patch new file mode 100644 index 0000000..83d3967 --- /dev/null +++ b/0017-test-Add-test-installing-an-RPM-with-reflink-plugin.patch @@ -0,0 +1,63 @@ +From e86207d3395e0963f19363b047551100569900df Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 8 Feb 2022 17:12:55 -0800 +Subject: [PATCH 17/30] [test] Add test installing an RPM with reflink plugin + +The test only runs for reflinkable FS, like XFS and BTRFS. +dbus_announce is being disabled as it generates warning to stderr when +running within the fakeroot. +--- + tests/atlocal.in | 13 +++++++++++++ + tests/rpm2extents.at | 15 +++++++++++++++ + 2 files changed, 28 insertions(+) + +diff --git a/tests/atlocal.in b/tests/atlocal.in +index c18637362..a110564e2 100644 +--- a/tests/atlocal.in ++++ b/tests/atlocal.in +@@ -50,6 +50,19 @@ else + CAP_DISABLED=true; + fi + ++FSTYPE=$(stat -f -c %T /) ++REFLINKABLE_FS=("xfs" "brtfs") ++ ++REFLINK_DISABLED=true; ++for item in "${REFLINKABLE_FS[@]}" ++do ++ if test "${FSTYPE}" = "${item}" ++ then ++ REFLINK_DISABLED=false; ++ break ++ fi ++done ++ + function setup_env() + { + if [ -d testing ]; then +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +index 18accfc75..44e46a68e 100644 +--- a/tests/rpm2extents.at ++++ b/tests/rpm2extents.at +@@ -94,3 +94,18 @@ runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $? + ], + []) + AT_CLEANUP ++ ++AT_SETUP([rpm2extents install package]) ++AT_KEYWORDS([rpm2extents install package]) ++AT_SKIP_IF([$REFLINK_DISABLED]) ++AT_CHECK([ ++RPMDB_INIT ++ ++runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/hello-2.0-1.x86_64.rpm 2> /dev/null ++runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /tmp/hello-2.0-1.x86_64.rpm ++test -f ${RPMTEST}/usr/bin/hello ++], ++[0], ++[], ++[]) ++AT_CLEANUP +-- +2.35.1 + diff --git a/0018-plugin-add-rpmpluginsCallFsmFileArchiveReader.patch b/0018-plugin-add-rpmpluginsCallFsmFileArchiveReader.patch new file mode 100644 index 0000000..7a5c5fd --- /dev/null +++ b/0018-plugin-add-rpmpluginsCallFsmFileArchiveReader.patch @@ -0,0 +1,97 @@ +From 048db395b6de8544dc88231f0afebee8570daee6 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 8 Feb 2022 18:21:33 -0800 +Subject: [PATCH 18/30] [plugin] add `rpmpluginsCallFsmFileArchiveReader` + +This allows plugins to provide a custom `rpmfi` to +`rpmPackageFilesInstall` function in fsm.c. It enables supporting +transcoded files such as with reflink plugin. +--- + lib/rpmplugin.h | 4 ++++ + lib/rpmplugins.c | 34 ++++++++++++++++++++++++++++++++++ + lib/rpmplugins.h | 4 +++- + 3 files changed, 41 insertions(+), 1 deletion(-) + +diff --git a/lib/rpmplugin.h b/lib/rpmplugin.h +index 877db81f3..6dbbcff35 100644 +--- a/lib/rpmplugin.h ++++ b/lib/rpmplugin.h +@@ -63,6 +63,9 @@ typedef rpmRC (*plugin_fsm_file_prepare_func)(rpmPlugin plugin, rpmfi fi, + typedef rpmRC (*plugin_fsm_file_install_func)(rpmPlugin plugin, rpmfi fi, + const char* path, + mode_t file_mode, rpmFsmOp op); ++typedef rpmRC (*plugin_fsm_file_archive_reader_func)(rpmPlugin plugin, ++ FD_t payload, ++ rpmfiles files, rpmfi *fi); + + + typedef struct rpmPluginHooks_s * rpmPluginHooks; +@@ -85,6 +88,7 @@ struct rpmPluginHooks_s { + plugin_fsm_file_post_func fsm_file_post; + plugin_fsm_file_prepare_func fsm_file_prepare; + plugin_fsm_file_install_func fsm_file_install; ++ plugin_fsm_file_archive_reader_func fsm_file_archive_reader; + }; + + #ifdef __cplusplus +diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c +index 850a025a0..901af1ac5 100644 +--- a/lib/rpmplugins.c ++++ b/lib/rpmplugins.c +@@ -457,4 +457,38 @@ rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi, + return rc; + } + ++rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload, ++ rpmfiles files, rpmfi *fi) ++{ ++ plugin_fsm_file_archive_reader_func hookFunc; ++ int i; ++ rpmRC rc = RPMRC_OK; ++ rpmRC hook_rc; ++ ++ for (i = 0; i < plugins->count; i++) { ++ rpmPlugin plugin = plugins->plugins[i]; ++ RPMPLUGINS_SET_HOOK_FUNC(fsm_file_archive_reader); ++ if (hookFunc) { ++ hook_rc = hookFunc(plugin, payload, files, fi); ++ if (hook_rc == RPMRC_FAIL) { ++ rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_archive_reader failed\n", plugin->name); ++ rc = RPMRC_FAIL; ++ } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) { ++ if (rc == RPMRC_PLUGIN_CONTENTS) { ++ /* Another plugin already said it'd handle contents. It's ++ * undefined how these would combine, so treat this as a ++ * failure condition. ++ */ ++ rc = RPMRC_FAIL; ++ } else { ++ /* Plugin will handle content */ ++ rc = RPMRC_PLUGIN_CONTENTS; ++ } ++ } ++ } ++ } ++ ++ return rc; ++} ++ + +diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h +index 5365cf698..88807c53c 100644 +--- a/lib/rpmplugins.h ++++ b/lib/rpmplugins.h +@@ -181,7 +181,9 @@ rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi, + const char* path, mode_t file_mode, + rpmFsmOp op); + +- ++RPM_GNUC_INTERNAL ++rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload, ++ rpmfiles files, rpmfi *fi); + #ifdef __cplusplus + } + #endif +-- +2.35.1 + diff --git a/0018-rpmsign-Handle-certpath-for-signing-certificate.patch b/0018-rpmsign-Handle-certpath-for-signing-certificate.patch new file mode 100644 index 0000000..77a8b31 --- /dev/null +++ b/0018-rpmsign-Handle-certpath-for-signing-certificate.patch @@ -0,0 +1,52 @@ +From 5e74846046e06f5a3401c4d2eb4ccfadacd4ab53 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Fri, 3 Apr 2020 16:26:06 -0400 +Subject: [PATCH 18/33] rpmsign: Handle --certpath for signing certificate + +fsverirty needs a certificate for signing, in addition to the signing key. + +Signed-off-by: Jes Sorensen +--- + rpmsign.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/rpmsign.c b/rpmsign.c +index 94cbf1d1a..074dd8b13 100644 +--- a/rpmsign.c ++++ b/rpmsign.c +@@ -22,6 +22,9 @@ static int mode = MODE_NONE; + static int fskpass = 0; + static char * fileSigningKey = NULL; + #endif ++#ifdef WITH_FSVERITY ++static char * fileSigningCert = NULL; ++#endif + + static struct rpmSignArgs sargs = {NULL, 0, 0}; + +@@ -44,6 +47,9 @@ static struct poptOption signOptsTable[] = { + { "signverity", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), + &sargs.signflags, RPMSIGN_FLAG_FSVERITY, + N_("generate fsverity signatures for package(s) files"), NULL}, ++ { "certpath", '\0', POPT_ARG_STRING, &fileSigningCert, 0, ++ N_("use file signing cert "), ++ N_("") }, + #endif + #if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) + { "fskpath", '\0', POPT_ARG_STRING, &fileSigningKey, 0, +@@ -123,6 +129,12 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + rpmPushMacro(NULL, "_file_signing_key", NULL, fileSigningKey, RMIL_GLOBAL); + } + ++#ifdef WITH_FSVERITY ++ if (fileSigningCert) { ++ rpmPushMacro(NULL, "_file_signing_cert", NULL, fileSigningCert, RMIL_GLOBAL); ++ } ++#endif ++ + if (flags_sign_files(sargs->signflags)) { + char *fileSigningKeyPassword = NULL; + char *key = rpmExpand("%{?_file_signing_key}", NULL); +-- +2.27.0 + diff --git a/0019-Implement-rpmSignVerity.patch b/0019-Implement-rpmSignVerity.patch new file mode 100644 index 0000000..70e873a --- /dev/null +++ b/0019-Implement-rpmSignVerity.patch @@ -0,0 +1,243 @@ +From d447376aa2bf66a5d5b6a928fb0c6e65189910ba Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Fri, 3 Apr 2020 16:38:08 -0400 +Subject: [PATCH 19/33] Implement rpmSignVerity() + +This generates the root Merkle tree hash and signs it using the +specified key and certificate. + +Signed-off-by: Jes Sorensen +--- + sign/rpmgensig.c | 36 +++++++++++++ + sign/rpmsignverity.c | 121 +++++++++++++++++++++++++++++++++++++++++++ + sign/rpmsignverity.h | 29 +++++++++++ + 3 files changed, 186 insertions(+) + create mode 100644 sign/rpmsignverity.c + create mode 100644 sign/rpmsignverity.h + +diff --git a/sign/rpmgensig.c b/sign/rpmgensig.c +index a6e37e71b..8d5c5858f 100644 +--- a/sign/rpmgensig.c ++++ b/sign/rpmgensig.c +@@ -22,6 +22,7 @@ + #include "lib/signature.h" + #include "lib/rpmvs.h" + #include "sign/rpmsignfiles.h" ++#include "sign/rpmsignverity.h" + + #include "debug.h" + +@@ -446,6 +447,36 @@ static rpmRC includeFileSignatures(Header *sigp, Header *hdrp) + #endif + } + ++static rpmRC includeVeritySignatures(FD_t fd, Header *sigp, Header *hdrp) ++{ ++#ifdef WITH_FSVERITY ++ rpmRC rc; ++ char *key = rpmExpand("%{?_file_signing_key}", NULL); ++ char *keypass = rpmExpand("%{?_file_signing_key_password}", NULL); ++ char *cert = rpmExpand("%{?_file_signing_cert}", NULL); ++ ++ if (rstreq(keypass, "")) { ++ free(keypass); ++ keypass = NULL; ++ } ++ ++ if (key && cert) { ++ rc = rpmSignVerity(fd, *sigp, *hdrp, key, keypass, cert); ++ } else { ++ rpmlog(RPMLOG_ERR, _("fsverity signatures requires a key and a cert\n")); ++ rc = RPMRC_FAIL; ++ } ++ ++ free(keypass); ++ free(key); ++ free(cert); ++ return rc; ++#else ++ rpmlog(RPMLOG_ERR, _("fsverity signing support not built in\n")); ++ return RPMRC_FAIL; ++#endif ++} ++ + static int msgCb(struct rpmsinfo_s *sinfo, void *cbdata) + { + char **msg = cbdata; +@@ -544,6 +575,11 @@ static int rpmSign(const char *rpm, int deleting, int flags) + goto exit; + } + ++ if (flags & RPMSIGN_FLAG_FSVERITY) { ++ if (includeVeritySignatures(fd, &sigh, &h)) ++ goto exit; ++ } ++ + if (deleting) { /* Nuke all the signature tags. */ + deleteSigs(sigh); + } else { +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +new file mode 100644 +index 000000000..5346c3bc8 +--- /dev/null ++++ b/sign/rpmsignverity.c +@@ -0,0 +1,121 @@ ++/** ++ * Copyright (C) 2020 Facebook ++ * ++ * Author: Jes Sorensen ++ */ ++ ++#include "system.h" ++ ++#include /* RPMSIGTAG & related */ ++#include /* rpmlog */ ++#include ++#include /* rpmDigestLength */ ++#include "lib/header.h" /* HEADERGET_MINMEM */ ++#include "lib/header_internal.h" ++#include "lib/rpmtypes.h" /* rpmRC */ ++#include ++#include "rpmio/rpmio_internal.h" ++#include "lib/rpmvs.h" ++ ++#include "sign/rpmsignverity.h" ++ ++#define MAX_SIGNATURE_LENGTH 1024 ++ ++static int rpmVerityRead(void *opaque, void *buf, size_t size) ++{ ++ int retval; ++ rpmfi fi = (rpmfi)opaque; ++ ++ retval = rpmfiArchiveRead(fi, buf, size); ++ ++ if (retval > 0) ++ retval = 0; ++ return retval; ++} ++ ++rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, ++ char *keypass, char *cert) ++{ ++ int rc, status; ++ FD_t gzdi; ++ rpmfiles files = NULL; ++ rpmfi fi = NULL; ++ rpmts ts = rpmtsCreate(); ++ struct libfsverity_digest *digest = NULL; ++ struct libfsverity_merkle_tree_params params; ++ struct libfsverity_signature_params sig_params; ++ rpm_loff_t file_size; ++ off_t offset = Ftell(fd); ++ const char *compr; ++ char *rpmio_flags = NULL; ++ char *digest_hex; ++ uint8_t *sig; ++ size_t sig_size; ++ ++ Fseek(fd, 0, SEEK_SET); ++ rpmtsSetVSFlags(ts, RPMVSF_MASK_NODIGESTS | RPMVSF_MASK_NOSIGNATURES | ++ RPMVSF_NOHDRCHK); ++ rc = rpmReadPackageFile(ts, fd, "fsverity", &h); ++ if (rc != RPMRC_OK) { ++ rpmlog(RPMLOG_DEBUG, _("%s: rpmReadPackageFile returned %i\n"), ++ __func__, rc); ++ goto out; ++ } ++ ++ rpmlog(RPMLOG_DEBUG, _("key: %s\n"), key); ++ rpmlog(RPMLOG_DEBUG, _("cert: %s\n"), cert); ++ ++ compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR); ++ rpmio_flags = rstrscat(NULL, "r.", compr ? compr : "gzip", NULL); ++ ++ gzdi = Fdopen(fdDup(Fileno(fd)), rpmio_flags); ++ free(rpmio_flags); ++ ++ files = rpmfilesNew(NULL, h, RPMTAG_BASENAMES, RPMFI_FLAGS_QUERY); ++ fi = rpmfiNewArchiveReader(gzdi, files, ++ RPMFI_ITER_READ_ARCHIVE_OMIT_HARDLINKS); ++ ++ while (rpmfiNext(fi) >= 0) { ++ if (!S_ISREG(rpmfiFMode(fi))) ++ continue; ++ file_size = rpmfiFSize(fi); ++ rpmlog(RPMLOG_DEBUG, _("file: %s, (size %li)\n"), ++ rpmfiFN(fi), file_size); ++ ++ memset(¶ms, 0, sizeof(struct libfsverity_merkle_tree_params)); ++ params.version = 1; ++ params.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; ++ params.block_size = sysconf(_SC_PAGESIZE); ++ params.salt_size = 0 /* salt_size */; ++ params.salt = NULL /* salt */; ++ params.file_size = file_size; ++ status = libfsverity_compute_digest(fi, rpmVerityRead, ++ ¶ms, &digest); ++ if (!status) { ++ digest_hex = pgpHexStr(digest->digest, digest->digest_size); ++ rpmlog(RPMLOG_DEBUG, _("digest(%i): %s\n"), ++ digest->digest_size, digest_hex); ++ free(digest_hex); ++ } ++ memset(&sig_params, 0, sizeof(struct libfsverity_signature_params)); ++ sig_params.keyfile = key; ++ sig_params.certfile = cert; ++ if (libfsverity_sign_digest(digest, &sig_params, &sig, &sig_size)) { ++ rpmlog(RPMLOG_DEBUG, _("failed to sign digest\n")); ++ rc = RPMRC_FAIL; ++ goto out; ++ } ++ rpmlog(RPMLOG_DEBUG, _("digest signing success\n")); ++ ++ free(digest); ++ free(sig); ++ } ++ ++out: ++ Fseek(fd, offset, SEEK_SET); ++ ++ rpmfilesFree(files); ++ rpmfiFree(fi); ++ rpmtsFree(ts); ++ return rc; ++} +diff --git a/sign/rpmsignverity.h b/sign/rpmsignverity.h +new file mode 100644 +index 000000000..f3ad3bb18 +--- /dev/null ++++ b/sign/rpmsignverity.h +@@ -0,0 +1,29 @@ ++#ifndef H_RPMSIGNVERITY ++#define H_RPMSIGNVERITY ++ ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/** ++ * Sign file digests in header into signature header ++ * @param fd file descriptor of RPM ++ * @param sigh package signature header ++ * @param h package header ++ * @param key signing key ++ * @param keypass signing key password ++ * @param cert signing cert ++ * @return RPMRC_OK on success ++ */ ++RPM_GNUC_INTERNAL ++rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, ++ char *keypass, char *cert); ++ ++#ifdef _cplusplus ++} ++#endif ++ ++#endif /* H_RPMSIGNVERITY */ +-- +2.27.0 + diff --git a/0019-reflink-use-rpmpluginsCallFsmFileArchiveReader-to-pr.patch b/0019-reflink-use-rpmpluginsCallFsmFileArchiveReader-to-pr.patch new file mode 100644 index 0000000..1e98fd1 --- /dev/null +++ b/0019-reflink-use-rpmpluginsCallFsmFileArchiveReader-to-pr.patch @@ -0,0 +1,156 @@ +From 5f762af17c6e72e86e4710975dcdfd71fc5d1b07 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 8 Feb 2022 18:24:12 -0800 +Subject: [PATCH 19/30] [reflink] use `rpmpluginsCallFsmFileArchiveReader` to + provide custom rpmfi + +As the plugin can now be responsible to provide an Archive Reader to +`rpmPackageFilesInstall`, we also don't need to mutate the +`RPMTAG_PAYLOADFORMAT` header in memory, removing some special-casing. +--- + lib/depends.c | 2 -- + lib/fsm.c | 38 +++++++++++++++++++------------------- + plugins/reflink.c | 18 +++++++++++++----- + 3 files changed, 32 insertions(+), 26 deletions(-) + +diff --git a/lib/depends.c b/lib/depends.c +index 8998afcd3..30234df3d 100644 +--- a/lib/depends.c ++++ b/lib/depends.c +@@ -81,8 +81,6 @@ static rpmRC headerCheckPayloadFormat(Header h) { + */ + if (!payloadfmt) return rc; + +- if (rstreq(payloadfmt, "clon")) return rc; +- + if (!rstreq(payloadfmt, "cpio")) { + char *nevra = headerGetAsString(h, RPMTAG_NEVRA); + if (payloadfmt && rstreq(payloadfmt, "drpm")) { +diff --git a/lib/fsm.c b/lib/fsm.c +index 711f553d3..6972602e0 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -19,7 +19,6 @@ + + #include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */ + #include "lib/fsm.h" +-#include "lib/rpmlib.h" + #include "lib/rpmte_internal.h" /* XXX rpmfs */ + #include "lib/rpmplugins.h" /* rpm plugins hooks */ + #include "lib/rpmug.h" +@@ -892,14 +891,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); + struct filedata_s *firstlink = NULL; + +- Header h = rpmteHeader(te); +- const char *payloadfmt = headerGetString(h, RPMTAG_PAYLOADFORMAT); +- int cpio = 1; +- +- if (payloadfmt && rstreq(payloadfmt, "clon")) { +- cpio = 0; +- } +- + /* transaction id used for temporary path suffix while installing */ + rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts)); + +@@ -933,14 +924,24 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + if (rc) + goto exit; + +- if (cpio) { +- fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); +- if (fi == NULL) { +- rc = RPMERR_BAD_MAGIC; +- goto exit; +- } +- } else { +- fi = rpmfilesIter(files, RPMFI_ITER_FWD); ++ rpmRC plugin_rc = rpmpluginsCallFsmFileArchiveReader(plugins, payload, files, &fi); ++ switch(plugin_rc) { ++ case RPMRC_PLUGIN_CONTENTS: ++ if(fi == NULL) { ++ rc = RPMERR_BAD_MAGIC; ++ goto exit; ++ } ++ rc = RPMRC_OK; ++ break; ++ case RPMRC_OK: ++ fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); ++ if (fi == NULL) { ++ rc = RPMERR_BAD_MAGIC; ++ goto exit; ++ } ++ break; ++ default: ++ rc = RPMRC_FAIL; + } + + /* Detect and create directories not explicitly in package. */ +@@ -988,7 +989,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + } else if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { + rc = fsmMkfile(fi, fp, files, psm, nodigest, +- &firstlink, &firstlinkfile); ++ &firstlink, &firstlinkfile); + } + } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -1096,7 +1097,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST)); + + exit: +- h = headerFree(h); + fi = rpmfiFree(fi); + Fclose(payload); + free(tid); +diff --git a/plugins/reflink.c b/plugins/reflink.c +index 7dda06d8e..d5e6db27a 100644 +--- a/plugins/reflink.c ++++ b/plugins/reflink.c +@@ -54,6 +54,7 @@ struct reflink_state_s { + FD_t fd; + rpmfiles files; + inodeIndexHash inodeIndexes; ++ int transcoded; + }; + + typedef struct reflink_state_s * reflink_state; +@@ -116,12 +117,8 @@ static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) { + break; + } + rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n")); +- Header h = rpmteHeader(te); ++ state->transcoded = 1; + +- /* replace/add header that main fsm.c can read */ +- headerDel(h, RPMTAG_PAYLOADFORMAT); +- headerPutString(h, RPMTAG_PAYLOADFORMAT, "clon"); +- headerFree(h); + state->files = rpmteFiles(te); + /* tail of file contains offset_table, offset_checksums then magic */ + if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(extents_magic_t)), SEEK_END) < 0) { +@@ -350,10 +347,21 @@ static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* pa + return RPMRC_OK; + } + ++static rpmRC reflink_fsm_file_archive_reader(rpmPlugin plugin, FD_t payload, ++ rpmfiles files, rpmfi *fi) { ++ reflink_state state = rpmPluginGetData(plugin); ++ if(state->transcoded) { ++ *fi = rpmfilesIter(files, RPMFI_ITER_FWD); ++ return RPMRC_PLUGIN_CONTENTS; ++ } ++ return RPMRC_OK; ++} ++ + struct rpmPluginHooks_s reflink_hooks = { + .init = reflink_init, + .cleanup = reflink_cleanup, + .psm_pre = reflink_psm_pre, + .psm_post = reflink_psm_post, + .fsm_file_install = reflink_fsm_file_install, ++ .fsm_file_archive_reader = reflink_fsm_file_archive_reader, + }; +-- +2.35.1 + diff --git a/0020-Introduce-base2bin-a-helper-to-convert-tag-array-of-.patch b/0020-Introduce-base2bin-a-helper-to-convert-tag-array-of-.patch new file mode 100644 index 0000000..b099eaf --- /dev/null +++ b/0020-Introduce-base2bin-a-helper-to-convert-tag-array-of-.patch @@ -0,0 +1,95 @@ +From a7e81a1b18c9e9d124a4ea917c8015af62584abb Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Thu, 28 May 2020 17:48:23 -0400 +Subject: [PATCH 20/33] Introduce base2bin() - a helper to convert tag array of + base64 strings + +This will convert a tag of base64 strings to a binary array, similar +to how hex2bin() works. It supports variable sized strings, and will +determine the maximum string length and build the output array based +on that. + +Signed-off-by: Jes Sorensen +--- + lib/rpmfi.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 58 insertions(+) + +diff --git a/lib/rpmfi.c b/lib/rpmfi.c +index 689ead2c5..8c69d3e40 100644 +--- a/lib/rpmfi.c ++++ b/lib/rpmfi.c +@@ -19,6 +19,7 @@ + #include "lib/fsm.h" /* rpmpsm stuff for now */ + #include "lib/rpmug.h" + #include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */ ++#include "rpmio/rpmbase64.h" + + #include "debug.h" + +@@ -1520,6 +1521,63 @@ static uint8_t *hex2bin(Header h, rpmTagVal tag, rpm_count_t num, size_t len) + return bin; + } + ++/* ++ * Convert a tag of base64 strings to binary presentation. ++ * This handles variable length strings by finding the longest string ++ * before building the output array. Dummy strings in the tag should be ++ * added as '\0' ++ */ ++static uint8_t *base2bin(Header h, rpmTagVal tag, rpm_count_t num, int *len) ++{ ++ struct rpmtd_s td; ++ uint8_t *bin = NULL, *t = NULL; ++ size_t maxlen = 0; ++ int status, i= 0; ++ void **arr = xmalloc(num * sizeof(void *)); ++ size_t *lengths = xcalloc(num, sizeof(size_t)); ++ const char *s; ++ ++ if (headerGet(h, tag, &td, HEADERGET_MINMEM) && rpmtdCount(&td) != num) ++ goto out; ++ ++ while ((s = rpmtdNextString(&td))) { ++ /* Insert a dummy entry for empty strings */ ++ if (*s == '\0') { ++ arr[i++] = NULL; ++ continue; ++ } ++ status = rpmBase64Decode(s, &arr[i], &lengths[i]); ++ if (lengths[i] > maxlen) ++ maxlen = lengths[i]; ++ if (status) { ++ rpmlog(RPMLOG_DEBUG, _("%s: base64 decode failed, len %li\n"), ++ __func__, lengths[i]); ++ goto out; ++ } ++ i++; ++ } ++ ++ if (maxlen) { ++ rpmlog(RPMLOG_DEBUG, _("%s: base64 decode success, len %li\n"), ++ __func__, maxlen); ++ ++ t = bin = xcalloc(num, maxlen); ++ ++ for (i = 0; i < num; i++) { ++ memcpy(t, arr[i], lengths[i]); ++ free(arr[i]); ++ t += maxlen; ++ } ++ *len = maxlen; ++ } ++ out: ++ free(arr); ++ free(lengths); ++ rpmtdFreeData(&td); ++ ++ return bin; ++} ++ + static int rpmfilesPopulate(rpmfiles fi, Header h, rpmfiFlags flags) + { + headerGetFlags scareFlags = (flags & RPMFI_KEEPHEADER) ? +-- +2.27.0 + diff --git a/0020-reflink-tests-Can-install-standard-RPM-with-reflink.patch b/0020-reflink-tests-Can-install-standard-RPM-with-reflink.patch new file mode 100644 index 0000000..24f0711 --- /dev/null +++ b/0020-reflink-tests-Can-install-standard-RPM-with-reflink.patch @@ -0,0 +1,35 @@ +From 13aea986ada3ed7d26182d81d8878bcc807a6ab5 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Thu, 10 Feb 2022 08:49:17 -0800 +Subject: [PATCH 20/30] [reflink][tests] Can install standard RPM with reflink + +Add a test to validate that if a file is a non-transcoded RPM, the reflink plugin correctly ignores the file and let core RPM do the job. +--- + tests/rpm2extents.at | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +index 44e46a68e..648304287 100644 +--- a/tests/rpm2extents.at ++++ b/tests/rpm2extents.at +@@ -109,3 +109,17 @@ test -f ${RPMTEST}/usr/bin/hello + [], + []) + AT_CLEANUP ++ ++AT_SETUP([reflink ignores non-transcoded package]) ++AT_KEYWORDS([reflink]) ++AT_CHECK([ ++RPMDB_INIT ++ ++runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /data/RPMS/hello-2.0-1.x86_64.rpm && exit $? ++# Check that the file is properly installed in chroot ++test -f ${RPMTEST}/usr/bin/hello ++], ++[0], ++[], ++[]) ++AT_CLEANUP +-- +2.35.1 + diff --git a/0021-rpmsignverity-Add-verity-signature-headers-to-the-pa.patch b/0021-rpmsignverity-Add-verity-signature-headers-to-the-pa.patch new file mode 100644 index 0000000..051c0bc --- /dev/null +++ b/0021-rpmsignverity-Add-verity-signature-headers-to-the-pa.patch @@ -0,0 +1,206 @@ +From ef819fecfed22cab2ccbd128e5ede33db8f2d3e9 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Thu, 9 Apr 2020 12:58:17 -0400 +Subject: [PATCH 21/33] rpmsignverity: Add verity signature headers to the + package + +This adds the array of verity signatures, and a signature length +header. We use 4K block for the Merkle tree, and rely on the kernel +doing the right thing. + +Signed-off-by: Jes Sorensen +--- + lib/rpmtag.h | 6 ++- + sign/rpmsignverity.c | 112 +++++++++++++++++++++++++++++-------------- + sign/rpmsignverity.h | 7 +++ + 3 files changed, 87 insertions(+), 38 deletions(-) + +diff --git a/lib/rpmtag.h b/lib/rpmtag.h +index 40ff5fa5d..478457ecb 100644 +--- a/lib/rpmtag.h ++++ b/lib/rpmtag.h +@@ -67,6 +67,7 @@ typedef enum rpmTag_e { + RPMTAG_SHA256HEADER = RPMTAG_SIG_BASE+17, /* s */ + /* RPMTAG_SIG_BASE+18 reserved for RPMSIGTAG_FILESIGNATURES */ + /* RPMTAG_SIG_BASE+19 reserved for RPMSIGTAG_FILESIGNATURELENGTH */ ++ RPMTAG_VERITYSIGNATURES = RPMTAG_SIG_BASE+20, /* s[] */ + + RPMTAG_NAME = 1000, /* s */ + #define RPMTAG_N RPMTAG_NAME /* s */ +@@ -427,6 +428,7 @@ typedef enum rpmSigTag_e { + RPMSIGTAG_SHA256 = RPMTAG_SHA256HEADER, + RPMSIGTAG_FILESIGNATURES = RPMTAG_SIG_BASE + 18, + RPMSIGTAG_FILESIGNATURELENGTH = RPMTAG_SIG_BASE + 19, ++ RPMSIGTAG_VERITYSIGNATURES = RPMTAG_VERITYSIGNATURES, + } rpmSigTag; + + +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index 5346c3bc8..a9818bd08 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -33,23 +33,66 @@ static int rpmVerityRead(void *opaque, void *buf, size_t size) + return retval; + } + ++static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, ++ char *keypass, char *cert) ++{ ++ struct libfsverity_merkle_tree_params params; ++ struct libfsverity_signature_params sig_params; ++ struct libfsverity_digest *digest = NULL; ++ rpm_loff_t file_size; ++ char *digest_hex, *sig_hex = NULL; ++ uint8_t *sig; ++ int status; ++ ++ file_size = rpmfiFSize(fi); ++ ++ memset(¶ms, 0, sizeof(struct libfsverity_merkle_tree_params)); ++ params.version = 1; ++ params.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; ++ params.block_size = RPM_FSVERITY_BLKSZ; ++ params.salt_size = 0 /* salt_size */; ++ params.salt = NULL /* salt */; ++ params.file_size = file_size; ++ status = libfsverity_compute_digest(fi, rpmVerityRead, ¶ms, &digest); ++ if (status) { ++ rpmlog(RPMLOG_DEBUG, _("failed to compute digest\n")); ++ goto out; ++ } ++ ++ digest_hex = pgpHexStr(digest->digest, digest->digest_size); ++ rpmlog(RPMLOG_DEBUG, _("digest(%i): %s\n"), ++ digest->digest_size, digest_hex); ++ free(digest_hex); ++ ++ memset(&sig_params, 0, sizeof(struct libfsverity_signature_params)); ++ sig_params.keyfile = key; ++ sig_params.certfile = cert; ++ if (libfsverity_sign_digest(digest, &sig_params, &sig, sig_size)) { ++ rpmlog(RPMLOG_DEBUG, _("failed to sign digest\n")); ++ goto out; ++ } ++ ++ sig_hex = pgpHexStr(sig, *sig_size + 1); ++ out: ++ free(digest); ++ free(sig); ++ return sig_hex; ++} ++ + rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + char *keypass, char *cert) + { +- int rc, status; ++ int rc; + FD_t gzdi; + rpmfiles files = NULL; + rpmfi fi = NULL; + rpmts ts = rpmtsCreate(); +- struct libfsverity_digest *digest = NULL; +- struct libfsverity_merkle_tree_params params; +- struct libfsverity_signature_params sig_params; ++ struct rpmtd_s td; + rpm_loff_t file_size; + off_t offset = Ftell(fd); + const char *compr; + char *rpmio_flags = NULL; +- char *digest_hex; +- uint8_t *sig; ++ char *sig_hex; + size_t sig_size; + + Fseek(fd, 0, SEEK_SET); +@@ -75,43 +118,40 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + fi = rpmfiNewArchiveReader(gzdi, files, + RPMFI_ITER_READ_ARCHIVE_OMIT_HARDLINKS); + ++ /* ++ * Should this be sigh from the cloned fd or the sigh we received? ++ */ ++ headerDel(sigh, RPMSIGTAG_VERITYSIGNATURES); ++ ++ rpmtdReset(&td); ++ td.tag = RPMSIGTAG_VERITYSIGNATURES; ++ td.type = RPM_STRING_ARRAY_TYPE; ++ td.count = 1; ++ + while (rpmfiNext(fi) >= 0) { +- if (!S_ISREG(rpmfiFMode(fi))) +- continue; + file_size = rpmfiFSize(fi); +- rpmlog(RPMLOG_DEBUG, _("file: %s, (size %li)\n"), +- rpmfiFN(fi), file_size); +- +- memset(¶ms, 0, sizeof(struct libfsverity_merkle_tree_params)); +- params.version = 1; +- params.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; +- params.block_size = sysconf(_SC_PAGESIZE); +- params.salt_size = 0 /* salt_size */; +- params.salt = NULL /* salt */; +- params.file_size = file_size; +- status = libfsverity_compute_digest(fi, rpmVerityRead, +- ¶ms, &digest); +- if (!status) { +- digest_hex = pgpHexStr(digest->digest, digest->digest_size); +- rpmlog(RPMLOG_DEBUG, _("digest(%i): %s\n"), +- digest->digest_size, digest_hex); +- free(digest_hex); +- } +- memset(&sig_params, 0, sizeof(struct libfsverity_signature_params)); +- sig_params.keyfile = key; +- sig_params.certfile = cert; +- if (libfsverity_sign_digest(digest, &sig_params, &sig, &sig_size)) { +- rpmlog(RPMLOG_DEBUG, _("failed to sign digest\n")); ++ ++ rpmlog(RPMLOG_DEBUG, _("file: %s, (size %li, link %s, idx %i)\n"), ++ rpmfiFN(fi), file_size, rpmfiFLink(fi), rpmfiFX(fi)); ++ ++ sig_hex = rpmVeritySignFile(fi, &sig_size, key, keypass, cert); ++ td.data = &sig_hex; ++ rpmlog(RPMLOG_DEBUG, _("digest signed, len: %li\n"), sig_size); ++#if 0 ++ rpmlog(RPMLOG_DEBUG, _("digest: %s\n"), (char *)sig_hex); ++#endif ++ if (!headerPut(sigh, &td, HEADERPUT_APPEND)) { ++ rpmlog(RPMLOG_ERR, _("headerPutString failed\n")); + rc = RPMRC_FAIL; + goto out; + } +- rpmlog(RPMLOG_DEBUG, _("digest signing success\n")); +- +- free(digest); +- free(sig); ++ free(sig_hex); + } + +-out: ++ rpmlog(RPMLOG_DEBUG, _("sigh size: %i\n"), headerSizeof(sigh, 0)); ++ ++ rc = RPMRC_OK; ++ out: + Fseek(fd, offset, SEEK_SET); + + rpmfilesFree(files); +diff --git a/sign/rpmsignverity.h b/sign/rpmsignverity.h +index f3ad3bb18..69bbaf7f7 100644 +--- a/sign/rpmsignverity.h ++++ b/sign/rpmsignverity.h +@@ -8,6 +8,13 @@ + extern "C" { + #endif + ++/* ++ * Block size used to generate the Merkle tree for fsverity. For now ++ * we only support 4K blocks, if we ever decide to support different ++ * block sizes, we will need a tag to indicate this. ++ */ ++#define RPM_FSVERITY_BLKSZ 4096 ++ + /** + * Sign file digests in header into signature header + * @param fd file descriptor of RPM +-- +2.27.0 + diff --git a/0021-tests-Fix-tests-AT_KEYWORDS-usage.patch b/0021-tests-Fix-tests-AT_KEYWORDS-usage.patch new file mode 100644 index 0000000..250aa6b --- /dev/null +++ b/0021-tests-Fix-tests-AT_KEYWORDS-usage.patch @@ -0,0 +1,43 @@ +From 3e363f853a4379e0199db81f777f4b610e158eae Mon Sep 17 00:00:00 2001 +From: chantra +Date: Thu, 10 Feb 2022 08:49:58 -0800 +Subject: [PATCH 21/30] [tests] Fix tests AT_KEYWORDS usage + +--- + tests/rpm2extents.at | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +index 648304287..fa124ac03 100644 +--- a/tests/rpm2extents.at ++++ b/tests/rpm2extents.at +@@ -32,7 +32,7 @@ AT_CLEANUP + # Check that transcoder writes checksig return code and content. + # + AT_SETUP([rpm2extents signature]) +-AT_KEYWORDS([rpm2extents digest signature]) ++AT_KEYWORDS([rpm2extents]) + AT_CHECK([ + RPMDB_INIT + +@@ -64,7 +64,7 @@ RPMSignOutput Content Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK + AT_CLEANUP + + AT_SETUP([rpm2extents signature verification]) +-AT_KEYWORDS([rpm2extents digest signature]) ++AT_KEYWORDS([rpm2extents]) + AT_CHECK([ + RPMDB_INIT + +@@ -96,7 +96,7 @@ runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $? + AT_CLEANUP + + AT_SETUP([rpm2extents install package]) +-AT_KEYWORDS([rpm2extents install package]) ++AT_KEYWORDS([rpm2extents reflink]) + AT_SKIP_IF([$REFLINK_DISABLED]) + AT_CHECK([ + RPMDB_INIT +-- +2.35.1 + diff --git a/0022-reflink-fix-support-for-hardlinks.patch b/0022-reflink-fix-support-for-hardlinks.patch new file mode 100644 index 0000000..e4d6558 --- /dev/null +++ b/0022-reflink-fix-support-for-hardlinks.patch @@ -0,0 +1,132 @@ +From fd8ffffced543e248847d63e9375fb95584f998d Mon Sep 17 00:00:00 2001 +From: chantra +Date: Fri, 11 Feb 2022 18:09:47 -0800 +Subject: [PATCH 22/30] [reflink] fix support for hardlinks + +a `suffix` made of ";${tid}" is now used until the changes are commited. As such, when +linking the file, we should link to the suffixed file. + +The `suffix` is currently internal only to rpmPackageFilesInstall and there is +no obvious way to use data from struct `filedata_s` without exposing it to +a bunch of other places such as rpmplugin*. +We already keep track of the first file index. We used to use this to fetch the +file name, but due to suffix, the file is not named correctly, e.g it +has the name we will want at the end, once the transaction is commited, +not the temporary name. +Instead of re-implementing a local suffix that may change over time as +the implementation evolves, we can store the name of the file we should link to +in the hash instead of the index, which we were then using to fetch the +file name. +When hardlinking, we can then retrieve the name of the target file +instead of the index, like we previously did, and hardlink to that +without any issues. +Add a test to validate that reflink can handle hardlinking. + +Originally, the test added would fail with: + +``` +error: reflink: Unable to hard link /foo/hello -> /foo/hello-bar;6207219c due to No such file or directory +error: Plugin reflink: hook fsm_file_install failed +error: unpacking of archive failed on file /foo/hello-bar;6207219c: +cpio: (error 0x2) +error: hlinktest-1.0-1.noarch: install failed +./rpm2extents.at:114: exit code was 1, expected 0 +499. rpm2extents.at:112: 499. reflink hardlink package +(rpm2extents.at:112): FAILED (rpm2extents.at:114) +``` + +After this change, the test passes. +--- + plugins/reflink.c | 19 ++++++++----------- + tests/rpm2extents.at | 15 +++++++++++++++ + 2 files changed, 23 insertions(+), 11 deletions(-) + +diff --git a/plugins/reflink.c b/plugins/reflink.c +index d5e6db27a..4fc1d74d1 100644 +--- a/plugins/reflink.c ++++ b/plugins/reflink.c +@@ -29,7 +29,7 @@ + #undef HTDATATYPE + #define HASHTYPE inodeIndexHash + #define HTKEYTYPE rpm_ino_t +-#define HTDATATYPE int ++#define HTDATATYPE const char * + #include "lib/rpmhash.H" + #include "lib/rpmhash.C" + +@@ -163,7 +163,7 @@ static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) { + return RPMRC_FAIL; + } + state->inodeIndexes = inodeIndexHashCreate( +- state->keys, inodeId, inodeCmp, NULL, NULL ++ state->keys, inodeId, inodeCmp, NULL, (inodeIndexHashFreeData)rfree + ); + } + +@@ -226,7 +226,7 @@ static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* pa + struct file_clone_range fcr; + rpm_loff_t size; + int dst, rc; +- int *hlix; ++ const char **hl_target = NULL; + + reflink_state state = rpmPluginGetData(plugin); + if (state->table == NULL) { +@@ -243,18 +243,15 @@ static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* pa + /* check for hard link entry in table. GetEntry overwrites hlix with + * the address of the first match. + */ +- if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hlix, NULL, +- NULL)) { ++ if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hl_target, ++ NULL, NULL)) { + /* entry is in table, use hard link */ +- char *fn = rpmfilesFN(state->files, hlix[0]); +- if (link(fn, path) != 0) { ++ if (link(hl_target[0], path) != 0) { + rpmlog(RPMLOG_ERR, + _("reflink: Unable to hard link %s -> %s due to %s\n"), +- fn, path, strerror(errno)); +- free(fn); ++ hl_target[0], path, strerror(errno)); + return RPMRC_FAIL; + } +- free(fn); + return RPMRC_PLUGIN_CONTENTS; + } + /* if we didn't hard link, then we'll track this inode as being +@@ -262,7 +259,7 @@ static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* pa + */ + if (rpmfiFNlink(fi) > 1) { + /* minor optimization: only store files with more than one link */ +- inodeIndexHashAddEntry(state->inodeIndexes, inode, rpmfiFX(fi)); ++ inodeIndexHashAddEntry(state->inodeIndexes, inode, rstrdup(path)); + } + /* derived from wfd_open in fsm.c */ + mode_t old_umask = umask(0577); +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +index fa124ac03..5c66de7f6 100644 +--- a/tests/rpm2extents.at ++++ b/tests/rpm2extents.at +@@ -123,3 +123,18 @@ test -f ${RPMTEST}/usr/bin/hello + [], + []) + AT_CLEANUP ++ ++AT_SETUP([reflink hardlink package]) ++AT_KEYWORDS([reflink hardlink]) ++AT_SKIP_IF([$REFLINK_DISABLED]) ++AT_CHECK([ ++RPMDB_INIT ++ ++PKG=hlinktest-1.0-1.noarch.rpm ++runroot_other cat /data/RPMS/${PKG} | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/${PKG} 2> /dev/null ++runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /tmp/${PKG} ++], ++[0], ++[], ++[]) ++AT_CLEANUP +-- +2.35.1 + diff --git a/0022-rpmSignVerity-Generate-signatures-for-files-not-pres.patch b/0022-rpmSignVerity-Generate-signatures-for-files-not-pres.patch new file mode 100644 index 0000000..dba0ca6 --- /dev/null +++ b/0022-rpmSignVerity-Generate-signatures-for-files-not-pres.patch @@ -0,0 +1,162 @@ +From 22420d9ee652a25357727b00585dc3cfe78b2a80 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 13 Apr 2020 18:14:15 -0400 +Subject: [PATCH 22/33] rpmSignVerity: Generate signatures for files not + present in archive + +This generates signatures for all files in the archive, then picks up +ghost files from the header metadata and generates signatures for them +as well. It finally submits them to RPMTAG_VERITYSIGNATURES in header +file order as we cannot rely on archive order and header order being +the same. + +Signed-off-by: Jes Sorensen +--- + lib/package.c | 1 + + sign/rpmsignverity.c | 55 ++++++++++++++++++++++++++++++++++---------- + 2 files changed, 44 insertions(+), 12 deletions(-) + +diff --git a/lib/package.c b/lib/package.c +index b7d996a12..c6108f686 100644 +--- a/lib/package.c ++++ b/lib/package.c +@@ -45,6 +45,7 @@ struct taglate_s { + { RPMSIGTAG_PAYLOADSIZE, RPMTAG_ARCHIVESIZE, 1, 1 }, + { RPMSIGTAG_FILESIGNATURES, RPMTAG_FILESIGNATURES, 0, 1 }, + { RPMSIGTAG_FILESIGNATURELENGTH, RPMTAG_FILESIGNATURELENGTH, 1, 1 }, ++ { RPMSIGTAG_VERITYSIGNATURES, RPMTAG_VERITYSIGNATURES, 0, 0 }, + { RPMSIGTAG_SHA1, RPMTAG_SHA1HEADER, 1, 0 }, + { RPMSIGTAG_SHA256, RPMTAG_SHA256HEADER, 1, 0 }, + { RPMSIGTAG_DSA, RPMTAG_DSAHEADER, 0, 0 }, +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index a9818bd08..3bb23a18d 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -41,7 +41,7 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + struct libfsverity_digest *digest = NULL; + rpm_loff_t file_size; + char *digest_hex, *sig_hex = NULL; +- uint8_t *sig; ++ uint8_t *sig = NULL; + int status; + + file_size = rpmfiFSize(fi); +@@ -72,7 +72,7 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + goto out; + } + +- sig_hex = pgpHexStr(sig, *sig_size + 1); ++ sig_hex = pgpHexStr(sig, *sig_size); + out: + free(digest); + free(sig); +@@ -86,6 +86,7 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + FD_t gzdi; + rpmfiles files = NULL; + rpmfi fi = NULL; ++ rpmfi hfi = rpmfiNew(NULL, h, RPMTAG_BASENAMES, RPMFI_FLAGS_QUERY); + rpmts ts = rpmtsCreate(); + struct rpmtd_s td; + rpm_loff_t file_size; +@@ -93,11 +94,14 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + const char *compr; + char *rpmio_flags = NULL; + char *sig_hex; ++ char **signatures = NULL; + size_t sig_size; ++ int nr_files, idx; + + Fseek(fd, 0, SEEK_SET); + rpmtsSetVSFlags(ts, RPMVSF_MASK_NODIGESTS | RPMVSF_MASK_NOSIGNATURES | + RPMVSF_NOHDRCHK); ++ + rc = rpmReadPackageFile(ts, fd, "fsverity", &h); + if (rc != RPMRC_OK) { + rpmlog(RPMLOG_DEBUG, _("%s: rpmReadPackageFile returned %i\n"), +@@ -113,6 +117,8 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + + gzdi = Fdopen(fdDup(Fileno(fd)), rpmio_flags); + free(rpmio_flags); ++ if (!gzdi) ++ rpmlog(RPMLOG_DEBUG, _("Fdopen() failed\n")); + + files = rpmfilesNew(NULL, h, RPMTAG_BASENAMES, RPMFI_FLAGS_QUERY); + fi = rpmfiNewArchiveReader(gzdi, files, +@@ -123,39 +129,64 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + */ + headerDel(sigh, RPMSIGTAG_VERITYSIGNATURES); + +- rpmtdReset(&td); +- td.tag = RPMSIGTAG_VERITYSIGNATURES; +- td.type = RPM_STRING_ARRAY_TYPE; +- td.count = 1; ++ /* ++ * The payload doesn't include special files, like ghost files, and ++ * we cannot rely on the file order in the payload to match that of ++ * the header. Instead we allocate an array of pointers and populate ++ * it as we go along. Then walk the header fi and account for the ++ * special files. Last we walk the array and populate the header. ++ */ ++ nr_files = rpmfiFC(hfi); ++ signatures = xcalloc(nr_files, sizeof(char *)); ++ ++ rpmlog(RPMLOG_DEBUG, _("file count - header: %i, payload %i\n"), ++ nr_files, rpmfiFC(fi)); + + while (rpmfiNext(fi) >= 0) { + file_size = rpmfiFSize(fi); ++ idx = rpmfiFX(fi); + + rpmlog(RPMLOG_DEBUG, _("file: %s, (size %li, link %s, idx %i)\n"), + rpmfiFN(fi), file_size, rpmfiFLink(fi), rpmfiFX(fi)); + +- sig_hex = rpmVeritySignFile(fi, &sig_size, key, keypass, cert); ++ signatures[idx] = rpmVeritySignFile(fi, &sig_size, key, keypass, cert); ++ } ++ ++ while (rpmfiNext(hfi) >= 0) { ++ idx = rpmfiFX(hfi); ++ if (signatures[idx]) ++ continue; ++ signatures[idx] = rpmVeritySignFile(hfi, &sig_size, key, keypass, cert); ++ } ++ ++ rpmtdReset(&td); ++ td.tag = RPMSIGTAG_VERITYSIGNATURES; ++ td.type = RPM_STRING_ARRAY_TYPE; ++ td.count = 1; ++ for (idx = 0; idx < nr_files; idx++) { ++ sig_hex = signatures[idx]; + td.data = &sig_hex; +- rpmlog(RPMLOG_DEBUG, _("digest signed, len: %li\n"), sig_size); +-#if 0 +- rpmlog(RPMLOG_DEBUG, _("digest: %s\n"), (char *)sig_hex); +-#endif + if (!headerPut(sigh, &td, HEADERPUT_APPEND)) { + rpmlog(RPMLOG_ERR, _("headerPutString failed\n")); + rc = RPMRC_FAIL; + goto out; + } +- free(sig_hex); ++ rpmlog(RPMLOG_DEBUG, _("signature: %s\n"), signatures[idx]); ++ rpmlog(RPMLOG_DEBUG, _("digest signed, len: %li\n"), sig_size); ++ free(signatures[idx]); ++ signatures[idx] = NULL; + } + + rpmlog(RPMLOG_DEBUG, _("sigh size: %i\n"), headerSizeof(sigh, 0)); + + rc = RPMRC_OK; + out: ++ signatures = _free(signatures); + Fseek(fd, offset, SEEK_SET); + + rpmfilesFree(files); + rpmfiFree(fi); ++ rpmfiFree(hfi); + rpmtsFree(ts); + return rc; + } +-- +2.27.0 + diff --git a/0023-Process-verity-tag-on-package-read.patch b/0023-Process-verity-tag-on-package-read.patch new file mode 100644 index 0000000..c0b6b74 --- /dev/null +++ b/0023-Process-verity-tag-on-package-read.patch @@ -0,0 +1,189 @@ +From 34e751ccee43f799dd32f6b9c64020106dba9fac Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 13 Apr 2020 18:21:36 -0400 +Subject: [PATCH 23/33] Process verity tag on package read + +This processes verity signature tags on package read, and provides +accessor functions to access them. + +Signed-off-by: Jes Sorensen +--- + lib/rpmfi.c | 27 +++++++++++++++++++++++++++ + lib/rpmfi.h | 8 ++++++++ + lib/rpmfiles.h | 10 ++++++++++ + sign/rpmsignverity.c | 20 ++++++++++++++++---- + 4 files changed, 61 insertions(+), 4 deletions(-) + +diff --git a/lib/rpmfi.c b/lib/rpmfi.c +index 8c69d3e40..5fdbe02a2 100644 +--- a/lib/rpmfi.c ++++ b/lib/rpmfi.c +@@ -116,8 +116,10 @@ struct rpmfiles_s { + + int digestalgo; /*!< File digest algorithm */ + uint32_t *signatureoffs; /*!< File signature offsets */ ++ int veritysiglength; /*!< Verity signature length */ + unsigned char * digests; /*!< File digests in binary. */ + unsigned char * signatures; /*!< File signatures in binary. */ ++ unsigned char * veritysigs; /*!< Verity signatures in binary. */ + + struct nlinkHash_s * nlinks;/*!< Files connected by hardlinks */ + rpm_off_t * replacedSizes; /*!< (TR_ADDED) */ +@@ -582,6 +584,19 @@ const unsigned char * rpmfilesFSignature(rpmfiles fi, int ix, size_t *len) + return signature; + } + ++const unsigned char * rpmfilesVSignature(rpmfiles fi, int ix, size_t *len) ++{ ++ const unsigned char *vsignature = NULL; ++ ++ if (fi != NULL && ix >= 0 && ix < rpmfilesFC(fi)) { ++ if (fi->veritysigs != NULL) ++ vsignature = fi->veritysigs + (fi->veritysiglength * ix); ++ if (len) ++ *len = fi->veritysiglength; ++ } ++ return vsignature; ++} ++ + const char * rpmfilesFLink(rpmfiles fi, int ix) + { + const char * flink = NULL; +@@ -1268,6 +1283,7 @@ rpmfiles rpmfilesFree(rpmfiles fi) + fi->digests = _free(fi->digests); + fi->signatures = _free(fi->signatures); + fi->signatureoffs = _free(fi->signatureoffs); ++ fi->veritysigs = _free(fi->veritysigs); + fi->fcaps = _free(fi->fcaps); + + fi->cdict = _free(fi->cdict); +@@ -1649,6 +1665,12 @@ static int rpmfilesPopulate(rpmfiles fi, Header h, rpmfiFlags flags) + totalfc, &fi->signatureoffs); + } + ++ fi->veritysigs = NULL; ++ if (!(flags & RPMFI_NOVERITYSIGNATURES)) { ++ fi->veritysigs = base2bin(h, RPMTAG_VERITYSIGNATURES, ++ totalfc, &fi->veritysiglength); ++ } ++ + /* XXX TR_REMOVED doesn;t need fmtimes, frdevs, finodes */ + if (!(flags & RPMFI_NOFILEMTIMES)) + _hgfi(h, RPMTAG_FILEMTIMES, &td, scareFlags, fi->fmtimes); +@@ -1939,6 +1961,11 @@ const unsigned char * rpmfiFSignature(rpmfi fi, size_t *len) + return rpmfilesFSignature(fi->files, fi ? fi->i : -1, len); + } + ++const unsigned char * rpmfiVSignature(rpmfi fi, size_t *len) ++{ ++ return rpmfilesVSignature(fi->files, fi ? fi->i : -1, len); ++} ++ + uint32_t rpmfiFDepends(rpmfi fi, const uint32_t ** fddictp) + { + return rpmfilesFDepends(fi->files, fi ? fi->i : -1, fddictp); +diff --git a/lib/rpmfi.h b/lib/rpmfi.h +index 6ef70cd28..fcb9d3acd 100644 +--- a/lib/rpmfi.h ++++ b/lib/rpmfi.h +@@ -190,6 +190,14 @@ char * rpmfiFDigestHex(rpmfi fi, int *algo); + */ + const unsigned char * rpmfiFSignature(rpmfi fi, size_t *siglen); + ++/** \ingroup rpmfi ++ * Return current verity (binary) signature of file info set iterator. ++ * @param fi file info set iterator ++ * @retval siglen signature length (pass NULL to ignore) ++ * @return current verity signature, NULL on invalid ++ */ ++const unsigned char * rpmfiVSignature(rpmfi fi, size_t *siglen); ++ + /** \ingroup rpmfi + * Return current file linkto (i.e. symlink(2) target) from file info set iterator. + * @param fi file info set iterator +diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h +index daf572cf4..81b3d01a1 100644 +--- a/lib/rpmfiles.h ++++ b/lib/rpmfiles.h +@@ -119,6 +119,7 @@ enum rpmfiFlags_e { + RPMFI_NOFILEVERIFYFLAGS = (1 << 16), + RPMFI_NOFILEFLAGS = (1 << 17), + RPMFI_NOFILESIGNATURES = (1 << 18), ++ RPMFI_NOVERITYSIGNATURES = (1 << 19), + }; + + typedef rpmFlags rpmfiFlags; +@@ -442,6 +443,15 @@ const unsigned char * rpmfilesFDigest(rpmfiles fi, int ix, int *algo, size_t *le + */ + const unsigned char * rpmfilesFSignature(rpmfiles fi, int ix, size_t *len); + ++/** \ingroup rpmfiles ++ * Return file verity signature (binary) ++ * @param fi file info set ++ * @param ix file index ++ * @retval len signature length (pass NULL to ignore) ++ * @return verity signature, NULL on invalid ++ */ ++const unsigned char * rpmfilesVSignature(rpmfiles fi, int ix, size_t *len); ++ + /** \ingroup rpmfiles + * Return file rdev from file info set. + * @param fi file info set +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index 3bb23a18d..177561957 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -15,6 +15,7 @@ + #include "lib/rpmtypes.h" /* rpmRC */ + #include + #include "rpmio/rpmio_internal.h" ++#include "rpmio/rpmbase64.h" + #include "lib/rpmvs.h" + + #include "sign/rpmsignverity.h" +@@ -40,7 +41,7 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + struct libfsverity_signature_params sig_params; + struct libfsverity_digest *digest = NULL; + rpm_loff_t file_size; +- char *digest_hex, *sig_hex = NULL; ++ char *digest_hex, *digest_base64, *sig_base64 = NULL, *sig_hex = NULL; + uint8_t *sig = NULL; + int status; + +@@ -60,8 +61,14 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + } + + digest_hex = pgpHexStr(digest->digest, digest->digest_size); +- rpmlog(RPMLOG_DEBUG, _("digest(%i): %s\n"), +- digest->digest_size, digest_hex); ++ digest_base64 = rpmBase64Encode(digest->digest, digest->digest_size, -1); ++ rpmlog(RPMLOG_DEBUG, _("file(size %li): %s: digest(%i): %s, idx %i\n"), ++ file_size, rpmfiFN(fi), digest->digest_size, digest_hex, ++ rpmfiFX(fi)); ++ rpmlog(RPMLOG_DEBUG, _("file(size %li): %s: digest sz (%i): base64 sz (%li), %s, idx %i\n"), ++ file_size, rpmfiFN(fi), digest->digest_size, strlen(digest_base64), ++ digest_base64, rpmfiFX(fi)); ++ + free(digest_hex); + + memset(&sig_params, 0, sizeof(struct libfsverity_signature_params)); +@@ -73,10 +80,15 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + } + + sig_hex = pgpHexStr(sig, *sig_size); ++ sig_base64 = rpmBase64Encode(sig, *sig_size, -1); ++ rpmlog(RPMLOG_DEBUG, _("%s: sig_size(%li), base64_size(%li), idx %i: signature:\n%s\n"), ++ rpmfiFN(fi), *sig_size, strlen(sig_base64), rpmfiFX(fi), sig_hex); + out: ++ free(sig_hex); ++ + free(digest); + free(sig); +- return sig_hex; ++ return sig_base64; + } + + rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, +-- +2.27.0 + diff --git a/0023-rpm2extents-Improve-logging.patch b/0023-rpm2extents-Improve-logging.patch new file mode 100644 index 0000000..8ac52f7 --- /dev/null +++ b/0023-rpm2extents-Improve-logging.patch @@ -0,0 +1,393 @@ +From a2c959085250d186c54130b78af076095c3d3cd3 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Tue, 15 Feb 2022 09:11:33 -0800 +Subject: [PATCH 23/30] [rpm2extents] Improve logging + +``` +$ ls -l tests/data/RPMS/hello-2.0-1.x86_64.rpm +-rw-r--r--. 1 chantra chantra 9915 Jan 19 09:10 tests/data/RPMS/hello-2.0-1.x86_64.rpm +$ cp tests/data/RPMS/hello-2.0-1.x86_64.rpm ~/trunc-hello-2.0-1.x86_64.rpm +$ truncate -s 9000 ~/trunc-hello-2.0-1.x86_64.rpm +$ ls -l ~/trunc-hello-2.0-1.x86_64.rpm +-rw-r--r--. 1 chantra chantra 9000 Feb 15 09:13 /home/chantra/trunc-hello-2.0-1.x86_64.rpm +$ cat ~/trunc-hello-2.0-1.x86_64.rpm| ./rpm2extents SHA256 > /dev/null +error: rpmfiArchiveReadToFile failed while extracting "/usr/bin/hello" with RC -32784: cpio: read failed - Illegal seek +error: Package processor failed: -32784 +error: Unable to write input length 9000: 32, Broken pipe +error: Failed to write digests: 32, Broken pipe +error: Validator failed with RC 2 +``` +Before: +``` +$ cat ~/trunc-hello-2.0-1.x86_64.rpm| ./rpm2extents SHA256 > /dev/null +rpmfiArchiveReadToFile failed with -32784 +Validator failed +Unable to write input length 9000 +Failed to write digestsValidator failed +``` +--- + rpm2extents.c | 120 ++++++++++++++++++++++++++++++-------------------- + 1 file changed, 72 insertions(+), 48 deletions(-) + +diff --git a/rpm2extents.c b/rpm2extents.c +index a326e3857..e1a19fedb 100644 +--- a/rpm2extents.c ++++ b/rpm2extents.c +@@ -84,13 +84,15 @@ static int FDWriteDigests( + + len = sizeof(fdilength); + if (Fwrite(&fdilength, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write input length %zd\n"), fdilength); ++ rpmlog(RPMLOG_ERR, _("Unable to write input length %zd: %d, %s\n"), ++ fdilength, errno, strerror(errno)); + goto exit; + } + len = sizeof(algos_len); + if (Fwrite(&algos_len, len, 1, fdo) != len) { + algo_digest_len = (uint32_t)filedigest_len; +- fprintf(stderr, _("Unable to write number of digests\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write number of digests: %d, %s\n"), ++ errno, strerror(errno)); + goto exit; + } + for (algo = 0; algo < algos_len; algo++) { +@@ -102,24 +104,28 @@ static int FDWriteDigests( + + len = sizeof(algo_name_len); + if (Fwrite(&algo_name_len, len, 1, fdo) != len) { +- fprintf(stderr, +- _("Unable to write digest algo name length\n")); ++ rpmlog(RPMLOG_ERR, ++ _("Unable to write digest algo name length: %d, %s\n"), ++ errno, strerror(errno)); + goto exit; + } + len = sizeof(algo_digest_len); + if (Fwrite(&algo_digest_len, len, 1, fdo) != len) { +- fprintf(stderr, +- _("Unable to write number of bytes for digest\n")); ++ rpmlog(RPMLOG_ERR, ++ _("Unable to write number of bytes for digest: %d, %s\n"), ++ errno, strerror(errno)); + goto exit; + } + if (Fwrite(algo_name, algo_name_len, 1, fdo) != algo_name_len) { +- fprintf(stderr, _("Unable to write digest algo name\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write digest algo name: %d, %s\n"), ++ errno, strerror(errno)); + goto exit; + } + if (Fwrite(filedigest, algo_digest_len, 1, fdo ) != algo_digest_len) { +- fprintf(stderr, +- _("Unable to write digest value %u, %zu\n"), +- algo_digest_len, filedigest_len); ++ rpmlog(RPMLOG_ERR, ++ _("Unable to write digest value %u, %zu: %d, %s\n"), ++ algo_digest_len, filedigest_len, ++ errno, strerror(errno)); + goto exit; + } + } +@@ -133,22 +139,29 @@ static rpmRC FDWriteSignaturesValidation(FD_t fdo, int rpmvsrc, char *msg) { + rpmRC rc = RPMRC_FAIL; + + if(rpmvsrc){ +- fprintf(stderr, _("Error verifying package signatures\n")); ++ rpmlog(RPMLOG_WARNING, ++ _("Error verifying package signatures:\n%s\n"), msg); + } + + len = sizeof(rpmvsrc); + if (Fwrite(&rpmvsrc, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write signature verification RC code %d\n"), rpmvsrc); ++ rpmlog(RPMLOG_ERR, ++ _("Unable to write signature verification RC code %d: %d, %s\n"), ++ rpmvsrc, errno, strerror(errno)); + goto exit; + } + size_t content_len = msg ? strlen(msg) : 0; + len = sizeof(content_len); + if (Fwrite(&content_len, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write signature verification output length %zd\n"), content_len); ++ rpmlog(RPMLOG_ERR, ++ _("Unable to write signature verification output length %zd: %d, %s\n"), ++ content_len, errno, strerror(errno)); + goto exit; + } + if (Fwrite(msg, content_len, 1, fdo) != content_len) { +- fprintf(stderr, _("Unable to write signature verification output %s\n"), msg); ++ rpmlog(RPMLOG_ERR, ++ _("Unable to write signature verification output %s: %d, %s\n"), ++ msg, errno, strerror(errno)); + goto exit; + } + +@@ -174,12 +187,16 @@ static rpmRC validator(FD_t fdi, FD_t digesto, FD_t sigo, + + // Write result of digest computation + if(FDWriteDigests(fdi, digesto, algos, algos_len) != RPMRC_OK) { +- fprintf(stderr, _("Failed to write digests")); ++ rpmlog(RPMLOG_ERR, _("Failed to write digests: %d, %s\n"), ++ errno, strerror(errno)); + goto exit; + } + + // Write result of signature validation. + if(FDWriteSignaturesValidation(sigo, rpmvsrc, msg)) { ++ rpmlog(RPMLOG_ERR, ++ _("Failed to write signature verification result: %d, %s\n"), ++ errno, strerror(errno)); + goto exit; + } + rc = RPMRC_OK; +@@ -226,24 +243,24 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + fdo = fdDup(STDOUT_FILENO); + + if (rpmReadPackageRaw(fdi, &sigh, &h)) { +- fprintf(stderr, _("Error reading package\n")); ++ rpmlog(RPMLOG_ERR, _("Error reading package\n")); + exit(EXIT_FAILURE); + } + + if (rpmLeadWrite(fdo, h)) + { +- fprintf(stderr, _("Unable to write package lead: %s\n"), ++ rpmlog(RPMLOG_ERR, _("Unable to write package lead: %s\n"), + Fstrerror(fdo)); + exit(EXIT_FAILURE); + } + + if (rpmWriteSignature(fdo, sigh)) { +- fprintf(stderr, _("Unable to write signature: %s\n"), Fstrerror(fdo)); ++ rpmlog(RPMLOG_ERR, _("Unable to write signature: %s\n"), Fstrerror(fdo)); + exit(EXIT_FAILURE); + } + + if (headerWrite(fdo, h, HEADER_MAGIC_YES)) { +- fprintf(stderr, _("Unable to write headers: %s\n"), Fstrerror(fdo)); ++ rpmlog(RPMLOG_ERR, _("Unable to write headers: %s\n"), Fstrerror(fdo)); + exit(EXIT_FAILURE); + } + +@@ -257,7 +274,7 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + free(rpmio_flags); + + if (gzdi == NULL) { +- fprintf(stderr, _("cannot re-open payload: %s\n"), Fstrerror(gzdi)); ++ rpmlog(RPMLOG_ERR, _("cannot re-open payload: %s\n"), Fstrerror(gzdi)); + exit(EXIT_FAILURE); + } + +@@ -300,7 +317,7 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + } + pad = pad_to(pos, fundamental_block_size); + if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) { +- fprintf(stderr, _("Unable to write padding\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write padding\n")); + rc = RPMRC_FAIL; + goto exit; + } +@@ -313,7 +330,12 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + size = rpmfiFSize(fi); + rc = rpmfiArchiveReadToFile(fi, fdo, 0); + if (rc != RPMRC_OK) { +- fprintf(stderr, _("rpmfiArchiveReadToFile failed with %d\n"), rc); ++ char *errstr = rpmfileStrerror(rc); ++ rpmlog(RPMLOG_ERR, ++ _("rpmfiArchiveReadToFile failed while extracting "\ ++ "\"%s\" with RC %d: %s\n"), ++ rpmfiFN(fi), rc, errstr); ++ free(errstr); + goto exit; + } + pos += size; +@@ -326,7 +348,7 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + validation_pos = pos; + ssize_t validation_len = ufdCopy(validationi, fdo); + if (validation_len == -1) { +- fprintf(stderr, _("validation output ufdCopy failed\n")); ++ rpmlog(RPMLOG_ERR, _("validation output ufdCopy failed\n")); + rc = RPMRC_FAIL; + goto exit; + } +@@ -335,25 +357,25 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + + len = sizeof(offset_ix); + if (Fwrite(&offset_ix, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write length of table\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write length of table\n")); + rc = RPMRC_FAIL; + goto exit; + } + len = sizeof(diglen); + if (Fwrite(&diglen, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write length of digest\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write length of digest\n")); + rc = RPMRC_FAIL; + goto exit; + } + len = sizeof(rpm_loff_t); + for (int x = 0; x < offset_ix; x++) { + if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) { +- fprintf(stderr, _("Unable to write digest\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write digest\n")); + rc = RPMRC_FAIL; + goto exit; + } + if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write offset\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write offset\n")); + rc = RPMRC_FAIL; + goto exit; + } +@@ -365,7 +387,7 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + + ssize_t digest_len = ufdCopy(digestori, fdo); + if (digest_len == -1) { +- fprintf(stderr, _("digest table ufdCopy failed\n")); ++ rpmlog(RPMLOG_ERR, _("digest table ufdCopy failed\n")); + rc = RPMRC_FAIL; + goto exit; + } +@@ -378,30 +400,30 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + pad = pad_to((validation_pos + validation_len + 2 * sizeof(rpm_loff_t) + + sizeof(uint64_t)), fundamental_block_size); + if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) { +- fprintf(stderr, _("Unable to write final padding\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write final padding\n")); + rc = RPMRC_FAIL; + goto exit; + } + zeros = _free(zeros); + if (Fwrite(&validation_pos, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write offset of validation output\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write offset of validation output\n")); + rc = RPMRC_FAIL; + goto exit; + } + if (Fwrite(&digest_table_pos, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write offset of digest table\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write offset of digest table\n")); + rc = RPMRC_FAIL; + goto exit; + } + if (Fwrite(&digest_pos, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write offset of validation table\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write offset of validation table\n")); + rc = RPMRC_FAIL; + goto exit; + } + extents_magic_t magic = EXTENTS_MAGIC; + len = sizeof(magic); + if (Fwrite(&magic, len, 1, fdo) != len) { +- fprintf(stderr, _("Unable to write magic\n")); ++ rpmlog(RPMLOG_ERR, _("Unable to write magic\n")); + rc = RPMRC_FAIL; + goto exit; + } +@@ -426,7 +448,9 @@ static off_t ufdTee(FD_t sfd, FD_t *fds, int len) + for(int i=0; i < len; i++) { + wrbytes = Fwrite(buf, sizeof(buf[0]), rdbytes, fds[i]); + if (wrbytes != rdbytes) { +- fprintf(stderr, "Error wriing to FD %d: %s\n", i, Fstrerror(fds[i])); ++ rpmlog(RPMLOG_ERR, ++ _("Error wriing to FD %d: %s\n"), ++ i, Fstrerror(fds[i])); + total = -1; + break; + } +@@ -460,22 +484,22 @@ static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) { + FD_t fds[2]; + + if (pipe(processorpipefd) == -1) { +- fprintf(stderr, _("Processor pipe failure\n")); ++ rpmlog(RPMLOG_ERR, _("Processor pipe failure\n")); + return RPMRC_FAIL; + } + + if (pipe(validatorpipefd) == -1) { +- fprintf(stderr, _("Validator pipe failure\n")); ++ rpmlog(RPMLOG_ERR, _("Validator pipe failure\n")); + return RPMRC_FAIL; + } + + if (pipe(meta_digestpipefd) == -1) { +- fprintf(stderr, _("Meta digest pipe failure\n")); ++ rpmlog(RPMLOG_ERR, _("Meta digest pipe failure\n")); + return RPMRC_FAIL; + } + + if (pipe(meta_rpmsignpipefd) == -1) { +- fprintf(stderr, _("Meta rpm signature pipe failure\n")); ++ rpmlog(RPMLOG_ERR, _("Meta rpm signature pipe failure\n")); + return RPMRC_FAIL; + } + +@@ -494,7 +518,7 @@ static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) { + close(meta_rpmsignpipefd[1]); + rc = validator(fdi, digesto, sigo, algos, algos_len); + if(rc != RPMRC_OK) { +- fprintf(stderr, _("Validator failed\n")); ++ rpmlog(RPMLOG_ERR, _("Validator failed with RC %d\n"), rc); + } + Fclose(fdi); + Fclose(digesto); +@@ -522,7 +546,7 @@ static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) { + + rc = process_package(fdi, digestori, sigi); + if(rc != RPMRC_OK) { +- fprintf(stderr, _("Validator failed\n")); ++ rpmlog(RPMLOG_ERR, _("Package processor failed: %d\n"), rc); + } + Fclose(digestori); + Fclose(sigi); +@@ -552,19 +576,19 @@ static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) { + rc = RPMRC_OK; + offt = ufdTee(fdi, fds, 2); + if(offt == -1){ +- fprintf(stderr, _("Failed to tee RPM\n")); ++ rpmlog(RPMLOG_ERR, _("Failed to tee RPM\n")); + rc = RPMRC_FAIL; + } + Fclose(fds[0]); + Fclose(fds[1]); + w = waitpid(cpids[0], &wstatus, 0); + if (w == -1) { +- fprintf(stderr, _("waitpid cpids[0] failed\n")); ++ rpmlog(RPMLOG_ERR, _("waitpid cpids[0] failed\n")); + rc = RPMRC_FAIL; + } + w = waitpid(cpids[1], &wstatus, 0); + if (w == -1) { +- fprintf(stderr, _("waitpid cpids[1] failed\n")); ++ rpmlog(RPMLOG_ERR, _("waitpid cpids[1] failed\n")); + rc = RPMRC_FAIL; + } + } +@@ -585,8 +609,8 @@ int main(int argc, char *argv[]) { + poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); + + if (poptPeekArg(optCon) == NULL) { +- fprintf(stderr, +- _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n")); ++ rpmlog(RPMLOG_ERR, ++ _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n")); + poptPrintUsage(optCon, stderr, 0); + exit(EXIT_FAILURE); + } +@@ -598,9 +622,9 @@ int main(int argc, char *argv[]) { + for (int x = 0; x < nb_algos; x++) { + if (pgpStringVal(PGPVAL_HASHALGO, args[x], &algos[x]) != 0) + { +- fprintf(stderr, +- _("Unable to resolve '%s' as a digest algorithm, exiting\n"), +- args[x]); ++ rpmlog(RPMLOG_ERR, ++ _("Unable to resolve '%s' as a digest algorithm, exiting\n"), ++ args[x]); + exit(EXIT_FAILURE); + } + } +-- +2.35.1 + diff --git a/0024-Generate-a-zero-length-signature-for-symlinks.patch b/0024-Generate-a-zero-length-signature-for-symlinks.patch new file mode 100644 index 0000000..2fc3f5a --- /dev/null +++ b/0024-Generate-a-zero-length-signature-for-symlinks.patch @@ -0,0 +1,33 @@ +From 202359dc598f2162175e3a8552c9b338d27b8989 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Tue, 14 Apr 2020 10:33:32 -0400 +Subject: [PATCH 24/33] Generate a zero-length signature for symlinks + +The fsverity utility follows the symlink when generating a signature. +Since we don't want to sign the same file twice, we need to skip these +links, and instead just generate a dummy zero-length signature here. + +Signed-off-by: Jes Sorensen +--- + sign/rpmsignverity.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index 177561957..2c7d21620 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -45,7 +45,10 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + uint8_t *sig = NULL; + int status; + +- file_size = rpmfiFSize(fi); ++ if (S_ISLNK(rpmfiFMode(fi))) ++ file_size = 0; ++ else ++ file_size = rpmfiFSize(fi); + + memset(¶ms, 0, sizeof(struct libfsverity_merkle_tree_params)); + params.version = 1; +-- +2.27.0 + diff --git a/0024-rpm2extents-create-footer-struct-and-helpers.patch b/0024-rpm2extents-create-footer-struct-and-helpers.patch new file mode 100644 index 0000000..baabce0 --- /dev/null +++ b/0024-rpm2extents-create-footer-struct-and-helpers.patch @@ -0,0 +1,200 @@ +From aabaa6c6587c37b84a1b9cfd98bff31f1b69345e Mon Sep 17 00:00:00 2001 +From: chantra +Date: Wed, 16 Feb 2022 17:00:09 -0800 +Subject: [PATCH 24/30] [rpm2extents] create footer struct and helpers + +new extents_footer and extents_footer_offset struct with a function to +load offsets from an FD. +Change existing code to use new struct/functions +--- + lib/rpmchecksig.c | 16 +++------------- + lib/rpmextents.c | 26 +++++++++++++++++--------- + lib/rpmextents_internal.h | 31 ++++++++++++++++++++++++++++++- + rpm2extents.c | 23 ++++------------------- + 4 files changed, 54 insertions(+), 42 deletions(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index dc1726a18..729f79f9f 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -223,29 +223,19 @@ exit: + + static int rpmpkgVerifySigsTranscoded(FD_t fd){ + rpm_loff_t current; +- uint64_t magic; +- rpm_loff_t offset; + int32_t rc; + size_t len; + uint64_t content_len; + char *content = NULL; ++ struct extents_footer_t footer; + + current = Ftell(fd); + +- if(Fseek(fd, -(sizeof(magic) + 3 * sizeof(offset) ), SEEK_END) < 0) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: failed to seek for offset\n")); ++ if(extentsFooterFromFD(fd, &footer) != RPMRC_OK) { + rc = -1; + goto exit; + } +- +- len = sizeof(offset); +- if (Fread(&offset, len, 1, fd) != len) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read Signature Verification offset\n")); +- rc = -1; +- goto exit; +- } +- +- if(Fseek(fd, offset, SEEK_SET) < 0) { ++ if(Fseek(fd, footer.offsets.checksig_offset, SEEK_SET) < 0) { + rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to seek signature verification offset\n")); + rc = -1; + goto exit; +diff --git a/lib/rpmextents.c b/lib/rpmextents.c +index 015277751..46b7aadff 100644 +--- a/lib/rpmextents.c ++++ b/lib/rpmextents.c +@@ -3,13 +3,16 @@ + + #include + #include ++#include ++#include ++ + + #include "lib/rpmextents_internal.h" + +-rpmRC isTranscodedRpm(FD_t fd) { ++rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer) { ++ + rpmRC rc = RPMRC_NOTFOUND; + rpm_loff_t current; +- extents_magic_t magic; + size_t len; + + // If the file is not seekable, we cannot detect whether or not it is transcoded. +@@ -18,19 +21,19 @@ rpmRC isTranscodedRpm(FD_t fd) { + } + current = Ftell(fd); + +- if(Fseek(fd, -(sizeof(magic)), SEEK_END) < 0) { +- rpmlog(RPMLOG_ERR, _("isTranscodedRpm: failed to seek for magic\n")); ++ len = sizeof(struct extents_footer_t); ++ if(Fseek(fd, -len, SEEK_END) < 0) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: failed to seek for footer: %s\n"), strerror(errno)); + rc = RPMRC_FAIL; + goto exit; + } +- len = sizeof(magic); +- if (Fread(&magic, len, 1, fd) != len) { +- rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read magic\n")); ++ if (Fread(footer, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read footer\n")); + rc = RPMRC_FAIL; + goto exit; + } +- if (magic != EXTENTS_MAGIC) { +- rpmlog(RPMLOG_DEBUG, _("isTranscodedRpm: not transcoded\n")); ++ if (footer->magic != EXTENTS_MAGIC) { ++ rpmlog(RPMLOG_ERR, _("isTranscodedRpm: not transcoded\n")); + rc = RPMRC_NOTFOUND; + goto exit; + } +@@ -43,4 +46,9 @@ exit: + return rc; + } + ++rpmRC isTranscodedRpm(FD_t fd) { ++ struct extents_footer_t footer; ++ return extentsFooterFromFD(fd, &footer); ++} ++ + +diff --git a/lib/rpmextents_internal.h b/lib/rpmextents_internal.h +index 57cecfc31..f0c29c807 100644 +--- a/lib/rpmextents_internal.h ++++ b/lib/rpmextents_internal.h +@@ -7,6 +7,10 @@ extern "C" { + + #include + ++/** \ingroup rpmextents ++ * RPM extents library ++ */ ++ + /* magic value at end of file (64 bits) that indicates this is a transcoded + * rpm. + */ +@@ -14,9 +18,34 @@ extern "C" { + + typedef uint64_t extents_magic_t; + ++struct __attribute__ ((__packed__)) extents_footer_offsets_t { ++ off64_t checksig_offset; ++ off64_t table_offset; ++ off64_t csum_offset; ++}; ++ ++struct __attribute__ ((__packed__)) extents_footer_t { ++ struct extents_footer_offsets_t offsets; ++ extents_magic_t magic; ++}; ++ ++ ++/** \ingroup rpmextents ++ * Read the RPM Extents footer from a file descriptor. ++ * @param fd The FD_t of the transcoded RPM ++ * @param[out] footer A pointer to an allocated extents_footer_t with a copy of the footer. ++ * @return RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure. ++ */ ++rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer); ++ ++/** \ingroup rpmextents ++ * Check if a RPM is a transcoded RPM ++ * @param fd The FD_t of the transcoded RPM ++ * return RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure. ++ */ + rpmRC isTranscodedRpm(FD_t fd); + + #ifdef __cplusplus + } + #endif +-#endif ++#endif /* _RPMEXTENTS_INTERNAL_H */ +diff --git a/rpm2extents.c b/rpm2extents.c +index e1a19fedb..7dd5128de 100644 +--- a/rpm2extents.c ++++ b/rpm2extents.c +@@ -405,25 +405,10 @@ static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi) + goto exit; + } + zeros = _free(zeros); +- if (Fwrite(&validation_pos, len, 1, fdo) != len) { +- rpmlog(RPMLOG_ERR, _("Unable to write offset of validation output\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- if (Fwrite(&digest_table_pos, len, 1, fdo) != len) { +- rpmlog(RPMLOG_ERR, _("Unable to write offset of digest table\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- if (Fwrite(&digest_pos, len, 1, fdo) != len) { +- rpmlog(RPMLOG_ERR, _("Unable to write offset of validation table\n")); +- rc = RPMRC_FAIL; +- goto exit; +- } +- extents_magic_t magic = EXTENTS_MAGIC; +- len = sizeof(magic); +- if (Fwrite(&magic, len, 1, fdo) != len) { +- rpmlog(RPMLOG_ERR, _("Unable to write magic\n")); ++ struct extents_footer_t footer = {.offsets = {validation_pos, digest_table_pos, digest_pos}, .magic = EXTENTS_MAGIC}; ++ len = sizeof(footer); ++ if (Fwrite(&footer, len, 1, fdo) != len) { ++ rpmlog(RPMLOG_ERR, _("Unable to write footer\n")); + rc = RPMRC_FAIL; + goto exit; + } +-- +2.35.1 + diff --git a/0025-extents-move-more-functions-helpers-behind-rpmextent.patch b/0025-extents-move-more-functions-helpers-behind-rpmextent.patch new file mode 100644 index 0000000..71a54e6 --- /dev/null +++ b/0025-extents-move-more-functions-helpers-behind-rpmextent.patch @@ -0,0 +1,176 @@ +From 8235711d92d8783abe63d6e4f29afd495fc4b22e Mon Sep 17 00:00:00 2001 +From: chantra +Date: Wed, 16 Feb 2022 23:21:14 -0800 +Subject: [PATCH 25/30] [extents] move more functions/helpers behind + rpmextents_internal.h + +--- + lib/rpmchecksig.c | 58 ++------------------------------------- + lib/rpmextents.c | 56 +++++++++++++++++++++++++++++++++++++ + lib/rpmextents_internal.h | 6 ++++ + 3 files changed, 64 insertions(+), 56 deletions(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index 729f79f9f..5e8794e2d 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -221,61 +221,6 @@ exit: + return rc; + } + +-static int rpmpkgVerifySigsTranscoded(FD_t fd){ +- rpm_loff_t current; +- int32_t rc; +- size_t len; +- uint64_t content_len; +- char *content = NULL; +- struct extents_footer_t footer; +- +- current = Ftell(fd); +- +- if(extentsFooterFromFD(fd, &footer) != RPMRC_OK) { +- rc = -1; +- goto exit; +- } +- if(Fseek(fd, footer.offsets.checksig_offset, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to seek signature verification offset\n")); +- rc = -1; +- goto exit; +- } +- len = sizeof(rc); +- if (Fread(&rc, len, 1, fd) != len) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read Signature Verification RC\n")); +- rc = -1; +- goto exit; +- } +- +- len = sizeof(content_len); +- if (Fread(&content_len, len, 1, fd) != len) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read signature content length\n")); +- goto exit; +- } +- +- content = malloc(content_len + 1); +- if(content == NULL) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to allocate memory to read signature content\n")); +- goto exit; +- } +- content[content_len] = 0; +- if (Fread(content, content_len, 1, fd) != content_len) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: Failed to read signature content\n")); +- goto exit; +- } +- +- rpmlog(RPMLOG_NOTICE, "%s", content); +-exit: +- if(content){ +- free(content); +- } +- if (Fseek(fd, current, SEEK_SET) < 0) { +- rpmlog(RPMLOG_ERR, _("rpmpkgVerifySigsTranscoded: unable to seek back to original location\n")); +- } +- return rc; +- +-} +- + static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, + FD_t fd, const char *fn) + { +@@ -289,8 +234,9 @@ static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, + rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd.verbose ? "\n" : ""); + + if(isTranscodedRpm(fd) == RPMRC_OK){ +- return rpmpkgVerifySigsTranscoded(fd); ++ return extentsVerifySigs(fd); + } ++ + struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring); + + rc = rpmpkgRead(vs, fd, NULL, NULL, &msg); +diff --git a/lib/rpmextents.c b/lib/rpmextents.c +index 46b7aadff..f28596f0b 100644 +--- a/lib/rpmextents.c ++++ b/lib/rpmextents.c +@@ -9,6 +9,62 @@ + + #include "lib/rpmextents_internal.h" + ++ ++int extentsVerifySigs(FD_t fd){ ++ rpm_loff_t current; ++ int32_t rc; ++ size_t len; ++ uint64_t content_len; ++ char *content = NULL; ++ struct extents_footer_t footer; ++ ++ current = Ftell(fd); ++ ++ if(extentsFooterFromFD(fd, &footer) != RPMRC_OK) { ++ rc = -1; ++ goto exit; ++ } ++ if(Fseek(fd, footer.offsets.checksig_offset, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to seek signature verification offset\n")); ++ rc = -1; ++ goto exit; ++ } ++ len = sizeof(rc); ++ if (Fread(&rc, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read Signature Verification RC\n")); ++ rc = -1; ++ goto exit; ++ } ++ ++ len = sizeof(content_len); ++ if (Fread(&content_len, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content length\n")); ++ goto exit; ++ } ++ ++ content = rmalloc(content_len + 1); ++ if(content == NULL) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to allocate memory to read signature content\n")); ++ goto exit; ++ } ++ content[content_len] = 0; ++ if (Fread(content, content_len, 1, fd) != content_len) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content\n")); ++ goto exit; ++ } ++ ++ rpmlog(RPMLOG_NOTICE, "%s", content); ++exit: ++ if(content){ ++ rfree(content); ++ } ++ if (Fseek(fd, current, SEEK_SET) < 0) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: unable to seek back to original location\n")); ++ } ++ return rc; ++ ++} ++ + rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer) { + + rpmRC rc = RPMRC_NOTFOUND; +diff --git a/lib/rpmextents_internal.h b/lib/rpmextents_internal.h +index f0c29c807..380c08425 100644 +--- a/lib/rpmextents_internal.h ++++ b/lib/rpmextents_internal.h +@@ -29,6 +29,12 @@ struct __attribute__ ((__packed__)) extents_footer_t { + extents_magic_t magic; + }; + ++/** \ingroup rpmextents ++ * Checks the results of the signature verification ran during transcoding. ++ * @param fd The FD_t of the transcoded RPM ++ * @return The number of checks that `rpmvsVerify` failed during transcoding. ++ */ ++int extentsVerifySigs(FD_t fd); + + /** \ingroup rpmextents + * Read the RPM Extents footer from a file descriptor. +-- +2.35.1 + diff --git a/0025-rpmsignverity.c-Clean-up-debug-logging.patch b/0025-rpmsignverity.c-Clean-up-debug-logging.patch new file mode 100644 index 0000000..bb6e4f3 --- /dev/null +++ b/0025-rpmsignverity.c-Clean-up-debug-logging.patch @@ -0,0 +1,40 @@ +From 7e50b3f4b2ebb963d1080a0a1469517ef81f780c Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Tue, 14 Apr 2020 12:08:09 -0400 +Subject: [PATCH 25/33] rpmsignverity.c: Clean up debug logging + +Put most logging in one place and avoid printing the same info twice. + +Signed-off-by: Jes Sorensen +--- + sign/rpmsignverity.c | 5 ----- + 1 file changed, 5 deletions(-) + +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index 2c7d21620..445e1197c 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -104,7 +104,6 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + rpmfi hfi = rpmfiNew(NULL, h, RPMTAG_BASENAMES, RPMFI_FLAGS_QUERY); + rpmts ts = rpmtsCreate(); + struct rpmtd_s td; +- rpm_loff_t file_size; + off_t offset = Ftell(fd); + const char *compr; + char *rpmio_flags = NULL; +@@ -158,12 +157,8 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + nr_files, rpmfiFC(fi)); + + while (rpmfiNext(fi) >= 0) { +- file_size = rpmfiFSize(fi); + idx = rpmfiFX(fi); + +- rpmlog(RPMLOG_DEBUG, _("file: %s, (size %li, link %s, idx %i)\n"), +- rpmfiFN(fi), file_size, rpmfiFLink(fi), rpmfiFX(fi)); +- + signatures[idx] = rpmVeritySignFile(fi, &sig_size, key, keypass, cert); + } + +-- +2.27.0 + diff --git a/0026-fix-integer-underflow-in-vfyFDCb.patch b/0026-fix-integer-underflow-in-vfyFDCb.patch new file mode 100644 index 0000000..5fe1900 --- /dev/null +++ b/0026-fix-integer-underflow-in-vfyFDCb.patch @@ -0,0 +1,25 @@ +From 3372e6c917e54b3a84c04ca4274000da04a98e86 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Thu, 17 Feb 2022 08:54:47 -0800 +Subject: [PATCH 26/30] fix integer underflow in vfyFDCb + +--- + lib/rpmchecksig.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index 5e8794e2d..7ad4e7034 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -345,7 +345,7 @@ static int vfyFDCb(struct rpmsinfo_s *sinfo, void *cbdata) + struct vfydatafd_s *vd = cbdata; + char *vmsg, *msg; + size_t n; +- size_t remainder = BUFSIZ - vd->len; ++ size_t remainder = BUFSIZ - vd->len >= 0 ? BUFSIZ - vd->len : 0; + + vmsg = rpmsinfoMsg(sinfo); + rasprintf(&msg, " %s\n", vmsg); +-- +2.35.1 + diff --git a/0026-fsverity-add-tag-for-fsverity-algorithm.patch b/0026-fsverity-add-tag-for-fsverity-algorithm.patch new file mode 100644 index 0000000..74b2fc1 --- /dev/null +++ b/0026-fsverity-add-tag-for-fsverity-algorithm.patch @@ -0,0 +1,161 @@ +From e339fdbbd0b81dc1fcdc2032e861b8a5fa6e062d Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 20 Apr 2020 13:40:26 -0400 +Subject: [PATCH 26/33] fsverity - add tag for fsverity algorithm + +The default algorith is SHA256, but fsverity allows for other +algorithms, so add a tag to handle this. + +Signed-off-by: Jes Sorensen +--- + lib/package.c | 1 + + lib/rpmfi.c | 2 ++ + lib/rpmtag.h | 2 ++ + sign/rpmsignverity.c | 32 ++++++++++++++++++++++++++++---- + 4 files changed, 33 insertions(+), 4 deletions(-) + +diff --git a/lib/package.c b/lib/package.c +index c6108f686..3c761d365 100644 +--- a/lib/package.c ++++ b/lib/package.c +@@ -46,6 +46,7 @@ struct taglate_s { + { RPMSIGTAG_FILESIGNATURES, RPMTAG_FILESIGNATURES, 0, 1 }, + { RPMSIGTAG_FILESIGNATURELENGTH, RPMTAG_FILESIGNATURELENGTH, 1, 1 }, + { RPMSIGTAG_VERITYSIGNATURES, RPMTAG_VERITYSIGNATURES, 0, 0 }, ++ { RPMSIGTAG_VERITYSIGNATUREALGO, RPMTAG_VERITYSIGNATUREALGO, 1, 0 }, + { RPMSIGTAG_SHA1, RPMTAG_SHA1HEADER, 1, 0 }, + { RPMSIGTAG_SHA256, RPMTAG_SHA256HEADER, 1, 0 }, + { RPMSIGTAG_DSA, RPMTAG_DSAHEADER, 0, 0 }, +diff --git a/lib/rpmfi.c b/lib/rpmfi.c +index 5fdbe02a2..70f05f509 100644 +--- a/lib/rpmfi.c ++++ b/lib/rpmfi.c +@@ -118,6 +118,7 @@ struct rpmfiles_s { + int digestalgo; /*!< File digest algorithm */ + uint32_t *signatureoffs; /*!< File signature offsets */ + int veritysiglength; /*!< Verity signature length */ ++ uint16_t verityalgo; /*!< Verity algorithm */ + unsigned char * digests; /*!< File digests in binary. */ + unsigned char * signatures; /*!< File signatures in binary. */ + unsigned char * veritysigs; /*!< Verity signatures in binary. */ +@@ -1667,6 +1668,7 @@ static int rpmfilesPopulate(rpmfiles fi, Header h, rpmfiFlags flags) + + fi->veritysigs = NULL; + if (!(flags & RPMFI_NOVERITYSIGNATURES)) { ++ fi->verityalgo = headerGetNumber(h, RPMTAG_VERITYSIGNATUREALGO); + fi->veritysigs = base2bin(h, RPMTAG_VERITYSIGNATURES, + totalfc, &fi->veritysiglength); + } +diff --git a/lib/rpmtag.h b/lib/rpmtag.h +index 478457ecb..8d1efcc79 100644 +--- a/lib/rpmtag.h ++++ b/lib/rpmtag.h +@@ -68,6 +68,7 @@ typedef enum rpmTag_e { + /* RPMTAG_SIG_BASE+18 reserved for RPMSIGTAG_FILESIGNATURES */ + /* RPMTAG_SIG_BASE+19 reserved for RPMSIGTAG_FILESIGNATURELENGTH */ + RPMTAG_VERITYSIGNATURES = RPMTAG_SIG_BASE+20, /* s[] */ ++ RPMTAG_VERITYSIGNATUREALGO = RPMTAG_SIG_BASE+21, /* i */ + + RPMTAG_NAME = 1000, /* s */ + #define RPMTAG_N RPMTAG_NAME /* s */ +@@ -431,6 +432,7 @@ typedef enum rpmSigTag_e { + RPMSIGTAG_FILESIGNATURES = RPMTAG_SIG_BASE + 18, + RPMSIGTAG_FILESIGNATURELENGTH = RPMTAG_SIG_BASE + 19, + RPMSIGTAG_VERITYSIGNATURES = RPMTAG_VERITYSIGNATURES, ++ RPMSIGTAG_VERITYSIGNATUREALGO = RPMTAG_VERITYSIGNATUREALGO, + } rpmSigTag; + + +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index 445e1197c..55096e732 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -35,7 +35,7 @@ static int rpmVerityRead(void *opaque, void *buf, size_t size) + } + + static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, +- char *keypass, char *cert) ++ char *keypass, char *cert, uint16_t algo) + { + struct libfsverity_merkle_tree_params params; + struct libfsverity_signature_params sig_params; +@@ -52,7 +52,7 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + + memset(¶ms, 0, sizeof(struct libfsverity_merkle_tree_params)); + params.version = 1; +- params.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; ++ params.hash_algorithm = algo; + params.block_size = RPM_FSVERITY_BLKSZ; + params.salt_size = 0 /* salt_size */; + params.salt = NULL /* salt */; +@@ -111,6 +111,8 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + char **signatures = NULL; + size_t sig_size; + int nr_files, idx; ++ uint16_t algo; ++ uint32_t algo32; + + Fseek(fd, 0, SEEK_SET); + rpmtsSetVSFlags(ts, RPMVSF_MASK_NODIGESTS | RPMVSF_MASK_NOSIGNATURES | +@@ -142,6 +144,7 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + * Should this be sigh from the cloned fd or the sigh we received? + */ + headerDel(sigh, RPMSIGTAG_VERITYSIGNATURES); ++ headerDel(sigh, RPMSIGTAG_VERITYSIGNATUREALGO); + + /* + * The payload doesn't include special files, like ghost files, and +@@ -153,20 +156,24 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + nr_files = rpmfiFC(hfi); + signatures = xcalloc(nr_files, sizeof(char *)); + ++ algo = FS_VERITY_HASH_ALG_SHA256; ++ + rpmlog(RPMLOG_DEBUG, _("file count - header: %i, payload %i\n"), + nr_files, rpmfiFC(fi)); + + while (rpmfiNext(fi) >= 0) { + idx = rpmfiFX(fi); + +- signatures[idx] = rpmVeritySignFile(fi, &sig_size, key, keypass, cert); ++ signatures[idx] = rpmVeritySignFile(fi, &sig_size, key, keypass, cert, ++ algo); + } + + while (rpmfiNext(hfi) >= 0) { + idx = rpmfiFX(hfi); + if (signatures[idx]) + continue; +- signatures[idx] = rpmVeritySignFile(hfi, &sig_size, key, keypass, cert); ++ signatures[idx] = rpmVeritySignFile(hfi, &sig_size, key, keypass, cert, ++ algo); + } + + rpmtdReset(&td); +@@ -187,6 +194,23 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + signatures[idx] = NULL; + } + ++ if (sig_size == 0) { ++ rpmlog(RPMLOG_ERR, _("Zero length fsverity signature\n")); ++ rc = RPMRC_FAIL; ++ goto out; ++ } ++ ++ rpmtdReset(&td); ++ ++ /* RPM doesn't like new 16 bit types, so use a 32 bit tag */ ++ algo32 = algo; ++ rpmtdReset(&td); ++ td.tag = RPMSIGTAG_VERITYSIGNATUREALGO; ++ td.type = RPM_INT32_TYPE; ++ td.data = &algo32; ++ td.count = 1; ++ headerPut(sigh, &td, HEADERPUT_DEFAULT); ++ + rpmlog(RPMLOG_DEBUG, _("sigh size: %i\n"), headerSizeof(sigh, 0)); + + rc = RPMRC_OK; +-- +2.27.0 + diff --git a/0027-plugins-fsverity-Install-fsverity-signatures.patch b/0027-plugins-fsverity-Install-fsverity-signatures.patch new file mode 100644 index 0000000..c302c97 --- /dev/null +++ b/0027-plugins-fsverity-Install-fsverity-signatures.patch @@ -0,0 +1,257 @@ +From f1a92e02faa2715777286acd07b8d0f465c5df37 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 20 Apr 2020 11:11:25 -0400 +Subject: [PATCH 27/33] plugins/fsverity: Install fsverity signatures + +This plugin installs fsverity signatures for regular files, when a signature +is found in the RPM. It tries to enable them unconditionally, but fails +gracefully if fsverity isn't supported or enabled. + +Signed-off-by: Jes Sorensen +--- + configure.ac | 29 ++++++++ + macros.in | 4 + + plugins/Makefile.am | 7 ++ + plugins/fsverity.c | 177 ++++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 217 insertions(+) + create mode 100644 plugins/fsverity.c + +diff --git a/configure.ac b/configure.ac +index cc7144440..7d3c31831 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1049,6 +1049,11 @@ AS_IF([test "$enable_plugins" != no],[ + ]) + AM_CONDITIONAL(IMA, [test "x$ac_cv_func_lsetxattr" = xyes]) + ++AS_IF([test "$enable_plugins" != no],[ ++AC_CHECK_HEADERS([linux/fsverity.h],[FSVERITY_IOCTL="yes"]) ++]) ++AM_CONDITIONAL(FSVERITY_IOCTL,[test "x$FSVERITY_IOCTL" = xyes]) ++ + #================= + # Check for fapolicyd support + AC_ARG_WITH(fapolicyd, +diff --git a/macros.in b/macros.in +index fe8862903..3c722146b 100644 +--- a/macros.in ++++ b/macros.in +@@ -767,6 +767,9 @@ package or when debugging this package.\ + # a wrong or missing signature. + #%_ima_sign_config_files 0 + ++# Set to 1 to have fsverity signatures written for %config files. ++#%_fsverity_sign_config_files 0 ++ + # + # Default output format string for rpm -qa + # +@@ -1185,6 +1188,7 @@ package or when debugging this package.\ + %__transaction_syslog %{__plugindir}/syslog.so + %__transaction_ima %{__plugindir}/ima.so + %__transaction_fapolicyd %{__plugindir}/fapolicyd.so ++%__transaction_fsverity %{__plugindir}/fsverity.so + %__transaction_prioreset %{__plugindir}/prioreset.so + %__transaction_audit %{__plugindir}/audit.so + +diff --git a/plugins/Makefile.am b/plugins/Makefile.am +index cbfb81e19..e51b71f62 100644 +--- a/plugins/Makefile.am ++++ b/plugins/Makefile.am +@@ -48,3 +48,10 @@ 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 ++ ++if FSVERITY_IOCTL ++fsverity_la_sources = fsverity.c ++fsverity_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la ++plugins_LTLIBRARIES += fsverity.la ++endif ++ +diff --git a/plugins/fsverity.c b/plugins/fsverity.c +new file mode 100644 +index 000000000..15ddcf33e +--- /dev/null ++++ b/plugins/fsverity.c +@@ -0,0 +1,177 @@ ++/** ++ * Copyright (C) 2020 Facebook ++ * ++ * Author: Jes Sorensen ++ */ ++ ++#include "system.h" ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "lib/rpmfs.h" ++#include "lib/rpmplugin.h" ++#include "lib/rpmte_internal.h" ++ ++#include "sign/rpmsignverity.h" ++ ++static int sign_config_files = 0; ++ ++/* ++ * This unconditionally tries to apply the fsverity signature to a file, ++ * but fails gracefully if the file system doesn't support it or the ++ * verity feature flag isn't enabled in the file system (ext4). ++ */ ++static rpmRC fsverity_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, ++ const char *path, const char *dest, ++ mode_t file_mode, rpmFsmOp op) ++{ ++ struct fsverity_enable_arg arg; ++ const unsigned char * signature = NULL; ++ size_t len; ++ int rc = RPMRC_OK; ++ int fd; ++ rpmFileAction action = XFO_ACTION(op); ++ char *buffer; ++ ++ /* Ignore skipped files and unowned directories */ ++ if (XFA_SKIPPING(action) || (op & FAF_UNOWNED)) { ++ rpmlog(RPMLOG_DEBUG, "fsverity skipping early: path %s dest %s\n", ++ path, dest); ++ goto exit; ++ } ++ ++ /* ++ * Do not install signatures for config files unless the ++ * user explicitly asks for it. ++ */ ++ if (rpmfiFFlags(fi) & RPMFILE_CONFIG) { ++ if (!(rpmfiFMode(fi) & (S_IXUSR|S_IXGRP|S_IXOTH)) && ++ !sign_config_files) { ++ rpmlog(RPMLOG_DEBUG, "fsverity skipping: path %s dest %s\n", ++ path, dest); ++ ++ goto exit; ++ } ++ } ++ ++ /* ++ * Right now fsverity doesn't deal with symlinks or directories, so do ++ * not try to install signatures for non regular files. ++ */ ++ if (!S_ISREG(rpmfiFMode(fi))) { ++ rpmlog(RPMLOG_DEBUG, "fsverity skipping non regular: path %s dest %s\n", ++ path, dest); ++ goto exit; ++ } ++ ++ signature = rpmfiVSignature(fi, &len); ++ if (!signature || !len) { ++ rpmlog(RPMLOG_DEBUG, "fsverity no signature for: path %s dest %s\n", ++ path, dest); ++ goto exit; ++ } ++ ++ memset(&arg, 0, sizeof(arg)); ++ arg.version = 1; ++ arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; ++ arg.block_size = RPM_FSVERITY_BLKSZ; ++ arg.sig_ptr = (uintptr_t)signature; ++ arg.sig_size = len; ++ ++ buffer = pgpHexStr(signature, arg.sig_size); ++ rpmlog(RPMLOG_DEBUG, "applying signature: %s\n", buffer); ++ free(buffer); ++ ++ fd = open(path, O_RDONLY); ++ if (fd < 0) { ++ rpmlog(RPMLOG_ERR, "failed to open path %s\n", path); ++ goto exit; ++ } ++ ++ /* ++ * Enable fsverity on the file. ++ * fsverity not supported by file system (ENOTTY) and fsverity not ++ * enabled on file system are expected and not considered ++ * errors. Every other non-zero error code will result in the ++ * installation failing. ++ */ ++ if (ioctl(fd, FS_IOC_ENABLE_VERITY, &arg) != 0) { ++ switch(errno) { ++ case EBADMSG: ++ rpmlog(RPMLOG_DEBUG, "invalid or malformed fsverity signature for %s\n", path); ++ rc = RPMRC_FAIL; ++ break; ++ case EEXIST: ++ rpmlog(RPMLOG_DEBUG, "fsverity signature already enabled %s\n", ++ path); ++ rc = RPMRC_FAIL; ++ break; ++ case EINVAL: ++ rpmlog(RPMLOG_DEBUG, "invalid arguments for ioctl %s\n", path); ++ rc = RPMRC_FAIL; ++ break; ++ case EKEYREJECTED: ++ rpmlog(RPMLOG_DEBUG, "signature doesn't match file %s\n", path); ++ rc = RPMRC_FAIL; ++ break; ++ case EMSGSIZE: ++ rpmlog(RPMLOG_DEBUG, "invalid signature size for %s\n", path); ++ rc = RPMRC_FAIL; ++ break; ++ case ENOPKG: ++ rpmlog(RPMLOG_DEBUG, "unsupported signature algoritm (%i) for %s\n", ++ arg.hash_algorithm, path); ++ rc = RPMRC_FAIL; ++ break; ++ case ETXTBSY: ++ rpmlog(RPMLOG_DEBUG, "file is open by other process %s\n", ++ path); ++ rc = RPMRC_FAIL; ++ break; ++ case ENOTTY: ++ rpmlog(RPMLOG_DEBUG, "fsverity not supported by file system for %s\n", ++ path); ++ break; ++ case EOPNOTSUPP: ++ rpmlog(RPMLOG_DEBUG, "fsverity not enabled on file system for %s\n", ++ path); ++ break; ++ default: ++ rpmlog(RPMLOG_DEBUG, "failed to enable verity (errno %i) for %s\n", ++ errno, path); ++ rc = RPMRC_FAIL; ++ break; ++ } ++ } ++ ++ rpmlog(RPMLOG_DEBUG, "fsverity enabled signature for: path %s dest %s\n", ++ path, dest); ++ close(fd); ++exit: ++ return rc; ++} ++ ++static rpmRC fsverity_init(rpmPlugin plugin, rpmts ts) ++{ ++ sign_config_files = rpmExpandNumeric("%{?_fsverity_sign_config_files}"); ++ ++ rpmlog(RPMLOG_DEBUG, "fsverity_init\n"); ++ ++ return RPMRC_OK; ++} ++ ++struct rpmPluginHooks_s fsverity_hooks = { ++ .init = fsverity_init, ++ .fsm_file_prepare = fsverity_fsm_file_prepare, ++}; +-- +2.27.0 + diff --git a/0027-rpmchecksig-Refactor-rpmpkgVerifySigs-with-custom-ve.patch b/0027-rpmchecksig-Refactor-rpmpkgVerifySigs-with-custom-ve.patch new file mode 100644 index 0000000..7bb79b2 --- /dev/null +++ b/0027-rpmchecksig-Refactor-rpmpkgVerifySigs-with-custom-ve.patch @@ -0,0 +1,169 @@ +From 1e0850cf7649578e1d7da815751efaa8101773e7 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Fri, 18 Feb 2022 11:29:06 -0800 +Subject: [PATCH 27/30] [rpmchecksig] Refactor rpmpkgVerifySigs with custom + verify callback + +The current `rpmpkgVerifySigs` was conflating logging and the actual +package verification. + +This change makes it possible to pass the verify callback and its data to +`rpmpkgVerifySigs` so callers can customize how they handle the outcome +of signature verifications. +--- + lib/rpmchecksig.c | 78 ++++++++++++++++++++++------------------------- + lib/rpmextents.c | 1 - + 2 files changed, 36 insertions(+), 43 deletions(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index 7ad4e7034..c9fc3bbc9 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -222,16 +222,11 @@ exit: + } + + static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, +- FD_t fd, const char *fn) ++ FD_t fd, rpmsinfoCb cb, void *cbdata) + { + char *msg = NULL; +- struct vfydata_s vd = { .seen = 0, +- .bad = 0, +- .verbose = rpmIsVerbose(), +- }; + int rc; + +- rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd.verbose ? "\n" : ""); + + if(isTranscodedRpm(fd) == RPMRC_OK){ + return extentsVerifySigs(fd); +@@ -244,19 +239,7 @@ static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, + if (rc) + goto exit; + +- rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd); +- +- if (!vd.verbose) { +- if (vd.seen & RPMSIG_DIGEST_TYPE) { +- rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_DIGEST_TYPE) ? +- _("DIGESTS") : _("digests")); +- } +- if (vd.seen & RPMSIG_SIGNATURE_TYPE) { +- rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_SIGNATURE_TYPE) ? +- _("SIGNATURES") : _("signatures")); +- } +- rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK")); +- } ++ rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, cb, cbdata); + + exit: + if (rc && msg) +@@ -266,38 +249,39 @@ exit: + return rc; + } + +-static int rpmpkgVerifySigsFD(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, +- FD_t fd, rpmsinfoCb cb, void *cbdata) +-{ +- char *msg = NULL; +- int rc; +- struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring); +- +- rc = rpmpkgRead(vs, fd, NULL, NULL, &msg); +- +- if (rc) +- goto exit; +- +- rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, cb, cbdata); +- +-exit: +- if (rc && msg) +- rpmlog(RPMLOG_ERR, "%s\n", msg); +- rpmvsFree(vs); +- free(msg); +- return rc; ++static void rpmkgVerifySigsPreLogging(struct vfydata_s *vd, const char *fn){ ++ rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd->verbose ? "\n" : ""); + } + ++static void rpmkgVerifySigsPostLogging(struct vfydata_s *vd, int rc){ ++ if (!vd->verbose) { ++ if (vd->seen & RPMSIG_DIGEST_TYPE) { ++ rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_DIGEST_TYPE) ? ++ _("DIGESTS") : _("digests")); ++ } ++ if (vd->seen & RPMSIG_SIGNATURE_TYPE) { ++ rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_SIGNATURE_TYPE) ? ++ _("SIGNATURES") : _("signatures")); ++ } ++ rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK")); ++ } ++} + + /* Wrapper around rpmkVerifySigs to preserve API */ + int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn) + { + int rc = 1; /* assume failure */ ++ struct vfydata_s vd = { .seen = 0, ++ .bad = 0, ++ .verbose = rpmIsVerbose(), ++ }; + if (ts && qva && fd && fn) { + rpmKeyring keyring = rpmtsGetKeyring(ts, 1); + rpmVSFlags vsflags = rpmtsVfyFlags(ts); + int vfylevel = rpmtsVfyLevel(ts); +- rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, fn); ++ rpmkgVerifySigsPreLogging(&vd, fn); ++ rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, vfyCb, &vd); ++ rpmkgVerifySigsPostLogging(&vd, rc); + rpmKeyringFree(keyring); + } + return rc; +@@ -319,12 +303,22 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv) + + while ((arg = *argv++) != NULL) { + FD_t fd = Fopen(arg, "r.ufdio"); ++ struct vfydata_s vd = { .seen = 0, ++ .bad = 0, ++ .verbose = rpmIsVerbose(), ++ }; + if (fd == NULL || Ferror(fd)) { + rpmlog(RPMLOG_ERR, _("%s: open failed: %s\n"), + arg, Fstrerror(fd)); + res++; +- } else if (rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, arg)) { ++ } else { ++ rpmkgVerifySigsPreLogging(&vd, arg); ++ int rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, ++ vfyCb, &vd); ++ rpmkgVerifySigsPostLogging(&vd, rc); ++ if (rc) { + res++; ++ } + } + + Fclose(fd); +@@ -373,7 +367,7 @@ int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi, char **msg) + rpmtsSetVfyLevel(ts, vfylevel); + } + +- if (!rpmpkgVerifySigsFD(keyring, vfylevel, vsflags, fdi, vfyFDCb, &vd)) { ++ if (!rpmpkgVerifySigs(keyring, vfylevel, vsflags, fdi, vfyFDCb, &vd)) { + rc = RPMRC_OK; + } + *msg = strdup(vd.msg); +diff --git a/lib/rpmextents.c b/lib/rpmextents.c +index f28596f0b..59ba427a4 100644 +--- a/lib/rpmextents.c ++++ b/lib/rpmextents.c +@@ -89,7 +89,6 @@ rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer) { + goto exit; + } + if (footer->magic != EXTENTS_MAGIC) { +- rpmlog(RPMLOG_ERR, _("isTranscodedRpm: not transcoded\n")); + rc = RPMRC_NOTFOUND; + goto exit; + } +-- +2.35.1 + diff --git a/0028-fsverity-plugin-Use-tag-for-algorithm.patch b/0028-fsverity-plugin-Use-tag-for-algorithm.patch new file mode 100644 index 0000000..0b9bb41 --- /dev/null +++ b/0028-fsverity-plugin-Use-tag-for-algorithm.patch @@ -0,0 +1,116 @@ +From 5a5286ac37cd58779cc0e5b69088d9acc8f40c4e Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 20 Apr 2020 14:13:51 -0400 +Subject: [PATCH 28/33] fsverity plugin: Use tag for algorithm + +This uses the algorithm from the tag, if available. Fallback is SHA256. + +Signed-off-by: Jes Sorensen +--- + lib/rpmfi.c | 9 ++++++--- + lib/rpmfi.h | 3 ++- + lib/rpmfiles.h | 3 ++- + plugins/fsverity.c | 8 ++++++-- + 4 files changed, 16 insertions(+), 7 deletions(-) + +diff --git a/lib/rpmfi.c b/lib/rpmfi.c +index 70f05f509..3e2b4e676 100644 +--- a/lib/rpmfi.c ++++ b/lib/rpmfi.c +@@ -585,7 +585,8 @@ const unsigned char * rpmfilesFSignature(rpmfiles fi, int ix, size_t *len) + return signature; + } + +-const unsigned char * rpmfilesVSignature(rpmfiles fi, int ix, size_t *len) ++const unsigned char * rpmfilesVSignature(rpmfiles fi, int ix, size_t *len, ++ uint16_t *algo) + { + const unsigned char *vsignature = NULL; + +@@ -594,6 +595,8 @@ const unsigned char * rpmfilesVSignature(rpmfiles fi, int ix, size_t *len) + vsignature = fi->veritysigs + (fi->veritysiglength * ix); + if (len) + *len = fi->veritysiglength; ++ if (algo) ++ *algo = fi->verityalgo; + } + return vsignature; + } +@@ -1963,9 +1966,9 @@ const unsigned char * rpmfiFSignature(rpmfi fi, size_t *len) + return rpmfilesFSignature(fi->files, fi ? fi->i : -1, len); + } + +-const unsigned char * rpmfiVSignature(rpmfi fi, size_t *len) ++const unsigned char * rpmfiVSignature(rpmfi fi, size_t *len, uint16_t *algo) + { +- return rpmfilesVSignature(fi->files, fi ? fi->i : -1, len); ++ return rpmfilesVSignature(fi->files, fi ? fi->i : -1, len, algo); + } + + uint32_t rpmfiFDepends(rpmfi fi, const uint32_t ** fddictp) +diff --git a/lib/rpmfi.h b/lib/rpmfi.h +index fcb9d3acd..6fd2747d6 100644 +--- a/lib/rpmfi.h ++++ b/lib/rpmfi.h +@@ -194,9 +194,10 @@ const unsigned char * rpmfiFSignature(rpmfi fi, size_t *siglen); + * Return current verity (binary) signature of file info set iterator. + * @param fi file info set iterator + * @retval siglen signature length (pass NULL to ignore) ++ * @retval algo fsverity algorithm + * @return current verity signature, NULL on invalid + */ +-const unsigned char * rpmfiVSignature(rpmfi fi, size_t *siglen); ++const unsigned char * rpmfiVSignature(rpmfi fi, size_t *siglen, uint16_t *algo); + + /** \ingroup rpmfi + * Return current file linkto (i.e. symlink(2) target) from file info set iterator. +diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h +index 81b3d01a1..64b33281a 100644 +--- a/lib/rpmfiles.h ++++ b/lib/rpmfiles.h +@@ -450,7 +450,8 @@ const unsigned char * rpmfilesFSignature(rpmfiles fi, int ix, size_t *len); + * @retval len signature length (pass NULL to ignore) + * @return verity signature, NULL on invalid + */ +-const unsigned char * rpmfilesVSignature(rpmfiles fi, int ix, size_t *len); ++const unsigned char * rpmfilesVSignature(rpmfiles fi, int ix, size_t *len, ++ uint16_t *algo); + + /** \ingroup rpmfiles + * Return file rdev from file info set. +diff --git a/plugins/fsverity.c b/plugins/fsverity.c +index 15ddcf33e..1e7f38b38 100644 +--- a/plugins/fsverity.c ++++ b/plugins/fsverity.c +@@ -39,6 +39,7 @@ static rpmRC fsverity_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, + struct fsverity_enable_arg arg; + const unsigned char * signature = NULL; + size_t len; ++ uint16_t algo = 0; + int rc = RPMRC_OK; + int fd; + rpmFileAction action = XFO_ACTION(op); +@@ -75,7 +76,7 @@ static rpmRC fsverity_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, + goto exit; + } + +- signature = rpmfiVSignature(fi, &len); ++ signature = rpmfiVSignature(fi, &len, &algo); + if (!signature || !len) { + rpmlog(RPMLOG_DEBUG, "fsverity no signature for: path %s dest %s\n", + path, dest); +@@ -84,7 +85,10 @@ static rpmRC fsverity_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, + + memset(&arg, 0, sizeof(arg)); + arg.version = 1; +- arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; ++ if (algo) ++ arg.hash_algorithm = algo; ++ else ++ arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; + arg.block_size = RPM_FSVERITY_BLKSZ; + arg.sig_ptr = (uintptr_t)signature; + arg.sig_size = len; +-- +2.27.0 + diff --git a/0028-reflink-remove-requirement-for-executable-stack-flag.patch b/0028-reflink-remove-requirement-for-executable-stack-flag.patch new file mode 100644 index 0000000..a3158f6 --- /dev/null +++ b/0028-reflink-remove-requirement-for-executable-stack-flag.patch @@ -0,0 +1,117 @@ +From 57aa660de4d1b8375cd56f7b8b5fcaf8ad9a5af7 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Fri, 25 Mar 2022 08:13:08 -0700 +Subject: [PATCH 28/30] [reflink] remove requirement for executable stack flag + +reflink was calling `bsearch` with a nested function comparator which +make GCC require the executable stack flag (see `man execstack`). +selinux prevents the use of this flag: +``` +error: Failed to dlopen /usr/lib64/rpm-plugins/reflink.so +/usr/lib64/rpm-plugins/reflink.so: cannot enable executable stack as +shared object requires: Permission denied +``` + +To fix this, either rpm could be granted the correct selinux permission, +but this would open up execstack for more the whole rpm process. +Fundamentally, this is happening because there is no re-entrant version +of `bsearch`. We could probably use a global variable and be done with +it given that each rpm is processed sequencially, but that contract may +not hold true for ever. +Here we are copying stdlib's `bsearch` and making it re-entrant by +allowing to pass an void * data parameter where we can pass the key +size. + +After applying this patch, when reflink.o is installed, it has the executable +flag cleared: +``` +- /usr/lib64/rpm-plugins/reflink.so +``` +--- + plugins/reflink.c | 60 +++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 48 insertions(+), 12 deletions(-) + +diff --git a/plugins/reflink.c b/plugins/reflink.c +index 4fc1d74d1..69e6b51e6 100644 +--- a/plugins/reflink.c ++++ b/plugins/reflink.c +@@ -59,6 +59,50 @@ struct reflink_state_s { + + typedef struct reflink_state_s * reflink_state; + ++/* ++ * bsearch_r: implements a re-entrant version of stdlib's bsearch. ++ * code taken and adapted from /usr/include/bits/stdlib-bsearch.h ++ */ ++inline void * ++bsearch_r (const void *__key, const void *__base, size_t __nmemb, size_t __size, ++ __compar_d_fn_t __compar, void *__arg) ++{ ++ size_t __l, __u, __idx; ++ const void *__p; ++ int __comparison; ++ ++ __l = 0; ++ __u = __nmemb; ++ while (__l < __u) ++ { ++ __idx = (__l + __u) / 2; ++ __p = (const void *) (((const char *) __base) + (__idx * __size)); ++ __comparison = (*__compar) (__key, __p, __arg); ++ if (__comparison < 0) ++ __u = __idx; ++ else if (__comparison > 0) ++ __l = __idx + 1; ++ else ++ { ++#if __GNUC_PREREQ(4, 6) ++# pragma GCC diagnostic push ++# pragma GCC diagnostic ignored "-Wcast-qual" ++#endif ++ return (void *) __p; ++#if __GNUC_PREREQ(4, 6) ++# pragma GCC diagnostic pop ++#endif ++ } ++ } ++ ++ return NULL; ++} ++ ++static int cmpdigest(const void *k1, const void *k2, void *data) { ++ rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2); ++ return memcmp(k1, k2, *(int *)data); ++} ++ + static int inodeCmp(rpm_ino_t a, rpm_ino_t b) + { + return (a != b); +@@ -198,21 +242,13 @@ static rpmRC reflink_psm_post(rpmPlugin plugin, rpmte te, int res) + rpm_loff_t find(const unsigned char *digest, reflink_state state); + + rpm_loff_t find(const unsigned char *digest, reflink_state state) { +-# if defined(__GNUC__) +- /* GCC nested function because bsearch's comparison function can't access +- * state-keysize otherwise +- */ +- int cmpdigest(const void *k1, const void *k2) { +- rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2); +- return memcmp(k1, k2, state->keysize); +- } +-# endif + rpmlog(RPMLOG_DEBUG, +- _("reflink: bsearch(key=%p, base=%p, nmemb=%d, size=%lu)\n"), ++ _("reflink: bsearch_r(key=%p, base=%p, nmemb=%d, size=%lu)\n"), + digest, state->table, state->keys, + state->keysize + sizeof(rpm_loff_t)); +- char *entry = bsearch(digest, state->table, state->keys, +- state->keysize + sizeof(rpm_loff_t), cmpdigest); ++ char *entry = bsearch_r(digest, state->table, state->keys, ++ state->keysize + sizeof(rpm_loff_t), cmpdigest, ++ &state->keysize); + if (entry == NULL) { + return NOT_FOUND; + } +-- +2.35.1 + diff --git a/0029-Add-fsverity-tags-to-rpmgeneral.at.patch b/0029-Add-fsverity-tags-to-rpmgeneral.at.patch new file mode 100644 index 0000000..6575b99 --- /dev/null +++ b/0029-Add-fsverity-tags-to-rpmgeneral.at.patch @@ -0,0 +1,28 @@ +From 3132053a066b3dc8aa38b358ecd316ee60fd0f7c Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 20 Apr 2020 18:52:08 -0400 +Subject: [PATCH 29/33] Add fsverity tags to rpmgeneral.at + +Make sure we pass the checks with the new tags in place. + +Signed-off-by: Jes Sorensen +--- + tests/rpmgeneral.at | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/tests/rpmgeneral.at b/tests/rpmgeneral.at +index 45d38698b..8a7dc827f 100644 +--- a/tests/rpmgeneral.at ++++ b/tests/rpmgeneral.at +@@ -291,6 +291,8 @@ VERBOSE + VERIFYSCRIPT + VERIFYSCRIPTFLAGS + VERIFYSCRIPTPROG ++VERITYSIGNATUREALGO ++VERITYSIGNATURES + VERSION + XPM + ]) +-- +2.27.0 + diff --git a/0029-extentsVerifySigs-Make-it-optional-to-print-the-sign.patch b/0029-extentsVerifySigs-Make-it-optional-to-print-the-sign.patch new file mode 100644 index 0000000..f4bf8e3 --- /dev/null +++ b/0029-extentsVerifySigs-Make-it-optional-to-print-the-sign.patch @@ -0,0 +1,109 @@ +From 5753b178a08043316e6f3556754741cdd9cd19c5 Mon Sep 17 00:00:00 2001 +From: chantra +Date: Mon, 28 Mar 2022 14:00:13 -0700 +Subject: [PATCH 29/30] [extentsVerifySigs] Make it optional to print the + signature verification output + +--- + lib/rpmchecksig.c | 2 +- + lib/rpmextents.c | 39 ++++++++++++++++++++------------------- + lib/rpmextents_internal.h | 3 ++- + 3 files changed, 23 insertions(+), 21 deletions(-) + +diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c +index c9fc3bbc9..7f856154e 100644 +--- a/lib/rpmchecksig.c ++++ b/lib/rpmchecksig.c +@@ -229,7 +229,7 @@ static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags, + + + if(isTranscodedRpm(fd) == RPMRC_OK){ +- return extentsVerifySigs(fd); ++ return extentsVerifySigs(fd, 1); + } + + struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring); +diff --git a/lib/rpmextents.c b/lib/rpmextents.c +index 59ba427a4..ac43264af 100644 +--- a/lib/rpmextents.c ++++ b/lib/rpmextents.c +@@ -10,7 +10,7 @@ + #include "lib/rpmextents_internal.h" + + +-int extentsVerifySigs(FD_t fd){ ++int extentsVerifySigs(FD_t fd, int print_content){ + rpm_loff_t current; + int32_t rc; + size_t len; +@@ -36,24 +36,26 @@ int extentsVerifySigs(FD_t fd){ + goto exit; + } + +- len = sizeof(content_len); +- if (Fread(&content_len, len, 1, fd) != len) { +- rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content length\n")); +- goto exit; +- } +- +- content = rmalloc(content_len + 1); +- if(content == NULL) { +- rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to allocate memory to read signature content\n")); +- goto exit; ++ if(print_content) { ++ len = sizeof(content_len); ++ if (Fread(&content_len, len, 1, fd) != len) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content length\n")); ++ goto exit; ++ } ++ ++ content = rmalloc(content_len + 1); ++ if(content == NULL) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to allocate memory to read signature content\n")); ++ goto exit; ++ } ++ content[content_len] = 0; ++ if (Fread(content, content_len, 1, fd) != content_len) { ++ rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content\n")); ++ goto exit; ++ } ++ ++ rpmlog(RPMLOG_NOTICE, "%s", content); + } +- content[content_len] = 0; +- if (Fread(content, content_len, 1, fd) != content_len) { +- rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content\n")); +- goto exit; +- } +- +- rpmlog(RPMLOG_NOTICE, "%s", content); + exit: + if(content){ + rfree(content); +@@ -79,7 +81,6 @@ rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer) { + + len = sizeof(struct extents_footer_t); + if(Fseek(fd, -len, SEEK_END) < 0) { +- rpmlog(RPMLOG_ERR, _("isTranscodedRpm: failed to seek for footer: %s\n"), strerror(errno)); + rc = RPMRC_FAIL; + goto exit; + } +diff --git a/lib/rpmextents_internal.h b/lib/rpmextents_internal.h +index 380c08425..0a3318c8e 100644 +--- a/lib/rpmextents_internal.h ++++ b/lib/rpmextents_internal.h +@@ -32,9 +32,10 @@ struct __attribute__ ((__packed__)) extents_footer_t { + /** \ingroup rpmextents + * Checks the results of the signature verification ran during transcoding. + * @param fd The FD_t of the transcoded RPM ++ * @param print_content Whether or not to print the result from rpmsig + * @return The number of checks that `rpmvsVerify` failed during transcoding. + */ +-int extentsVerifySigs(FD_t fd); ++int extentsVerifySigs(FD_t fd, int print_content); + + /** \ingroup rpmextents + * Read the RPM Extents footer from a file descriptor. +-- +2.35.1 + diff --git a/0030-Add-delfilesign-flag-to-delete-IMA-and-fsverity-file.patch b/0030-Add-delfilesign-flag-to-delete-IMA-and-fsverity-file.patch new file mode 100644 index 0000000..d872d5e --- /dev/null +++ b/0030-Add-delfilesign-flag-to-delete-IMA-and-fsverity-file.patch @@ -0,0 +1,117 @@ +From 46db4f6827840e828f42424454410b930895d9a7 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Mon, 13 Apr 2020 18:24:31 -0400 +Subject: [PATCH 30/33] Add --delfilesign flag to delete IMA and fsverity file + signatures + +This allows a user to remove both types of file signatures from the +package. Previously there was no way to delete IMA signatures, only +replace them by first removing the package signature and then +resigning the package and the files. + +Signed-off-by: Jes Sorensen +--- + rpmsign.c | 12 ++++++++++++ + sign/rpmgensig.c | 17 ++++++++++++++++- + sign/rpmsign.h | 9 +++++++++ + 3 files changed, 37 insertions(+), 1 deletion(-) + +diff --git a/rpmsign.c b/rpmsign.c +index 074dd8b13..e43811e9f 100644 +--- a/rpmsign.c ++++ b/rpmsign.c +@@ -14,6 +14,7 @@ enum modes { + MODE_ADDSIGN = (1 << 0), + MODE_RESIGN = (1 << 1), + MODE_DELSIGN = (1 << 2), ++ MODE_DELFILESIGN = (1 << 3), + }; + + static int mode = MODE_NONE; +@@ -35,6 +36,10 @@ static struct poptOption signOptsTable[] = { + N_("sign package(s) (identical to --addsign)"), NULL }, + { "delsign", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_DELSIGN, + N_("delete package signatures"), NULL }, ++#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) ++ { "delfilesign", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, ++ MODE_DELFILESIGN, N_("delete IMA and fsverity file signatures"), NULL }, ++#endif + { "rpmv3", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), + &sargs.signflags, RPMSIGN_FLAG_RPMV3, + N_("create rpm v3 header+payload signatures") }, +@@ -207,6 +212,13 @@ int main(int argc, char *argv[]) + ec++; + } + break; ++ case MODE_DELFILESIGN: ++ ec = 0; ++ while ((arg = poptGetArg(optCon)) != NULL) { ++ if (rpmPkgDelFileSign(arg, &sargs) < 0) ++ ec++; ++ } ++ break; + case MODE_NONE: + printUsage(optCon, stderr, 0); + break; +diff --git a/sign/rpmgensig.c b/sign/rpmgensig.c +index 8d5c5858f..02cf0bc62 100644 +--- a/sign/rpmgensig.c ++++ b/sign/rpmgensig.c +@@ -336,6 +336,14 @@ static void deleteSigs(Header sigh) + headerDel(sigh, RPMSIGTAG_PGP5); + } + ++static void deleteFileSigs(Header sigh) ++{ ++ headerDel(sigh, RPMSIGTAG_FILESIGNATURELENGTH); ++ headerDel(sigh, RPMSIGTAG_FILESIGNATURES); ++ headerDel(sigh, RPMSIGTAG_VERITYSIGNATURES); ++ headerDel(sigh, RPMSIGTAG_VERITYSIGNATUREALGO); ++} ++ + static int haveSignature(rpmtd sigtd, Header h) + { + pgpDigParams sig1 = NULL; +@@ -580,7 +588,9 @@ static int rpmSign(const char *rpm, int deleting, int flags) + goto exit; + } + +- if (deleting) { /* Nuke all the signature tags. */ ++ if (deleting == 2) { /* Nuke IMA + fsverity file signature tags. */ ++ deleteFileSigs(sigh); ++ } else if (deleting) { /* Nuke all the signature tags. */ + deleteSigs(sigh); + } else { + /* Signature target containing header + payload */ +@@ -745,3 +755,8 @@ int rpmPkgDelSign(const char *path, const struct rpmSignArgs * args) + { + return rpmSign(path, 1, 0); + } ++ ++int rpmPkgDelFileSign(const char *path, const struct rpmSignArgs * args) ++{ ++ return rpmSign(path, 2, 0); ++} +diff --git a/sign/rpmsign.h b/sign/rpmsign.h +index 2b8a10a1a..5169741dd 100644 +--- a/sign/rpmsign.h ++++ b/sign/rpmsign.h +@@ -44,6 +44,15 @@ int rpmPkgSign(const char *path, const struct rpmSignArgs * args); + */ + int rpmPkgDelSign(const char *path, const struct rpmSignArgs * args); + ++ ++/** \ingroup rpmsign ++ * Delete file signature(s) from a package ++ * @param path path to package ++ * @param args signing parameters (or NULL for defaults) ++ * @return 0 on success ++ */ ++int rpmPkgDelFileSign(const char *path, const struct rpmSignArgs * args); ++ + #ifdef __cplusplus + } + #endif +-- +2.27.0 + diff --git a/0030-rpmcow-Make-rpm-i-install-package-without-the-need-o.patch b/0030-rpmcow-Make-rpm-i-install-package-without-the-need-o.patch new file mode 100644 index 0000000..4e19a52 --- /dev/null +++ b/0030-rpmcow-Make-rpm-i-install-package-without-the-need-o.patch @@ -0,0 +1,98 @@ +From dc53b002bd3d03a21e9af406a9aff5e588710b5b Mon Sep 17 00:00:00 2001 +From: chantra +Date: Mon, 28 Mar 2022 19:42:39 -0700 +Subject: [PATCH 30/30] [rpmcow] Make rpm -i install package without the need + of --nodigest + +When using transcoded files, the logic to check signature is different +and was done while the file was transcoded. This change the code path +used by `rpm -{i,U}` to check if the file is transcoded, and in such +cases, assume it was already verified. +--- + lib/transaction.c | 29 ++++++++++++++++++----------- + tests/rpm2extents.at | 6 +++--- + 2 files changed, 21 insertions(+), 14 deletions(-) + +diff --git a/lib/transaction.c b/lib/transaction.c +index 36c2a7a64..703e4140c 100644 +--- a/lib/transaction.c ++++ b/lib/transaction.c +@@ -37,6 +37,7 @@ + #include "lib/rpmfi_internal.h" /* only internal apis */ + #include "lib/rpmte_internal.h" /* only internal apis */ + #include "lib/rpmts_internal.h" ++#include "lib/rpmextents_internal.h" + #include "lib/rpmvs.h" + #include "rpmio/rpmhook.h" + #include "lib/rpmtriggers.h" +@@ -1286,19 +1287,25 @@ static int verifyPackageFiles(rpmts ts, rpm_loff_t total) + + rpmtsNotify(ts, p, RPMCALLBACK_VERIFY_PROGRESS, oc++, total); + FD_t fd = rpmtsNotify(ts, p, RPMCALLBACK_INST_OPEN_FILE, 0, 0); +- if (fd != NULL) { +- prc = rpmpkgRead(vs, fd, NULL, NULL, &vd.msg); +- rpmtsNotify(ts, p, RPMCALLBACK_INST_CLOSE_FILE, 0, 0); ++ if(fd != NULL && isTranscodedRpm(fd) == RPMRC_OK) { ++ /* Transcoded RPMs are validated at transcoding time */ ++ prc = RPMRC_OK; ++ verified = 1; ++ } else { ++ if (fd != NULL) { ++ prc = rpmpkgRead(vs, fd, NULL, NULL, &vd.msg); ++ rpmtsNotify(ts, p, RPMCALLBACK_INST_CLOSE_FILE, 0, 0); ++ } ++ if (prc == RPMRC_OK) ++ prc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd); ++ ++ /* Record verify result */ ++ if (vd.type[RPMSIG_SIGNATURE_TYPE] == RPMRC_OK) ++ verified |= RPMSIG_SIGNATURE_TYPE; ++ if (vd.type[RPMSIG_DIGEST_TYPE] == RPMRC_OK) ++ verified |= RPMSIG_DIGEST_TYPE; + } + +- if (prc == RPMRC_OK) +- prc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd); +- +- /* Record verify result */ +- if (vd.type[RPMSIG_SIGNATURE_TYPE] == RPMRC_OK) +- verified |= RPMSIG_SIGNATURE_TYPE; +- if (vd.type[RPMSIG_DIGEST_TYPE] == RPMRC_OK) +- verified |= RPMSIG_DIGEST_TYPE; + rpmteSetVerified(p, verified); + + if (prc) +diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at +index 5c66de7f6..5135c9cf8 100644 +--- a/tests/rpm2extents.at ++++ b/tests/rpm2extents.at +@@ -102,7 +102,7 @@ AT_CHECK([ + RPMDB_INIT + + runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/hello-2.0-1.x86_64.rpm 2> /dev/null +-runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /tmp/hello-2.0-1.x86_64.rpm ++runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /tmp/hello-2.0-1.x86_64.rpm + test -f ${RPMTEST}/usr/bin/hello + ], + [0], +@@ -115,7 +115,7 @@ AT_KEYWORDS([reflink]) + AT_CHECK([ + RPMDB_INIT + +-runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /data/RPMS/hello-2.0-1.x86_64.rpm && exit $? ++runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /data/RPMS/hello-2.0-1.x86_64.rpm && exit $? + # Check that the file is properly installed in chroot + test -f ${RPMTEST}/usr/bin/hello + ], +@@ -132,7 +132,7 @@ RPMDB_INIT + + PKG=hlinktest-1.0-1.noarch.rpm + runroot_other cat /data/RPMS/${PKG} | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/${PKG} 2> /dev/null +-runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /tmp/${PKG} ++runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /tmp/${PKG} + ], + [0], + [], +-- +2.35.1 + diff --git a/0031-Update-man-page-for-rpmsign.patch b/0031-Update-man-page-for-rpmsign.patch new file mode 100644 index 0000000..938e0f1 --- /dev/null +++ b/0031-Update-man-page-for-rpmsign.patch @@ -0,0 +1,72 @@ +From 4d243b7e692e3803a764343dfed23feb1c656f0b Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Tue, 12 May 2020 13:42:34 -0400 +Subject: [PATCH 31/33] Update man page for rpmsign + +This documents the new arguments --signverity and --certpath required +to sign a package with fsverity signatures. + +Signed-off-by: Jes Sorensen +--- + doc/rpmsign.8 | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/doc/rpmsign.8 b/doc/rpmsign.8 +index f7ceae89b..a212746fe 100644 +--- a/doc/rpmsign.8 ++++ b/doc/rpmsign.8 +@@ -9,6 +9,8 @@ rpmsign \- RPM Package Signing + + \fBrpm\fR \fB--delsign\fR \fB\fIPACKAGE_FILE\fB\fR\fI ...\fR + ++\fBrpm\fR \fB--delfilesign\fR \fB\fIPACKAGE_FILE\fB\fR\fI ...\fR ++ + .SS "rpmsign-options" + .PP + [\fb--rpmv3\fR] +@@ -30,6 +32,12 @@ packages with a MD5/SHA1 checksums cannot be signed in FIPS mode. + .PP + Delete all signatures from each package \fIPACKAGE_FILE\fR given. + ++\fBrpm\fR \fB--delfilesign\fR \fB\fIPACKAGE_FILE\fB\fR\fI ...\fR ++ ++.PP ++Delete all IMA and fsverity file signatures from each package ++\fIPACKAGE_FILE\fR given. ++ + .SS "SIGN OPTIONS" + .PP + .TP +@@ -44,12 +52,23 @@ signature verifiable with rpm < 4.14 or other interoperability reasons. + \fB--fskpath \fIKEY\fB\fR + Used with \fB--signfiles\fR, use file signing key \fIKey\fR. + .TP ++\fB--certpath \fICERT\fB\fR ++Used with \fB--signverity\fR, use file signing certificate \fICert\fR. ++.TP + \fB--signfiles\fR + Sign package files. The macro \fB%_binary_filedigest_algorithm\fR must + be set to a supported algorithm before building the package. The + supported algorithms are SHA1, SHA256, SHA384, and SHA512, which are + represented as 2, 8, 9, and 10 respectively. The file signing key (RSA + private key) must be set before signing the package, it can be configured on the command line with \fB--fskpath\fR or the macro %_file_signing_key. ++.TP ++\fB--signverity\fR ++Sign package files with fsverity signatures. The file signing key (RSA ++private key) and the signing certificate must be set before signing ++the package. The key can be configured on the command line with ++\fB--fskpath\fR or the macro %_file_signing_key, and the cert can be ++configured on the command line with \fB--certpath\fR or the macro ++%_file_signing_cert. + + .SS "USING GPG TO SIGN PACKAGES" + .PP +@@ -110,4 +129,5 @@ Jeff Johnson + Erik Troan + Panu Matilainen + Fionnuala Gunter ++Jes Sorensen + .fi +-- +2.27.0 + diff --git a/0032-rpmsign-Add-argument-to-specify-algorithm-for-fsveri.patch b/0032-rpmsign-Add-argument-to-specify-algorithm-for-fsveri.patch new file mode 100644 index 0000000..6a20363 --- /dev/null +++ b/0032-rpmsign-Add-argument-to-specify-algorithm-for-fsveri.patch @@ -0,0 +1,168 @@ +From 3669fecaba2858aeca44d1bfc265760611ea8834 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Wed, 10 Jun 2020 12:30:54 -0400 +Subject: [PATCH 32/33] rpmsign: Add argument to specify algorithm for fsverity + signatures + +The argument --verity-algo can be used to specify the algorithm for +the fsverity signatures. If nothing is specified, this will default to +sha256. The available algorithms depend on libfsverity, currently +sha256 and sha512 are supported. + +Signed-off-by: Jes Sorensen +--- + doc/rpmsign.8 | 3 +++ + rpmsign.c | 7 +++++++ + sign/rpmgensig.c | 22 ++++++++++++++++++++-- + sign/rpmsignverity.c | 6 +++--- + sign/rpmsignverity.h | 2 +- + 5 files changed, 34 insertions(+), 6 deletions(-) + +diff --git a/doc/rpmsign.8 b/doc/rpmsign.8 +index a212746fe..5165e39f9 100644 +--- a/doc/rpmsign.8 ++++ b/doc/rpmsign.8 +@@ -55,6 +55,9 @@ Used with \fB--signfiles\fR, use file signing key \fIKey\fR. + \fB--certpath \fICERT\fB\fR + Used with \fB--signverity\fR, use file signing certificate \fICert\fR. + .TP ++\fB--verityalgo \fIALG\fB\fR ++Used with \fB--signverity\fR, to specify the signing algorithm. sha256 and sha512 are supported, with sha256 being the default if this argument is not specified. This can also be specified with the macro %_verity_algorithm ++.TP + \fB--signfiles\fR + Sign package files. The macro \fB%_binary_filedigest_algorithm\fR must + be set to a supported algorithm before building the package. The +diff --git a/rpmsign.c b/rpmsign.c +index e43811e9f..12299379c 100644 +--- a/rpmsign.c ++++ b/rpmsign.c +@@ -25,6 +25,7 @@ static char * fileSigningKey = NULL; + #endif + #ifdef WITH_FSVERITY + static char * fileSigningCert = NULL; ++static char * verityAlgorithm = NULL; + #endif + + static struct rpmSignArgs sargs = {NULL, 0, 0}; +@@ -52,6 +53,9 @@ static struct poptOption signOptsTable[] = { + { "signverity", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), + &sargs.signflags, RPMSIGN_FLAG_FSVERITY, + N_("generate fsverity signatures for package(s) files"), NULL}, ++ { "verityalgo", '\0', POPT_ARG_STRING, &verityAlgorithm, 0, ++ N_("algorithm to use for verity signatures, default sha256"), ++ N_("") }, + { "certpath", '\0', POPT_ARG_STRING, &fileSigningCert, 0, + N_("use file signing cert "), + N_("") }, +@@ -138,6 +142,9 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + if (fileSigningCert) { + rpmPushMacro(NULL, "_file_signing_cert", NULL, fileSigningCert, RMIL_GLOBAL); + } ++ if (verityAlgorithm) { ++ rpmPushMacro(NULL, "_verity_algorithm", NULL, verityAlgorithm, RMIL_GLOBAL); ++ } + #endif + + if (flags_sign_files(sargs->signflags)) { +diff --git a/sign/rpmgensig.c b/sign/rpmgensig.c +index 02cf0bc62..b3b02828a 100644 +--- a/sign/rpmgensig.c ++++ b/sign/rpmgensig.c +@@ -8,6 +8,10 @@ + #include + #include + #include ++#include ++#ifdef WITH_FSVERITY ++#include ++#endif + + #include /* RPMSIGTAG & related */ + #include +@@ -458,23 +462,37 @@ static rpmRC includeFileSignatures(Header *sigp, Header *hdrp) + static rpmRC includeVeritySignatures(FD_t fd, Header *sigp, Header *hdrp) + { + #ifdef WITH_FSVERITY +- rpmRC rc; ++ rpmRC rc = RPMRC_OK; + char *key = rpmExpand("%{?_file_signing_key}", NULL); + char *keypass = rpmExpand("%{?_file_signing_key_password}", NULL); + char *cert = rpmExpand("%{?_file_signing_cert}", NULL); ++ char *algorithm = rpmExpand("%{?_verity_algorithm}", NULL); ++ uint16_t algo = 0; + + if (rstreq(keypass, "")) { + free(keypass); + keypass = NULL; + } + ++ if (algorithm && strlen(algorithm) > 0) { ++ algo = libfsverity_find_hash_alg_by_name(algorithm); ++ rpmlog(RPMLOG_DEBUG, _("Searching for algorithm %s got %i\n"), ++ algorithm, algo); ++ if (!algo) { ++ rpmlog(RPMLOG_ERR, _("Unsupported fsverity algorithm %s\n"), ++ algorithm); ++ rc = RPMRC_FAIL; ++ goto out; ++ } ++ } + if (key && cert) { +- rc = rpmSignVerity(fd, *sigp, *hdrp, key, keypass, cert); ++ rc = rpmSignVerity(fd, *sigp, *hdrp, key, keypass, cert, algo); + } else { + rpmlog(RPMLOG_ERR, _("fsverity signatures requires a key and a cert\n")); + rc = RPMRC_FAIL; + } + ++ out: + free(keypass); + free(key); + free(cert); +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index 55096e732..e6c830cdc 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -95,7 +95,7 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + } + + rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, +- char *keypass, char *cert) ++ char *keypass, char *cert, uint16_t algo) + { + int rc; + FD_t gzdi; +@@ -111,7 +111,6 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + char **signatures = NULL; + size_t sig_size; + int nr_files, idx; +- uint16_t algo; + uint32_t algo32; + + Fseek(fd, 0, SEEK_SET); +@@ -156,7 +155,8 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + nr_files = rpmfiFC(hfi); + signatures = xcalloc(nr_files, sizeof(char *)); + +- algo = FS_VERITY_HASH_ALG_SHA256; ++ if (!algo) ++ algo = FS_VERITY_HASH_ALG_SHA256; + + rpmlog(RPMLOG_DEBUG, _("file count - header: %i, payload %i\n"), + nr_files, rpmfiFC(fi)); +diff --git a/sign/rpmsignverity.h b/sign/rpmsignverity.h +index 69bbaf7f7..d869e8d8e 100644 +--- a/sign/rpmsignverity.h ++++ b/sign/rpmsignverity.h +@@ -27,7 +27,7 @@ extern "C" { + */ + RPM_GNUC_INTERNAL + rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, +- char *keypass, char *cert); ++ char *keypass, char *cert, uint16_t algo); + + #ifdef _cplusplus + } +-- +2.27.0 + diff --git a/0033-Enable-fsverity-in-CI.patch b/0033-Enable-fsverity-in-CI.patch new file mode 100644 index 0000000..0fc6fde --- /dev/null +++ b/0033-Enable-fsverity-in-CI.patch @@ -0,0 +1,27 @@ +From 84ee9dc61b14056fec489bb099f1f212b3b169a9 Mon Sep 17 00:00:00 2001 +From: Jes Sorensen +Date: Fri, 28 Aug 2020 11:10:41 -0400 +Subject: [PATCH 33/33] Enable fsverity in CI + +Add fsverity-utils and fsverity-utils-devel as dependencies. + +Signed-off-by: Jes Sorensen +--- + Makefile.am | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/Makefile.am b/Makefile.am +index 8e92f0cde..3c1451049 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -17,6 +17,7 @@ DISTCHECK_CONFIGURE_FLAGS = \ + --with-selinux \ + --with-imaevm \ + --with-fapolicyd \ ++ --with-fsverity \ + --disable-dependency-tracking + + include $(top_srcdir)/rpm.am +-- +2.27.0 + diff --git a/0034-rpmsign-Adopting-PKCS11-opaque-keys-support-in-libfsverity-for-fsverity-signatures.patch b/0034-rpmsign-Adopting-PKCS11-opaque-keys-support-in-libfsverity-for-fsverity-signatures.patch new file mode 100644 index 0000000..692ab33 --- /dev/null +++ b/0034-rpmsign-Adopting-PKCS11-opaque-keys-support-in-libfsverity-for-fsverity-signatures.patch @@ -0,0 +1,257 @@ +From aee5af8e4fe7908df90649eb699c3a1decf06b0c Mon Sep 17 00:00:00 2001 +From: Yu Wu +Date: Wed, 3 Nov 2021 23:13:15 -0700 +Subject: [PATCH 1/2] rpmsign: Adopting PKCS#11 opaque keys support in + libfsverity for fsverity signatures + +--- + rpmsign.c | 29 ++++++++++++++++++++---- + sign/rpmgensig.c | 53 +++++++++++++++++++++++++++++++++++++++----- + sign/rpmsignverity.c | 24 +++++++++++++------- + sign/rpmsignverity.h | 9 +++++--- + 4 files changed, 94 insertions(+), 21 deletions(-) + +diff --git a/rpmsign.c b/rpmsign.c +index 12299379ce..63b8616382 100644 +--- a/rpmsign.c ++++ b/rpmsign.c +@@ -24,6 +24,9 @@ static int fskpass = 0; + static char * fileSigningKey = NULL; + #endif + #ifdef WITH_FSVERITY ++static char * pkcs11Engine = NULL; ++static char * pkcs11Module = NULL; ++static char * pkcs11KeyId = NULL; + static char * fileSigningCert = NULL; + static char * verityAlgorithm = NULL; + #endif +@@ -59,6 +62,15 @@ static struct poptOption signOptsTable[] = { + { "certpath", '\0', POPT_ARG_STRING, &fileSigningCert, 0, + N_("use file signing cert "), + N_("") }, ++ { "pkcs11_engine", '\0', POPT_ARG_STRING, &pkcs11Engine, 0, ++ N_("use pkcs#11 token for fsverity signing key with openssl engine "), ++ N_("") }, ++ { "pkcs11_module", '\0', POPT_ARG_STRING, &pkcs11Module, 0, ++ N_("use pkcs#11 token for fsverity signing key with openssl module "), ++ N_("") }, ++ { "pkcs11_keyid", '\0', POPT_ARG_STRING, &pkcs11KeyId, 0, ++ N_("use pkcs#11 token for fsverity signing key with keyid "), ++ N_("") }, + #endif + #if defined(WITH_IMAEVM) || defined(WITH_FSVERITY) + { "fskpath", '\0', POPT_ARG_STRING, &fileSigningKey, 0, +@@ -139,6 +151,15 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + } + + #ifdef WITH_FSVERITY ++ if (pkcs11Engine) { ++ rpmPushMacro(NULL, "_pkcs11_engine", NULL, pkcs11Engine, RMIL_GLOBAL); ++ } ++ if (pkcs11Module) { ++ rpmPushMacro(NULL, "_pkcs11_module", NULL, pkcs11Module, RMIL_GLOBAL); ++ } ++ if (pkcs11KeyId) { ++ rpmPushMacro(NULL, "_pkcs11_keyid", NULL, pkcs11KeyId, RMIL_GLOBAL); ++ } + if (fileSigningCert) { + rpmPushMacro(NULL, "_file_signing_cert", NULL, fileSigningCert, RMIL_GLOBAL); + } +@@ -149,9 +170,9 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + + if (flags_sign_files(sargs->signflags)) { + char *fileSigningKeyPassword = NULL; +- char *key = rpmExpand("%{?_file_signing_key}", NULL); +- if (rstreq(key, "")) { +- fprintf(stderr, _("You must set \"%%_file_signing_key\" in your macro file or on the command line with --fskpath\n")); ++ char *cert = rpmExpand("%{?_file_signing_cert}", NULL); ++ if (rstreq(cert, "")) { ++ fprintf(stderr, _("You must set \"%%_file_signing_cert\" in your macro file or on the command line with --certpath\n")); + goto exit; + } + +@@ -166,7 +187,7 @@ static int doSign(poptContext optCon, struct rpmSignArgs *sargs) + free(fileSigningKeyPassword); + } + +- free(key); ++ free(cert); + } + #endif + +diff --git a/sign/rpmgensig.c b/sign/rpmgensig.c +index d8c84e9377..cb264679b6 100644 +--- a/sign/rpmgensig.c ++++ b/sign/rpmgensig.c +@@ -461,15 +461,56 @@ static rpmRC includeVeritySignatures(FD_t fd, Header *sigp, Header *hdrp) + rpmRC rc = RPMRC_OK; + char *key = rpmExpand("%{?_file_signing_key}", NULL); + char *keypass = rpmExpand("%{?_file_signing_key_password}", NULL); ++ char *pkcs11_engine = rpmExpand("%{?_pkcs11_engine}", NULL); ++ char *pkcs11_module = rpmExpand("%{?_pkcs11_module}", NULL); ++ char *pkcs11_keyid = rpmExpand("%{?_pkcs11_keyid}", NULL); + char *cert = rpmExpand("%{?_file_signing_cert}", NULL); + char *algorithm = rpmExpand("%{?_verity_algorithm}", NULL); + uint16_t algo = 0; + ++ if (rstreq(key, "")) { ++ free(key); ++ key = NULL; ++ } ++ ++ if (rstreq(pkcs11_engine, "")) { ++ free(pkcs11_engine); ++ pkcs11_engine = NULL; ++ } ++ ++ if (rstreq(pkcs11_module, "")) { ++ free(pkcs11_module); ++ pkcs11_module = NULL; ++ } ++ ++ if (rstreq(pkcs11_keyid, "")) { ++ free(pkcs11_keyid); ++ pkcs11_keyid = NULL; ++ } ++ + if (rstreq(keypass, "")) { + free(keypass); + keypass = NULL; + } + ++ if (key) { ++ if (pkcs11_engine || pkcs11_module || pkcs11_keyid) { ++ rpmlog( ++ RPMLOG_ERR, ++ _("fsverity signatures require a key specified either by file or by PKCS#11 token, not both\n")); ++ rc = RPMRC_FAIL; ++ goto out; ++ } ++ } else { ++ if (!pkcs11_engine || !pkcs11_module) { ++ rpmlog( ++ RPMLOG_ERR, ++ _("fsverity signatures require both PKCS#11 engine and module to use PKCS#11 token\n")); ++ rc = RPMRC_FAIL; ++ goto out; ++ } ++ } ++ + if (algorithm && strlen(algorithm) > 0) { + algo = libfsverity_find_hash_alg_by_name(algorithm); + rpmlog(RPMLOG_DEBUG, _("Searching for algorithm %s got %i\n"), +@@ -481,16 +522,16 @@ static rpmRC includeVeritySignatures(FD_t fd, Header *sigp, Header *hdrp) + goto out; + } + } +- if (key && cert) { +- rc = rpmSignVerity(fd, *sigp, *hdrp, key, keypass, cert, algo); +- } else { +- rpmlog(RPMLOG_ERR, _("fsverity signatures requires a key and a cert\n")); +- rc = RPMRC_FAIL; +- } ++ ++ rc = rpmSignVerity(fd, *sigp, *hdrp, key, keypass, ++ pkcs11_engine, pkcs11_module, pkcs11_keyid, cert, algo); + + out: + free(keypass); + free(key); ++ free(pkcs11_engine); ++ free(pkcs11_module); ++ free(pkcs11_keyid); + free(cert); + return rc; + #else +diff --git a/sign/rpmsignverity.c b/sign/rpmsignverity.c +index e6c830cdcb..b7924e7ad1 100644 +--- a/sign/rpmsignverity.c ++++ b/sign/rpmsignverity.c +@@ -34,8 +34,9 @@ static int rpmVerityRead(void *opaque, void *buf, size_t size) + return retval; + } + +-static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, +- char *keypass, char *cert, uint16_t algo) ++static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, char *keypass, ++ char *pkcs11_engine, char *pkcs11_module, char *pkcs11_keyid, ++ char *cert, uint16_t algo) + { + struct libfsverity_merkle_tree_params params; + struct libfsverity_signature_params sig_params; +@@ -76,6 +77,9 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + + memset(&sig_params, 0, sizeof(struct libfsverity_signature_params)); + sig_params.keyfile = key; ++ sig_params.pkcs11_engine = pkcs11_engine; ++ sig_params.pkcs11_module = pkcs11_module; ++ sig_params.pkcs11_keyid = pkcs11_keyid; + sig_params.certfile = cert; + if (libfsverity_sign_digest(digest, &sig_params, &sig, sig_size)) { + rpmlog(RPMLOG_DEBUG, _("failed to sign digest\n")); +@@ -94,8 +98,9 @@ static char *rpmVeritySignFile(rpmfi fi, size_t *sig_size, char *key, + return sig_base64; + } + +-rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, +- char *keypass, char *cert, uint16_t algo) ++rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, char *keypass, ++ char *pkcs11_engine, char *pkcs11_module, char *pkcs11_keyid, ++ char *cert, uint16_t algo) + { + int rc; + FD_t gzdi; +@@ -125,6 +130,9 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + } + + rpmlog(RPMLOG_DEBUG, _("key: %s\n"), key); ++ rpmlog(RPMLOG_DEBUG, _("pkcs11_engine: %s\n"), pkcs11_engine); ++ rpmlog(RPMLOG_DEBUG, _("pkcs11_module: %s\n"), pkcs11_module); ++ rpmlog(RPMLOG_DEBUG, _("pkcs11_keyid: %s\n"), pkcs11_keyid); + rpmlog(RPMLOG_DEBUG, _("cert: %s\n"), cert); + + compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR); +@@ -164,16 +172,16 @@ rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, + while (rpmfiNext(fi) >= 0) { + idx = rpmfiFX(fi); + +- signatures[idx] = rpmVeritySignFile(fi, &sig_size, key, keypass, cert, +- algo); ++ signatures[idx] = rpmVeritySignFile(fi, &sig_size, key, keypass, pkcs11_engine, ++ pkcs11_module, pkcs11_keyid, cert, algo); + } + + while (rpmfiNext(hfi) >= 0) { + idx = rpmfiFX(hfi); + if (signatures[idx]) + continue; +- signatures[idx] = rpmVeritySignFile(hfi, &sig_size, key, keypass, cert, +- algo); ++ signatures[idx] = rpmVeritySignFile(fi, &sig_size, key, keypass, pkcs11_engine, ++ pkcs11_module, pkcs11_keyid, cert, algo); + } + + rpmtdReset(&td); +diff --git a/sign/rpmsignverity.h b/sign/rpmsignverity.h +index d869e8d8e8..32d2d6359a 100644 +--- a/sign/rpmsignverity.h ++++ b/sign/rpmsignverity.h +@@ -22,12 +22,15 @@ extern "C" { + * @param h package header + * @param key signing key + * @param keypass signing key password ++ * @param pkcs11_engine PKCS#11 engine to use PKCS#11 token support for signing key ++ * @param pkcs11_module PKCS#11 module to use PKCS#11 token support for signing key ++ * @param pkcs11_keyid PKCS#11 key identifier + * @param cert signing cert + * @return RPMRC_OK on success + */ +-RPM_GNUC_INTERNAL +-rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, +- char *keypass, char *cert, uint16_t algo); ++rpmRC rpmSignVerity(FD_t fd, Header sigh, Header h, char *key, char *keypass, ++ char *pkcs11_engine, char *pkcs11_module, char *pkcs11_keyid, ++ char *cert, uint16_t algo); + + #ifdef _cplusplus + } diff --git a/1534.patch b/1534.patch new file mode 100644 index 0000000..e2a7cf6 --- /dev/null +++ b/1534.patch @@ -0,0 +1,1162 @@ +From 22d18785b2c41f1a8937b712920b4342f7596a7e Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Mon, 8 Feb 2021 10:45:59 +0200 +Subject: [PATCH 1/9] Clean up file unpack iteration logic a bit + +Handle rpmfiNext() in the while-condition directly to make it more like +similar other constructs elsewhere, adjust for the end of iteration +code after the loop. Also take the file index from rpmfiNext() so +we don't need multiple calls to rpmfiFX() later. +--- + lib/fsm.c | 19 +++++++------------ + 1 file changed, 7 insertions(+), 12 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 35dcda081c..7c291adb02 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -841,6 +841,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct stat sb; + int saveerrno = errno; + int rc = 0; ++ int fx = -1; + int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0; + int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0; + int firsthardlink = -1; +@@ -862,17 +863,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Detect and create directories not explicitly in package. */ + rc = fsmMkdirs(files, fs, plugins); + +- while (!rc) { +- /* Read next payload header. */ +- rc = rpmfiNext(fi); +- +- if (rc < 0) { +- if (rc == RPMERR_ITER_END) +- rc = 0; +- break; +- } +- +- action = rpmfsGetAction(fs, rpmfiFX(fi)); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ action = rpmfsGetAction(fs, fx); + skip = XFA_SKIPPING(action); + if (action != FA_TOUCH) { + suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; +@@ -896,7 +888,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + if (rc) { + skip = 1; + } else { +- setFileState(fs, rpmfiFX(fi)); ++ setFileState(fs, fx); + } + + if (!skip) { +@@ -1005,6 +997,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + fpath = _free(fpath); + } + ++ if (!rc && fx != RPMERR_ITER_END) ++ rc = fx; ++ + rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ)); + rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST)); + + +From c0dc57b820791dd76ce8baafac59b9a58ab0f0d3 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 08:25:28 +0200 +Subject: [PATCH 2/9] Drop unused filename variable + +--- + lib/fsm.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 7c291adb02..41b6267ddc 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -959,11 +959,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* On FA_TOUCH no hardlinks are created thus this is skipped. */ + /* we skip the hard linked file containing the content */ + /* write the content to the first used instead */ +- char *fn = rpmfilesFN(files, firsthardlink); + rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm); + wfd_close(&firstlinkfile); + firsthardlink = -1; +- free(fn); + } + + if (rc) { + +From dcb5791066afd5caa1003a5d35903a7f5dc79cc2 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 09:57:17 +0200 +Subject: [PATCH 3/9] Don't update path info if rename failed on file commit + +--- + lib/fsm.c | 16 +++++++++------- + 1 file changed, 9 insertions(+), 7 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 41b6267ddc..c581a918a5 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -773,14 +773,16 @@ static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *su + /* Rename temporary to final file name if needed. */ + if (dest != *path) { + rc = fsmRename(*path, dest); +- if (!rc && nsuffix) { +- char * opath = fsmFsPath(fi, NULL); +- rpmlog(RPMLOG_WARNING, _("%s created as %s\n"), +- opath, dest); +- free(opath); ++ if (!rc) { ++ if (nsuffix) { ++ char * opath = fsmFsPath(fi, NULL); ++ rpmlog(RPMLOG_WARNING, _("%s created as %s\n"), ++ opath, dest); ++ free(opath); ++ } ++ free(*path); ++ *path = dest; + } +- free(*path); +- *path = dest; + } + } + + +From 7fdf248e7d29a244b97c46b19be0df64992fdfae Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 09:47:19 +0200 +Subject: [PATCH 4/9] Refactor file install and remove around a common struct + +Collect the common state info into a struct shared by both file install +and remove, update code accordingly. The change looks much more drastic +than it is - it's just adding fp-> prefix to a lot of places. +While we're at it, remember the state data throughout the operation. + +No functional changes here, just paving way for the next steps which +will look clearer with these pre-requisites in place. +--- + lib/fsm.c | 158 +++++++++++++++++++++++++++++------------------------- + 1 file changed, 85 insertions(+), 73 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index c581a918a5..9dba30560f 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -38,6 +38,14 @@ static int strict_erasures = 0; + #define _dirPerms 0755 + #define _filePerms 0644 + ++struct filedata_s { ++ int skip; ++ rpmFileAction action; ++ const char *suffix; ++ char *fpath; ++ struct stat sb; ++}; ++ + /* + * XXX Forward declarations for previously exported functions to avoid moving + * things around needlessly +@@ -840,19 +848,16 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); + rpmfs fs = rpmteGetFileStates(te); + rpmPlugins plugins = rpmtsPlugins(ts); +- struct stat sb; + int saveerrno = errno; + int rc = 0; + int fx = -1; ++ int fc = rpmfilesFC(files); + int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0; + int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0; + int firsthardlink = -1; + FD_t firstlinkfile = NULL; +- int skip; +- rpmFileAction action; + char *tid = NULL; +- const char *suffix; +- char *fpath = NULL; ++ struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); + + if (fi == NULL) { + rc = RPMERR_BAD_MAGIC; +@@ -866,96 +871,99 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rc = fsmMkdirs(files, fs, plugins); + + while (!rc && (fx = rpmfiNext(fi)) >= 0) { +- action = rpmfsGetAction(fs, fx); +- skip = XFA_SKIPPING(action); +- if (action != FA_TOUCH) { +- suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; ++ struct filedata_s *fp = &fdata[fx]; ++ fp->action = rpmfsGetAction(fs, fx); ++ fp->skip = XFA_SKIPPING(fp->action); ++ if (fp->action != FA_TOUCH) { ++ fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; + } else { +- suffix = NULL; ++ fp->suffix = NULL; + } +- fpath = fsmFsPath(fi, suffix); ++ fp->fpath = fsmFsPath(fi, fp->suffix); + + /* Remap file perms, owner, and group. */ +- rc = rpmfiStat(fi, 1, &sb); ++ rc = rpmfiStat(fi, 1, &fp->sb); + +- fsmDebug(fpath, action, &sb); ++ fsmDebug(fp->fpath, fp->action, &fp->sb); + + /* Exit on error. */ + if (rc) + break; + + /* Run fsm file pre hook for all plugins */ +- rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath, +- sb.st_mode, action); ++ rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action); + if (rc) { +- skip = 1; ++ fp->skip = 1; + } else { + setFileState(fs, fx); + } + +- if (!skip) { ++ if (!fp->skip) { + int setmeta = 1; + + /* Directories replacing something need early backup */ +- if (!suffix) { +- rc = fsmBackup(fi, action); ++ if (!fp->suffix) { ++ rc = fsmBackup(fi, fp->action); + } + /* Assume file does't exist when tmp suffix is in use */ +- if (!suffix) { +- rc = fsmVerify(fpath, fi); ++ if (!fp->suffix) { ++ rc = fsmVerify(fp->fpath, fi); + } else { + rc = RPMERR_ENOENT; + } + + /* See if the file was removed while our attention was elsewhere */ +- if (rc == RPMERR_ENOENT && action == FA_TOUCH) { +- rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n", fpath); +- action = FA_CREATE; +- fsmDebug(fpath, action, &sb); ++ if (rc == RPMERR_ENOENT && fp->action == FA_TOUCH) { ++ rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n", ++ fp->fpath); ++ fp->action = FA_CREATE; ++ fsmDebug(fp->fpath, fp->action, &fp->sb); + } + + /* When touching we don't need any of this... */ +- if (action == FA_TOUCH) ++ if (fp->action == FA_TOUCH) + goto touch; + +- if (S_ISREG(sb.st_mode)) { ++ if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- rc = fsmMkfile(fi, fpath, files, psm, nodigest, ++ rc = fsmMkfile(fi, fp->fpath, files, psm, nodigest, + &setmeta, &firsthardlink, &firstlinkfile); + } +- } else if (S_ISDIR(sb.st_mode)) { ++ } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- mode_t mode = sb.st_mode; ++ mode_t mode = fp->sb.st_mode; + mode &= ~07777; + mode |= 00700; +- rc = fsmMkdir(fpath, mode); ++ rc = fsmMkdir(fp->fpath, mode); + } +- } else if (S_ISLNK(sb.st_mode)) { ++ } else if (S_ISLNK(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- rc = fsmSymlink(rpmfiFLink(fi), fpath); ++ rc = fsmSymlink(rpmfiFLink(fi), fp->fpath); + } +- } else if (S_ISFIFO(sb.st_mode)) { ++ } else if (S_ISFIFO(fp->sb.st_mode)) { + /* This mimics cpio S_ISSOCK() behavior but probably isn't right */ + if (rc == RPMERR_ENOENT) { +- rc = fsmMkfifo(fpath, 0000); ++ rc = fsmMkfifo(fp->fpath, 0000); + } +- } else if (S_ISCHR(sb.st_mode) || +- S_ISBLK(sb.st_mode) || +- S_ISSOCK(sb.st_mode)) ++ } else if (S_ISCHR(fp->sb.st_mode) || ++ S_ISBLK(fp->sb.st_mode) || ++ S_ISSOCK(fp->sb.st_mode)) + { + if (rc == RPMERR_ENOENT) { +- rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev); ++ rc = fsmMknod(fp->fpath, fp->sb.st_mode, fp->sb.st_rdev); + } + } else { + /* XXX Special case /dev/log, which shouldn't be packaged anyways */ +- if (!IS_DEV_LOG(fpath)) ++ if (!IS_DEV_LOG(fp->fpath)) + rc = RPMERR_UNKNOWN_FILETYPE; + } + + touch: + /* Set permissions, timestamps etc for non-hardlink entries */ + if (!rc && setmeta) { +- rc = fsmSetmeta(fpath, fi, plugins, action, &sb, nofcaps); ++ rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, ++ &fp->sb, nofcaps); + } + } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) { + /* On FA_TOUCH no hardlinks are created thus this is skipped. */ +@@ -967,10 +975,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + } + + if (rc) { +- if (!skip) { ++ if (!fp->skip) { + /* XXX only erase if temp fn w suffix is in use */ +- if (suffix) { +- (void) fsmRemove(fpath, sb.st_mode); ++ if (fp->suffix) { ++ (void) fsmRemove(fp->fpath, fp->sb.st_mode); + } + errno = saveerrno; + } +@@ -978,23 +986,22 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Notify on success. */ + rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi)); + +- if (!skip) { ++ if (!fp->skip) { + /* Backup file if needed. Directories are handled earlier */ +- if (suffix) +- rc = fsmBackup(fi, action); ++ if (fp->suffix) ++ rc = fsmBackup(fi, fp->action); + + if (!rc) +- rc = fsmCommit(&fpath, fi, action, suffix); ++ rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix); + } + } + + if (rc) +- *failedFile = xstrdup(fpath); ++ *failedFile = xstrdup(fp->fpath); + + /* Run fsm file post hook for all plugins */ +- rpmpluginsCallFsmFilePost(plugins, fi, fpath, +- sb.st_mode, action, rc); +- fpath = _free(fpath); ++ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action, rc); + } + + if (!rc && fx != RPMERR_ITER_END) +@@ -1010,7 +1017,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rpmfiFree(fi); + Fclose(payload); + free(tid); +- free(fpath); ++ for (int i = 0; i < fc; i++) ++ free(fdata[i].fpath); ++ free(fdata); + + return rc; + } +@@ -1022,29 +1031,31 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, + rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK); + rpmfs fs = rpmteGetFileStates(te); + rpmPlugins plugins = rpmtsPlugins(ts); +- struct stat sb; ++ int fc = rpmfilesFC(files); ++ int fx = -1; ++ struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); + int rc = 0; +- char *fpath = NULL; + +- while (!rc && rpmfiNext(fi) >= 0) { +- rpmFileAction action = rpmfsGetAction(fs, rpmfiFX(fi)); +- fpath = fsmFsPath(fi, NULL); +- rc = fsmStat(fpath, 1, &sb); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ fp->action = rpmfsGetAction(fs, rpmfiFX(fi)); ++ fp->fpath = fsmFsPath(fi, NULL); ++ rc = fsmStat(fp->fpath, 1, &fp->sb); + +- fsmDebug(fpath, action, &sb); ++ fsmDebug(fp->fpath, fp->action, &fp->sb); + + /* Run fsm file pre hook for all plugins */ +- rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath, +- sb.st_mode, action); ++ rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action); + +- if (!XFA_SKIPPING(action)) +- rc = fsmBackup(fi, action); ++ if (!XFA_SKIPPING(fp->action)) ++ rc = fsmBackup(fi, fp->action); + + /* Remove erased files. */ +- if (action == FA_ERASE) { ++ if (fp->action == FA_ERASE) { + int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST)); + +- rc = fsmRemove(fpath, sb.st_mode); ++ rc = fsmRemove(fp->fpath, fp->sb.st_mode); + + /* + * Missing %ghost or %missingok entries are not errors. +@@ -1069,20 +1080,20 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, + if (rc) { + int lvl = strict_erasures ? RPMLOG_ERR : RPMLOG_WARNING; + rpmlog(lvl, _("%s %s: remove failed: %s\n"), +- S_ISDIR(sb.st_mode) ? _("directory") : _("file"), +- fpath, strerror(errno)); ++ S_ISDIR(fp->sb.st_mode) ? _("directory") : _("file"), ++ fp->fpath, strerror(errno)); + } + } + + /* Run fsm file post hook for all plugins */ +- rpmpluginsCallFsmFilePost(plugins, fi, fpath, +- sb.st_mode, action, rc); ++ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action, rc); + + /* XXX Failure to remove is not (yet) cause for failure. */ + if (!strict_erasures) rc = 0; + + if (rc) +- *failedFile = xstrdup(fpath); ++ *failedFile = xstrdup(fp->fpath); + + if (rc == 0) { + /* Notify on success. */ +@@ -1090,10 +1101,11 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, + rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi); + rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount); + } +- fpath = _free(fpath); + } + +- free(fpath); ++ for (int i = 0; i < fc; i++) ++ free(fdata[i].fpath); ++ free(fdata); + rpmfiFree(fi); + + return rc; + +From 202c9e9cd2e199b7e7c9655704a98ab2d4fad69c Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 10:08:27 +0200 +Subject: [PATCH 5/9] Refactor fsmMkfile() to take advantage of the new state + struct + +Move setmeta into the struct too (we'll want this later anyhow), +and now we only need to pass the struct to fsmMkfile(). One less +argument to pass around, it has way too many still. + +No functional changes. +--- + lib/fsm.c | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 9dba30560f..80ca234b1e 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -39,6 +39,7 @@ static int strict_erasures = 0; + #define _filePerms 0644 + + struct filedata_s { ++ int setmeta; + int skip; + rpmFileAction action; + const char *suffix; +@@ -279,8 +280,8 @@ static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest) + return rc; + } + +-static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files, +- rpmpsm psm, int nodigest, int *setmeta, ++static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files, ++ rpmpsm psm, int nodigest, + int * firsthardlink, FD_t *firstlinkfile) + { + int rc = 0; +@@ -290,11 +291,11 @@ static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files, + /* Create first hardlinked file empty */ + if (*firsthardlink < 0) { + *firsthardlink = rpmfiFX(fi); +- rc = wfd_open(firstlinkfile, dest); ++ rc = wfd_open(firstlinkfile, fp->fpath); + } else { + /* Create hard links for others */ + char *fn = rpmfilesFN(files, *firsthardlink); +- rc = link(fn, dest); ++ rc = link(fn, fp->fpath); + if (rc < 0) { + rc = RPMERR_LINK_FAILED; + } +@@ -305,14 +306,14 @@ static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files, + existing) file with content */ + if (numHardlinks<=1) { + if (!rc) +- rc = expandRegular(fi, dest, psm, nodigest); ++ rc = expandRegular(fi, fp->fpath, psm, nodigest); + } else if (rpmfiArchiveHasContent(fi)) { + if (!rc) + rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm); + wfd_close(firstlinkfile); + *firsthardlink = -1; + } else { +- *setmeta = 0; ++ fp->setmeta = 0; + } + + return rc; +@@ -874,6 +875,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct filedata_s *fp = &fdata[fx]; + fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); ++ fp->setmeta = 1; + if (fp->action != FA_TOUCH) { + fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; + } else { +@@ -900,8 +902,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + } + + if (!fp->skip) { +- int setmeta = 1; +- + /* Directories replacing something need early backup */ + if (!fp->suffix) { + rc = fsmBackup(fi, fp->action); +@@ -927,8 +927,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- rc = fsmMkfile(fi, fp->fpath, files, psm, nodigest, +- &setmeta, &firsthardlink, &firstlinkfile); ++ rc = fsmMkfile(fi, fp, files, psm, nodigest, ++ &firsthardlink, &firstlinkfile); + } + } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -961,7 +961,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + touch: + /* Set permissions, timestamps etc for non-hardlink entries */ +- if (!rc && setmeta) { ++ if (!rc && fp->setmeta) { + rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, + &fp->sb, nofcaps); + } + +From f014fc46325efe92b79841145f8dc0cb40896c64 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 10:24:22 +0200 +Subject: [PATCH 6/9] Clarify file installation temporary suffix rule + +We only use a temporary suffix for regular files that we are actually +creating, skipped and touched files should not have it. Add XFA_CREATING() +macro to accomppany XFA_SKIPPING() to easily check whether the file +is being created or something else. + +No functional changes but makes the logic clearer. +--- + lib/fsm.c | 5 +---- + lib/rpmfiles.h | 3 +++ + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 80ca234b1e..554ea712f5 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -876,11 +876,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); + fp->setmeta = 1; +- if (fp->action != FA_TOUCH) { ++ if (XFA_CREATING(fp->action)) + fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; +- } else { +- fp->suffix = NULL; +- } + fp->fpath = fsmFsPath(fi, fp->suffix); + + /* Remap file perms, owner, and group. */ +diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h +index 7ce1712323..e0adbd8aff 100644 +--- a/lib/rpmfiles.h ++++ b/lib/rpmfiles.h +@@ -121,6 +121,9 @@ typedef enum rpmFileAction_e { + #define XFA_SKIPPING(_a) \ + ((_a) == FA_SKIP || (_a) == FA_SKIPNSTATE || (_a) == FA_SKIPNETSHARED || (_a) == FA_SKIPCOLOR) + ++#define XFA_CREATING(_a) \ ++ ((_a) == FA_CREATE || (_a) == FA_BACKUP || (_a) == FA_SAVE || (_a) == FA_ALTNAME) ++ + /** + * We pass these around as an array with a sentinel. + */ + +From fc54439f8a10901cd72028cdbdb924e3e7501624 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 10:24:22 +0200 +Subject: [PATCH 7/9] Clarify file installation temporary suffix rule + +We only use a temporary suffix for regular files that we are actually +creating, skipped and touched files should not have it. Add XFA_CREATING() +macro to accomppany XFA_SKIPPING() to easily check whether the file +is being created or something else. + +No functional changes but makes the logic clearer. +--- + lib/fsm.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 554ea712f5..094f5e2bb6 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -876,8 +876,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); + fp->setmeta = 1; +- if (XFA_CREATING(fp->action)) +- fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; ++ if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi))) ++ fp->suffix = tid; + fp->fpath = fsmFsPath(fi, fp->suffix); + + /* Remap file perms, owner, and group. */ + +From 9294ffa898bc7f1f77ff80c0a3f4f0f70c4dfef2 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 11:25:10 +0200 +Subject: [PATCH 8/9] Handle hardlink tracking with a file state pointer + +No functional changes, just makes it a little cleaner as firstlink now +points to the actual file data instead of a index number somewhere. +--- + lib/fsm.c | 20 +++++++++----------- + 1 file changed, 9 insertions(+), 11 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 094f5e2bb6..f86383a986 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -282,24 +282,22 @@ static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest) + + static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files, + rpmpsm psm, int nodigest, +- int * firsthardlink, FD_t *firstlinkfile) ++ struct filedata_s ** firstlink, FD_t *firstlinkfile) + { + int rc = 0; + int numHardlinks = rpmfiFNlink(fi); + + if (numHardlinks > 1) { + /* Create first hardlinked file empty */ +- if (*firsthardlink < 0) { +- *firsthardlink = rpmfiFX(fi); ++ if (*firstlink == NULL) { ++ *firstlink = fp; + rc = wfd_open(firstlinkfile, fp->fpath); + } else { + /* Create hard links for others */ +- char *fn = rpmfilesFN(files, *firsthardlink); +- rc = link(fn, fp->fpath); ++ rc = link((*firstlink)->fpath, fp->fpath); + if (rc < 0) { + rc = RPMERR_LINK_FAILED; + } +- free(fn); + } + } + /* Write normal files or fill the last hardlinked (already +@@ -311,7 +309,7 @@ static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files, + if (!rc) + rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm); + wfd_close(firstlinkfile); +- *firsthardlink = -1; ++ *firstlink = NULL; + } else { + fp->setmeta = 0; + } +@@ -855,10 +853,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + int fc = rpmfilesFC(files); + int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0; + int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0; +- int firsthardlink = -1; + FD_t firstlinkfile = NULL; + char *tid = NULL; + struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); ++ struct filedata_s *firstlink = NULL; + + if (fi == NULL) { + rc = RPMERR_BAD_MAGIC; +@@ -925,7 +923,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { + rc = fsmMkfile(fi, fp, files, psm, nodigest, +- &firsthardlink, &firstlinkfile); ++ &firstlink, &firstlinkfile); + } + } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -962,13 +960,13 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, + &fp->sb, nofcaps); + } +- } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) { ++ } else if (firstlink && rpmfiArchiveHasContent(fi)) { + /* On FA_TOUCH no hardlinks are created thus this is skipped. */ + /* we skip the hard linked file containing the content */ + /* write the content to the first used instead */ + rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm); + wfd_close(&firstlinkfile); +- firsthardlink = -1; ++ firstlink = NULL; + } + + if (rc) { + +From d6a9a00396a89d14858c6b6e2548eca2065c1d64 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 14:15:33 +0200 +Subject: [PATCH 9/9] Handle file install failures more gracefully + +Run the file installation in multiple stages: +1) gather intel +2) unpack the archive to temporary files +3) set file metadatas +4) commit files to final location +5) mop up leftovers on failure + +This means we no longer leave behind a trail of untracked, potentially +harmful junk on installation failure. + +If commit step fails the package can still be left in an inconsistent stage, +this could be further improved by first backing up old files to temporary +location to allow undo on failure, but leaving that for some other day. +Also unowned directories will still be left behind. + +And yes, this is a somewhat scary change as it's the biggest ever change +to how rpm lays down files on install. Adopt the hardlink test spec +over to install tests and add some more tests for the new behavior. + +Fixes: #967 (+ multiple reports over the years) +--- + lib/fsm.c | 147 ++++++++++++++++++++------------ + tests/data/SPECS/hlinktest.spec | 4 + + tests/rpmbuild.at | 32 ------- + tests/rpmi.at | 92 ++++++++++++++++++++ + 4 files changed, 189 insertions(+), 86 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index f86383a986..6efd25bddd 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -38,7 +38,17 @@ static int strict_erasures = 0; + #define _dirPerms 0755 + #define _filePerms 0644 + ++enum filestage_e { ++ FILE_COMMIT = -1, ++ FILE_NONE = 0, ++ FILE_PRE = 1, ++ FILE_UNPACK = 2, ++ FILE_PREP = 3, ++ FILE_POST = 4, ++}; ++ + struct filedata_s { ++ int stage; + int setmeta; + int skip; + rpmFileAction action; +@@ -844,10 +854,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rpmpsm psm, char ** failedFile) + { + FD_t payload = rpmtePayload(te); +- rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); ++ rpmfi fi = NULL; + rpmfs fs = rpmteGetFileStates(te); + rpmPlugins plugins = rpmtsPlugins(ts); +- int saveerrno = errno; + int rc = 0; + int fx = -1; + int fc = rpmfilesFC(files); +@@ -858,20 +867,17 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); + struct filedata_s *firstlink = NULL; + +- if (fi == NULL) { +- rc = RPMERR_BAD_MAGIC; +- goto exit; +- } +- + /* transaction id used for temporary path suffix while installing */ + rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts)); + +- /* Detect and create directories not explicitly in package. */ +- rc = fsmMkdirs(files, fs, plugins); +- ++ /* Collect state data for the whole operation */ ++ fi = rpmfilesIter(files, RPMFI_ITER_FWD); + while (!rc && (fx = rpmfiNext(fi)) >= 0) { + struct filedata_s *fp = &fdata[fx]; +- fp->action = rpmfsGetAction(fs, fx); ++ if (rpmfiFFlags(fi) & RPMFILE_GHOST) ++ fp->action = FA_SKIP; ++ else ++ fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); + fp->setmeta = 1; + if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi))) +@@ -881,20 +887,32 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Remap file perms, owner, and group. */ + rc = rpmfiStat(fi, 1, &fp->sb); + ++ setFileState(fs, fx); + fsmDebug(fp->fpath, fp->action, &fp->sb); + +- /* Exit on error. */ +- if (rc) +- break; +- + /* Run fsm file pre hook for all plugins */ + rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, + fp->sb.st_mode, fp->action); +- if (rc) { +- fp->skip = 1; +- } else { +- setFileState(fs, fx); +- } ++ fp->stage = FILE_PRE; ++ } ++ fi = rpmfiFree(fi); ++ ++ if (rc) ++ goto exit; ++ ++ fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); ++ if (fi == NULL) { ++ rc = RPMERR_BAD_MAGIC; ++ goto exit; ++ } ++ ++ /* Detect and create directories not explicitly in package. */ ++ if (!rc) ++ rc = fsmMkdirs(files, fs, plugins); ++ ++ /* Process the payload */ ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; + + if (!fp->skip) { + /* Directories replacing something need early backup */ +@@ -918,7 +936,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + /* When touching we don't need any of this... */ + if (fp->action == FA_TOUCH) +- goto touch; ++ continue; + + if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -954,12 +972,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rc = RPMERR_UNKNOWN_FILETYPE; + } + +-touch: +- /* Set permissions, timestamps etc for non-hardlink entries */ +- if (!rc && fp->setmeta) { +- rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, +- &fp->sb, nofcaps); +- } + } else if (firstlink && rpmfiArchiveHasContent(fi)) { + /* On FA_TOUCH no hardlinks are created thus this is skipped. */ + /* we skip the hard linked file containing the content */ +@@ -969,47 +981,74 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + firstlink = NULL; + } + +- if (rc) { +- if (!fp->skip) { +- /* XXX only erase if temp fn w suffix is in use */ +- if (fp->suffix) { +- (void) fsmRemove(fp->fpath, fp->sb.st_mode); +- } +- errno = saveerrno; +- } +- } else { +- /* Notify on success. */ ++ /* Notify on success. */ ++ if (rc) ++ *failedFile = xstrdup(fp->fpath); ++ else + rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi)); ++ fp->stage = FILE_UNPACK; ++ } ++ fi = rpmfiFree(fi); + +- if (!fp->skip) { +- /* Backup file if needed. Directories are handled earlier */ +- if (fp->suffix) +- rc = fsmBackup(fi, fp->action); ++ if (!rc && fx < 0 && fx != RPMERR_ITER_END) ++ rc = fx; + +- if (!rc) +- rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix); +- } ++ /* Set permissions, timestamps etc for non-hardlink entries */ ++ fi = rpmfilesIter(files, RPMFI_ITER_FWD); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ if (!fp->skip && fp->setmeta) { ++ rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, ++ &fp->sb, nofcaps); + } +- + if (rc) + *failedFile = xstrdup(fp->fpath); ++ fp->stage = FILE_PREP; ++ } ++ fi = rpmfiFree(fi); + +- /* Run fsm file post hook for all plugins */ +- rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, +- fp->sb.st_mode, fp->action, rc); ++ /* If all went well, commit files to final destination */ ++ fi = rpmfilesIter(files, RPMFI_ITER_FWD); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ ++ if (!fp->skip) { ++ /* Backup file if needed. Directories are handled earlier */ ++ if (!rc && fp->suffix) ++ rc = fsmBackup(fi, fp->action); ++ ++ if (!rc) ++ rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix); ++ ++ if (!rc) ++ fp->stage = FILE_COMMIT; ++ else ++ *failedFile = xstrdup(fp->fpath); ++ } + } ++ fi = rpmfiFree(fi); + +- if (!rc && fx != RPMERR_ITER_END) +- rc = fx; ++ /* Walk backwards in case we need to erase */ ++ fi = rpmfilesIter(files, RPMFI_ITER_BACK); ++ while ((fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ /* Run fsm file post hook for all plugins for all processed files */ ++ if (fp->stage) { ++ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action, rc); ++ } ++ ++ /* On failure, erase non-committed files */ ++ if (rc && fp->stage > FILE_NONE && !fp->skip) { ++ (void) fsmRemove(fp->fpath, fp->sb.st_mode); ++ } ++ } + + rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ)); + rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST)); + + exit: +- +- /* No need to bother with close errors on read */ +- rpmfiArchiveClose(fi); +- rpmfiFree(fi); ++ fi = rpmfiFree(fi); + Fclose(payload); + free(tid); + for (int i = 0; i < fc; i++) +diff --git a/tests/data/SPECS/hlinktest.spec b/tests/data/SPECS/hlinktest.spec +index 753c174cd8..3f1437d89c 100644 +--- a/tests/data/SPECS/hlinktest.spec ++++ b/tests/data/SPECS/hlinktest.spec +@@ -1,5 +1,6 @@ + %bcond_with unpackaged_dirs + %bcond_with unpackaged_files ++%bcond_with owned_dir + + Summary: Testing hard link behavior + Name: hlinktest +@@ -43,4 +44,7 @@ touch $RPM_BUILD_ROOT/teet + + %files + %defattr(-,root,root) ++%if %{with owned_dir} ++%dir /foo ++%endif + /foo/* +diff --git a/tests/rpmbuild.at b/tests/rpmbuild.at +index 1f0e679c44..f0eef4eff0 100644 +--- a/tests/rpmbuild.at ++++ b/tests/rpmbuild.at +@@ -253,38 +253,6 @@ drwxrwxrwx zoot zoot /j/dir + []) + AT_CLEANUP + +-# ------------------------------ +-# hardlink tests +-AT_SETUP([rpmbuild hardlink]) +-AT_KEYWORDS([build]) +-RPMDB_INIT +-AT_CHECK([ +-RPMDB_INIT +- +-runroot rpmbuild \ +- -bb --quiet /data/SPECS/hlinktest.spec +- +-runroot rpm -i /build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm +- +-runroot rpm -q --qf "[[%{filenlinks} %{filenames}\n]]%{longsize}\n" hlinktest +-runroot rpm -V --nouser --nogroup hlinktest +-ls -i "${RPMTEST}"/foo/hello* | awk {'print $1'} | sort -u | wc -l +- +-], +-[0], +-[2 /foo/aaaa +-1 /foo/copyllo +-4 /foo/hello +-4 /foo/hello-bar +-4 /foo/hello-foo +-4 /foo/hello-world +-2 /foo/zzzz +-87 +-1 +-], +-[]) +-AT_CLEANUP +- + AT_SETUP([rpmbuild unpackaged files]) + AT_KEYWORDS([build]) + RPMDB_INIT +diff --git a/tests/rpmi.at b/tests/rpmi.at +index 42dc52ba35..295fbc230f 100644 +--- a/tests/rpmi.at ++++ b/tests/rpmi.at +@@ -722,3 +722,95 @@ runroot rpm -V --nouser --nogroup suicidal + [], + []) + AT_CLEANUP ++ ++# ------------------------------ ++# hardlink tests ++AT_SETUP([rpm -i hardlinks]) ++AT_KEYWORDS([build install]) ++RPMDB_INIT ++ ++# Need a reproducable test package ++runroot rpmbuild \ ++ --define "%optflags -O2 -g" \ ++ --define "%_target_platform noarch-linux" \ ++ --define "%_binary_payload w.ufdio" \ ++ --define "%_buildhost localhost" \ ++ --define "%use_source_date_epoch_as_buildtime 1" \ ++ --define "%source_date_epoch_from_changelog 1" \ ++ --define "%clamp_mtime_to_source_date_epoch 1" \ ++ --with owned_dir \ ++ -bb --quiet /data/SPECS/hlinktest.spec ++ ++pkg="/build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm" ++ ++cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/1.rpm" ++dd if=/dev/zero of="${RPMTEST}/tmp/1.rpm" \ ++ conv=notrunc bs=1 seek=8180 count=6 2> /dev/null ++ ++cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/2.rpm" ++dd if=/dev/zero of="${RPMTEST}/tmp/2.rpm" \ ++ conv=notrunc bs=1 seek=8150 count=6 2> /dev/null ++ ++cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/3.rpm" ++dd if=/dev/zero of="${RPMTEST}/tmp/3.rpm" \ ++ conv=notrunc bs=1 seek=8050 count=6 2> /dev/null ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i --noverify /tmp/1.rpm ++# test that nothing of the contents remains after failure ++test -d "${RPMTEST}/foo" ++], ++[1], ++[], ++[error: unpacking of archive failed: cpio: Archive file not in header ++error: hlinktest-1.0-1.noarch: install failed ++]) ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i --noverify /tmp/2.rpm ++# test that nothing of the contents remains after failure ++test -d "${RPMTEST}/foo" ++], ++[1], ++[], ++[error: unpacking of archive failed: cpio: Bad/unreadable header ++error: hlinktest-1.0-1.noarch: install failed ++]) ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i --noverify /tmp/3.rpm 2>&1| sed 's/;.*:/:/g' ++# test that nothing of the contents remains after failure ++test -d "${RPMTEST}/foo" ++], ++[1], ++[error: unpacking of archive failed on file /foo/hello-world: Digest mismatch ++error: hlinktest-1.0-1.noarch: install failed ++], ++[]) ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i /build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm ++runroot rpm -q --qf "[[%{filenlinks} %{filenames}\n]]%{longsize}\n" hlinktest ++ls -i "${RPMTEST}"/foo/hello* | awk {'print $1'} | sort -u | wc -l ++runroot rpm -e hlinktest ++ ++], ++[0], ++[1 /foo ++2 /foo/aaaa ++1 /foo/copyllo ++4 /foo/hello ++4 /foo/hello-bar ++4 /foo/hello-foo ++4 /foo/hello-world ++2 /foo/zzzz ++87 ++1 ++], ++[]) ++AT_CLEANUP ++ diff --git a/measure.patch b/measure.patch new file mode 100644 index 0000000..b0c580f --- /dev/null +++ b/measure.patch @@ -0,0 +1,279 @@ +From 517a37ae7beeb77e2b4870be11611f82c1200b3c Mon Sep 17 00:00:00 2001 +From: Matthew Almond +Date: Thu, 11 Jun 2020 13:01:04 -0700 +Subject: [PATCH 2/2] Measure plugin + +--- + macros.in | 1 + + plugins/Makefile.am | 4 + + plugins/measure.c | 231 ++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 236 insertions(+) + create mode 100644 plugins/measure.c + +diff --git a/macros.in b/macros.in +index 3cc8a3555..c8a087959 100644 +--- a/macros.in ++++ b/macros.in +@@ -1173,6 +1173,7 @@ package or when debugging this package.\ + # Transaction plugin macros + %__plugindir %{_libdir}/rpm-plugins + %__transaction_reflink %{__plugindir}/reflink.so ++%__transaction_measure %{__plugindir}/measure.so + %__transaction_systemd_inhibit %{__plugindir}/systemd_inhibit.so + %__transaction_selinux %{__plugindir}/selinux.so + %__transaction_syslog %{__plugindir}/syslog.so +diff --git a/plugins/Makefile.am b/plugins/Makefile.am +index 06393ce8d..daed6423c 100644 +--- a/plugins/Makefile.am ++++ b/plugins/Makefile.am +@@ -29,6 +29,10 @@ systemd_inhibit_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/ + plugins_LTLIBRARIES += systemd_inhibit.la + endif + ++measure_la_SOURCES = measure.c ++measure_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la ++plugins_LTLIBRARIES += measure.la ++ + prioreset_la_SOURCES = prioreset.c + prioreset_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la + plugins_LTLIBRARIES += prioreset.la +diff --git a/plugins/measure.c b/plugins/measure.c +new file mode 100644 +index 000000000..2cdfc885e +--- /dev/null ++++ b/plugins/measure.c +@@ -0,0 +1,231 @@ ++#include "system.h" ++#include "time.h" ++ ++#include ++#include ++#include ++#include "lib/rpmlib.h" ++ ++#include "lib/rpmplugin.h" ++ ++struct measurestat { ++ /* We're counting psm not packages because packages often run psm_pre/post ++ more than once and we want to accumulate the time ++ */ ++ unsigned int psm_count; ++ unsigned int scriptlet_count; ++ struct timespec plugin_start; ++ struct timespec psm_start; ++ struct timespec scriptlet_start; ++}; ++ ++static rpmRC push(const char *format, const char *value, const char *formatted) ++{ ++ char *key = NULL; ++ rpmRC rc = RPMRC_FAIL; ++ if (formatted == NULL) { ++ /* yes we're okay with discarding const here */ ++ key = (char *)format; ++ } else { ++ if (rasprintf(&key, format, formatted) == -1) { ++ rpmlog( ++ RPMLOG_ERR, ++ _("measure: Failed to allocate formatted key %s, %s\n"), ++ format, ++ formatted ++ ); ++ goto exit; ++ } ++ } ++ if (rpmPushMacro(NULL, key, NULL, value, RMIL_GLOBAL)) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to set %s\n"), key); ++ goto exit; ++ } ++ rc = RPMRC_OK; ++exit: ++ if (formatted != NULL) { ++ free(key); ++ } ++ return rc; ++} ++ ++static rpmRC diff_ms(char **ms, struct timespec *start, struct timespec *end) ++{ ++ if (rasprintf(ms, "%ld", ( ++ (end->tv_sec - start->tv_sec) * 1000 + ++ (end->tv_nsec - start->tv_nsec) / 1000000 ++ )) == -1) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted ms\n")); ++ return RPMRC_FAIL; ++ } ++ return RPMRC_OK; ++} ++ ++static rpmRC measure_init(rpmPlugin plugin, rpmts ts) ++{ ++ rpmRC rc = RPMRC_FAIL; ++ struct measurestat *state = rcalloc(1, sizeof(*state)); ++ if (clock_gettime(CLOCK_MONOTONIC, &state->plugin_start)) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to get plugin_start time\n")); ++ goto exit; ++ } ++ state->psm_count = 0; ++ state->scriptlet_count = 0; ++ rpmPluginSetData(plugin, state); ++ rc = RPMRC_OK; ++exit: ++ return rc; ++} ++ ++static void measure_cleanup(rpmPlugin plugin) ++{ ++ struct measurestat *state = rpmPluginGetData(plugin); ++ free(state); ++} ++ ++static rpmRC measure_tsm_post(rpmPlugin plugin, rpmts ts, int res) ++{ ++ struct measurestat *state = rpmPluginGetData(plugin); ++ char *psm_count = NULL, *scriptlet_count = NULL; ++ rpmRC rc = RPMRC_FAIL; ++ ++ if (rasprintf(&psm_count, "%d", state->psm_count) == -1) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted psm_count\n")); ++ goto exit; ++ } ++ if (rasprintf(&scriptlet_count, "%d", state->scriptlet_count) == -1) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted scriptlet_count\n")); ++ goto exit; ++ } ++ if (push("_measure_plugin_psm_count", psm_count, NULL) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("_measure_plugin_scriptlet_count", scriptlet_count, NULL) != RPMRC_OK) { ++ goto exit; ++ } ++ rc = RPMRC_OK; ++exit: ++ free(psm_count); ++ free(scriptlet_count); ++ return rc; ++} ++ ++static rpmRC measure_psm_pre(rpmPlugin plugin, rpmte te) ++{ ++ struct measurestat *state = rpmPluginGetData(plugin); ++ rpmRC rc = RPMRC_FAIL; ++ ++ if (clock_gettime(CLOCK_MONOTONIC, &state->psm_start)) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to get psm_start time\n")); ++ goto exit; ++ } ++ rc = RPMRC_OK; ++exit: ++ return rc; ++} ++ ++static rpmRC measure_psm_post(rpmPlugin plugin, rpmte te, int res) ++{ ++ struct measurestat *state = rpmPluginGetData(plugin); ++ struct timespec end; ++ char *offset = NULL, *duration = NULL, *prefix = NULL; ++ Header h = rpmteHeader(te); ++ rpmRC rc = RPMRC_FAIL; ++ ++ if (clock_gettime(CLOCK_MONOTONIC, &end)) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to get psm end time\n")); ++ goto exit; ++ } ++ if (rasprintf(&prefix, "_measure_plugin_package_%u", state->psm_count) == -1) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to allocate prefix\n")); ++ goto exit; ++ } ++ if (diff_ms(&offset, &state->plugin_start, &state->psm_start) != RPMRC_OK) { ++ goto exit; ++ } ++ if (diff_ms(&duration, &state->psm_start, &end) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("%s_nevra", rpmteNEVRA(te), prefix) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("%s_compressor", headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR), prefix) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("%s_offset", offset, prefix) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("%s_ms", duration, prefix) != RPMRC_OK) { ++ goto exit; ++ } ++ state->psm_count += 1; ++ rc = RPMRC_OK; ++exit: ++ headerFree(h); ++ free(prefix); ++ free(duration); ++ free(offset); ++ return rc; ++} ++ ++static rpmRC measure_scriptlet_pre(rpmPlugin plugin, ++ const char *s_name, int type) ++{ ++ struct measurestat *state = rpmPluginGetData(plugin); ++ if (clock_gettime(CLOCK_MONOTONIC, &state->scriptlet_start)) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to get scriptlet_start time\n")); ++ return RPMRC_FAIL; ++ } ++ return RPMRC_OK; ++} ++ ++static rpmRC measure_scriptlet_post(rpmPlugin plugin, ++ const char *s_name, int type, int res) ++{ ++ struct measurestat *state = rpmPluginGetData(plugin); ++ struct timespec end; ++ char *offset = NULL, *duration = NULL, *prefix = NULL; ++ rpmRC rc = RPMRC_FAIL; ++ ++ if (clock_gettime(CLOCK_MONOTONIC, &end)) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to get end time\n")); ++ goto exit; ++ } ++ ++ if (rasprintf(&prefix, "_measure_plugin_scriptlet_%d", state->scriptlet_count) == -1) { ++ rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted prefix\n")); ++ goto exit; ++ } ++ if (diff_ms(&offset, &state->plugin_start, &state->scriptlet_start) != RPMRC_OK) { ++ goto exit; ++ } ++ if (diff_ms(&duration, &state->scriptlet_start, &end) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("%s_name", s_name, prefix) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("%s_offset", offset, prefix) != RPMRC_OK) { ++ goto exit; ++ } ++ if (push("%s_ms", duration, prefix) != RPMRC_OK) { ++ goto exit; ++ } ++ state->scriptlet_count += 1; ++ rc = RPMRC_OK; ++exit: ++ free(prefix); ++ free(duration); ++ free(offset); ++ return rc; ++} ++ ++struct rpmPluginHooks_s measure_hooks = { ++ .init = measure_init, ++ .cleanup = measure_cleanup, ++ .tsm_post = measure_tsm_post, ++ .psm_pre = measure_psm_pre, ++ .psm_post = measure_psm_post, ++ .scriptlet_pre = measure_scriptlet_pre, ++ .scriptlet_post = measure_scriptlet_post, ++}; +-- +2.24.1 + diff --git a/rpm.spec b/rpm.spec index fe87fbd..612be83 100644 --- a/rpm.spec +++ b/rpm.spec @@ -15,6 +15,16 @@ %bcond_without libarchive # build with libimaevm.so %bcond_without libimaevm +%ifarch aarch64 ppc64le +# no fsverity on RHEL based aarch64 or ppc64le +# due to PAGESIZE == 64k +# https://pagure.io/centos-sig-hyperscale/package-bugs/issue/8 +# build without libfsverity.so +%bcond_with libfsverity +%else +# build with libfsverity.so +%bcond_without libfsverity +%endif # build with zstd support? %bcond_without zstd # build with ndb backend? @@ -32,7 +42,7 @@ %global rpmver 4.16.1.3 #global snapver rc1 -%global rel 11.1 +%global rel 11.3 %global sover 9 %global srcver %{rpmver}%{?snapver:-%{snapver}} @@ -105,6 +115,68 @@ Patch916: 0006-debugedit-Handle-DWARF-5-debug_line-and-debug_line_s.patch # Downstream-only patches Patch1000: rpm-4.16.1.3-hashtab-use-after-free-fix.patch +# fsverity support +%if %{with libfsverity} +Patch1964: 0015-rpmsign-RPMSIGN_FLAG_IMA-is-already-set.patch +Patch1965: 0016-Add-basic-autoconf-and-framework-for-fsverity-suppor.patch +Patch1966: 0017-rpmsign-Add-helper-to-indicate-file-signing-enabled.patch +Patch1967: 0018-rpmsign-Handle-certpath-for-signing-certificate.patch +Patch1968: 0019-Implement-rpmSignVerity.patch +Patch1969: 0020-Introduce-base2bin-a-helper-to-convert-tag-array-of-.patch +Patch1970: 0021-rpmsignverity-Add-verity-signature-headers-to-the-pa.patch +Patch1971: 0022-rpmSignVerity-Generate-signatures-for-files-not-pres.patch +Patch1972: 0023-Process-verity-tag-on-package-read.patch +Patch1973: 0024-Generate-a-zero-length-signature-for-symlinks.patch +Patch1974: 0025-rpmsignverity.c-Clean-up-debug-logging.patch +Patch1975: 0026-fsverity-add-tag-for-fsverity-algorithm.patch +Patch1976: 0027-plugins-fsverity-Install-fsverity-signatures.patch +Patch1977: 0028-fsverity-plugin-Use-tag-for-algorithm.patch +Patch1978: 0029-Add-fsverity-tags-to-rpmgeneral.at.patch +Patch1979: 0030-Add-delfilesign-flag-to-delete-IMA-and-fsverity-file.patch +Patch1980: 0031-Update-man-page-for-rpmsign.patch +Patch1981: 0032-rpmsign-Add-argument-to-specify-algorithm-for-fsveri.patch +Patch1982: 0033-Enable-fsverity-in-CI.patch +Patch1983: 0034-rpmsign-Adopting-PKCS11-opaque-keys-support-in-libfsverity-for-fsverity-signatures.patch +%endif + +Patch9800: 1534.patch + +# Copy-on-Write +Patch9901: 0001-RPM-with-Copy-on-Write.patch +Patch9902: 0002-Remove-use-of-bool-type-for-consistency.patch +Patch9903: 0003-Match-formatting-style-of-existing-code.patch +Patch9904: 0004-Fix-printf-formatting-in-reflink.c.patch +Patch9905: 0005-tests-rpm2extents-Add-basic-tests-for-rpm2extents.patch +Patch9906: 0006-rpm2extents-verify-package-signature-during-transcod.patch +Patch9907: 0007-rpm2extents-write-RC-and-output-to-transcodedfile-me.patch +Patch9908: 0008-rpm2extents-Add-script-to-troubleshoot-transcoded-fi.patch +Patch9909: 0009-rpm2extents-Add-test-verifying-RC-code-and-signature.patch +Patch9910: 0010-rpm2extents-Make-rpmkeys-support-reading-metadata-fr.patch +Patch9911: 0011-rpm2extents-Perform-digest-computation-within-the-va.patch +Patch9912: 0012-rpmextents-Create-an-internal-library-to-make-rpmext.patch +Patch9913: 0013-plugin-add-plugin_fsm_file_install_func-plugin-hook.patch +Patch9914: 0014-fsm-Call-new-rpmpluginsCallFsmFileInstall-in-rpmPack.patch +Patch9915: 0015-reflink-use-reflink_fsm_file_install-hook-instead-of.patch +Patch9916: 0016-test-new-runroot_plugins-function-to-run-command-in-.patch +Patch9917: 0017-test-Add-test-installing-an-RPM-with-reflink-plugin.patch +Patch9918: 0018-plugin-add-rpmpluginsCallFsmFileArchiveReader.patch +Patch9919: 0019-reflink-use-rpmpluginsCallFsmFileArchiveReader-to-pr.patch +Patch9920: 0020-reflink-tests-Can-install-standard-RPM-with-reflink.patch +Patch9921: 0021-tests-Fix-tests-AT_KEYWORDS-usage.patch +Patch9922: 0022-reflink-fix-support-for-hardlinks.patch +Patch9923: 0023-rpm2extents-Improve-logging.patch +Patch9924: 0024-rpm2extents-create-footer-struct-and-helpers.patch +Patch9925: 0025-extents-move-more-functions-helpers-behind-rpmextent.patch +Patch9926: 0026-fix-integer-underflow-in-vfyFDCb.patch +Patch9927: 0027-rpmchecksig-Refactor-rpmpkgVerifySigs-with-custom-ve.patch +Patch9928: 0028-reflink-remove-requirement-for-executable-stack-flag.patch +Patch9929: 0029-extentsVerifySigs-Make-it-optional-to-print-the-sign.patch +Patch9930: 0030-rpmcow-Make-rpm-i-install-package-without-the-need-o.patch +Provides: rpm(pr1470) +Provides: rpm(pr1470_1) + +Patch9999: measure.patch + # Partially GPL/LGPL dual-licensed and some bits with BSD # SourceLicense: (GPLv2+ and LGPLv2+ with exceptions) and BSD License: GPLv2+ @@ -166,6 +238,10 @@ BuildRequires: audit-libs-devel BuildRequires: ima-evm-utils-devel >= 1.0 %endif +%if %{with libfsverity} +BuildRequires: fsverity-utils-devel >= 1.4 +%endif + # For the rpmdb migration scriptlet (#2055033) Requires(pre): coreutils Requires(pre): findutils @@ -318,6 +394,17 @@ Requires: rpm-libs%{_isa} = %{version}-%{release} %description plugin-ima %{summary}. +%if %{with libfsverity} +%package plugin-fsverity +Summary: Rpm plugin fsverity file signatures +Group: System Environment/Base +Requires: rpm-libs%{_isa} = %{version}-%{release} + +%description plugin-fsverity +%{summary} + +%endif + %package plugin-fapolicyd Summary: Rpm plugin for fapolicyd functionality Requires: rpm-libs%{_isa} = %{version}-%{release} @@ -327,6 +414,13 @@ Obsoletes: fapolicyd-dnf-plugin %description plugin-fapolicyd %{summary}. +%package plugin-reflink +Summary: Rpm plugin for reflink functionality +Requires: rpm-libs%{_isa} = %{version}-%{release} + +%description plugin-reflink +%{summary}. + %package plugin-prioreset Summary: Rpm plugin for resetting scriptlet priorities for SysV init Requires: rpm-libs%{_isa} = %{version}-%{release} @@ -344,6 +438,14 @@ Requires: rpm-libs%{_isa} = %{version}-%{release} %description plugin-audit %{summary}. +%package plugin-measure +Summary: Rpm plugin for measure +Group: System Environment/Base +Requires: rpm-libs%{_isa} = %{version}-%{release} + +%description plugin-measure +Adds measure support + # with plugins %endif @@ -390,6 +492,7 @@ done; %{?with_ndb: --enable-ndb} \ %{!?with_libarchive: --without-archive} \ %{?with_libimaevm: --with-imaevm} \ + %{?with_libfsverity: --with-fsverity} \ %{?with_zstd: --enable-zstd} \ %{?with_sqlite: --enable-sqlite} \ %{?with_bdb_ro: --enable-bdb-ro} \ @@ -467,7 +570,7 @@ if [ -d /var/lib/rpm ]; then done fi -%triggerun -- rpm < 4.16.1.3-11.1 +%triggerun -- rpm < 4.16.1.3-11.3 # Handle rpmdb migrate service on erasure of old to avoid ordering issues if [ -x /usr/bin/systemctl ]; then systemctl --no-reload preset rpmdb-migrate ||: @@ -549,6 +652,11 @@ fi %if %{with plugins} %dir %{_libdir}/rpm-plugins +%if %{with libfsverity} +%files plugin-fsverity +%{_libdir}/rpm-plugins/fsverity.so +%endif + %files plugin-syslog %{_libdir}/rpm-plugins/syslog.so %{_mandir}/man8/rpm-plugin-syslog.8* @@ -569,6 +677,11 @@ fi %{_libdir}/rpm-plugins/fapolicyd.so %{_mandir}/man8/rpm-plugin-fapolicyd.8* +%files plugin-reflink +%{_bindir}/rpm2extents +%{_bindir}/rpm2extents_dump +%{_libdir}/rpm-plugins/reflink.so + %files plugin-prioreset %{_libdir}/rpm-plugins/prioreset.so %{_mandir}/man8/rpm-plugin-prioreset.8* @@ -576,6 +689,9 @@ fi %files plugin-audit %{_libdir}/rpm-plugins/audit.so %{_mandir}/man8/rpm-plugin-audit.8* + +%files plugin-measure +%{_libdir}/rpm-plugins/measure.so # with plugins %endif @@ -635,10 +751,21 @@ fi %doc doc/librpm/html/* %changelog -* Thu Mar 03 2022 Neal Gompa - 4.16.1.3-11.1 +* Sun Apr 03 2022 Neal Gompa - 4.16.1.3-11.3 - Migrate rpmdb to /usr/lib/sysimage/rpm https://fedoraproject.org/wiki/Changes/RelocateRPMToUsr +* Tue Mar 29 2022 Manu Bretelle - 4.16.1.3-11.2 +- Make `rpm -i` work without `--nodigest` +- Remove need of executable stack for reflink plugin +- Split RPM CoW diffs in individual patches + +* Fri Feb 25 2022 Manu Bretelle - 4.16.1.3-11.1 +- Added fsverity backport +- Added measure plugin support +- Apply GH1534 in preparation of RPM cow patch +- Add support for RPM CoW + * Mon Feb 14 2022 Michal Domonkos - 4.16.1.3-11 - Fix IMA signature lengths assumed constant, take III (#2018937) - Fix regression reading rpm v3 and other rare packages (#2037186)