Bug 2470977 (CVE-2026-95519) - CVE-2026-95519 rpm: Code Execution via Macro Expansion of Manifest Entries in `rpmgi` (`-q -p` / verify manifest flows)
Summary: CVE-2026-95519 rpm: Code Execution via Macro Expansion of Manifest Entries in...
Keywords:
Status: NEW
Alias: CVE-2026-95519
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Product Security
QA Contact:
URL:
Whiteboard:
Depends On: 2540029
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-05-11 20:55 UTC by OSIDB Bzimport
Modified: 2026-09-24 12:39 UTC (History)
3 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-05-11 20:55:15 UTC
AI_ONLY_REPORT
package: rpm-6.0.1-5.1.hum1
------
Summary: Code Execution via Macro Expansion of Manifest Entries in `rpmgi`  
(`-q -p` / verify manifest flows): attacker-controlled manifest entries are  
macro-expanded before open, allowing `%()` shell command execution in the  
caller context when non-package files are processed as manifests.
Requirements to exploit: An attacker must supply a crafted manifest file  
and cause a user or automation to invoke `rpm -q -p` or a similar  
manifest-processing package-file flow on it without `--nomanifest`. No  
prior privileges are required beyond supplying the manifest; the resulting  
command executes with the privileges of the `rpm` process.
Component affected: `rpm-6.0.1-5.1.hum1`, primarily `lib/rpmgi.cc`  
`rpmgiOpen()`, reached from manifest parsing in `lib/manifest.cc` during  
package-file query/verify handling.
Version affected: `rpm-6.0.1-5.1.hum1`
Patch available: no released package fix established; proposed patch  
included below
Version fixed: unknown
Upstream coordination: Not notified.
CVSS: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - 7.8 (HIGH)
AV:L - Exploitation occurs through a local manifest file processed by  
the `rpm` CLI.
AC:L - A single crafted manifest entry using RPM macro syntax is  
sufficient; no race or uncommon precondition is required.
PR:N - The attacker does not need an account on the target system;  
delivering the crafted manifest is enough.
UI:R - A user or automation must run an affected package-file  
query/verify flow on the attacker-controlled manifest.
S:U - Exploitation affects the same security scope as the `rpm` process  
that parses the manifest.
C:H - Successful command execution can read data available to the  
invoking context.
I:H - Successful command execution can modify files or system state  
available to the invoking context.
A:H - Successful command execution can disrupt or destroy data or  
service availability available to the invoking context.
Impact: Important. This issue requires user-assisted processing of  
attacker-supplied manifest content, so it does not meet the criteria for  
Critical impact. However, once triggered it can execute arbitrary commands  
in the `rpm` caller context, which is sufficient to fully compromise  
confidentiality, integrity, and availability for that account. If the  
affected workflow is run by an administrator or privileged automation, the  
consequence is correspondingly severe.
Embargo: no
Reason: This is a user-assisted local CLI issue with a straightforward  
operational mitigation (`--nomanifest`) and no silent remote attack surface.
Acknowledgement: Aisle Research
Vulnerability Details: `rpm` package-file query handling constructs an  
`rpmgi` iterator for `-p` inputs. When the supplied file is not recognized  
as an RPM header, the iterator falls back to manifest handling. Manifest  
entries are read as plaintext strings and later reopened through  
`rpmgiOpen()`, which macro-expands the path before `Fopen()`:
```cpp
static FD_t rpmgiOpen(const char * path, const char * fmode)
{
char * fn = rpmExpand(path, NULL);
FD_t fd = Fopen(fn, fmode);
if (fd == NULL || Ferror(fd)) {
rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), fn, Fstrerror(fd));
if (fd != NULL) (void) Fclose(fd);
fd = NULL;
}
free(fn);
return fd;
}
```
RPM macro expansion supports executable forms, including `%()` shell  
escapes. As a result, a manifest entry such as `%(id >  
/tmp/rpm_manifest_poc)` is evaluated before the open attempt, and the  
embedded command runs in the caller context. This is unexpected for  
`rpm-manifest(5)`, which documents manifests as plaintext glob/path entries  
naming RPM packages or other manifests rather than executable macro content.
The concrete reproduction below demonstrates the issue in `-q -p`. Query  
and verify share the same package-file source handling around `rpmgi`, so  
manifest-processing verify flows should be treated as relevant as well, but  
the directly reproduced case here is query mode.
Steps to reproduce:
1. Create a proof-of-concept manifest:
```bash
cat > /tmp/poc.mft <<'EOF'
%(id > /tmp/rpm_manifest_poc)
EOF
```
2. Run package-file query mode on the manifest:
```bash
rpm -q -p /tmp/poc.mft
```
3. Confirm the side effect:
```bash
cat /tmp/rpm_manifest_poc
```
4. A successful reproduction creates `/tmp/rpm_manifest_poc` containing the  
output of `id` from the invoking user context.
5. Control check: disable manifest processing and rerun:
```bash
rpm -q -p --nomanifest /tmp/poc.mft
```
6. With `--nomanifest`, the manifest should not be processed and the  
side-effect file should not be created by this invocation.
Mitigation: Until a fix is available, do not run `rpm` package-file query  
or verify operations on untrusted manifest files. Use `--nomanifest` when  
handling untrusted inputs, and if manifest support is required in  
automation, pre-validate entries so that only expected literal paths or  
globs are accepted and macro syntax is rejected.
Proposed Fix: Stop macro-expanding manifest-derived paths in `rpmgiOpen()`  
and open the original path string directly.
```diff
diff --git a/lib/rpmgi.cc b/lib/rpmgi.cc
— a/lib/rpmgi.cc
+++ b/lib/rpmgi.cc
@@ -49,8 +49,7 @@
static FD_t rpmgiOpen(const char * path, const char * fmode)
{
   char * fn = rpmExpand(path, NULL);

   FD_t fd = Fopen(fn, fmode);
+    FD_t fd = Fopen(path, fmode);


if (fd == NULL || Ferror(fd)) {
       rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), fn,  
Fstrerror(fd));
+        rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), path,  
Fstrerror(fd));
          if (fd != NULL) (void) Fclose(fd);
          fd = NULL;
      }

   free(fn);


return fd;
}
```
------
This report was generated using AI technology. Always review AI-generated  
content prior to use

Comment 1 Christopher Lusk 2026-06-26 17:33:22 UTC
Tracker filed for rhel-10.3: https://issues.redhat.com/browse/RHEL-189189


Note You need to log in before you can comment on or make changes to this bug.