https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125981

            Bug ID: 125981
           Summary: std::find_if/std::mismatch dereference the
                    past-the-end iterator when the predicate result type
                    has an ADL-reachable operator&&
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: yanchurkin at gmail dot com
  Target Milestone: ---

std::__find_if (and similarly std::__mismatch and std::__push_heap)
drives its loop with a condition of the form

    while (first != last && !pred(*first))
      ++first;

When the predicate/comparator returns a class type and the program
declares a namespace-scope operator&&(bool, T) reachable by ADL,
overload resolution selects that user operator&& for the loop
condition. An overloaded operator&& is not short-circuiting, so the
right-hand operand -- which dereferences *first -- is evaluated even
when first == last. For an empty range this dereferences the
past-the-end iterator.

Reproducer (last.cpp):

    #include <algorithm>
    #include <cstdint>
    #include <list>
    uint8_t value = 0;
    struct logic_t { 
      logic_t operator!() { return logic_t(); } 
      operator bool() { return value; } 
    };
    struct SVInt { 
      logic_t t;
      logic_t operator>(SVInt) { return t; } 
      uint32_t bitWidth;
      bool unknownFlag;
    };
    bool operator&&(bool, logic_t) { return 0; } 
    int main() { 
      std::list<SVInt> ranges;
      (void) std::find_if(ranges.begin(), ranges.end(),
                          [](SVInt cur) { return cur > cur; });
    } 

Build and run:

    g++ -fsanitize=address,undefined last.cpp && ./a.out

AddressSanitizer reports a stack-buffer-overflow READ inside
std::__find_if on the empty list (the past-the-end iterator of the
empty list is dereferenced).

Expected: find_if on an empty range must not dereference any iterator.

Older releases are unaffected because find_if used to wrap the
predicate in a helper whose operator() returned bool; the refactored
code passes the raw predicate through. The ranges:: versions are
unaffected because their comparator/predicate wrappers return bool.

A patch (force the predicate result to bool in these loop conditions)

Tested on x86_64-pc-linux-gnu, no regressions.

Reply via email to