diff --git a/src/mem/alloc.h b/src/mem/alloc.h index 3ee67c7..af9f741 100644 --- a/src/mem/alloc.h +++ b/src/mem/alloc.h @@ -817,6 +817,11 @@ namespace snmalloc { Superslab* super = Superslab::get(p); + +#ifndef NDEBUG + if (p->target_id() != super->get_allocator()->id()) + error("Detected memory corruption. Potential use-after-free"); +#endif if (super->get_kind() == Super) { Slab* slab = Slab::get(p); diff --git a/src/mem/metaslab.h b/src/mem/metaslab.h index e933b19..3c2ff08 100644 --- a/src/mem/metaslab.h +++ b/src/mem/metaslab.h @@ -91,6 +91,32 @@ namespace snmalloc return reinterpret_cast(pointer_offset(slab, link)); } + /// Value used to check for corruptions in a block + static constexpr size_t POISON = + static_cast(bits::is64() ? 0xDEADBEEFDEAD0000 : 0xDEAD0000); + + /// Store next pointer in a block. In Debug using magic value to detect some + /// simple corruptions. + static void store_next(void* p, uint16_t head) + { +#ifdef NDEBUG + *static_cast(p) = head; +#else + *static_cast(p) = head ^ POISON; +#endif + } + + /// Accessor function for the next pointer in a block. + /// In Debug checks for simple corruptions. + static uint16_t follow_next(void* node) + { + size_t next = *static_cast(node); +#ifndef NDEBUG + if ((next ^ POISON) > 0xFFFF) + error("Detected memory corruption. Use-after-free."); +#endif + return static_cast(next); + } bool valid_head(bool is_short) { size_t size = sizeclass_to_size(sizeclass); @@ -122,8 +148,33 @@ namespace snmalloc // Block is not full assert(SLAB_SIZE > accounted_for); - // Walk bump-free-list-segment accounting for unused space + // Walk bump-free-list-segment checking for cycles. + // Using + // https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_Tortoise_and_Hare + // We don't expect a cycle, so worst case is only followed by a crash, so + // slow doesn't mater. uint16_t curr = head; + uint16_t curr_slow = head; + bool both = false; + while ((curr & 1) != 1) + { + curr = follow_next(pointer_offset(slab, curr)); + if (both) + { + curr_slow = follow_next(pointer_offset(slab, curr_slow)); + } + + if (curr == curr_slow) + { + error("Free list contains a cycle, typically indicates double free."); + } + + both = !both; + } + + + // Walk bump-free-list-segment accounting for unused space + curr = head; while ((curr & 1) != 1) { // Check we are looking at a correctly aligned block @@ -137,7 +188,7 @@ namespace snmalloc assert(curr != link); // Iterate bump/free list segment - curr = *reinterpret_cast(pointer_offset(slab, curr)); + curr = follow_next(pointer_offset(slab, curr)); } if (curr != 1) diff --git a/src/mem/slab.h b/src/mem/slab.h index 21afeb9..cdc2e99 100644 --- a/src/mem/slab.h +++ b/src/mem/slab.h @@ -51,8 +51,7 @@ namespace snmalloc void* node = pointer_offset(this, head); // Read the next slot from the memory that's about to be allocated. - uint16_t next = *static_cast(node); - meta.head = next; + meta.head = Metaslab::follow_next(node); p = remove_cache_friendly_offset(node, meta.sizeclass); } @@ -101,6 +100,12 @@ namespace snmalloc Metaslab& meta = super->get_meta(this); bool was_full = meta.is_full(); + +#ifndef NDEBUG + if (meta.is_unused()) + error("Detected potential double free."); +#endif + meta.debug_slab_invariant(is_short(), this); meta.sub_use(); @@ -152,7 +157,8 @@ namespace snmalloc assert(meta.valid_head(is_short())); // Set the next pointer to the previous head. - *static_cast(p) = head; + Metaslab::store_next(p, head); + meta.debug_slab_invariant(is_short(), this); } return Superslab::NoSlabReturn;