Control: tags -1 + fixed-upstream patch Hi,
Upstream issue #4314 was closed on 2026-09-03 by: 2a11eb3e4e51094082e0123aebd797ec051dc094 "Prevent command injection via rpmbuild -t" (Michal Domonkos) https://www.google.com/url?q=https://github.com/rpm-software-management/rpm/commit/2a11eb3e4e51094082e0123aebd797ec051dc094&source=gmail&ust=1793790385674000&sa=E The fix: - builds the destination path with basename(3) + rasprintf() instead of rpmExpand("%{_specdir}/%{basename:", tarbuf, "}"), so the member name is no longer macro-expanded in getTarSpec(); - in parseSpec(), replaces rpmGetPath(specFile, NULL) with rpmCleanPath(xstrdup(specFile)), removing the second expansion; - adds a regression test (tests/ https://www.google.com/url?q=http://rpmbuild.at&source=gmail&ust=1793790385674000&sa=E, "rpmbuild -ts <spec> (evil)"). It is on master only; it is not on the rpm-6.1.x or rpm-6.0.x branches yet. What I checked (static only): - rpm-6.1.0 (sid/forky, 6.1.0-1): the vulnerable line is present at tools/rpmbuild.cc:423. The patch applies with patch -p1 with no fuzz (the https://www.google.com/url?q=http://rpmbuild.at&source=gmail&ust=1793790385674000&sa=E hunk has a -19 line offset). Debian's only patch, macro.patch, touches https://www.google.com/url?q=http://macros.in&source=gmail&ust=1793790385674000&sa=E only, so there is no conflict. - rpm-4.20.1 (trixie): the same code is in tools/rpmbuild.c:413 and build/parseSpec.c. The code hunks apply after renaming .cc to .c (offsets -10/-3, no fuzz). <libgen.h> is already included there. - rpm-4.18.0 (bookworm): the same expression is in rpmbuild.c:421, and parseSpec.c:902 uses rpmGetPath(specFile, NULL). Not done: I did not build the patched package or run the testsuite (that needs the full rpm-sequoia/cmake build environment), and I did not run the reproducer. Related: master also has the follow-up 7a4111bc5d ("Prevent command injection via rpmbuild -t filename", Fixes: CVE-2026-84837, CVE-2026-84838, i.e. #1146602). It depends on intermediate commits and does not apply directly to 6.1.0, so it is a separate backport. The upstream patch is attached. Regards
From 2a11eb3e4e51094082e0123aebd797ec051dc094 Mon Sep 17 00:00:00 2001 From: Michal Domonkos <[email protected]> Date: Wed, 26 Aug 2026 10:53:12 +0200 Subject: [PATCH] Prevent command injection via rpmbuild -t When running in -t mode, the spec member name in the passed tarball will get macro-expanded twice: first when constructing the target filename in getTarSpec() and then in parseSpec(). This allows a crafted tarball to execute arbitrary code when the user just runs rpmbuild -ts or similar. Luckily, neither of these expansions is necessary, they are only done as a convenience and/or historical artifact. The former in getTarSpec() was only added recently via commit dfffedb5ebe46e039c0313b3d2fb939dfc63efd6 and can easily be split into a basename(3) call so that the member name is not included in the expansion, effectively reverting that part of the original commit. The latter seems to be only used as a way to normalize the spec filename stored in the rpmSpec struct and can be replaced by a mere rpmCleanPath(). Make both changes, and add a corresponding test, too. Fixes: CVE-2026-78367, #4314 --- build/parseSpec.cc | 2 +- tests/data/misc/CVE-2026-78367.py | 30 ++++++++++++++++++++++++++++++ tests/rpmbuild.at | 21 +++++++++++++++++++++ tools/rpmbuild.cc | 9 +++++++-- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100755 tests/data/misc/CVE-2026-78367.py diff --git a/build/parseSpec.cc b/build/parseSpec.cc index 4df32f2d51..4ed90ea0ea 100644 --- a/build/parseSpec.cc +++ b/build/parseSpec.cc @@ -1311,7 +1311,7 @@ static rpmSpec parseSpec(const char *specFile, rpmSpecFlags flags, /* Set up a new Spec structure with no packages. */ spec = newSpec(); - spec->specFile = rpmGetPath(specFile, NULL); + spec->specFile = rpmCleanPath(xstrdup(specFile)); pushOFI(spec, spec->specFile); /* If explicit --buildroot was passed, grab hold of it */ if (buildRoot) diff --git a/tests/data/misc/CVE-2026-78367.py b/tests/data/misc/CVE-2026-78367.py new file mode 100755 index 0000000000..8824e149bc --- /dev/null +++ b/tests/data/misc/CVE-2026-78367.py @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# Generate a PoC archive for CVE-2026-78367 +# Adapted from https://github.com/rpm-software-management/rpm/pull/4317 + +import os, tarfile, io + +spec_content = b"""\ +Name: poc +Version: 1.0 +Release: 1 +Summary: PoC for getTarSpec member-name injection +License: MIT +BuildArch: noarch + +%description +%{summary}. + +%files +""" + +member_name = ( + "%{lua:io.open(os.getenv('RPM_POC_PATH'),'w')" + ":write('gotcha'):close()}" + ".spec" +) + +with tarfile.open("evil.tar.gz", "w:gz") as tf: + info = tarfile.TarInfo(name=member_name) + info.size = len(spec_content) + tf.addfile(info, io.BytesIO(spec_content)) diff --git a/tests/rpmbuild.at b/tests/rpmbuild.at index 1ac7c21579..963d17f5df 100644 --- a/tests/rpmbuild.at +++ b/tests/rpmbuild.at @@ -885,6 +885,27 @@ runroot rpm -q --qf "%{packager}\n" /build/SRPMS/hello-2.0-1.src.rpm []) RPMTEST_CLEANUP +RPMTEST_SETUP_RW([rpmbuild -ts <spec> (evil)]) +AT_KEYWORDS([build]) + +runroot /data/misc/CVE-2026-78367.py + +RPMTEST_CHECK([ +runroot --setenv RPM_POC_PATH /tmp/gotcha \ + rpmbuild --quiet -ts evil.tar.gz +], +[0], +[], +[]) + +RPMTEST_CHECK([ +test -f $RPMTEST/tmp/gotcha +], +[1], +[], +[]) +RPMTEST_CLEANUP + # weird filename survival test RPMTEST_SETUP_RW([rpmbuild package with weird filenames]) AT_KEYWORDS([build]) diff --git a/tools/rpmbuild.cc b/tools/rpmbuild.cc index a32cda7bc8..04a419f802 100644 --- a/tools/rpmbuild.cc +++ b/tools/rpmbuild.cc @@ -369,6 +369,8 @@ static int isSpecFile(const char * specfile) static char * getTarSpec(const char *arg) { char *specFile = NULL; + char *specDir = NULL; + char *specBase = NULL; char *specFinal = NULL; const char **spec; char tarbuf[BUFSIZ]; @@ -418,9 +420,11 @@ static char * getTarSpec(const char *arg) if (!gotspec) { rpmlog(RPMLOG_ERR, _("Failed to read spec file from %s\n"), arg); } else { + specDir = rpmExpand("%{_specdir}", NULL); + specBase = basename(tarbuf); /* remove trailing \n */ - tarbuf[strlen(tarbuf)-1] = '\0'; - specFinal = rpmExpand("%{_specdir}/%{basename:", tarbuf, "}", NULL); + specBase[strlen(specBase)-1] = '\0'; + rasprintf(&specFinal, "%s/%s", specDir, specBase); if (rename(specFile, specFinal)) { rpmlog(RPMLOG_ERR, _("Failed to rename %s to %s: %m\n"), specFile, specFinal); @@ -431,6 +435,7 @@ static char * getTarSpec(const char *arg) exit: free(specFile); + free(specDir); Fclose(fd); return specFinal; }

