Skip to content

Potential pointer use after free in current lock-free mpsc queue #1

@zyounan

Description

@zyounan

The following code comes from 7ab32b4

template<typename T>
void MpscQueue<T>::enqueue(const T &input) {
    BufferNode *node{new BufferNode(input)};  // #1
    BufferNode *prevhead{head_.exchange(node, std::memory_order_acq_rel)};      // #2
    prevhead->next_.store(node, std::memory_order_release);  // #3
}

template<typename T>
bool MpscQueue<T>::dequeue(T &output) {
    BufferNode *tail = tail_.load(std::memory_order_relaxed);
    BufferNode *next = tail->next_.load(std::memory_order_acquire);

    if (next == nullptr) {
        return false;
    }
    output = std::move(*(next->dataPtr_));
    delete next->dataPtr_;    // #4
    tail_.store(next, std::memory_order_release);   // #5
    delete tail;
    return true;
}

Consider that 4 threads A, B, C, D. Assume that we have the queue with 3 elements and thread A is putting a new element 4 into the queue, in which case it's running in enqueue. After A executes #2 successfully, the current state of the queue looks like this:

                |---------------------- head
1 -> 2 -> 3 --> 4
          ^
          |------------------ prev

Assume that A is switched out by scheduler. Now thread B, C, D are trying to dequeue elements from the queue. They run to #4 and delete 1, 2, 3 respectively.
Note that current state of prev is freed and no longer valid. However, if A resumes at #3, the prevhead is actually deleted by other threads and it's UB now.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions