Merge pull request #60 from microsoft/detect_cooruption

A few checks for easy corruptions
This commit is contained in:
Matthew Parkinson
2019-06-12 16:14:43 +01:00
committed by GitHub
5 changed files with 123 additions and 39 deletions

View File

@@ -26,6 +26,41 @@ macro(warnings_high)
endif()
endmacro()
macro(clangformat_targets)
# The clang-format tool is installed under a variety of different names. Try
# to find a sensible one. Only look for 6.0 and 7.0 versions explicitly - we
# don't know whether our clang-format file will work with newer versions of the
# tool
set(CLANG_FORMAT_NAMES
clang-format-7.0
clang-format-6.0
clang-format70
clang-format60
clang-format)
# Loop over each of the possible names of clang-format and try to find one.
set(CLANG_FORMAT CLANG_FORMAT-NOTFOUND)
foreach (NAME IN ITEMS ${CLANG_FORMAT_NAMES})
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
find_program(CLANG_FORMAT ${NAME})
endif ()
endforeach()
# If we've found a clang-format tool, generate a target for it, otherwise emit
# a warning.
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
message(WARNING "Not generating clangformat target, no clang-format tool found")
else ()
message(STATUS "Generating clangformat target using ${CLANG_FORMAT}")
file(GLOB_RECURSE ALL_SOURCE_FILES *.cc *.h *.hh)
add_custom_target(
clangformat
COMMAND ${CLANG_FORMAT}
-i
${ALL_SOURCE_FILES})
endif()
endmacro()
# The main target for snmalloc
add_library(snmalloc_lib INTERFACE)
target_include_directories(snmalloc_lib INTERFACE src/)
@@ -143,36 +178,5 @@ if(NOT DEFINED SNMALLOC_ONLY_HEADER_LIBRARY)
endforeach()
endforeach()
# The clang-format tool is installed under a variety of different names. Try
# to find a sensible one. Only look for 6.0 and 7.0 versions explicitly - we
# don't know whether our clang-format file will work with newer versions of the
# tool
set(CLANG_FORMAT_NAMES
clang-format-7.0
clang-format-6.0
clang-format70
clang-format60
clang-format)
# Loop over each of the possible names of clang-format and try to find one.
set(CLANG_FORMAT CLANG_FORMAT-NOTFOUND)
foreach (NAME IN ITEMS ${CLANG_FORMAT_NAMES})
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
find_program(CLANG_FORMAT ${NAME})
endif ()
endforeach()
# If we've found a clang-format tool, generate a target for it, otherwise emit
# a warning.
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
message(WARNING "Not generating clangformat target, no clang-format tool found")
else ()
message(STATUS "Generating clangformat target using ${CLANG_FORMAT}")
file(GLOB_RECURSE ALL_SOURCE_FILES *.cc *.h *.hh)
add_custom_target(
clangformat
COMMAND ${CLANG_FORMAT}
-i
${ALL_SOURCE_FILES})
endif()
clangformat_targets()
endif()

View File

@@ -469,7 +469,7 @@ namespace snmalloc
return;
}
# ifndef NDEBUG
# ifdef CHECK_CLIENT
if (size > 64 || address_cast(super) != address_cast(p))
{
error("Not deallocating start of an object");
@@ -817,6 +817,10 @@ namespace snmalloc
{
Superslab* super = Superslab::get(p);
#ifdef CHECK_CLIENT
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);
@@ -1009,7 +1013,7 @@ namespace snmalloc
void small_dealloc(Superslab* super, void* p, uint8_t sizeclass)
{
#ifndef NDEBUG
#ifdef CHECK_CLIENT
Slab* slab = Slab::get(p);
if (!slab->is_start_of_object(super, p))
{
@@ -1146,7 +1150,7 @@ namespace snmalloc
stats().sizeclass_dealloc(sizeclass);
bool was_full = slab->dealloc(p, large_allocator.memory_provider);
#ifndef NDEBUG
#ifdef CHECK_CLIENT
if (!is_multiple_of_sizeclass(
sizeclass_to_size(sizeclass),
address_cast(slab) + SUPERSLAB_SIZE - address_cast(p)))

View File

@@ -4,6 +4,12 @@
namespace snmalloc
{
// The CHECK_CLIENT macro is used to turn on minimal checking of the client
// calling the API correctly.
#if !defined(NDEBUG) && !defined(CHECK_CLIENT)
# define CHECK_CLIENT
#endif
// 0 intermediate bits results in power of 2 small allocs. 1 intermediate
// bit gives additional sizeclasses at the midpoint between each power of 2.
// 2 intermediate bits gives 3 intermediate sizeclasses, etc.

View File

@@ -91,6 +91,33 @@ namespace snmalloc
return reinterpret_cast<SlabLink*>(pointer_offset(slab, link));
}
/// Value used to check for corruptions in a block
static constexpr size_t POISON =
static_cast<size_t>(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)
{
#ifndef CHECK_CLIENT
*static_cast<size_t*>(p) = head;
#else
*static_cast<size_t*>(p) =
head ^ POISON ^ (static_cast<size_t>(head) << (bits::BITS - 16));
#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<size_t*>(node);
#ifdef CHECK_CLIENT
if (((next ^ POISON) ^ (next << (bits::BITS - 16))) > 0xFFFF)
error("Detected memory corruption. Use-after-free.");
#endif
return static_cast<uint16_t>(next);
}
bool valid_head(bool is_short)
{
size_t size = sizeclass_to_size(sizeclass);
@@ -103,6 +130,40 @@ namespace snmalloc
return ((head_start - slab_start) % size) == 0;
}
/**
* Check bump-free-list-segment 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.
**/
void debug_slab_acyclic_free_list(Slab* slab)
{
#ifndef NDEBUG
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;
}
#else
UNUSED(slab);
#endif
}
void debug_slab_invariant(bool is_short, Slab* slab)
{
#if !defined(NDEBUG) && !defined(SNMALLOC_CHEAP_CHECKS)
@@ -119,9 +180,12 @@ namespace snmalloc
// 'link' value is not important if full.
return;
}
// Block is not full
assert(SLAB_SIZE > accounted_for);
debug_slab_acyclic_free_list(slab);
// Walk bump-free-list-segment accounting for unused space
uint16_t curr = head;
while ((curr & 1) != 1)
@@ -137,7 +201,7 @@ namespace snmalloc
assert(curr != link);
// Iterate bump/free list segment
curr = *reinterpret_cast<uint16_t*>(pointer_offset(slab, curr));
curr = follow_next(pointer_offset(slab, curr));
}
if (curr != 1)

View File

@@ -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<uint16_t*>(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();
#ifdef CHECK_CLIENT
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<uint16_t*>(p) = head;
Metaslab::store_next(p, head);
meta.debug_slab_invariant(is_short(), this);
}
return Superslab::NoSlabReturn;