Bug 2478395 (CVE-2026-102010) - CVE-2026-102010 gcc-toolset-15-gcc: gcc: gcc-toolset-16: gcc: Denial of Service via use-after-free in binary heap erase_if
Summary: CVE-2026-102010 gcc-toolset-15-gcc: gcc: gcc-toolset-16: gcc: Denial of Servi...
Keywords:
Status: NEW
Alias: CVE-2026-102010
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Product Security DevOps Team
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-05-18 03:33 UTC by OSIDB Bzimport
Modified: 2026-09-30 08:45 UTC (History)
22 users (show)

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


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
GNU Compiler Collection 127656 0 None None None 2026-09-30 08:45:51 UTC

Description OSIDB Bzimport 2026-05-18 03:33:34 UTC
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

Comment 3 Jonathan Wakely 2026-09-30 08:45:52 UTC
Upstream has been fixed.


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