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;
 }

Reply via email to