Changeset 173224 in webkit


Ignore:
Timestamp:
Sep 3, 2014 2:00:33 PM (10 years ago)
Author:
ggaren@apple.com
Message:

bmalloc crashes on the EWS bots (due to bad large object allocation)
https://bugs.webkit.org/show_bug.cgi?id=136469

Reviewed by Andreas Kling.

It's possible to convince bmalloc to perform a bad large object allocation,
through these steps:

(1) Insert object A into freelist F0.

(2) Split, merge and split again A's neighbors such that object B is
inserted into freelist F0, with boundary tag and size equal to object A,
but pointer not completely equal to object A. Put object B at the head of F0.

(3) Allocate some other object from F0, swapping its position in the
freelist with object B, such that object A is now ahead of object B.

--> Now, the next allocation for size A/B will allocate object A, which
has a slightly wrong idea about where the object actually begins.
Immediately, you'll corrupt a little memory, and over time, you'll also
corrupt boundary tag metadata.

The solution is to store the begin pointer in the boundary tag. Luckily,
this doesn't make the tag any bigger, and it's not a noticeable slowdown
on MallocBench.

  • bmalloc/Algorithm.h:

(bmalloc::rightShift):

  • bmalloc/BeginTag.h:

(bmalloc::BeginTag::isInFreeList): This is the bug fix. Make sure to
validate the start pointer when popping off the free list. Through a
very uncommon set of steps, it is possible to have an item in the free
list that is valid by all accounts except for its start pointer.

  • bmalloc/BoundaryTag.h:

(bmalloc::BoundaryTag::compactBegin):
(bmalloc::BoundaryTag::setRange):
(bmalloc::BoundaryTag::setSize): Deleted. Record a compact version of the
start pointer. We don't need the whole pointer -- just the offset, in
largeAlignment increments, into the relevant boundary tag bucket.

  • bmalloc/BoundaryTagInlines.h:

(bmalloc::validateNext):
(bmalloc::BoundaryTag::init):
(bmalloc::BoundaryTag::mergeLarge):
(bmalloc::BoundaryTag::splitLarge):

  • bmalloc/SegregatedFreeList.cpp:

(bmalloc::SegregatedFreeList::insert):
(bmalloc::SegregatedFreeList::takeGreedy):
(bmalloc::SegregatedFreeList::take): Provide the whole range instead of
the size when establishing a boundary tag, as required by the new
interface.

  • bmalloc/Sizes.h:
Location:
trunk/Source/bmalloc
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/bmalloc/ChangeLog

    r172608 r173224  
     12014-09-02  Geoffrey Garen  <ggaren@apple.com>
     2
     3        bmalloc crashes on the EWS bots (due to bad large object allocation)
     4        https://bugs.webkit.org/show_bug.cgi?id=136469
     5
     6        Reviewed by Andreas Kling.
     7
     8        It's possible to convince bmalloc to perform a bad large object allocation,
     9        through these steps:
     10
     11        (1) Insert object A into freelist F0.
     12
     13        (2) Split, merge and split again A's neighbors such that object B is
     14        inserted into freelist F0, with boundary tag and size equal to object A,
     15        but pointer not completely equal to object A. Put object B at the head of F0.
     16
     17        (3) Allocate some other object from F0, swapping its position in the
     18        freelist with object B, such that object A is now ahead of object B.
     19
     20        --> Now, the next allocation for size A/B will allocate object A, which
     21        has a slightly wrong idea about where the object actually begins.
     22        Immediately, you'll corrupt a little memory, and over time, you'll also
     23        corrupt boundary tag metadata.
     24
     25        The solution is to store the begin pointer in the boundary tag. Luckily,
     26        this doesn't make the tag any bigger, and it's not a noticeable slowdown
     27        on MallocBench.
     28
     29        * bmalloc/Algorithm.h:
     30        (bmalloc::rightShift):
     31        * bmalloc/BeginTag.h:
     32        (bmalloc::BeginTag::isInFreeList): This is the bug fix. Make sure to
     33        validate the start pointer when popping off the free list. Through a
     34        very uncommon set of steps, it is possible to have an item in the free
     35        list that is valid by all accounts except for its start pointer.
     36
     37        * bmalloc/BoundaryTag.h:
     38        (bmalloc::BoundaryTag::compactBegin):
     39        (bmalloc::BoundaryTag::setRange):
     40        (bmalloc::BoundaryTag::setSize): Deleted. Record a compact version of the
     41        start pointer. We don't need the whole pointer -- just the offset, in
     42        largeAlignment increments, into the relevant boundary tag bucket.
     43
     44        * bmalloc/BoundaryTagInlines.h:
     45        (bmalloc::validateNext):
     46        (bmalloc::BoundaryTag::init):
     47        (bmalloc::BoundaryTag::mergeLarge):
     48        (bmalloc::BoundaryTag::splitLarge):
     49        * bmalloc/SegregatedFreeList.cpp:
     50        (bmalloc::SegregatedFreeList::insert):
     51        (bmalloc::SegregatedFreeList::takeGreedy):
     52        (bmalloc::SegregatedFreeList::take): Provide the whole range instead of
     53        the size when establishing a boundary tag, as required by the new
     54        interface.
     55
     56        * bmalloc/Sizes.h:
     57
    1582014-08-14  Geoffrey Garen  <ggaren@apple.com>
    259
  • trunk/Source/bmalloc/bmalloc/Algorithm.h

    r166893 r173224  
    4747    return a < b ? a : b;
    4848}
    49    
     49
    5050template<typename T> inline constexpr T mask(T value, uintptr_t mask)
    5151{
    5252    return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(value) & mask);
     53}
     54
     55template<typename T> inline constexpr T rightShift(T value, uintptr_t shift)
     56{
     57    return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(value) >> shift);
    5358}
    5459
  • trunk/Source/bmalloc/bmalloc/BeginTag.h

    r166893 r173224  
    3333class BeginTag : public BoundaryTag {
    3434public:
    35     bool isInFreeList(size_t);
     35    bool isInFreeList(const Range&);
    3636};
    3737
    38 inline bool BeginTag::isInFreeList(size_t size)
     38inline bool BeginTag::isInFreeList(const Range& range)
    3939{
    40     return isFree() && !isEnd() && this->size() == size;
     40    return isFree() && !isEnd() && this->size() == range.size() && this->compactBegin() == compactBegin(range);
    4141}
    4242
  • trunk/Source/bmalloc/bmalloc/BoundaryTag.h

    r166956 r173224  
    2828
    2929#include "BAssert.h"
     30#include "Range.h"
    3031#include "Sizes.h"
    3132
     
    4243    static Range deallocate(void*);
    4344    static void allocate(size_t, Range&, Range& leftover, bool& hasPhysicalPages);
     45    static unsigned compactBegin(const Range&);
    4446
    4547    bool isXLarge() { return m_size == xLargeMarker; }
     
    5961   
    6062    size_t size() { return m_size; }
    61     void setSize(size_t);
     63    unsigned compactBegin() { return m_compactBegin; }
     64
     65    void setRange(const Range&);
    6266   
    6367    EndTag* prev();
     
    6670private:
    6771    static const size_t flagBits = 3;
    68     static const size_t sizeBits = bitCount<unsigned>() - flagBits;
     72    static const size_t compactBeginBits = 5;
     73    static const size_t sizeBits = bitCount<unsigned>() - flagBits - compactBeginBits;
    6974    static const size_t xLargeMarker = 1; // This size is unused because our minimum object size is greater than it.
    7075
    7176    static_assert(largeMin > xLargeMarker, "largeMin must provide enough umbrella to fit xLargeMarker.");
     77    static_assert((1 << compactBeginBits) - 1 >= largeMin / largeAlignment, "compactBegin must be encodable in a BoundaryTag.");
    7278    static_assert((1 << sizeBits) - 1 >= largeMax, "largeMax must be encodable in a BoundaryTag.");
    7379
     
    8086    bool m_isEnd: 1;
    8187    bool m_hasPhysicalPages: 1;
     88    unsigned m_compactBegin: compactBeginBits;
    8289    unsigned m_size: sizeBits;
    8390};
    8491
    85 inline void BoundaryTag::setSize(size_t size)
     92inline unsigned BoundaryTag::compactBegin(const Range& range)
    8693{
    87     m_size = static_cast<unsigned>(size);
    88     BASSERT(this->size() == size);
     94    return static_cast<unsigned>(
     95        reinterpret_cast<uintptr_t>(
     96            rightShift(
     97                mask(range.begin(), largeMin - 1), largeAlignmentShift)));
     98}
     99
     100inline void BoundaryTag::setRange(const Range& range)
     101{
     102    m_compactBegin = compactBegin(range);
     103    m_size = static_cast<unsigned>(range.size());
     104    BASSERT(this->size() == range.size());
    89105    BASSERT(!isXLarge());
    90106}
  • trunk/Source/bmalloc/bmalloc/BoundaryTagInlines.h

    r166956 r173224  
    6565static inline void validateNext(BeginTag* next, const Range& range)
    6666{
    67     if (next->size() == largeMin && !next->isFree()) // Right sentinel tag.
     67    if (next->size() == largeMin && !next->compactBegin() && !next->isFree()) // Right sentinel tag.
    6868        return;
    6969
     
    8585
    8686    BeginTag* beginTag = LargeChunk::beginTag(range.begin());
    87     beginTag->setSize(range.size());
     87    beginTag->setRange(range);
    8888    beginTag->setFree(true);
    8989    beginTag->setHasPhysicalPages(false);
     
    9898    EndTag* leftSentinel = beginTag->prev();
    9999    BASSERT(leftSentinel >= static_cast<void*>(chunk));
    100     leftSentinel->setSize(largeMin);
     100    leftSentinel->setRange(Range(nullptr, largeMin));
    101101    leftSentinel->setFree(false);
    102102
    103103    BeginTag* rightSentinel = endTag->next();
    104104    BASSERT(rightSentinel < static_cast<void*>(range.begin()));
    105     rightSentinel->setSize(largeMin);
     105    rightSentinel->setRange(Range(nullptr, largeMin));
    106106    rightSentinel->setFree(false);
    107107   
     
    151151        mergeLargeRight(endTag, next, range, hasPhysicalPages);
    152152
    153     beginTag->setSize(range.size());
     153    beginTag->setRange(range);
    154154    beginTag->setFree(true);
    155155    beginTag->setHasPhysicalPages(hasPhysicalPages);
     
    176176INLINE void BoundaryTag::splitLarge(BeginTag* beginTag, size_t size, EndTag*& endTag, Range& range, Range& leftover)
    177177{
    178     beginTag->setSize(size);
     178    leftover = Range(range.begin() + size, range.size() - size);
     179    range = Range(range.begin(), size);
     180
     181    beginTag->setRange(range);
    179182
    180183    EndTag* splitEndTag = LargeChunk::endTag(range.begin(), size);
     
    182185        *splitEndTag = *beginTag;
    183186
    184     leftover = Range(range.begin() + size, range.size() - size);
    185187    BASSERT(leftover.size() >= largeMin);
    186188    BeginTag* leftoverBeginTag = LargeChunk::beginTag(leftover.begin());
    187189    *leftoverBeginTag = *beginTag;
    188     leftoverBeginTag->setSize(leftover.size());
     190    leftoverBeginTag->setRange(leftover);
    189191
    190192    if (leftoverBeginTag != static_cast<BoundaryTag*>(endTag))
    191193        *endTag = *leftoverBeginTag;
    192194
    193     validate(beginTag->prev(), Range(range.begin(), size), leftoverBeginTag);
     195    validate(beginTag->prev(), range, leftoverBeginTag);
    194196    validate(leftoverBeginTag->prev(), leftover, endTag->next());
    195197
    196     range = Range(range.begin(), size);
    197198    endTag = splitEndTag;
    198199}
  • trunk/Source/bmalloc/bmalloc/SegregatedFreeList.cpp

    r166956 r173224  
    4040IF_DEBUG(
    4141    BeginTag* beginTag = LargeChunk::beginTag(range.begin());
    42     BASSERT(beginTag->isInFreeList(range.size()));
     42    BASSERT(beginTag->isInFreeList(range));
    4343)
    4444
     
    6767        // so we need to validate each free list entry before using it.
    6868        BeginTag* beginTag = LargeChunk::beginTag(range.begin());
    69         if (!beginTag->isInFreeList(range.size())) {
     69        if (!beginTag->isInFreeList(range)) {
    7070            list.pop(i);
    7171            continue;
     
    115115        // we need to validate each free list entry before using it.
    116116        BeginTag* beginTag = LargeChunk::beginTag(range.begin());
    117         if (!beginTag->isInFreeList(range.size())) {
     117        if (!beginTag->isInFreeList(range)) {
    118118            list.pop(i);
    119119            continue;
  • trunk/Source/bmalloc/bmalloc/Sizes.h

    r167570 r173224  
    6969
    7070    static const size_t largeAlignment = 64;
     71    static const size_t largeAlignmentShift = 6;
     72    static_assert(1 << largeAlignmentShift == largeAlignment, "largeAlignmentShift be log2(largeAlignment).");
    7173    static const size_t largeMax = largeChunkSize * 99 / 100; // Plenty of room for metadata.
    7274    static const size_t largeMin = 1024;
Note: See TracChangeset for help on using the changeset viewer.