Fedora Account System
Red Hat Associate
Red Hat Customer
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