-
Notifications
You must be signed in to change notification settings - Fork 62
Description
I am developing a lock-free IOCP game server using ramalhete_queue to manage std::unique_ptr which is implemented like this.
struct RecvPacket {
clientid_t senderClientId;
PacketPtr packet;
RecvPacket() = default;
~RecvPacket() = default;
RecvPacket(const RecvPacket&) = delete;
RecvPacket& operator=(const RecvPacket&) = delete;
RecvPacket(RecvPacket&&) = default;
RecvPacket& operator=(RecvPacket&&) = default;
};Under high push contention, I've encountered a double-free error. The issue appears to be in the queue's node destructor:
~node() override {
for (unsigned i = pop_idx; i < push_idx; i += step_size) {
traits::delete_value(entries[i % entries_per_node].value.load(std::memory_order_relaxed).get());
}
}A race condition can lead to a state where pop_idx and push_idx have wrapped around, and push_idx > pop_idx.
In this case, i % entries_per_node indexed an entry that has already been popped and its unique_ptr moved. The destructor then calls delete_value on the stale pointer, causing a double-free.
I was able to fix this by modifying the pop logic to atomically nullify the entry upon retrieval using exchange
if (value != nullptr) {
// (14) - this acquire-load synchronizes-with the release-CAS (8)
// std::ignore = h->entries[idx].value.load(std::memory_order_acquire); //original code
std::ignore = h->entries[idx].value.exchange(marked_value(nullptr, 1), std::memory_order_acquire); // my fix
return traits::get(value.get());
}With this change, the double-free error is completely gone, even under heavy load.
While I'm not an expert in lock-free programming, I think this might be potential vulnerability in the current implementation I'm submitting this issue for your consideration, as it might be a genuine bug.
Thanks.