Bug 2554154
| Summary: | CVE-2026-102010 gcc: gcc: Denial of Service via use-after-free in binary heap erase_if [fedora-all] | ||
|---|---|---|---|
| Product: | [Fedora] Fedora | Reporter: | Marco Benatto <mbenatto> |
| Component: | gcc | Assignee: | Jakub Jelinek <jakub> |
| Status: | CLOSED ERRATA | QA Contact: | Fedora Extras Quality Assurance <extras-qa> |
| Severity: | medium | Docs Contact: | |
| Priority: | medium | ||
| Version: | rawhide | CC: | dmalcolm, fweimer, jakub, jlaw, josmyers, jwakely, mcermak, mpolacek, msebor, nickc, nixuser, sipoyare |
| Target Milestone: | --- | Keywords: | Security, SecurityTracking |
| Target Release: | --- | ||
| Hardware: | Unspecified | ||
| OS: | Unspecified | ||
| Whiteboard: | {"flaws": ["96427eea-700b-41db-ab1c-d026fda3caef"]} | ||
| Fixed In Version: | gcc-16.2.1-3.fc45 gcc-16.2.1-3.fc44 | Doc Type: | --- |
| Doc Text: | Story Points: | --- | |
| Clone Of: | Environment: | ||
| Last Closed: | 2026-10-31 18:53:46 UTC | Type: | --- |
| Regression: | --- | Mount Type: | --- |
| Documentation: | --- | CRM: | |
| Verified Versions: | Category: | --- | |
| oVirt Team: | --- | RHEL 7.3 requirements from Atomic Host: | |
| Cloudforms Team: | --- | Target Upstream Version: | |
| Embargoed: | |||
| Bug Depends On: | |||
| Bug Blocks: | 2478395 | ||
FEDORA-2026-589138efb1 (gcc-16.2.1-3.fc45) has been submitted as an update to Fedora 45. https://bodhi.fedoraproject.org/updates/FEDORA-2026-589138efb1 FEDORA-2026-d732fb97a0 (gcc-16.2.1-3.fc44) has been submitted as an update to Fedora 44. https://bodhi.fedoraproject.org/updates/FEDORA-2026-d732fb97a0 FEDORA-2026-d732fb97a0 has been pushed to the Fedora 44 testing repository. Soon you'll be able to install the update with the following command: `sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2026-d732fb97a0` You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2026-d732fb97a0 See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates. FEDORA-2026-589138efb1 has been pushed to the Fedora 45 testing repository. Soon you'll be able to install the update with the following command: `sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2026-589138efb1` You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2026-589138efb1 See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates. FEDORA-2026-589138efb1 (gcc-16.2.1-3.fc45) has been pushed to the Fedora 45 stable repository. If problem still persists, please make note of it in this bug report. FEDORA-2026-d732fb97a0 (gcc-16.2.1-3.fc44) has been pushed to the Fedora 44 stable repository. If problem still persists, please make note of it in this bug report. |
Disclaimer: Community trackers are created by Red Hat Product Security team on a best effort basis. Package maintainers are required to ascertain if the flaw indeed affects their package, before starting the update process. AI_ONLY_REPORT package: gcc-toolset-15-gcc-15.2.1-7.el10 ------ Summary: `__gnu_pbds::detail::binary_heap::erase_if` use-after-free and stale-pointer free in reallocation path: calling `erase_if` on a `binary_heap_tag`-backed pb_ds priority queue can leave the internal entry-array pointer dangling, leading to heap use-after-free during `make_heap()` and a later stale-pointer free during cleanup. Requirements to exploit: An attacker needs a path to make an application built from this SRPM instantiate `__gnu_pbds::priority_queue<..., binary_heap_tag>` and call `erase_if`; once that public API path is exercised, subsequent heap operations or object teardown can act on freed storage. Component affected: `gcc-toolset-15-gcc-15.2.1-7.el10`, `libstdc++-v3/include/ext/pb_ds/detail/binary_heap_/erase_fn_imps.hpp`, `__gnu_pbds::detail::binary_heap::erase_if` Version affected: `gcc-toolset-15-gcc-15.2.1-7.el10` 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:N/S:U/C:L/I:L/A:H - 7.8 (HIGH) AV:L - Exploitation requires reaching this local library API in a process using the affected pb_ds container path. AC:L - Once `binary_heap_tag` and `erase_if` are in use, the vulnerable sequence is straightforward to trigger. PR:N - No privileges are required beyond reaching the affected call path in the target process. UI:N - No separate user interaction is required. S:U - The resulting corruption stays within the security scope of the calling process. C:L - In-process memory misuse could expose some data, but broader disclosure is not established here. I:L - Heap corruption can alter in-process state, but arbitrary code execution is not demonstrated by the available evidence. A:H - Use-after-free and stale-pointer free conditions are likely to crash or seriously destabilize the affected process. Impact: Moderate. This is a real memory-corruption flaw with likely process-level availability impact and some in-process integrity or confidentiality risk, but exploitability is limited to consumers that opt into the pb_ds `binary_heap_tag` backend and invoke `erase_if` on that path. Under Red Hat's severity guidance, that narrower, non-default reachability fits Moderate more closely than Important or Critical. Embargo: no Reason: The issue is local and API-specific, with no evidence of default remote exposure in the shipped package. Public coordination without extended embargo is proportionate unless a broadly exposed consumer of this path is identified. Acknowledgement: Aisle Research Vulnerability Details: The pb_ds binary heap backend reallocates the entry array inside `erase_if`, but does not update `m_a_entries` to the new allocation before continuing. The vulnerable block is: ```cpp const size_type new_size = resize_policy::get_new_size_for_arbitrary(left); entry_pointer new_entries = s_entry_allocator.allocate(new_size); std::copy(m_a_entries, m_a_entries + left, new_entries); s_entry_allocator.deallocate(m_a_entries, m_actual_size); m_actual_size = new_size; resize_policy::notify_arbitrary(m_actual_size); ``` After that, `erase_if` rebuilds the heap: ```cpp m_size = left; make_heap(); ``` `make_heap()` uses `m_a_entries` directly: ```cpp entry_pointer end = m_a_entries + m_size; std::make_heap(m_a_entries, end, m_cmp); ``` Because `m_a_entries = new_entries;` is missing, `make_heap()` operates on freed storage after successful reallocation. Later cleanup may deallocate the stale `m_a_entries` again, leading to an invalid free or double-free condition. Nearby reallocation paths in the same component do update `m_a_entries`, which supports that this omission is unintended. Steps to reproduce: 1. Build a small C++ test with AddressSanitizer enabled and use pb_ds `priority_queue` with `binary_heap_tag`. 2. Insert enough elements to force dynamic storage growth. 3. Call `erase_if` with a predicate that removes entries. 4. Continue heap activity, or allow the object to destruct. 5. Observe invalid memory behavior, typically a use-after-free during heap repair and potentially an invalid free during teardown. Minimal PoC: ```cpp #include <ext/pb_ds/priority_queue.hpp> using namespace __gnu_pbds; int main() { priority_queue<int, std::less<int>, binary_heap_tag> q; for (int i = 0; i < 1000; ++i) q.push; q.erase_if([](const int& v) { return (v % 2) == 0; }); q.push(42); q.pop(); } ``` Mitigation: Until a fixed package is available, avoid calling `erase_if` on `__gnu_pbds::priority_queue` instances that use `binary_heap_tag` in code built from this SRPM. If predicate-based removal is required, use an alternative container or backend, or rebuild with the patch below applied. Proposed Fix: Store the replacement entry array in `m_a_entries` before freeing the old array, and copy from the saved old pointer. ```diff diff --git a/libstdc++-v3/include/ext/pb_ds/detail/binary_heap_/erase_fn_imps.hpp b/libstdc++-v3/include/ext/pb_ds/detail/binary_heap_/erase_fn_imps.hpp @@ -118,9 +118,11 @@ PB_DS_CLASS_C_DEC::erase_if(Pred pred) const size_type new_size = resize_policy::get_new_size_for_arbitrary(left); + entry_pointer old_entries = m_a_entries; entry_pointer new_entries = s_entry_allocator.allocate(new_size); std::copy(m_a_entries, m_a_entries + left, new_entries); s_entry_allocator.deallocate(m_a_entries, m_actual_size); + std::copy(old_entries, old_entries + left, new_entries); + m_a_entries = new_entries; + s_entry_allocator.deallocate(old_entries, m_actual_size); m_actual_size = new_size; resize_policy::notify_arbitrary(m_actual_size); ``` ------ This report was generated using AI technology. Always review AI-generated content prior to use