From 0408f935ff5f3ea410d85c2949e339721d5b11c1 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Nov 02 2023 11:33:09 +0000 Subject: Backport file handling code from rpm-4.19 Fixes CVE-2021-35937, CVE-2021-35938 and CVE-2021-35939 Resolves: RHEL-9561 RHEL-9563 RHEL-9565 --- diff --git a/0001-Add-optional-callback-on-directory-changes-during-rp.patch b/0001-Add-optional-callback-on-directory-changes-during-rp.patch new file mode 100644 index 0000000..bda7110 --- /dev/null +++ b/0001-Add-optional-callback-on-directory-changes-during-rp.patch @@ -0,0 +1,107 @@ +From 186e0ab025b9ad92d900697f611633a6f6162f3b Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 9 Feb 2022 14:47:14 +0200 +Subject: [PATCH] Add optional callback on directory changes during rpmfi + iteration + +Internal only for now in case we need to fiddle with the API some more, +but no reason this couldn't be made public later. +--- + lib/rpmfi.c | 24 ++++++++++++++++++++---- + lib/rpmfi_internal.h | 17 +++++++++++++++++ + 2 files changed, 37 insertions(+), 4 deletions(-) + +diff --git a/lib/rpmfi.c b/lib/rpmfi.c +index aec8220a3..6c631fdb5 100644 +--- a/lib/rpmfi.c ++++ b/lib/rpmfi.c +@@ -53,6 +53,9 @@ struct rpmfi_s { + int intervalStart; /*!< Start of iterating interval. */ + int intervalEnd; /*!< End of iterating interval. */ + ++ rpmfiChdirCb onChdir; /*!< Callback for directory changes */ ++ void *onChdirData; /*!< Caller private callback data */ ++ + rpmfiles files; /*!< File info set */ + rpmcpio_t archive; /*!< Archive with payload */ + unsigned char * found; /*!< Bit field of files found in the archive */ +@@ -298,11 +301,16 @@ rpm_count_t rpmfiDC(rpmfi fi) + return (fi != NULL ? rpmfilesDC(fi->files) : 0); + } + +-#ifdef NOTYET +-int rpmfiDI(rpmfi fi) ++int rpmfiSetOnChdir(rpmfi fi, rpmfiChdirCb cb, void *data) + { ++ int rc = -1; ++ if (fi != NULL) { ++ fi->onChdir = cb; ++ fi->onChdirData = data; ++ rc = 0; ++ } ++ return rc; + } +-#endif + + int rpmfiFX(rpmfi fi) + { +@@ -314,9 +322,17 @@ int rpmfiSetFX(rpmfi fi, int fx) + int i = -1; + + if (fi != NULL && fx >= 0 && fx < rpmfilesFC(fi->files)) { ++ int dx = fi->j; + i = fi->i; + fi->i = fx; + fi->j = rpmfilesDI(fi->files, fi->i); ++ i = fi->i; ++ ++ if (fi->j != dx && fi->onChdir) { ++ int chrc = fi->onChdir(fi, fi->onChdirData); ++ if (chrc < 0) ++ i = chrc; ++ } + } + return i; + } +@@ -1682,9 +1698,9 @@ static rpmfi initIter(rpmfiles files, int itype, int link) + if (files && itype>=0 && itype<=RPMFILEITERMAX) { + fi = xcalloc(1, sizeof(*fi)); + fi->i = -1; ++ fi->j = -1; + fi->files = link ? rpmfilesLink(files) : files; + fi->next = nextfuncs[itype]; +- fi->i = -1; + if (itype == RPMFI_ITER_BACK) { + fi->i = rpmfilesFC(fi->files); + } else if (itype >=RPMFI_ITER_READ_ARCHIVE +diff --git a/lib/rpmfi_internal.h b/lib/rpmfi_internal.h +index dccc6ccbe..37f1d45f5 100644 +--- a/lib/rpmfi_internal.h ++++ b/lib/rpmfi_internal.h +@@ -13,6 +13,23 @@ + extern "C" { + #endif + ++/** \ingroup rpmfi ++ * Callback on file iterator directory changes ++ * @param fi file info ++ * @param data caller private callback data ++ * @return 0 on success, < 0 on error (to stop iteration) ++ */ ++typedef int (*rpmfiChdirCb)(rpmfi fi, void *data); ++ ++/** \ingroup rpmfi ++ * Set a callback for directory changes during iteration. ++ * @param fi file info ++ * @param cb callback function ++ * @param data caller private callback data ++ * @return string pool handle (weak reference) ++ */ ++int rpmfiSetOnChdir(rpmfi fi, rpmfiChdirCb cb, void *data); ++ + /** \ingroup rpmfi + * Return file info set string pool handle + * @param fi file info +-- +2.41.0 + diff --git a/0001-Eliminate-code-duplication-from-rpmfiNext.patch b/0001-Eliminate-code-duplication-from-rpmfiNext.patch new file mode 100644 index 0000000..a5e0463 --- /dev/null +++ b/0001-Eliminate-code-duplication-from-rpmfiNext.patch @@ -0,0 +1,35 @@ +From 0bc13d75b5883ccf4d6579f7a60fb1badd104649 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Thu, 10 Feb 2022 10:23:22 +0200 +Subject: [PATCH] Eliminate code duplication from rpmfiNext() + +Now that we can, let rpmfiSetFX() take care of the details. +--- + lib/rpmfi.c | 11 ++--------- + 1 file changed, 2 insertions(+), 9 deletions(-) + +diff --git a/lib/rpmfi.c b/lib/rpmfi.c +index 689ead2c5..aec8220a3 100644 +--- a/lib/rpmfi.c ++++ b/lib/rpmfi.c +@@ -856,15 +856,8 @@ int rpmfiNext(rpmfi fi) + next = fi->next(fi); + } while (next == RPMERR_ITER_SKIP); + +- if (next >= 0 && next < rpmfilesFC(fi->files)) { +- fi->i = next; +- fi->j = rpmfilesDI(fi->files, fi->i); +- } else { +- fi->i = -1; +- if (next >= 0) { +- next = -1; +- } +- } ++ if (next >= 0) ++ next = rpmfiSetFX(fi, next); + } + return next; + } +-- +2.41.0 + diff --git a/0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch b/0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch new file mode 100644 index 0000000..0cdde83 --- /dev/null +++ b/0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch @@ -0,0 +1,157 @@ +From ac7b0dbd5a18d2c57a942ca14ac856b8047425ff Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Tue, 15 Feb 2022 10:43:13 +0200 +Subject: [PATCH] Pass file descriptor to file prepare plugin hook, use when + possible + +Sadly the thing that allegedly makes things better mostly just makes +things more complicated as symlinks can't be opened, so we'll now have +to deal with both cases in plugins too. To make matters worse, most +APIs out there support either an fd or a path, but very few support +the *at() style dirfd + basename approach so plugins are stuck with +absolute paths for now. + +This is of course a plugin API/ABI change too. +--- + lib/rpmplugin.h | 2 +- + lib/rpmplugins.c | 4 ++-- + lib/rpmplugins.h | 3 ++- + plugins/ima.c | 9 +++++++-- + plugins/selinux.c | 13 ++++++++----- + 5 files changed, 20 insertions(+), 11 deletions(-) + +diff --git a/lib/rpmplugin.h b/lib/rpmplugin.h +index fd81aec8d..fab4b3e83 100644 +--- a/lib/rpmplugin.h ++++ b/lib/rpmplugin.h +@@ -57,7 +57,7 @@ typedef rpmRC (*plugin_fsm_file_post_func)(rpmPlugin plugin, rpmfi fi, + const char* path, mode_t file_mode, + rpmFsmOp op, int res); + typedef rpmRC (*plugin_fsm_file_prepare_func)(rpmPlugin plugin, rpmfi fi, +- const char* path, ++ int fd, const char* path, + const char *dest, + mode_t file_mode, rpmFsmOp op); + +diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c +index 65e684e84..923084b78 100644 +--- a/lib/rpmplugins.c ++++ b/lib/rpmplugins.c +@@ -384,7 +384,7 @@ rpmRC rpmpluginsCallFsmFilePost(rpmPlugins plugins, rpmfi fi, const char *path, + } + + rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi, +- const char *path, const char *dest, ++ int fd, const char *path, const char *dest, + mode_t file_mode, rpmFsmOp op) + { + plugin_fsm_file_prepare_func hookFunc; +@@ -394,7 +394,7 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi, + for (i = 0; i < plugins->count; i++) { + rpmPlugin plugin = plugins->plugins[i]; + RPMPLUGINS_SET_HOOK_FUNC(fsm_file_prepare); +- if (hookFunc && hookFunc(plugin, fi, path, dest, file_mode, op) == RPMRC_FAIL) { ++ if (hookFunc && hookFunc(plugin, fi, fd, path, dest, file_mode, op) == RPMRC_FAIL) { + rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_prepare failed\n", plugin->name); + rc = RPMRC_FAIL; + } +diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h +index 39762c376..ddf5d7048 100644 +--- a/lib/rpmplugins.h ++++ b/lib/rpmplugins.h +@@ -156,6 +156,7 @@ rpmRC rpmpluginsCallFsmFilePost(rpmPlugins plugins, rpmfi fi, const char* path, + * permissions etc, but before committing file to destination path. + * @param plugins plugins structure + * @param fi file info iterator (or NULL) ++ * @param fd file descriptor (or -1 if not available) + * @param path file object current path + * @param dest file object destination path + * @param mode file object mode +@@ -164,7 +165,7 @@ rpmRC rpmpluginsCallFsmFilePost(rpmPlugins plugins, rpmfi fi, const char* path, + */ + RPM_GNUC_INTERNAL + rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi, +- const char *path, const char *dest, ++ int fd, const char *path, const char *dest, + mode_t mode, rpmFsmOp op); + + #ifdef __cplusplus +diff --git a/plugins/ima.c b/plugins/ima.c +index fe6d3ad7f..9c28a41a3 100644 +--- a/plugins/ima.c ++++ b/plugins/ima.c +@@ -39,7 +39,7 @@ static int check_zero_hdr(const unsigned char *fsig, size_t siglen) + return (memcmp(fsig, &zero_hdr, sizeof(zero_hdr)) == 0); + } + +-static rpmRC ima_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, ++static rpmRC ima_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, int fd, + const char *path, + const char *dest, + mode_t file_mode, rpmFsmOp op) +@@ -63,7 +63,12 @@ static rpmRC ima_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, + + fsig = rpmfiFSignature(fi, &len); + if (fsig && (check_zero_hdr(fsig, len) == 0)) { +- if (lsetxattr(path, XATTR_NAME_IMA, fsig, len, 0) < 0) { ++ int xx; ++ if (fd >= 0) ++ xx = fsetxattr(fd, XATTR_NAME_IMA, fsig, len, 0); ++ else ++ xx = lsetxattr(path, XATTR_NAME_IMA, fsig, len, 0); ++ if (xx < 0) { + rpmlog(RPMLOG_ERR, + "ima: could not apply signature on '%s': %s\n", + path, strerror(errno)); +diff --git a/plugins/fapolicyd.c b/plugins/fapolicyd.c +index 7ac44f0d0..1ff50c30f 100644 +--- a/plugins/fapolicyd.c ++++ b/plugins/fapolicyd.c +@@ -145,7 +145,8 @@ static rpmRC fapolicyd_scriptlet_pre(rpmPlugin plugin, const char *s_name, + } + + static rpmRC fapolicyd_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, +- const char *path, const char *dest, ++ int fd, const char *path, ++ const char *dest, + mode_t file_mode, rpmFsmOp op) + { + /* not ready */ +diff --git a/plugins/selinux.c b/plugins/selinux.c +index 32c3b7529..a7f20aeca 100644 +--- a/plugins/selinux.c ++++ b/plugins/selinux.c +@@ -149,7 +149,7 @@ static rpmRC selinux_scriptlet_fork_post(rpmPlugin plugin, + return rc; + } + +-static rpmRC selinux_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, ++static rpmRC selinux_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, int fd, + const char *path, const char *dest, + mode_t file_mode, rpmFsmOp op) + { +@@ -159,14 +159,17 @@ static rpmRC selinux_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, + if (sehandle && !XFA_SKIPPING(action)) { + security_context_t scon = NULL; + if (selabel_lookup_raw(sehandle, &scon, dest, file_mode) == 0) { +- int conrc = lsetfilecon(path, scon); ++ int conrc; ++ if (fd >= 0) ++ conrc = fsetfilecon(fd, scon); ++ else ++ conrc = lsetfilecon(path, scon); + + if (conrc == 0 || (conrc < 0 && errno == EOPNOTSUPP)) + rc = RPMRC_OK; + +- rpmlog((rc != RPMRC_OK) ? RPMLOG_ERR : RPMLOG_DEBUG, +- "lsetfilecon: (%s, %s) %s\n", +- path, scon, (conrc < 0 ? strerror(errno) : "")); ++ rpmlog((rc != RPMRC_OK) ? RPMLOG_ERR : RPMLOG_DEBUG, "lsetfilecon: (%d %s, %s) %s\n", ++ fd, path, scon, (conrc < 0 ? strerror(errno) : "")); + + freecon(scon); + } else { +-- +2.41.0 + diff --git a/0001-Use-file-state-machine-from-rpm-4.19.patch b/0001-Use-file-state-machine-from-rpm-4.19.patch new file mode 100644 index 0000000..19c2bdd --- /dev/null +++ b/0001-Use-file-state-machine-from-rpm-4.19.patch @@ -0,0 +1,1653 @@ +From 36ee14a07630668629a0d461fba8b5b2248d7d71 Mon Sep 17 00:00:00 2001 +From: Florian Festi +Date: Tue, 10 Oct 2023 16:46:17 +0200 +Subject: [PATCH] Use file state machine from rpm-4.19 + +This new implementation fixes several race conditions when placing down +files on disc +--- + lib/fsm.c | 1164 +++++++++++++++++++++++++--------------------- + lib/rpmarchive.h | 3 + + lib/rpmfiles.h | 3 + + +diff --git a/lib/rpmarchive.h b/lib/rpmarchive.h +index c864e5b56..e5cda4f97 100644 +--- a/lib/rpmarchive.h ++++ b/lib/rpmarchive.h +@@ -26,6 +26,8 @@ enum rpmfilesErrorCodes { + RPMERR_FILE_SIZE = -12, + RPMERR_ITER_SKIP = -13, + RPMERR_EXIST_AS_DIR = -14, ++ RPMERR_INVALID_SYMLINK = -15, ++ RPMERR_ENOTDIR = -16, + + RPMERR_OPEN_FAILED = -32768, + RPMERR_CHMOD_FAILED = -32769, +@@ -47,6 +49,7 @@ enum rpmfilesErrorCodes { + RPMERR_COPY_FAILED = -32785, + RPMERR_LSETFCON_FAILED = -32786, + RPMERR_SETCAP_FAILED = -32787, ++ RPMERR_CLOSE_FAILED = -32788, + }; + + #ifdef __cplusplus +diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h +index daf572cf4..e74bb2201 100644 +--- a/lib/rpmfiles.h ++++ b/lib/rpmfiles.h +@@ -90,6 +90,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. + */ +--- rpm-4.14.3/lib/fsm.c.orig 2023-10-11 16:00:49.610090807 +0200 ++++ rpm-4.14.3/lib/fsm.c 2023-10-11 16:01:16.976451270 +0200 +@@ -5,9 +5,11 @@ + + #include "system.h" + ++#include + #include + #include +-#if WITH_CAP ++#include ++#ifdef WITH_CAP + #include + #endif + +@@ -17,10 +19,11 @@ + #include + + #include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */ +-#include "lib/fsm.h" +-#include "lib/rpmte_internal.h" /* XXX rpmfs */ +-#include "lib/rpmplugins.h" /* rpm plugins hooks */ +-#include "lib/rpmug.h" ++#include "fsm.h" ++#include "rpmte_internal.h" /* XXX rpmfs */ ++#include "rpmfi_internal.h" /* rpmfiSetOnChdir */ ++#include "rpmplugins.h" /* rpm plugins hooks */ ++#include "rpmug.h" + + #include "debug.h" + +@@ -38,172 +41,92 @@ + #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; ++ const char *suffix; ++ char *fpath; ++ struct stat sb; ++}; ++ + /* + * XXX Forward declarations for previously exported functions to avoid moving + * things around needlessly + */ + static const char * fileActionString(rpmFileAction a); ++static int fsmOpenat(int dirfd, const char *path, int flags, int dir); ++static int fsmClose(int *wfdp); + + /** \ingroup payload + * Build path to file from file info, optionally ornamented with suffix. ++ * "/" needs special handling to avoid appearing as empty path. + * @param fi file info iterator + * @param suffix suffix to use (NULL disables) +- * @retval path to file (malloced) ++ * @param[out] path to file (malloced) + */ + static char * fsmFsPath(rpmfi fi, const char * suffix) + { +- return rstrscat(NULL, rpmfiDN(fi), rpmfiBN(fi), suffix ? suffix : "", NULL); +-} +- +-/** \ingroup payload +- * Directory name iterator. +- */ +-typedef struct dnli_s { +- rpmfiles fi; +- char * active; +- int reverse; +- int isave; +- int i; +-} * DNLI_t; +- +-/** \ingroup payload +- * Destroy directory name iterator. +- * @param dnli directory name iterator +- * @retval NULL always +- */ +-static DNLI_t dnlFreeIterator(DNLI_t dnli) +-{ +- if (dnli) { +- if (dnli->active) free(dnli->active); +- free(dnli); +- } +- return NULL; ++ const char *bn = rpmfiBN(fi); ++ return rstrscat(NULL, *bn ? bn : "/", suffix ? suffix : "", NULL); + } + +-/** \ingroup payload +- * Create directory name iterator. +- * @param fi file info set +- * @param fs file state set +- * @param reverse traverse directory names in reverse order? +- * @return directory name iterator +- */ +-static DNLI_t dnlInitIterator(rpmfiles fi, rpmfs fs, int reverse) ++static int fsmLink(int odirfd, const char *opath, int dirfd, const char *path) + { +- DNLI_t dnli; +- int i, j; +- int dc; +- +- if (fi == NULL) +- return NULL; +- dc = rpmfilesDC(fi); +- dnli = xcalloc(1, sizeof(*dnli)); +- dnli->fi = fi; +- dnli->reverse = reverse; +- dnli->i = (reverse ? dc : 0); +- +- if (dc) { +- dnli->active = xcalloc(dc, sizeof(*dnli->active)); +- int fc = rpmfilesFC(fi); +- +- /* Identify parent directories not skipped. */ +- for (i = 0; i < fc; i++) +- if (!XFA_SKIPPING(rpmfsGetAction(fs, i))) +- dnli->active[rpmfilesDI(fi, i)] = 1; +- +- /* Exclude parent directories that are explicitly included. */ +- for (i = 0; i < fc; i++) { +- int dil; +- size_t dnlen, bnlen; ++ int rc = linkat(odirfd, opath, dirfd, path, 0); + +- if (!S_ISDIR(rpmfilesFMode(fi, i))) +- continue; +- +- dil = rpmfilesDI(fi, i); +- dnlen = strlen(rpmfilesDN(fi, dil)); +- bnlen = strlen(rpmfilesBN(fi, i)); +- +- for (j = 0; j < dc; j++) { +- const char * dnl; +- size_t jlen; +- +- if (!dnli->active[j] || j == dil) +- continue; +- dnl = rpmfilesDN(fi, j); +- jlen = strlen(dnl); +- if (jlen != (dnlen+bnlen+1)) +- continue; +- if (!rstreqn(dnl, rpmfilesDN(fi, dil), dnlen)) +- continue; +- if (!rstreqn(dnl+dnlen, rpmfilesBN(fi, i), bnlen)) +- continue; +- if (dnl[dnlen+bnlen] != '/' || dnl[dnlen+bnlen+1] != '\0') +- continue; +- /* This directory is included in the package. */ +- dnli->active[j] = 0; +- break; +- } +- } +- +- /* Print only once per package. */ +- if (!reverse) { +- j = 0; +- for (i = 0; i < dc; i++) { +- if (!dnli->active[i]) continue; +- if (j == 0) { +- j = 1; +- rpmlog(RPMLOG_DEBUG, +- "========== Directories not explicitly included in package:\n"); +- } +- rpmlog(RPMLOG_DEBUG, "%10d %s\n", i, rpmfilesDN(fi, i)); +- } +- if (j) +- rpmlog(RPMLOG_DEBUG, "==========\n"); +- } ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, %d %s) %s\n", __func__, ++ odirfd, opath, dirfd, path, (rc < 0 ? strerror(errno) : "")); + } +- return dnli; ++ ++ if (rc < 0) ++ rc = RPMERR_LINK_FAILED; ++ return rc; + } + +-/** \ingroup payload +- * Return next directory name (from file info). +- * @param dnli directory name iterator +- * @return next directory name +- */ +-static +-const char * dnlNextIterator(DNLI_t dnli) ++#ifdef WITH_CAP ++static int cap_set_fileat(int dirfd, const char *path, cap_t fcaps) + { +- const char * dn = NULL; +- +- if (dnli) { +- rpmfiles fi = dnli->fi; +- int dc = rpmfilesDC(fi); +- int i = -1; +- +- if (dnli->active) +- do { +- i = (!dnli->reverse ? dnli->i++ : --dnli->i); +- } while (i >= 0 && i < dc && !dnli->active[i]); +- +- if (i >= 0 && i < dc) +- dn = rpmfilesDN(fi, i); +- else +- i = -1; +- dnli->isave = i; ++ int rc = -1; ++ int fd = fsmOpenat(dirfd, path, O_RDONLY|O_NOFOLLOW, 0); ++ if (fd >= 0) { ++ rc = cap_set_fd(fd, fcaps); ++ fsmClose(&fd); + } +- return dn; ++ return rc; + } ++#endif + +-static int fsmSetFCaps(const char *path, const char *captxt) ++static int fsmSetFCaps(int fd, int dirfd, const char *path, const char *captxt) + { + int rc = 0; +-#if WITH_CAP ++ ++#ifdef WITH_CAP + if (captxt && *captxt != '\0') { + cap_t fcaps = cap_from_text(captxt); +- if (fcaps == NULL || cap_set_file(path, fcaps) != 0) { +- rc = RPMERR_SETCAP_FAILED; ++ ++ if (fd >= 0) { ++ if (fcaps == NULL || cap_set_fd(fd, fcaps)) ++ rc = RPMERR_SETCAP_FAILED; ++ } else { ++ if (fcaps == NULL || cap_set_fileat(dirfd, path, fcaps)) ++ rc = RPMERR_SETCAP_FAILED; + } ++ + if (_fsm_debug) { +- rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__, +- path, captxt, (rc < 0 ? strerror(errno) : "")); ++ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, %s) %s\n", __func__, ++ fd, dirfd, path, captxt, (rc < 0 ? strerror(errno) : "")); + } + cap_free(fcaps); + } +@@ -211,101 +134,104 @@ + return rc; + } + +-static void wfd_close(FD_t *wfdp) ++static int fsmClose(int *wfdp) + { +- if (wfdp && *wfdp) { ++ int rc = 0; ++ if (wfdp && *wfdp >= 0) { + int myerrno = errno; + static int oneshot = 0; + static int flush_io = 0; ++ int fdno = *wfdp; ++ + if (!oneshot) { +- flush_io = rpmExpandNumeric("%{?_flush_io}"); ++ flush_io = (rpmExpandNumeric("%{?_flush_io}") > 0); + oneshot = 1; + } + if (flush_io) { +- int fdno = Fileno(*wfdp); + fsync(fdno); + } +- Fclose(*wfdp); +- *wfdp = NULL; ++ if (close(fdno)) ++ rc = RPMERR_CLOSE_FAILED; ++ ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s ([%d]) %s\n", __func__, ++ fdno, (rc < 0 ? strerror(errno) : "")); ++ } ++ *wfdp = -1; + errno = myerrno; + } ++ return rc; + } + +-static int wfd_open(FD_t *wfdp, const char *dest) ++static int fsmOpen(int *wfdp, int dirfd, const char *dest) + { + int rc = 0; + /* Create the file with 0200 permissions (write by owner). */ +- { +- mode_t old_umask = umask(0577); +- *wfdp = Fopen(dest, "wx.ufdio"); +- umask(old_umask); +- } +- if (Ferror(*wfdp)) { ++ int fd = openat(dirfd, dest, O_WRONLY|O_EXCL|O_CREAT, 0200); ++ ++ if (fd < 0) + rc = RPMERR_OPEN_FAILED; +- goto exit; +- } + +- return 0; ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s (%s [%d]) %s\n", __func__, ++ dest, fd, (rc < 0 ? strerror(errno) : "")); ++ } ++ *wfdp = fd; + +-exit: +- wfd_close(wfdp); + return rc; + } + +-/** \ingroup payload +- * Create file from payload stream. +- * @return 0 on success +- */ +-static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest) ++static int fsmUnpack(rpmfi fi, int fdno, rpmpsm psm, int nodigest) + { +- FD_t wfd = NULL; +- int rc; +- +- rc = wfd_open(&wfd, dest); +- if (rc != 0) +- goto exit; +- +- rc = rpmfiArchiveReadToFilePsm(fi, wfd, nodigest, psm); +- wfd_close(&wfd); +-exit: ++ FD_t fd = fdDup(fdno); ++ int rc = rpmfiArchiveReadToFilePsm(fi, fd, nodigest, psm); ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s (%s %" PRIu64 " bytes [%d]) %s\n", __func__, ++ rpmfiFN(fi), rpmfiFSize(fi), Fileno(fd), ++ (rc < 0 ? strerror(errno) : "")); ++ } ++ Fclose(fd); + return rc; + } + +-static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files, +- rpmpsm psm, int nodigest, int *setmeta, +- int * firsthardlink, FD_t *firstlinkfile) ++static int fsmMkfile(int dirfd, rpmfi fi, struct filedata_s *fp, rpmfiles files, ++ rpmpsm psm, int nodigest, ++ struct filedata_s ** firstlink, int *firstlinkfile, ++ int *firstdir, int *fdp) + { + int rc = 0; +- int numHardlinks = rpmfiFNlink(fi); ++ int fd = -1; + +- if (numHardlinks > 1) { +- /* Create first hardlinked file empty */ +- if (*firsthardlink < 0) { +- *firsthardlink = rpmfiFX(fi); +- rc = wfd_open(firstlinkfile, dest); +- } else { +- /* Create hard links for others */ +- char *fn = rpmfilesFN(files, *firsthardlink); +- rc = link(fn, dest); +- if (rc < 0) { +- rc = RPMERR_LINK_FAILED; +- } +- free(fn); ++ if (*firstlink == NULL) { ++ /* First encounter, open file for writing */ ++ rc = fsmOpen(&fd, dirfd, fp->fpath); ++ /* If it's a part of a hardlinked set, the content may come later */ ++ if (fp->sb.st_nlink > 1) { ++ *firstlink = fp; ++ *firstlinkfile = fd; ++ *firstdir = dup(dirfd); ++ } ++ } else { ++ /* Create hard links for others and avoid redundant metadata setting */ ++ if (*firstlink != fp) { ++ rc = fsmLink(*firstdir, (*firstlink)->fpath, dirfd, fp->fpath); + } ++ fd = *firstlinkfile; + } +- /* Write normal files or fill the last hardlinked (already +- existing) file with content */ +- if (numHardlinks<=1) { +- if (!rc) +- rc = expandRegular(fi, dest, psm, nodigest); +- } else if (rpmfiArchiveHasContent(fi)) { ++ ++ /* If the file has content, unpack it */ ++ if (rpmfiArchiveHasContent(fi)) { + if (!rc) +- rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm); +- wfd_close(firstlinkfile); +- *firsthardlink = -1; +- } else { +- *setmeta = 0; ++ rc = fsmUnpack(fi, fd, psm, nodigest); ++ /* Last file of hardlink set, ensure metadata gets set */ ++ if (*firstlink) { ++ fp->setmeta = 1; ++ *firstlink = NULL; ++ *firstlinkfile = -1; ++ fsmClose(firstdir); ++ } + } ++ *fdp = fd; + + return rc; + } +@@ -330,18 +256,15 @@ + return rc; + } + +-static int fsmStat(const char *path, int dolstat, struct stat *sb) ++static int fsmStat(int dirfd, const char *path, int dolstat, struct stat *sb) + { +- int rc; +- if (dolstat){ +- rc = lstat(path, sb); +- } else { +- rc = stat(path, sb); +- } ++ int flags = dolstat ? AT_SYMLINK_NOFOLLOW : 0; ++ int rc = fstatat(dirfd, path, sb, flags); ++ + if (_fsm_debug && rc && errno != ENOENT) +- rpmlog(RPMLOG_DEBUG, " %8s (%s, ost) %s\n", ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, ost) %s\n", + __func__, +- path, (rc < 0 ? strerror(errno) : "")); ++ dirfd, path, (rc < 0 ? strerror(errno) : "")); + if (rc < 0) { + rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_LSTAT_FAILED); + /* Ensure consistent struct content on failure */ +@@ -350,12 +273,12 @@ + return rc; + } + +-static int fsmRmdir(const char *path) ++static int fsmRmdir(int dirfd, const char *path) + { +- int rc = rmdir(path); ++ int rc = unlinkat(dirfd, path, AT_REMOVEDIR); + if (_fsm_debug) +- rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__, +- path, (rc < 0 ? strerror(errno) : "")); ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s) %s\n", __func__, ++ dirfd, path, (rc < 0 ? strerror(errno) : "")); + if (rc < 0) + switch (errno) { + case ENOENT: rc = RPMERR_ENOENT; break; +@@ -365,172 +288,193 @@ + return rc; + } + +-static int fsmMkdir(const char *path, mode_t mode) ++static int fsmMkdir(int dirfd, const char *path, mode_t mode) + { +- int rc = mkdir(path, (mode & 07777)); ++ int rc = mkdirat(dirfd, path, (mode & 07777)); + if (_fsm_debug) +- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__, +- path, (unsigned)(mode & 07777), ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%04o) %s\n", __func__, ++ dirfd, path, (unsigned)(mode & 07777), + (rc < 0 ? strerror(errno) : "")); + if (rc < 0) rc = RPMERR_MKDIR_FAILED; + return rc; + } + +-static int fsmMkfifo(const char *path, mode_t mode) ++static int fsmOpenat(int dirfd, const char *path, int flags, int dir) + { +- int rc = mkfifo(path, (mode & 07777)); +- +- if (_fsm_debug) { +- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", +- __func__, path, (unsigned)(mode & 07777), +- (rc < 0 ? strerror(errno) : "")); ++ struct stat lsb, sb; ++ int sflags = flags | O_NOFOLLOW; ++ int fd = openat(dirfd, path, sflags); ++ ++ /* ++ * Only ever follow symlinks by root or target owner. Since we can't ++ * open the symlink itself, the order matters: we stat the link *after* ++ * opening the target, and if the link ownership changed between the calls ++ * it could've only been the link owner or root. ++ */ ++ if (fd < 0 && errno == ELOOP && flags != sflags) { ++ int ffd = openat(dirfd, path, flags); ++ if (ffd >= 0) { ++ if (fstatat(dirfd, path, &lsb, AT_SYMLINK_NOFOLLOW) == 0) { ++ if (fstat(ffd, &sb) == 0) { ++ if (lsb.st_uid == 0 || lsb.st_uid == sb.st_uid) { ++ fd = ffd; ++ } ++ } ++ } ++ if (ffd != fd) ++ close(ffd); ++ } + } + +- if (rc < 0) +- rc = RPMERR_MKFIFO_FAILED; +- +- return rc; ++ /* O_DIRECTORY equivalent */ ++ if (dir && fd >= 0 && fstat(fd, &sb) == 0 && !S_ISDIR(sb.st_mode)) { ++ errno = ENOTDIR; ++ fsmClose(&fd); ++ } ++ return fd; + } + +-static int fsmMknod(const char *path, mode_t mode, dev_t dev) ++static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn, ++ const char *apath, ++ int owned, mode_t mode, int *fdp) + { +- /* FIX: check S_IFIFO or dev != 0 */ +- int rc = mknod(path, (mode & ~07777), dev); ++ int rc; ++ rpmFsmOp op = (FA_CREATE); ++ if (!owned) ++ op |= FAF_UNOWNED; + +- if (_fsm_debug) { +- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%o, 0x%x) %s\n", +- __func__, path, (unsigned)(mode & ~07777), +- (unsigned)dev, (rc < 0 ? strerror(errno) : "")); ++ /* Run fsm file pre hook for all plugins */ ++ rc = rpmpluginsCallFsmFilePre(plugins, NULL, apath, mode, op); ++ ++ if (!rc) ++ rc = fsmMkdir(dirfd, dn, mode); ++ ++ if (!rc) { ++ *fdp = fsmOpenat(dirfd, dn, O_RDONLY|O_NOFOLLOW, 1); ++ if (*fdp == -1) ++ rc = RPMERR_ENOTDIR; + } + +- if (rc < 0) +- rc = RPMERR_MKNOD_FAILED; ++ if (!rc) { ++ rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, *fdp, apath, apath, mode, op); ++ } ++ ++ /* Run fsm file post hook for all plugins */ ++ rpmpluginsCallFsmFilePost(plugins, NULL, apath, mode, op, rc); ++ ++ if (!rc) { ++ rpmlog(RPMLOG_DEBUG, ++ "%s directory created with perms %04o\n", ++ apath, (unsigned)(mode & 07777)); ++ } + + return rc; + } + +-/** +- * Create (if necessary) directories not explicitly included in package. +- * @param files file data +- * @param fs file states +- * @param plugins rpm plugins handle +- * @return 0 on success +- */ +-static int fsmMkdirs(rpmfiles files, rpmfs fs, rpmPlugins plugins) ++static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create, ++ int quiet, int *dirfdp) + { +- DNLI_t dnli = dnlInitIterator(files, fs, 0); +- struct stat sb; +- const char *dpath; +- int dc = rpmfilesDC(files); ++ char *sp = NULL, *bn; ++ char *apath = NULL; ++ int oflags = O_RDONLY; + int rc = 0; +- int i; +- int ldnlen = 0; +- int ldnalloc = 0; +- char * ldn = NULL; +- short * dnlx = NULL; +- +- dnlx = (dc ? xcalloc(dc, sizeof(*dnlx)) : NULL); +- +- if (dnlx != NULL) +- while ((dpath = dnlNextIterator(dnli)) != NULL) { +- size_t dnlen = strlen(dpath); +- char * te, dn[dnlen+1]; +- +- dc = dnli->isave; +- if (dc < 0) continue; +- dnlx[dc] = dnlen; +- if (dnlen <= 1) +- continue; + +- if (dnlen <= ldnlen && rstreq(dpath, ldn)) +- continue; ++ if (*dirfdp >= 0) ++ return rc; + +- /* Copy as we need to modify the string */ +- (void) stpcpy(dn, dpath); ++ int dirfd = fsmOpenat(-1, "/", oflags, 1); ++ int fd = dirfd; /* special case of "/" */ + +- /* Assume '/' directory exists, "mkdir -p" for others if non-existent */ +- for (i = 1, te = dn + 1; *te != '\0'; te++, i++) { +- if (*te != '/') +- continue; ++ char *path = xstrdup(p); ++ char *dp = path; + +- *te = '\0'; ++ while ((bn = strtok_r(dp, "/", &sp)) != NULL) { ++ fd = fsmOpenat(dirfd, bn, oflags, 1); ++ /* assemble absolute path for plugins benefit, sigh */ ++ apath = rstrscat(&apath, "/", bn, NULL); + +- /* Already validated? */ +- if (i < ldnlen && +- (ldn[i] == '/' || ldn[i] == '\0') && rstreqn(dn, ldn, i)) +- { +- *te = '/'; +- /* Move pre-existing path marker forward. */ +- dnlx[dc] = (te - dn); +- continue; ++ if (fd < 0 && errno == ENOENT && create) { ++ mode_t mode = S_IFDIR | (_dirPerms & 07777); ++ rc = fsmDoMkDir(plugins, dirfd, bn, apath, owned, mode, &fd); ++ } ++ ++ fsmClose(&dirfd); ++ if (fd >= 0) { ++ dirfd = fd; ++ } else { ++ if (!quiet) { ++ rpmlog(RPMLOG_ERR, _("failed to open dir %s of %s: %s\n"), ++ bn, p, strerror(errno)); + } ++ rc = RPMERR_OPEN_FAILED; ++ break; ++ } + +- /* Validate next component of path. */ +- rc = fsmStat(dn, 1, &sb); /* lstat */ +- *te = '/'; +- +- /* Directory already exists? */ +- if (rc == 0 && S_ISDIR(sb.st_mode)) { +- /* Move pre-existing path marker forward. */ +- dnlx[dc] = (te - dn); +- } else if (rc == RPMERR_ENOENT) { +- *te = '\0'; +- mode_t mode = S_IFDIR | (_dirPerms & 07777); +- rpmFsmOp op = (FA_CREATE|FAF_UNOWNED); +- +- /* Run fsm file pre hook for all plugins */ +- rc = rpmpluginsCallFsmFilePre(plugins, NULL, dn, mode, op); +- +- if (!rc) +- rc = fsmMkdir(dn, mode); +- +- if (!rc) { +- rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, dn, dn, +- mode, op); +- } ++ dp = NULL; ++ } + +- /* Run fsm file post hook for all plugins */ +- rpmpluginsCallFsmFilePost(plugins, NULL, dn, mode, op, rc); ++ if (rc) { ++ fsmClose(&fd); ++ fsmClose(&dirfd); ++ } else { ++ rc = 0; ++ } ++ *dirfdp = dirfd; + +- if (!rc) { +- rpmlog(RPMLOG_DEBUG, +- "%s directory created with perms %04o\n", +- dn, (unsigned)(mode & 07777)); +- } +- *te = '/'; +- } +- if (rc) +- break; +- } +- if (rc) break; ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s (%s: %d) %s\n", __func__, ++ p, dirfd, (rc < 0 ? strerror(errno) : "")); ++ } + +- /* Save last validated path. */ +- if (ldnalloc < (dnlen + 1)) { +- ldnalloc = dnlen + 100; +- ldn = xrealloc(ldn, ldnalloc); +- } +- if (ldn != NULL) { /* XXX can't happen */ +- strcpy(ldn, dn); +- ldnlen = dnlen; +- } ++ free(path); ++ free(apath); ++ return rc; ++} ++ ++static int fsmMkfifo(int dirfd, const char *path, mode_t mode) ++{ ++ int rc = mkfifoat(dirfd, path, (mode & 07777)); ++ ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%04o) %s\n", ++ __func__, dirfd, path, (unsigned)(mode & 07777), ++ (rc < 0 ? strerror(errno) : "")); ++ } ++ ++ if (rc < 0) ++ rc = RPMERR_MKFIFO_FAILED; ++ ++ return rc; ++} ++ ++static int fsmMknod(int dirfd, const char *path, mode_t mode, dev_t dev) ++{ ++ /* FIX: check S_IFIFO or dev != 0 */ ++ int rc = mknodat(dirfd, path, (mode & ~07777), dev); ++ ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%o, 0x%x) %s\n", ++ __func__, dirfd, path, (unsigned)(mode & ~07777), ++ (unsigned)dev, (rc < 0 ? strerror(errno) : "")); + } +- free(dnlx); +- free(ldn); +- dnlFreeIterator(dnli); ++ ++ if (rc < 0) ++ rc = RPMERR_MKNOD_FAILED; + + return rc; + } + +-static void removeSBITS(const char *path) ++static void removeSBITS(int dirfd, const char *path) + { + struct stat stb; +- if (lstat(path, &stb) == 0 && S_ISREG(stb.st_mode)) { ++ int flags = AT_SYMLINK_NOFOLLOW; ++ if (fstatat(dirfd, path, &stb, flags) == 0 && S_ISREG(stb.st_mode)) { + if ((stb.st_mode & 06000) != 0) { +- (void) chmod(path, stb.st_mode & 0777); ++ (void) fchmodat(dirfd, path, stb.st_mode & 0777, flags); + } +-#if WITH_CAP ++#ifdef WITH_CAP + if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) { +- (void) cap_set_file(path, NULL); ++ (void) cap_set_fileat(dirfd, path, NULL); + } + #endif + } +@@ -546,13 +490,13 @@ + (fpath ? fpath : "")); + } + +-static int fsmSymlink(const char *opath, const char *path) ++static int fsmSymlink(const char *opath, int dirfd, const char *path) + { +- int rc = symlink(opath, path); ++ int rc = symlinkat(opath, dirfd, path); + + if (_fsm_debug) { +- rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__, +- opath, path, (rc < 0 ? strerror(errno) : "")); ++ rpmlog(RPMLOG_DEBUG, " %8s (%s, %d %s) %s\n", __func__, ++ opath, dirfd, path, (rc < 0 ? strerror(errno) : "")); + } + + if (rc < 0) +@@ -560,96 +504,125 @@ + return rc; + } + +-static int fsmUnlink(const char *path) ++static int fsmUnlink(int dirfd, const char *path) + { + int rc = 0; +- removeSBITS(path); +- rc = unlink(path); ++ removeSBITS(dirfd, path); ++ rc = unlinkat(dirfd, path, 0); + if (_fsm_debug) +- rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__, +- path, (rc < 0 ? strerror(errno) : "")); ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s) %s\n", __func__, ++ dirfd, path, (rc < 0 ? strerror(errno) : "")); + if (rc < 0) + rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_UNLINK_FAILED); + return rc; + } + +-static int fsmRename(const char *opath, const char *path) ++static int fsmRename(int odirfd, const char *opath, int dirfd, const char *path) + { +- removeSBITS(path); +- int rc = rename(opath, path); ++ removeSBITS(dirfd, path); ++ int rc = renameat(odirfd, opath, dirfd, path); + #if defined(ETXTBSY) && defined(__HPUX__) + /* XXX HP-UX (and other os'es) don't permit rename to busy files. */ + if (rc && errno == ETXTBSY) { + char *rmpath = NULL; + rstrscat(&rmpath, path, "-RPMDELETE", NULL); +- rc = rename(path, rmpath); +- if (!rc) rc = rename(opath, path); ++ /* Rename within the original directory */ ++ rc = renameat(odirfd, path, odirfd, rmpath); ++ if (!rc) rc = renameat(odirfd, opath, dirfd, path); + free(rmpath); + } + #endif + if (_fsm_debug) +- rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__, +- opath, path, (rc < 0 ? strerror(errno) : "")); ++ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, %d %s) %s\n", __func__, ++ odirfd, opath, dirfd, path, (rc < 0 ? strerror(errno) : "")); + if (rc < 0) + rc = (errno == EISDIR ? RPMERR_EXIST_AS_DIR : RPMERR_RENAME_FAILED); + return rc; + } + +-static int fsmRemove(const char *path, mode_t mode) ++static int fsmRemove(int dirfd, const char *path, mode_t mode) + { +- return S_ISDIR(mode) ? fsmRmdir(path) : fsmUnlink(path); ++ return S_ISDIR(mode) ? fsmRmdir(dirfd, path) : fsmUnlink(dirfd, path); + } + +-static int fsmChown(const char *path, mode_t mode, uid_t uid, gid_t gid) ++static int fsmChown(int fd, int dirfd, const char *path, mode_t mode, uid_t uid, gid_t gid) + { +- int rc = S_ISLNK(mode) ? lchown(path, uid, gid) : chown(path, uid, gid); +- if (rc < 0) { +- struct stat st; +- if (lstat(path, &st) == 0 && st.st_uid == uid && st.st_gid == gid) +- rc = 0; ++ int rc; ++ struct stat st; ++ ++ if (fd >= 0) { ++ rc = fchown(fd, uid, gid); ++ if (rc < 0) { ++ if (fstat(fd, &st) == 0 && (st.st_uid == uid && st.st_gid == gid)) { ++ rc = 0; ++ } ++ } ++ } else { ++ int flags = AT_SYMLINK_NOFOLLOW; ++ rc = fchownat(dirfd, path, uid, gid, flags); ++ if (rc < 0) { ++ struct stat st; ++ if (fstatat(dirfd, path, &st, flags) == 0 && ++ (st.st_uid == uid && st.st_gid == gid)) { ++ rc = 0; ++ } ++ } + } +- if (_fsm_debug) +- rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", __func__, +- path, (int)uid, (int)gid, ++ if (_fsm_debug) { ++ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, %d, %d) %s\n", __func__, ++ fd, dirfd, path, (int)uid, (int)gid, + (rc < 0 ? strerror(errno) : "")); ++ } + if (rc < 0) rc = RPMERR_CHOWN_FAILED; + return rc; + } + +-static int fsmChmod(const char *path, mode_t mode) ++static int fsmChmod(int fd, int dirfd, const char *path, mode_t mode) + { +- int rc = chmod(path, (mode & 07777)); +- if (rc < 0) { +- struct stat st; +- if (lstat(path, &st) == 0 && (st.st_mode & 07777) == (mode & 07777)) +- rc = 0; ++ mode_t fmode = (mode & 07777); ++ int rc; ++ if (fd >= 0) { ++ rc = fchmod(fd, fmode); ++ if (rc < 0) { ++ struct stat st; ++ if (fstat(fd, &st) == 0 && (st.st_mode & 07777) == fmode) { ++ rc = 0; ++ } ++ } ++ } else { ++ rc = fchmodat(dirfd, path, fmode, 0); ++ if (rc < 0) { ++ struct stat st; ++ if (fstatat(dirfd, path, &st, AT_SYMLINK_NOFOLLOW) == 0 && ++ (st.st_mode & 07777) == fmode) { ++ rc = 0; ++ } ++ } + } + if (_fsm_debug) +- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__, +- path, (unsigned)(mode & 07777), ++ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, 0%04o) %s\n", __func__, ++ fd, dirfd, path, (unsigned)(mode & 07777), + (rc < 0 ? strerror(errno) : "")); + if (rc < 0) rc = RPMERR_CHMOD_FAILED; + return rc; + } + +-static int fsmUtime(const char *path, mode_t mode, time_t mtime) ++static int fsmUtime(int fd, int dirfd, const char *path, mode_t mode, time_t mtime) + { + int rc = 0; +- struct timeval stamps[2] = { +- { .tv_sec = mtime, .tv_usec = 0 }, +- { .tv_sec = mtime, .tv_usec = 0 }, ++ struct timespec stamps[2] = { ++ { .tv_sec = mtime, .tv_nsec = 0 }, ++ { .tv_sec = mtime, .tv_nsec = 0 }, + }; + +-#if HAVE_LUTIMES +- rc = lutimes(path, stamps); +-#else +- if (!S_ISLNK(mode)) +- rc = utimes(path, stamps); +-#endif ++ if (fd >= 0) ++ rc = futimens(fd, stamps); ++ else ++ rc = utimensat(dirfd, path, stamps, AT_SYMLINK_NOFOLLOW); + + if (_fsm_debug) +- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0x%x) %s\n", __func__, +- path, (unsigned)mtime, (rc < 0 ? strerror(errno) : "")); ++ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, 0x%x) %s\n", __func__, ++ fd, dirfd, path, (unsigned)mtime, (rc < 0 ? strerror(errno) : "")); + if (rc < 0) rc = RPMERR_UTIME_FAILED; + /* ...but utime error is not critical for directories */ + if (rc && S_ISDIR(mode)) +@@ -657,24 +630,24 @@ + return rc; + } + +-static int fsmVerify(const char *path, rpmfi fi) ++static int fsmVerify(int dirfd, const char *path, rpmfi fi) + { + int rc; + int saveerrno = errno; + struct stat dsb; + mode_t mode = rpmfiFMode(fi); + +- rc = fsmStat(path, 1, &dsb); ++ rc = fsmStat(dirfd, path, 1, &dsb); + if (rc) + return rc; + + if (S_ISREG(mode)) { + /* HP-UX (and other os'es) don't permit unlink on busy files. */ + char *rmpath = rstrscat(NULL, path, "-RPMDELETE", NULL); +- rc = fsmRename(path, rmpath); ++ rc = fsmRename(dirfd, path, dirfd, rmpath); + /* XXX shouldn't we take unlink return code here? */ + if (!rc) +- (void) fsmUnlink(rmpath); ++ (void) fsmUnlink(dirfd, rmpath); + else + rc = RPMERR_UNLINK_FAILED; + free(rmpath); +@@ -683,7 +656,7 @@ + if (S_ISDIR(dsb.st_mode)) return 0; + if (S_ISLNK(dsb.st_mode)) { + uid_t luid = dsb.st_uid; +- rc = fsmStat(path, 0, &dsb); ++ rc = fsmStat(dirfd, path, 0, &dsb); + if (rc == RPMERR_ENOENT) rc = 0; + if (rc) return rc; + errno = saveerrno; +@@ -709,7 +682,7 @@ + if (S_ISSOCK(dsb.st_mode)) return 0; + } + /* XXX shouldn't do this with commit/undo. */ +- rc = fsmUnlink(path); ++ rc = fsmUnlink(dirfd, path); + if (rc == 0) rc = RPMERR_ENOENT; + return (rc ? rc : RPMERR_ENOENT); /* XXX HACK */ + } +@@ -723,7 +696,7 @@ + + + /* Rename pre-existing modified or unmanaged file. */ +-static int fsmBackup(rpmfi fi, rpmFileAction action) ++static int fsmBackup(int dirfd, rpmfi fi, rpmFileAction action) + { + int rc = 0; + const char *suffix = NULL; +@@ -744,9 +717,10 @@ + if (suffix) { + char * opath = fsmFsPath(fi, NULL); + char * path = fsmFsPath(fi, suffix); +- rc = fsmRename(opath, path); ++ rc = fsmRename(dirfd, opath, dirfd, path); + if (!rc) { +- rpmlog(RPMLOG_WARNING, _("%s saved as %s\n"), opath, path); ++ rpmlog(RPMLOG_WARNING, _("%s%s saved as %s%s\n"), ++ rpmfiDN(fi), opath, rpmfiDN(fi), path); + } + free(path); + free(opath); +@@ -754,7 +728,8 @@ + return rc; + } + +-static int fsmSetmeta(const char *path, rpmfi fi, rpmPlugins plugins, ++static int fsmSetmeta(int fd, int dirfd, const char *path, ++ rpmfi fi, rpmPlugins plugins, + rpmFileAction action, const struct stat * st, + int nofcaps) + { +@@ -762,27 +737,28 @@ + const char *dest = rpmfiFN(fi); + + if (!rc && !getuid()) { +- rc = fsmChown(path, st->st_mode, st->st_uid, st->st_gid); ++ rc = fsmChown(fd, dirfd, path, st->st_mode, st->st_uid, st->st_gid); + } + if (!rc && !S_ISLNK(st->st_mode)) { +- rc = fsmChmod(path, st->st_mode); ++ rc = fsmChmod(fd, dirfd, path, st->st_mode); + } + /* Set file capabilities (if enabled) */ + if (!rc && !nofcaps && S_ISREG(st->st_mode) && !getuid()) { +- rc = fsmSetFCaps(path, rpmfiFCaps(fi)); ++ rc = fsmSetFCaps(fd, dirfd, path, rpmfiFCaps(fi)); + } + if (!rc) { +- rc = fsmUtime(path, st->st_mode, rpmfiFMtime(fi)); ++ rc = fsmUtime(fd, dirfd, path, st->st_mode, rpmfiFMtime(fi)); + } + if (!rc) { + rc = rpmpluginsCallFsmFilePrepare(plugins, fi, +- path, dest, st->st_mode, action); ++ fd, path, dest, ++ st->st_mode, action); + } + + return rc; + } + +-static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *suffix) ++static int fsmCommit(int dirfd, char **path, rpmfi fi, rpmFileAction action, const char *suffix) + { + int rc = 0; + +@@ -796,15 +772,18 @@ + + /* 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); +- } +- free(*path); +- *path = dest; ++ rc = fsmRename(dirfd, *path, dirfd, dest); ++ if (!rc) { ++ if (nsuffix) { ++ char * opath = fsmFsPath(fi, NULL); ++ rpmlog(RPMLOG_WARNING, _("%s%s created as %s%s\n"), ++ rpmfiDN(fi), opath, rpmfiDN(fi), dest); ++ free(opath); ++ } ++ free(*path); ++ *path = dest; ++ } else ++ free(dest); + } + } + +@@ -855,184 +834,277 @@ + } + } + ++struct diriter_s { ++ int dirfd; ++ int firstdir; ++}; ++ ++static int onChdir(rpmfi fi, void *data) ++{ ++ struct diriter_s *di = data; ++ ++ fsmClose(&(di->dirfd)); ++ return 0; ++} ++ ++static rpmfi fsmIter(FD_t payload, rpmfiles files, rpmFileIter iter, void *data) ++{ ++ rpmfi fi; ++ if (payload) ++ fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); ++ else ++ fi = rpmfilesIter(files, iter); ++ if (fi && data) ++ rpmfiSetOnChdir(fi, onChdir, data); ++ return fi; ++} ++ ++static rpmfi fsmIterFini(rpmfi fi, struct diriter_s *di) ++{ ++ fsmClose(&(di->dirfd)); ++ fsmClose(&(di->firstdir)); ++ return rpmfiFree(fi); ++} ++ + 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); +- 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; ++ int firstlinkfile = -1; + char *tid = NULL; +- const char *suffix; +- char *fpath = NULL; +- +- if (fi == NULL) { +- rc = RPMERR_BAD_MAGIC; +- goto exit; +- } ++ struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); ++ struct filedata_s *firstlink = NULL; ++ struct diriter_s di = { -1, -1 }; + + /* 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); +- +- while (!rc) { +- /* Read next payload header. */ +- rc = rpmfiNext(fi); ++ /* 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]; ++ if (rpmfiFFlags(fi) & RPMFILE_GHOST) ++ fp->action = FA_SKIP; ++ else ++ fp->action = rpmfsGetAction(fs, fx); ++ fp->skip = XFA_SKIPPING(fp->action); ++ if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi))) ++ fp->suffix = tid; ++ fp->fpath = fsmFsPath(fi, fp->suffix); + +- if (rc < 0) { +- if (rc == RPMERR_ITER_END) +- rc = 0; +- break; +- } ++ /* Remap file perms, owner, and group. */ ++ rc = rpmfiStat(fi, 1, &fp->sb); + +- action = rpmfsGetAction(fs, rpmfiFX(fi)); +- skip = XFA_SKIPPING(action); +- if (action != FA_TOUCH) { +- suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; +- } else { +- suffix = NULL; +- } +- fpath = fsmFsPath(fi, suffix); ++ /* Hardlinks are tricky and handled elsewhere for install */ ++ fp->setmeta = (fp->skip == 0) && ++ (fp->sb.st_nlink == 1 || fp->action == FA_TOUCH); + +- /* Remap file perms, owner, and group. */ +- rc = rpmfiStat(fi, 1, &sb); ++ setFileState(fs, fx); ++ fsmDebug(fp->fpath, fp->action, &fp->sb); + +- fsmDebug(fpath, action, &sb); ++ fp->stage = FILE_PRE; ++ } ++ fi = rpmfiFree(fi); + +- /* Exit on error. */ +- if (rc) +- break; ++ if (rc) ++ goto exit; + +- /* Run fsm file pre hook for all plugins */ +- rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath, +- sb.st_mode, action); +- if (rc) { +- skip = 1; +- } else { +- setFileState(fs, rpmfiFX(fi)); +- } ++ fi = fsmIter(payload, files, ++ payload ? RPMFI_ITER_READ_ARCHIVE : RPMFI_ITER_FWD, &di); + +- if (!skip) { +- int setmeta = 1; ++ if (fi == NULL) { ++ rc = RPMERR_BAD_MAGIC; ++ goto exit; ++ } + +- /* When touching we don't need any of this... */ +- if (action == FA_TOUCH) +- goto touch; ++ /* Process the payload */ ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ ++ /* ++ * Tricksy case: this file is a being skipped, but it's part of ++ * a hardlinked set and has the actual content linked with it. ++ * Write the content to the first non-skipped file of the set ++ * instead. ++ */ ++ if (fp->skip && firstlink && rpmfiArchiveHasContent(fi)) ++ fp = firstlink; ++ ++ if (!fp->skip) { ++ int mayopen = 0; ++ int fd = -1; ++ rc = ensureDir(plugins, rpmfiDN(fi), 0, ++ (fp->action == FA_CREATE), 0, &di.dirfd); + + /* Directories replacing something need early backup */ +- if (!suffix) { +- rc = fsmBackup(fi, action); ++ if (!rc && !fp->suffix && fp != firstlink) { ++ rc = fsmBackup(di.dirfd, fi, fp->action); + } ++ ++ /* Run fsm file pre hook for all plugins */ ++ if (!rc) ++ rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action); ++ if (rc) ++ goto setmeta; /* for error notification */ ++ + /* Assume file does't exist when tmp suffix is in use */ +- if (!suffix) { +- rc = fsmVerify(fpath, fi); ++ if (!fp->suffix) { ++ if (fp->action == FA_TOUCH) { ++ struct stat sb; ++ rc = fsmStat(di.dirfd, fp->fpath, 1, &sb); ++ } else { ++ rc = fsmVerify(di.dirfd, fp->fpath, fi); ++ } + } else { + rc = RPMERR_ENOENT; + } + +- if (S_ISREG(sb.st_mode)) { ++ /* See if the file was removed while our attention was elsewhere */ ++ 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 (fp->action == FA_TOUCH) ++ goto setmeta; ++ ++ if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- rc = fsmMkfile(fi, fpath, files, psm, nodigest, +- &setmeta, &firsthardlink, &firstlinkfile); ++ rc = fsmMkfile(di.dirfd, fi, fp, files, psm, nodigest, ++ &firstlink, &firstlinkfile, &di.firstdir, ++ &fd); + } +- } 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(di.dirfd, 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), di.dirfd, 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(di.dirfd, 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(di.dirfd, 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); ++setmeta: ++ /* Special files require path-based ops */ ++ mayopen = S_ISREG(fp->sb.st_mode) || S_ISDIR(fp->sb.st_mode); ++ if (!rc && fd == -1 && mayopen) { ++ int flags = O_RDONLY; ++ /* Only follow safe symlinks, and never on temporary files */ ++ if (fp->suffix) ++ flags |= AT_SYMLINK_NOFOLLOW; ++ fd = fsmOpenat(di.dirfd, fp->fpath, flags, ++ S_ISDIR(fp->sb.st_mode)); ++ if (fd < 0) ++ rc = RPMERR_OPEN_FAILED; + } +- } else if (firsthardlink >= 0 && 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 */ +- char *fn = rpmfilesFN(files, firsthardlink); +- rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm); +- wfd_close(&firstlinkfile); +- firsthardlink = -1; +- free(fn); +- } +- +- if (rc) { +- if (!skip) { +- /* XXX only erase if temp fn w suffix is in use */ +- if (suffix) { +- (void) fsmRemove(fpath, sb.st_mode); +- } +- errno = saveerrno; +- } +- } else { +- /* Notify on success. */ +- rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi)); +- +- if (!skip) { +- /* Backup file if needed. Directories are handled earlier */ +- if (suffix) +- rc = fsmBackup(fi, action); + +- if (!rc) +- rc = fsmCommit(&fpath, fi, action, suffix); ++ if (!rc && fp->setmeta) { ++ rc = fsmSetmeta(fd, di.dirfd, fp->fpath, ++ fi, plugins, fp->action, ++ &fp->sb, nofcaps); + } ++ ++ if (fd != firstlinkfile) ++ fsmClose(&fd); + } + ++ /* Notify on success. */ + if (rc) +- *failedFile = xstrdup(fpath); ++ *failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL); ++ else ++ rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi)); ++ fp->stage = FILE_UNPACK; ++ } ++ fi = fsmIterFini(fi, &di); + +- /* Run fsm file post hook for all plugins */ +- rpmpluginsCallFsmFilePost(plugins, fi, fpath, +- sb.st_mode, action, rc); +- fpath = _free(fpath); ++ if (!rc && fx < 0 && fx != RPMERR_ITER_END) ++ rc = fx; ++ ++ /* If all went well, commit files to final destination */ ++ fi = fsmIter(NULL, files, RPMFI_ITER_FWD, &di); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ ++ if (!fp->skip) { ++ if (!rc) ++ rc = ensureDir(NULL, rpmfiDN(fi), 0, 0, 0, &di.dirfd); ++ ++ /* Backup file if needed. Directories are handled earlier */ ++ if (!rc && fp->suffix) ++ rc = fsmBackup(di.dirfd, fi, fp->action); ++ ++ if (!rc) ++ rc = fsmCommit(di.dirfd, &fp->fpath, fi, fp->action, fp->suffix); ++ ++ if (!rc) ++ fp->stage = FILE_COMMIT; ++ else ++ *failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL); ++ ++ /* Run fsm file post hook for all plugins for all processed files */ ++ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action, rc); ++ } ++ } ++ fi = fsmIterFini(fi, &di); ++ ++ /* On failure, walk backwards and erase non-committed files */ ++ if (rc) { ++ fi = fsmIter(NULL, files, RPMFI_ITER_BACK, &di); ++ while ((fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ ++ /* If the directory doesn't exist there's nothing to clean up */ ++ if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd)) ++ continue; ++ ++ if (fp->stage > FILE_NONE && !fp->skip) { ++ (void) fsmRemove(di.dirfd, 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 = fsmIterFini(fi, &di); + Fclose(payload); + free(tid); +- free(fpath); ++ for (int i = 0; i < fc; i++) ++ free(fdata[i].fpath); ++ free(fdata); + + return rc; + } +@@ -1041,32 +1113,42 @@ + int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, + rpmpsm psm, char ** failedFile) + { +- rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK); ++ struct diriter_s di = { -1, -1 }; ++ rpmfi fi = fsmIter(NULL, files, RPMFI_ITER_BACK, &di); + 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)); ++ ++ if (XFA_SKIPPING(fp->action)) ++ continue; ++ ++ fp->fpath = fsmFsPath(fi, NULL); ++ /* If the directory doesn't exist there's nothing to clean up */ ++ if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd)) ++ continue; ++ ++ rc = fsmStat(di.dirfd, 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); ++ rc = fsmBackup(di.dirfd, 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(di.dirfd, fp->fpath, fp->sb.st_mode); + + /* + * Missing %ghost or %missingok entries are not errors. +@@ -1091,20 +1173,20 @@ + 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 = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL); + + if (rc == 0) { + /* Notify on success. */ +@@ -1112,11 +1194,12 @@ + rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi); + rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount); + } +- fpath = _free(fpath); + } + +- free(fpath); +- rpmfiFree(fi); ++ for (int i = 0; i < fc; i++) ++ free(fdata[i].fpath); ++ free(fdata); ++ fsmIterFini(fi, &di); + + return rc; + } diff --git a/rpm.spec b/rpm.spec index c105920..a9629f9 100644 --- a/rpm.spec +++ b/rpm.spec @@ -32,7 +32,7 @@ %global rpmver 4.14.3 #global snapver rc2 -%global rel 27 +%global rel 28 %global srcver %{version}%{?snapver:-%{snapver}} %global srcdir %{?snapver:testing}%{!?snapver:%{name}-%(echo %{version} | cut -d'.' -f1-2).x} @@ -119,6 +119,11 @@ Patch165: rpm-4.16.1.3-rpm2archive-error-handling.patch Patch166: rpm-4.14.3-rpm2archive-nocompression.patch Patch167: rpm-4.14.3-rpm2archive-parse-popt-options.patch Patch168: rpm-4.14.3-rpm2archive-Don-t-print-usage.patch +# Backport fsm to fix CVEs +Patch169: 0001-Eliminate-code-duplication-from-rpmfiNext.patch +Patch170: 0001-Add-optional-callback-on-directory-changes-during-rp.patch +Patch171: 0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch +Patch172: 0001-Use-file-state-machine-from-rpm-4.19.patch # Python 3 string API sanity Patch500: 0001-In-Python-3-return-all-our-string-data-as-surrogate-.patch @@ -701,6 +706,10 @@ make check || cat tests/rpmtests.log %doc doc/librpm/html/* %changelog +* Fri Oct 13 2023 Florian Festi - 4.14.3-28 +- Backport file handling code from rpm-4.19 to fix CVE-2021-35937, + CVE-2021-35938 and CVE-2021-35939 + * Tue Sep 26 2023 Lumír Balhar - 4.14.3-27 - Make brp-python-bytecompile script compatible with Python 3.10+ Resolves: RHEL-6423