MANUALLY_VERIFIED_REPORT package: libsolv-0.7.33-2.el10 ------ [Security] Heap Buffer Overflow in repo_add_solv via Negative maxsize Summary: Heap buffer overflow in `repo_add_solv` when parsing attacker-controlled `.solv` files; large encoded `maxsize`/`allsize` header values can decode to negative signed `Id` values, leading to undersized heap allocation while a subsequent `fread` uses `DATA_READ_CHUNK` (8192) bytes. Requirements to exploit: Ability to supply a crafted `.solv` file that a victim processes with libsolv (directly or via a consumer such as `dumpsolv` or an application that calls `repo_add_solv` on untrusted input). Component affected: libsolv Version affected: <= 0.7.36 Version fixed (if any already): >= TBD CVSS: 6.5 (Medium) — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H Impact: Moderate (proposed). Per https://access.redhat.com/security/updates/classification this is memory corruption reachable via untrusted `.solv` ingestion and can at least cause a denial of service; it is not clearly "High" because an attacker typically needs the victim to process attacker-controlled input (configuration/user action) and reliable system compromise is not demonstrated here. This may be "Low" instead in product contexts where the vulnerable path is not used by default, is only reachable via uncommon workflows, or is effectively mitigated (e.g., only trusted solvdb is processed). Embargo: no Acknowledgement: Aisle Research Steps to reproduce if available: See "Reproduction steps" below. Mitigation if available: Prefer only consuming trusted `.solv` / solvdb inputs; avoid parsing untrusted `.solv` files until patched. Original report: Hello libsolv maintainers, We believe that we have discovered a potential security vulnerability in `repo_add_solv` when parsing attacker-controlled `.solv` files. ### Vulnerability details `read_id` decodes into an unsigned value and returns `Id` (signed `int`), so large encoded values can become negative after conversion: ```c /* src/repo_solv.c */ static Id read_id(Repodata *data, Id max) { unsigned int x = 0; ... return x; } ``` In `repo_add_solv`, `maxsize` and `allsize` are read with `max=0` (no bounds check), then used for allocation and read length: ```c /* src/repo_solv.c */ maxsize = read_id(&data, 0); allsize = read_id(&data, 0); maxsize += 5; if (maxsize > allsize) maxsize = allsize; buf = solv_calloc(maxsize + DATA_READ_CHUNK + 4, 1); l = maxsize; if (l < DATA_READ_CHUNK) l = DATA_READ_CHUNK; if (l > allsize) l = allsize; if (!l || fread(buf, l, 1, data.fp) != 1) ``` If `maxsize` is negative, `solv_calloc(maxsize + 8192 + 4, 1)` can allocate a much smaller buffer, but `l` is then raised to `8192`, and `fread` writes `8192` bytes into that undersized heap buffer. Most relevant CWEs: - `CWE-122` (Heap-based Buffer Overflow): direct overflow sink. - `CWE-20` (Improper Input Validation): negative header fields are accepted. - `CWE-195` (Signed to Unsigned Conversion Error): signed `int` values flow into allocation sizing. ### Reproduction steps 1. Build libsolv with ASAN (or run a consumer binary that calls `repo_add_solv` on `.solv` input, e.g. `dumpsolv`). 2. Run the parser on this file (`dumpsolv crafted.solv` or equivalent). ### Crash: [root@c28a4ffb0823 workspace]# ./build-asan/tools/dumpsolv ./vuln_1_101_1_negative_maxsize.solv ================================================================= ==542==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5020000000b1 at pc 0x00000041fb3c bp 0x7ffffffc5fd0 sp 0x7ffffffc5798 WRITE of size 8192 at 0x5020000000b1 thread T0 #0 0x00000041fb3b (/workspace/build-asan/tools/dumpsolv+0x41fb3b) (BuildId: 3a1e71d74bd4d38c896ffc899393aedf86bf1cfc) #1 0x7fffff662147 (/workspace/build-asan/src/libsolv.so.1+0x57147) (BuildId: ebfff12c035b97f95b2d532a1d6d237ac31e770a) #2 0x0000004e45fe (/workspace/build-asan/tools/dumpsolv+0x4e45fe) (BuildId: 3a1e71d74bd4d38c896ffc899393aedf86bf1cfc) #3 0x7fffff2f0447 (/lib64/libc.so.6+0x3447) (BuildId: dae6ae6929d69dca842288f5300af5a33d1bdcd7) #4 0x7fffff2f050a (/lib64/libc.so.6+0x350a) (BuildId: dae6ae6929d69dca842288f5300af5a33d1bdcd7) #5 0x000000401514 (/workspace/build-asan/tools/dumpsolv+0x401514) (BuildId: 3a1e71d74bd4d38c896ffc899393aedf86bf1cfc) 0x5020000000b1 is located 0 bytes after 1-byte region [0x5020000000b0,0x5020000000b1) allocated by thread T0 here: #0 0x0000004a1343 (/workspace/build-asan/tools/dumpsolv+0x4a1343) (BuildId: 3a1e71d74bd4d38c896ffc899393aedf86bf1cfc) #1 0x7fffff6b6be6 (/workspace/build-asan/src/libsolv.so.1+0xabbe6) (BuildId: ebfff12c035b97f95b2d532a1d6d237ac31e770a) #2 0x7fffff662100 (/workspace/build-asan/src/libsolv.so.1+0x57100) (BuildId: ebfff12c035b97f95b2d532a1d6d237ac31e770a) #3 0x0000004e45fe (/workspace/build-asan/tools/dumpsolv+0x4e45fe) (BuildId: 3a1e71d74bd4d38c896ffc899393aedf86bf1cfc) #4 0x7fffff2f0447 (/lib64/libc.so.6+0x3447) (BuildId: dae6ae6929d69dca842288f5300af5a33d1bdcd7) #5 0x7fffff2f050a (/lib64/libc.so.6+0x350a) (BuildId: dae6ae6929d69dca842288f5300af5a33d1bdcd7) #6 0x000000401514 (/workspace/build-asan/tools/dumpsolv+0x401514) (BuildId: 3a1e71d74bd4d38c896ffc899393aedf86bf1cfc) SUMMARY: AddressSanitizer: heap-buffer-overflow (/workspace/build-asan/tools/dumpsolv+0x41fb3b) (BuildId: 3a1e71d74bd4d38c896ffc899393aedf86bf1cfc) Shadow bytes around the buggy address: 0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x502000000000: fa fa 00 00 fa fa 04 fa fa fa 00 00 fa fa 04 fa =>0x502000000080: fa fa 04 fa fa fa[01]fa fa fa fa fa fa fa fa fa 0x502000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x502000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x502000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x502000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa ### Proposed fix ```diff diff --git a/src/repo_solv.c b/src/repo_solv.c @@ maxsize = read_id(&data, 0); allsize = read_id(&data, 0); + if (maxsize < 0 || allsize < 0) + { + data.error = pool_error(pool, SOLV_ERROR_CORRUPT, "negative data size in solv header"); + id = 0; + goto data_error; + } + if (maxsize > INT_MAX - 5) + { + data.error = pool_error(pool, SOLV_ERROR_OVERFLOW, "data size overflow in solv header"); + id = 0; + goto data_error; + } maxsize += 5; /* so we can read the next schema of an array */ if (maxsize > allsize) maxsize = allsize; @@ if (keydepth) data.error = pool_error(pool, SOLV_ERROR_EOF, "unexpected EOF, depth = %d", keydepth); @@ +data_error: ``` Best wishes, Aisle Research ------ This report was generated using AI technology. Always review AI-generated content prior to use