refactor: Harden prevector::change_capacity against capacity < size #35166

pull asafmod wants to merge 1 commits into bitcoin:master from asafmod:harden-prevector-change-capacity changing 1 files +7 −0
  1. asafmod commented at 10:52 AM on April 27, 2026: none

    Summary

    Clamp new_capacity to never go below size() in change_capacity(), enforcing the same invariant that std::vector maintains (capacity >= size).

    Without this guard, if change_capacity() is called with new_capacity < size(), two problems arise:

    • Indirect-to-direct transition: memcpy copies size() elements into the direct buffer which only holds N elements — a heap buffer overflow.
    • Indirect-to-indirect realloc: the buffer shrinks below the number of live elements — subsequent access is out-of-bounds.

    While no current caller triggers either case, the 1.5x growth computation (new_size + (new_size >> 1)) wraps around uint32_t when new_size > 0xAAAAAAAA, producing a small value that would hit this path. The clamp prevents this overflow from becoming a heap buffer overflow.

    This is the same class of latent issue fixed in a7af72a697 ("prevector::swap: fix (unreached) data corruption").

    Test plan

    Verified with a standalone test exercising indirect-to-direct transitions, grow+shrink cycles, and the capacity >= size invariant. Existing PrevectorTestInt randomized tests are unaffected (the clamp is a no-op for all valid callers).

  2. refactor: Harden prevector::change_capacity against capacity < size
    Clamp new_capacity to never go below size() in change_capacity(),
    enforcing the same invariant that std::vector maintains (capacity
    is always >= size).
    
    Without this guard, if change_capacity() is called with
    new_capacity < size(), two problems arise:
    
    1. Indirect-to-direct transition: memcpy copies size() elements
       into the direct buffer which only holds N elements — a heap
       buffer overflow.
    
    2. Indirect-to-indirect realloc: the buffer shrinks below the
       number of live elements — subsequent access is out-of-bounds.
    
    While no current caller triggers either case, the 1.5x growth
    computation (new_size + (new_size >> 1)) wraps around uint32_t
    when new_size > 0xAAAAAAAA, producing a small value that would
    hit this path. The clamp prevents this overflow from becoming
    a heap buffer overflow.
    
    This is the same class of latent issue fixed in a7af72a697
    ("prevector::swap: fix (unreached) data corruption").
    ac210e94db
  3. DrahtBot added the label Refactoring on Apr 27, 2026
  4. DrahtBot commented at 10:52 AM on April 27, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process. A summary of reviews will appear here.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. dergoegge commented at 1:03 PM on April 27, 2026: member

    This looks entirely LLM generated without real understanding from the author, and should be closed.

    There is no test attached and the description even mentions "no current caller triggers either case".

  6. asafmod commented at 1:11 PM on April 27, 2026: none

    This looks entirely LLM generated without real understanding from the author, and should be closed.

    There is no test attached and the description even mentions "no current caller triggers either case".

    The issue was found by my manual code-review, but of course I used LLM to generate the PR description. Don't you? I didn't include the local test I ran because I'm not sure it fits in the existing prevector test-suite, as there are no public functions that can have the check pass and cause the capacity to not reduce. I can add the test results here, or do you prefer that the test itself to be added as part of the PR?

  7. sedited commented at 1:14 PM on April 27, 2026: contributor

    I didn't include the local test I ran because I'm not sure it fits in the existing prevector test-suite, as there are no public functions that can have the check pass and cause the capacity to not reduce.

    So why change it then if the public interface of the class already handles it correctly?

  8. asafmod commented at 1:27 PM on April 27, 2026: none

    I didn't include the local test I ran because I'm not sure it fits in the existing prevector test-suite, as there are no public functions that can have the check pass and cause the capacity to not reduce.

    So why change it then if the public interface of the class already handles it correctly?

    Currently, the public interface has an unexploitable integer overflow bug in the 1.5x growth factor. 4 places compute new_size + (new_size >> 1) which wraps uint32_t to a small value when new_size > 0xAAAAAAAA:

    insert(iterator, const T&) insert(iterator, size_type, const T&) insert(iterator, InputIterator, InputIterator) emplace_back

    If the overflow happens, change_capacity() is called with new_capacity < size(). My thought is that std::vector is immune to this because its allocator would reject the wrapped size, but prevector would happily accept it.

  9. maflcko commented at 9:17 AM on April 28, 2026: member

    I don't think this is useful? This will just move the UB or misbehaviour further down.

    If there ever was a use case to store more than idk 1GiB of data or so, it should be free and more correct to just use u64.

  10. sedited commented at 10:44 AM on April 28, 2026: contributor

    Closing this again. Not only is it clearly LLM generated, but I also don't think it is a good allocation of reviewer time to be working through this, especially given the mentioned defects of this PR.

  11. sedited closed this on Apr 28, 2026


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-11 12:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me