From 13f70e3710b2df49a923cc6450ff4a8f86e65666 Mon Sep 17 00:00:00 2001 Message-Id: <13f70e3710b2df49a923cc6450ff4a8f86e65666.1555050140.git.pmatilai@redhat.com> From: Panu Matilainen Date: Wed, 20 Mar 2019 12:38:00 +0200 Subject: [PATCH] Fix FA_TOUCH on files with suid/sgid bits and/or capabilities FA_TOUCH used to set suffix to "" instead of NULL which causes fsmCommit() to rename the file onto itself, which is a bit dumb but mostly harmless with regular permission. On suid/sgid/capabilities we strip any extra privileges on rename to make sure hardlinks are neutered, and because rename occurs after other permissions etc setting, on FA_TOUCH those extra privileges are stripped and much brokenness will follow. A more minimal fix would be a strategically placed strcmp(), but NULL is what the rest of the fsm expects for no suffix and differentiating between empty and NULL suffix is too subtle for its own good as witnessed here. So now, NULL suffix is no suffix again and the rest of the code will do the right thing except where related to creation, and creation is what FA_TOUCH wont do so lets just explicitly skip it and restore the original code otherwise. The goto is ugly but reindenting gets even uglier, shrug. Add a test-case to go with it. This has been broken since its introduction in commit 79ca74e15e15c1d91a9a31a9ee90abc91736f390 so all current 4.14.x versions are affected. --- lib/fsm.c | 17 ++++++++++---- tests/data/SPECS/replacetest.spec | 2 +- tests/rpmverify.at | 38 ++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/fsm.c b/lib/fsm.c index 8eb2c185c..432bcbd90 100644 --- a/lib/fsm.c +++ b/lib/fsm.c @@ -898,12 +898,12 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, action = rpmfsGetAction(fs, rpmfiFX(fi)); skip = XFA_SKIPPING(action); - suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; if (action != FA_TOUCH) { - fpath = fsmFsPath(fi, suffix); + suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; } else { - fpath = fsmFsPath(fi, ""); + suffix = NULL; } + fpath = fsmFsPath(fi, suffix); /* Remap file perms, owner, and group. */ rc = rpmfiStat(fi, 1, &sb); @@ -926,6 +926,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, if (!skip) { int setmeta = 1; + /* When touching we don't need any of this... */ + if (action == FA_TOUCH) + goto touch; + /* Directories replacing something need early backup */ if (!suffix) { rc = fsmBackup(fi, action); @@ -934,7 +938,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, if (!suffix) { rc = fsmVerify(fpath, fi); } else { - rc = (action == FA_TOUCH) ? 0 : RPMERR_ENOENT; + rc = RPMERR_ENOENT; } if (S_ISREG(sb.st_mode)) { @@ -970,11 +974,14 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, if (!IS_DEV_LOG(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); } } 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); @@ -987,7 +994,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, if (rc) { if (!skip) { /* XXX only erase if temp fn w suffix is in use */ - if (suffix && (action != FA_TOUCH)) { + if (suffix) { (void) fsmRemove(fpath, sb.st_mode); } errno = saveerrno; diff --git a/tests/data/SPECS/replacetest.spec b/tests/data/SPECS/replacetest.spec index 54974567b..d5a1729d3 100644 --- a/tests/data/SPECS/replacetest.spec +++ b/tests/data/SPECS/replacetest.spec @@ -46,4 +46,4 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,%{user},%{grp},-) -/opt/* +%{?fileattr} /opt/* diff --git a/tests/rpmverify.at b/tests/rpmverify.at index 52ee2abfb..f7dd57531 100644 --- a/tests/rpmverify.at +++ b/tests/rpmverify.at @@ -575,3 +575,39 @@ ], []) AT_CLEANUP + +AT_SETUP([Upgraded verification with min_writes 5 (suid files)]) +AT_KEYWORDS([upgrade verify min_writes]) +AT_CHECK([ +RPMDB_CLEAR +RPMDB_INIT +tf="${RPMTEST}"/opt/foo +rm -rf "${tf}" "${tf}".rpm* +rm -rf "${TOPDIR}" + +for v in "1.0" "2.0"; do + runroot rpmbuild --quiet -bb \ + --define "ver $v" \ + --define "filetype file" \ + --define "filedata foo" \ + --define "fileattr %attr(2755,-,-)" \ + /data/SPECS/replacetest.spec +done + +runroot rpm -U /build/RPMS/noarch/replacetest-1.0-1.noarch.rpm +runroot rpm -Va --nouser --nogroup replacetest +runroot rpm -U \ + --define "_minimize_writes 1" \ + /build/RPMS/noarch/replacetest-2.0-1.noarch.rpm +runroot rpm -Va --nouser --nogroup replacetest +chmod 777 "${tf}" +runroot rpm -U \ + --oldpackage \ + --define "_minimize_writes 1" \ + /build/RPMS/noarch/replacetest-1.0-1.noarch.rpm +runroot rpm -Va --nouser --nogroup replacetest +], +[0], +[], +[]) +AT_CLEANUP -- 2.20.1