Bug 2478703 (CVE-2026-92823)

Summary: CVE-2026-92823 libmodulemd: libmodulemd: missing decompressed-size limit when parsing compressed YAML
Product: [Other] Security Response Reporter: OSIDB Bzimport <bzimport>
Component: vulnerabilityAssignee: Product Security DevOps Team <prodsec-dev>
Status: NEW --- QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: unspecifiedCC: akhatavk, aos-team-art-private, asdas, dpaolell, jdelft, jupierce, lgarciaa, mbiarnes, ppalepu, ppostler, prdhamdh, rhel-process-autobot, security-response-team, sghai, sidsharm, suppawar, vlaad, watson-tool-maintainers
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: ---
Doc Text:
A flaw was found in libmodulemd. In builds with rpmio support enabled, compressed modulemd YAML is decompressed through a custom read handler that does not enforce a cumulative decompressed-size limit before parsing. An attacker who can supply a highly compressible compressed YAML file can cause excessive CPU and memory consumption, resulting in a denial of service.
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description OSIDB Bzimport 2026-05-18 03:42:55 UTC
AI_ONLY_REPORT
package: libmodulemd-2.15.2-7.1.hum1
------
Summary: Compressed-input path lacks decompressed-size limits (potential  
decompression bomb DoS): In rpmio-enabled builds, parsing  
attacker-controlled compressed YAML can trigger excessive CPU and memory  
consumption because decompressed bytes are not globally limited before  
parsing and subdocument re-emission.
Requirements to exploit: A vulnerable consumer must be built with rpmio  
support enabled or autodetected with librpm present, and it must parse  
attacker-controlled compressed YAML input. The attacker then supplies a  
highly compressible payload whose decompressed body is large enough to  
exhaust CPU and/or memory.
Component affected: `libmodulemd-2.15.2-7.1.hum1`; compressed YAML parsing  
path in `modulemd_module_index_update_from_file_ext()`,  
`compressed_stream_read_fn()`, YAML re-emission, and  
`ModulemdSubdocumentInfo` content duplication.
Version affected: `libmodulemd-2.15.2-7.1.hum1` (rpmio-enabled builds that  
parse attacker-controlled compressed YAML input)
Patch available: no released package fix established; proposed patch  
included below
Version fixed: unknown
Upstream coordination: Initial notification with this report.
CVSS: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - 5.5 (MEDIUM)
AV:L - The attacker must get a crafted file processed by a local  
libmodulemd consumer; the library itself is not a network-facing service.
AC:L - Exploitation requires only a highly compressible YAML payload  
with a large decompressed body and no special race or heap shaping.
PR:N - No prior privileges are required to create or provide the  
malicious input.
UI:N - Once a vulnerable consumer accepts the supplied file for parsing,  
no further user action is required to trigger the exhaustion condition.
S:U - The impact is confined to the consuming process and its own  
resources.
C:N - The available evidence does not show unauthorized disclosure of  
data.
I:N - The available evidence does not show unauthorized modification of  
data or control flow.
A:H - The process can consume large amounts of CPU and memory and may  
terminate or be killed by the system.
Impact: Moderate. Under Red Hat's severity guidance, this is best  
classified as Moderate rather than Important because the issue is  
availability-only and exploitation depends on a specific build  
configuration and on a consumer accepting attacker-controlled compressed  
metadata. It can still cause meaningful service disruption where that path  
is reachable, so Low would understate the risk.
Embargo: no
Reason: This is a constrained, availability-only resource-exhaustion  
issue with straightforward operational mitigations, and the available  
evidence does not indicate confidentiality loss, integrity loss, privilege  
gain, or code execution.
Acknowledgement: Aisle Research
Vulnerability Details: In rpmio-enabled builds, compressed files are routed  
through rpmio and a custom read handler. The handler forwards decompressed  
bytes from `Fread()` without a cumulative decompressed-size limit, and  
downstream parsing re-emits full YAML subdocuments into growing heap  
buffers and duplicates them into `ModulemdSubdocumentInfo`. This creates a  
compression-amplified resource-exhaustion path: a comparatively small  
compressed file can expand into large allocations and heavy CPU use before  
parsing or validation fails. The available evidence supports a  
denial-of-service condition only; it does not show memory corruption, code  
execution, or direct confidentiality/integrity impact.
```c
/* modulemd/modulemd-module-index.c */
return modulemd_module_index_update_from_custom (
self, compressed_stream_read_fn, rpmio_fd, strict, failures, error);
/* modulemd/modulemd-compression.c */
gint
compressed_stream_read_fn (void *data,
unsigned char *buffer,
size_t size,
size_t *size_read)
{
FD_t rpmio_fd = (FD_t)data;
ssize_t read = Fread (buffer, sizeof (*buffer), size, rpmio_fd);
...
*size_read = read;
return 1;
}
/* modulemd/modulemd-yaml-util.c */
yaml_string->str = g_realloc_n (yaml_string->str, total, sizeof (char));
/* modulemd/modulemd-subdocument-info.c */
self->contents = g_strdup (contents);
```
Large uncompressed YAML can also be expensive to process, but the  
compressed-input path lowers the delivery cost of oversized logical input  
because the expanded size is not bounded before these downstream  
allocations occur.
Steps to reproduce:
1. Build or use libmodulemd with rpmio support enabled.
2. Create a highly compressible YAML payload with a very large decompressed  
body, then compress it with a format accepted by the rpmio-enabled build,  
such as `.xz` or `.gz`.
3. Parse the file with `modulemd-validator <payload>.xz` or through the API  
path `modulemd_module_index_update_from_file()`.
4. Monitor CPU and resident memory with `/usr/bin/time -v` or an equivalent  
tool and observe sustained resource growth, potentially ending in OOM or  
process termination.
Mitigation: Until a fix is shipped, avoid parsing attacker-controlled  
compressed module metadata in rpmio-enabled builds. Where compressed input  
is required, decompress outside the process under strict size quotas or  
reject files whose decompressed size exceeds a conservative limit before  
passing data to libmodulemd. If compressed parsing is not needed, building  
without rpmio support removes the affected path.
Proposed Fix: Track cumulative decompressed bytes in the rpmio read context  
and abort once a maximum is exceeded, ideally with a configurable limit.  
This bounds the compressed-input path before further YAML emission and  
duplication can drive large allocations.
```diff
diff --git a/modulemd/include/private/modulemd-compression-private.h  
b/modulemd/include/private/modulemd-compression-private.h
@@
+typedef struct _ModulemdCompressedReadCtx
+{
+#ifdef HAVE_RPMIO
+  FD_t fd;
+#endif
+  size_t max_decompressed_bytes;
+  size_t total_decompressed_bytes;
+} ModulemdCompressedReadCtx;
diff --git a/modulemd/modulemd-compression.c  
b/modulemd/modulemd-compression.c
@@
gint
compressed_stream_read_fn (void *data,
unsigned char *buffer,
size_t size,
size_t *size_read)
{
 FD_t rpmio_fd = (FD_t)data;

 ssize_t read = Fread (buffer, sizeof (*buffer), size, rpmio_fd);
+  ModulemdCompressedReadCtx *ctx = (ModulemdCompressedReadCtx *)data;
+  ssize_t read = Fread (buffer, sizeof (*buffer), size, ctx->fd);
+  *size_read = 0;
@@

     g_warning ("Got error [%d] reading the file", Ferror (rpmio_fd));
+      g_warning ("Got error [%d] reading the file", Ferror (ctx->fd));
        return 0;
      }
+  if (ctx->total_decompressed_bytes > ctx->max_decompressed_bytes ||
+      (size_t)read > (ctx->max_decompressed_bytes -  
ctx->total_decompressed_bytes))
+    {
+      g_warning ("Decompressed input exceeded limit (%zu bytes)",
+                 ctx->max_decompressed_bytes);
+      return 0;
+    }
+  ctx->total_decompressed_bytes += (size_t)read;

 *size_read = read;
+  *size_read = (size_t)read;
    return 1;
  }
diff --git a/modulemd/modulemd-module-index.c  
b/modulemd/modulemd-module-index.c
@@

 return modulemd_module_index_update_from_custom (

   self, compressed_stream_read_fn, rpmio_fd, strict, failures, error);
+  ModulemdCompressedReadCtx read_ctx = {
+    .fd = rpmio_fd,
+    .max_decompressed_bytes = 64 * 1024 * 1024, /* ideally configurable */
+    .total_decompressed_bytes = 0,
+  };
+  return modulemd_module_index_update_from_custom (
+    self, compressed_stream_read_fn, &read_ctx, strict, failures, error);
```


------
This report was generated using AI technology. Always review AI-generated  
content prior to use