[1a/3][ASan][compiler-rt] API for double ended containers
authorAdvenam Tacet <advenam.tacet@trailofbits.com>
Sun, 20 Nov 2022 01:34:46 +0000 (17:34 -0800)
committerVitaly Buka <vitalybuka@google.com>
Tue, 22 Nov 2022 00:38:52 +0000 (16:38 -0800)
This revision is a part of a series of patches extending
AddressSanitizer C++ container overflow detection capabilities by adding
annotations, similar to those existing in std::vector, to std::string
and std::deque collections. These changes allow ASan to detect cases
when the instrumented program accesses memory which is internally
allocated by the collection but is still not in-use (accesses before or
after the stored elements for std::deque, or between the size and
capacity bounds for std::string).

The motivation for the research and those changes was a bug, found by
Trail of Bits, in a real code where an out-of-bounds read could happen
as two strings were compared via a std::equals function that took
iter1_begin, iter1_end, iter2_begin iterators (with a custom comparison
function). When object iter1 was longer than iter2, read out-of-bounds
on iter2 could happen. Container sanitization would detect it.

This revision adds a new compiler-rt ASan sanitization API function
sanitizer_annotate_double_ended_contiguous_container necessary to
sanitize/annotate double ended contiguous containers. Note that that
function annotates a single contiguous memory buffer (for example the
std::deque's internal chunk). Such containers have the beginning of
allocated memory block, beginning of the container in-use data, end of
the container's in-use data and the end of the allocated memory block.
This also adds a new API function to verify if a double ended contiguous
container is correctly annotated
(__sanitizer_verify_double_ended_contiguous_container).

Since we do not modify the ASan's shadow memory encoding values, the
capability of sanitizing/annotating a prefix of the internal contiguous
memory buffer is limited – up to SHADOW_GRANULARITY-1 bytes may not be
poisoned before the container's in-use data. This can cause false
negatives (situations when ASan will not detect memory corruption in
those areas).

On the other hand, API function interfaces are designed to work even if
this caveat would not exist. Therefore implementations using those
functions will poison every byte correctly, if only ASan (and
compiler-rt) is extended to support it. In other words, if ASan was
modified to support annotating/poisoning of objects lying on addresses
unaligned to SHADOW_GRANULARITY (so e.g. prefixes of those blocks),
which would require changing its shadow memory encoding, this would not
require any changes in the libcxx std::string/deque code which is added
in further commits of this patch series.

If you have any questions, please email:
advenam.tacet@trailofbits.com
disconnect3d@trailofbits.com

Differential Revision: https://reviews.llvm.org/D132090

compiler-rt/include/sanitizer/common_interface_defs.h
compiler-rt/lib/asan/asan_errors.cpp
compiler-rt/lib/asan/asan_errors.h
compiler-rt/lib/asan/asan_poisoning.cpp
compiler-rt/lib/asan/asan_report.cpp
compiler-rt/lib/asan/asan_report.h
compiler-rt/lib/sanitizer_common/sanitizer_common_interface.inc
compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h
compiler-rt/test/asan/TestCases/contiguous_container.cpp

index ba58ad4..2f415bd 100644 (file)
@@ -159,6 +159,40 @@ void __sanitizer_annotate_contiguous_container(const void *beg,
                                                const void *old_mid,
                                                const void *new_mid);
 
+/// Similar to <c>__sanitizer_annotate_contiguous_container</c>.
+///
+/// Annotates the current state of a contiguous container memory,
+/// such as <c>std::deque</c>'s single chunk, when the boundries are moved.
+///
+/// A contiguous chunk is a chunk that keeps all of its elements
+/// in a contiguous region of memory. The container owns the region of memory
+/// <c>[storage_beg, storage_end)</c>; the memory <c>[container_beg,
+/// container_end)</c> is used to store the current elements, and the memory
+/// <c>[storage_beg, container_beg), [container_end, storage_end)</c> is
+/// reserved for future elements (<c>storage_beg <= container_beg <=
+/// container_end <= storage_end</c>). For example, in <c> std::deque </c>:
+/// - chunk with a frist deques element will have container_beg equal to address
+///  of the first element.
+/// - in every next chunk with elements, true is  <c> container_beg ==
+/// storage_beg </c>.
+///
+/// Argument requirements:
+/// During unpoisoning memory of empty container (before first element is
+/// added):
+/// - old_container_beg_p == old_container_end_p
+/// During poisoning after last element was removed:
+/// - new_container_beg_p == new_container_end_p
+/// \param storage_beg Beginning of memory region.
+/// \param storage_end End of memory region.
+/// \param old_container_beg Old beginning of used region.
+/// \param old_container_end End of used region.
+/// \param new_container_beg New beginning of used region.
+/// \param new_container_end New end of used region.
+void __sanitizer_annotate_double_ended_contiguous_container(
+    const void *storage_beg, const void *storage_end,
+    const void *old_container_beg, const void *old_container_end,
+    const void *new_container_beg, const void *new_container_end);
+
 /// Returns true if the contiguous container <c>[beg, end)</c> is properly
 /// poisoned.
 ///
@@ -178,6 +212,31 @@ void __sanitizer_annotate_contiguous_container(const void *beg,
 int __sanitizer_verify_contiguous_container(const void *beg, const void *mid,
                                             const void *end);
 
+/// Returns true if the double ended contiguous
+/// container <c>[storage_beg, storage_end)</c> is properly poisoned.
+///
+/// Proper poisoning could occur, for example, with
+/// <c>__sanitizer_annotate_double_ended_contiguous_container</c>), that is, if
+/// <c>[storage_beg, container_beg)</c> is not addressable, <c>[container_beg,
+/// container_end)</c> is addressable and <c>[container_end, end)</c> is
+/// unaddressable. Full verification requires O (<c>storage_end -
+/// storage_beg</c>) time; this function tries to avoid such complexity by
+/// touching only parts of the container around <c><i>storage_beg</i></c>,
+/// <c><i>container_beg</i></c>, <c><i>container_end</i></c>, and
+/// <c><i>storage_end</i></c>.
+///
+/// \param storage_beg Beginning of memory region.
+/// \param container_beg Beginning of used region.
+/// \param container_end End of used region.
+/// \param storage_end End of memory region.
+///
+/// \returns True if the double-ended contiguous container <c>[storage_beg,
+/// container_beg, container_end, end)</c> is properly poisoned - only
+/// [container_beg; container_end) is addressable.
+int __sanitizer_verify_double_ended_contiguous_container(
+    const void *storage_beg, const void *container_beg,
+    const void *container_end, const void *storage_end);
+
 /// Similar to <c>__sanitizer_verify_contiguous_container()</c> but also
 /// returns the address of the first improperly poisoned byte.
 ///
@@ -192,6 +251,20 @@ const void *__sanitizer_contiguous_container_find_bad_address(const void *beg,
                                                               const void *mid,
                                                               const void *end);
 
+/// returns the address of the first improperly poisoned byte.
+///
+/// Returns NULL if the area is poisoned properly.
+///
+/// \param storage_beg Beginning of memory region.
+/// \param container_beg Beginning of used region.
+/// \param container_end End of used region.
+/// \param storage_end End of memory region.
+///
+/// \returns The bad address or NULL.
+const void *__sanitizer_double_ended_contiguous_container_find_bad_address(
+    const void *storage_beg, const void *container_beg,
+    const void *container_end, const void *storage_end);
+
 /// Prints the stack trace leading to this call (useful for calling from the
 /// debugger).
 void __sanitizer_print_stack_trace(void);
index f3befdf..cc8dc26 100644 (file)
@@ -334,6 +334,26 @@ void ErrorBadParamsToAnnotateContiguousContainer::Print() {
   ReportErrorSummary(scariness.GetDescription(), stack);
 }
 
+void ErrorBadParamsToAnnotateDoubleEndedContiguousContainer::Print() {
+  Report(
+      "ERROR: AddressSanitizer: bad parameters to "
+      "__sanitizer_annotate_double_ended_contiguous_container:\n"
+      "      storage_beg        : %p\n"
+      "      storage_end        : %p\n"
+      "      old_container_beg  : %p\n"
+      "      old_container_end  : %p\n"
+      "      new_container_beg  : %p\n"
+      "      new_container_end  : %p\n",
+      (void *)storage_beg, (void *)storage_end, (void *)old_container_beg,
+      (void *)old_container_end, (void *)new_container_beg,
+      (void *)new_container_end);
+  uptr granularity = ASAN_SHADOW_GRANULARITY;
+  if (!IsAligned(storage_beg, granularity))
+    Report("ERROR: storage_beg is not aligned by %zu\n", granularity);
+  stack->Print();
+  ReportErrorSummary(scariness.GetDescription(), stack);
+}
+
 void ErrorODRViolation::Print() {
   Decorator d;
   Printf("%s", d.Error());
index dc52a49..634f6da 100644 (file)
@@ -331,6 +331,28 @@ struct ErrorBadParamsToAnnotateContiguousContainer : ErrorBase {
   void Print();
 };
 
+struct ErrorBadParamsToAnnotateDoubleEndedContiguousContainer : ErrorBase {
+  const BufferedStackTrace *stack;
+  uptr storage_beg, storage_end, old_container_beg, old_container_end,
+      new_container_beg, new_container_end;
+
+  ErrorBadParamsToAnnotateDoubleEndedContiguousContainer() = default;  // (*)
+  ErrorBadParamsToAnnotateDoubleEndedContiguousContainer(
+      u32 tid, BufferedStackTrace *stack_, uptr storage_beg_, uptr storage_end_,
+      uptr old_container_beg_, uptr old_container_end_, uptr new_container_beg_,
+      uptr new_container_end_)
+      : ErrorBase(tid, 10,
+                  "bad-__sanitizer_annotate_double_ended_contiguous_container"),
+        stack(stack_),
+        storage_beg(storage_beg_),
+        storage_end(storage_end_),
+        old_container_beg(old_container_beg_),
+        old_container_end(old_container_end_),
+        new_container_beg(new_container_beg_),
+        new_container_end(new_container_end_) {}
+  void Print();
+};
+
 struct ErrorODRViolation : ErrorBase {
   __asan_global global1, global2;
   u32 stack_id1, stack_id2;
@@ -398,6 +420,7 @@ struct ErrorGeneric : ErrorBase {
   macro(StringFunctionMemoryRangesOverlap)                 \
   macro(StringFunctionSizeOverflow)                        \
   macro(BadParamsToAnnotateContiguousContainer)            \
+  macro(BadParamsToAnnotateDoubleEndedContiguousContainer) \
   macro(ODRViolation)                                      \
   macro(InvalidPointerPair)                                \
   macro(Generic)
index 8059a52..6439038 100644 (file)
@@ -472,6 +472,148 @@ void __sanitizer_annotate_contiguous_container(const void *beg_p,
   }
 }
 
+// Annotates a double ended contiguous memory area like std::deque's chunk.
+// It allows detecting buggy accesses to allocated but not used begining
+// or end items of such a container.
+void __sanitizer_annotate_double_ended_contiguous_container(
+    const void *storage_beg_p, const void *storage_end_p,
+    const void *old_container_beg_p, const void *old_container_end_p,
+    const void *new_container_beg_p, const void *new_container_end_p) {
+  if (!flags()->detect_container_overflow)
+    return;
+
+  VPrintf(2, "contiguous_container: %p %p %p %p %p %p\n", storage_beg_p,
+          storage_end_p, old_container_beg_p, old_container_end_p,
+          new_container_beg_p, new_container_end_p);
+
+  uptr storage_beg = reinterpret_cast<uptr>(storage_beg_p);
+  uptr storage_end = reinterpret_cast<uptr>(storage_end_p);
+  uptr old_beg = reinterpret_cast<uptr>(old_container_beg_p);
+  uptr old_end = reinterpret_cast<uptr>(old_container_end_p);
+  uptr new_beg = reinterpret_cast<uptr>(new_container_beg_p);
+  uptr new_end = reinterpret_cast<uptr>(new_container_end_p);
+
+  constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
+
+  if (!(storage_beg <= new_beg && new_beg <= storage_end) ||
+      !(storage_beg <= new_end && new_end <= storage_end) ||
+      !(storage_beg <= old_beg && old_beg <= storage_end) ||
+      !(storage_beg <= old_end && old_end <= storage_end) ||
+      !(old_beg <= old_end && new_beg <= new_end)) {
+    GET_STACK_TRACE_FATAL_HERE;
+    ReportBadParamsToAnnotateDoubleEndedContiguousContainer(
+        storage_beg, storage_end, old_beg, old_end, new_beg, new_end, &stack);
+  }
+
+  // Right now, the function does not support:
+  // - unaligned storage beginning
+  // - situations when container ends in the middle of granule
+  // (storage_end is unaligned by granularity)
+  //  and shares that granule with a different object.
+  if (!AddrIsAlignedByGranularity(storage_beg))
+    return;
+
+  if (old_beg == old_end) {
+    old_beg = old_end = new_beg;
+  } else if (new_end <= old_beg || old_end <= new_beg || new_beg == new_end) {
+    // Poisoining whole memory.
+    uptr a = RoundDownTo(old_beg, granularity);
+    uptr b = RoundUpTo(old_end, granularity);
+    PoisonShadow(a, b - a, kAsanContiguousContainerOOBMagic);
+
+    old_beg = old_end = new_beg;
+  }
+
+  if (old_beg != new_beg) {
+    CHECK_LE(storage_end - storage_beg,
+             FIRST_32_SECOND_64(1UL << 30, 1ULL << 40));  // Sanity check.
+
+    // There are two situations: we are poisoning or unpoisoning.
+    // WARNING: at the moment we do not poison prefixes of blocks described by
+    // one byte in shadow memory, so we have to unpoison prefixes of blocks with
+    // content. Up to (granularity - 1) bytes not-in-use may not be poisoned.
+
+    if (new_beg < old_beg) {  // We are unpoisoning
+      uptr a = RoundDownTo(new_beg, granularity);
+      uptr c = RoundDownTo(old_beg, granularity);
+      // State at the moment is:
+      // [storage_beg, a] is poisoned and should remain like that.
+      // [a, c] is poisoned as well (interval may be empty if new_beg
+      // and old_beg are in the same block). If the container is not
+      // empty, first element starts somewhere in [c, c+granularity]. Because we
+      // do not poison prefixes, memory [c, container_end] is not poisoned and
+      // we won't change it. If container is empty, we have to unpoison memory
+      // for elements after c, so [c, container_end]
+      PoisonShadow(a, c - a, 0);
+      if (old_beg == old_end &&
+          !AddrIsAlignedByGranularity(old_beg)) {  // was empty && ends in the
+                                                   // middle of a block
+        *(u8 *)MemToShadow(c) = static_cast<u8>(old_end - c);
+      }
+      // else: we cannot poison prefix of a block with elements or there is
+      // nothing to poison.
+    } else {  // we are poisoning as beginning moved further in memory
+      uptr a = RoundDownTo(old_beg, granularity);
+      uptr c = RoundDownTo(new_beg, granularity);
+      // State at the moment is:
+      // [storage_beg, a] is poisoned and should remain like that.
+      // [a, c] is not poisoned (interval may be empty if new_beg and
+      // old_beg are in the same block) [c, container_end] is not
+      // poisoned. If there are remaining elements in the container:
+      //   We have to poison [a, c], but because we do not poison prefixes, we
+      //   cannot poison memory after c (even that there are not elements of the
+      //   container). Up to granularity-1 unused bytes will not be poisoned.
+      // Otherwise:
+      //   We have to poison the last byte as well.
+      PoisonShadow(a, c - a, kAsanContiguousContainerOOBMagic);
+      if (new_beg == old_end &&
+          !AddrIsAlignedByGranularity(new_beg)) {  // is empty && ends in the
+                                                   // middle of a block
+        *(u8 *)MemToShadow(c) =
+            static_cast<u8>(kAsanContiguousContainerOOBMagic);
+      }
+    }
+
+    old_beg = new_beg;
+  }
+
+  if (old_end != new_end) {
+    CHECK_LE(storage_end - storage_beg,
+             FIRST_32_SECOND_64(1UL << 30, 1ULL << 40));  // Sanity check.
+
+    if (old_end < new_end) {  // We are unpoisoning memory
+      uptr a = RoundDownTo(old_end, granularity);
+      uptr c = RoundDownTo(new_end, granularity);
+      // State at the moment is:
+      // if container_beg < a : [container_beg, a] is correct and we will not be
+      // changing it. else [a, container_beg] cannot be poisoned, so we do not
+      // have to think about it. we have to makr as unpoisoned [a, c]. [c, end]
+      // is correctly poisoned.
+      PoisonShadow(a, c - a, 0);
+      if (!AddrIsAlignedByGranularity(
+              new_end))  // ends in the middle of a block
+        *(u8 *)MemToShadow(c) = static_cast<u8>(new_end - c);
+    } else {  // We are poisoning memory
+      uptr a = RoundDownTo(new_end, granularity);
+      // State at the moment is:
+      // [storage_beg, a] is correctly annotated
+      // if container is empty after the removal, then a < container_beg and we
+      // will have to poison memory which is adressable only because we are not
+      // poisoning prefixes.
+      uptr a2 = RoundUpTo(new_end, granularity);
+      uptr c2 = RoundUpTo(old_end, granularity);
+      PoisonShadow(a2, c2 - a2, kAsanContiguousContainerOOBMagic);
+      if (!AddrIsAlignedByGranularity(
+              new_end)) {        // Starts in the middle of the block
+        if (new_end == old_beg)  // empty
+          *(u8 *)MemToShadow(a) = kAsanContiguousContainerOOBMagic;
+        else  // not empty
+          *(u8 *)MemToShadow(a) = static_cast<u8>(new_end - a);
+      }
+    }
+  }
+}
+
 const void *__sanitizer_contiguous_container_find_bad_address(
     const void *beg_p, const void *mid_p, const void *end_p) {
   if (!flags()->detect_container_overflow)
@@ -486,8 +628,8 @@ const void *__sanitizer_contiguous_container_find_bad_address(
   uptr mid = reinterpret_cast<uptr>(mid_p);
   CHECK_LE(beg, mid);
   CHECK_LE(mid, end);
-  // Check some bytes starting from beg, some bytes around mid, and some bytes
-  // ending with end.
+  // Check some bytes starting from storage_beg, some bytes around mid, and some
+  // bytes ending with end.
   uptr kMaxRangeToCheck = 32;
   uptr r1_beg = beg;
   uptr r1_end = Min(beg + kMaxRangeToCheck, mid);
@@ -517,6 +659,71 @@ int __sanitizer_verify_contiguous_container(const void *beg_p,
                                                            end_p) == nullptr;
 }
 
+const void *__sanitizer_double_ended_contiguous_container_find_bad_address(
+    const void *storage_beg_p, const void *container_beg_p,
+    const void *container_end_p, const void *storage_end_p) {
+  uptr granularity = ASAN_SHADOW_GRANULARITY;
+  // This exists to verify double ended containers.
+  // We assume that such collection's internal memory layout
+  // consists of contiguous blocks:
+  // [a; b) [b; c) [c; d)
+  // where
+  // a - beginning address of contiguous memory block,
+  // b - beginning address of contiguous memory in use
+  //      (address of the first element in the block)
+  // c - end address of contiguous memory in use
+  //      (address just after the last element in the block)
+  // d - end address of contiguous memory block
+  // [a; b) - poisoned
+  // [b; c) - accessible
+  // [c; d) - poisoned
+  // WARNING: We can't poison [a; b) fully in all cases.
+  // This is because the current shadow memory encoding
+  // does not allow for marking/poisoning that a prefix
+  // of an 8-byte block (or, ASAN_SHADOW_GRANULARITY sized block)
+  // cannot be used by the instrumented program. It only has the
+  // 01, 02, 03, 04, 05, 06, 07 and 00 encodings
+  // for usable/addressable memory
+  // (where 00 means that the whole 8-byte block can be used).
+  //
+  // This means that there are cases where not whole of the [a; b)
+  // region is poisoned and instead only the [a; RoundDown(b))
+  // region is poisoned and we may not detect invalid memory accesses on
+  // [RegionDown(b), b).
+  // This is an inherent design limitation of how AddressSanitizer granularity
+  // and shadow memory encoding works at the moment.
+
+  // If empty, storage_beg_p == container_beg_p == container_end_p
+
+  const void *a = storage_beg_p;
+  // We do not suport poisoning prefixes of blocks, so
+  // memory in the first block with data in us,
+  // just before container beginning cannot be poisoned, as described above.
+  const void *b = reinterpret_cast<const void *>(
+      RoundDownTo(reinterpret_cast<uptr>(container_beg_p), granularity));
+  const void *c = container_end_p;
+  const void *d = storage_end_p;
+  if (container_beg_p == container_end_p)
+    return __sanitizer_contiguous_container_find_bad_address(a, a, d);
+  const void *result;
+  if (a < b &&
+      (result = __sanitizer_contiguous_container_find_bad_address(a, a, b)))
+    return result;
+  if (b < d &&
+      (result = __sanitizer_contiguous_container_find_bad_address(b, c, d)))
+    return result;
+
+  return nullptr;
+}
+
+int __sanitizer_verify_double_ended_contiguous_container(
+    const void *storage_beg_p, const void *container_beg_p,
+    const void *container_end_p, const void *storage_end_p) {
+  return __sanitizer_double_ended_contiguous_container_find_bad_address(
+             storage_beg_p, container_beg_p, container_end_p, storage_end_p) ==
+         nullptr;
+}
+
 extern "C" SANITIZER_INTERFACE_ATTRIBUTE
 void __asan_poison_intra_object_redzone(uptr ptr, uptr size) {
   AsanPoisonOrUnpoisonIntraObjectRedzone(ptr, size, true);
index 2a55d6c..f2c0434 100644 (file)
@@ -354,6 +354,18 @@ void ReportBadParamsToAnnotateContiguousContainer(uptr beg, uptr end,
   in_report.ReportError(error);
 }
 
+void ReportBadParamsToAnnotateDoubleEndedContiguousContainer(
+    uptr storage_beg, uptr storage_end, uptr old_container_beg,
+    uptr old_container_end, uptr new_container_beg, uptr new_container_end,
+    BufferedStackTrace *stack) {
+  ScopedInErrorReport in_report;
+  ErrorBadParamsToAnnotateDoubleEndedContiguousContainer error(
+      GetCurrentTidOrInvalid(), stack, storage_beg, storage_end,
+      old_container_beg, old_container_end, new_container_beg,
+      new_container_end);
+  in_report.ReportError(error);
+}
+
 void ReportODRViolation(const __asan_global *g1, u32 stack_id1,
                         const __asan_global *g2, u32 stack_id2) {
   ScopedInErrorReport in_report;
index dcf6089..248e30d 100644 (file)
@@ -83,6 +83,10 @@ void ReportStringFunctionSizeOverflow(uptr offset, uptr size,
 void ReportBadParamsToAnnotateContiguousContainer(uptr beg, uptr end,
                                                   uptr old_mid, uptr new_mid,
                                                   BufferedStackTrace *stack);
+void ReportBadParamsToAnnotateDoubleEndedContiguousContainer(
+    uptr storage_beg, uptr storage_end, uptr old_container_beg,
+    uptr old_container_end, uptr new_container_beg, uptr new_container_end,
+    BufferedStackTrace *stack);
 
 void ReportODRViolation(const __asan_global *g1, u32 stack_id1,
                         const __asan_global *g2, u32 stack_id2);
index 932e547..958f071 100644 (file)
@@ -9,12 +9,16 @@
 //===----------------------------------------------------------------------===//
 INTERFACE_FUNCTION(__sanitizer_acquire_crash_state)
 INTERFACE_FUNCTION(__sanitizer_annotate_contiguous_container)
+INTERFACE_FUNCTION(__sanitizer_annotate_double_ended_contiguous_container)
 INTERFACE_FUNCTION(__sanitizer_contiguous_container_find_bad_address)
+INTERFACE_FUNCTION(
+    __sanitizer_double_ended_contiguous_container_find_bad_address)
 INTERFACE_FUNCTION(__sanitizer_set_death_callback)
 INTERFACE_FUNCTION(__sanitizer_set_report_path)
 INTERFACE_FUNCTION(__sanitizer_set_report_fd)
 INTERFACE_FUNCTION(__sanitizer_get_report_path)
 INTERFACE_FUNCTION(__sanitizer_verify_contiguous_container)
+INTERFACE_FUNCTION(__sanitizer_verify_double_ended_contiguous_container)
 INTERFACE_WEAK_FUNCTION(__sanitizer_on_print)
 INTERFACE_WEAK_FUNCTION(__sanitizer_report_error_summary)
 INTERFACE_WEAK_FUNCTION(__sanitizer_sandbox_on_notify)
index ad34e5e..cd0d45e 100644 (file)
@@ -66,18 +66,30 @@ void __sanitizer_annotate_contiguous_container(const void *beg, const void *end,
                                                const void *old_mid,
                                                const void *new_mid);
 SANITIZER_INTERFACE_ATTRIBUTE
+void __sanitizer_annotate_double_ended_contiguous_container(
+    const void *storage_beg, const void *storage_end,
+    const void *old_container_beg, const void *old_container_end,
+    const void *new_container_beg, const void *new_container_end);
+SANITIZER_INTERFACE_ATTRIBUTE
 int __sanitizer_verify_contiguous_container(const void *beg, const void *mid,
                                             const void *end);
 SANITIZER_INTERFACE_ATTRIBUTE
+int __sanitizer_verify_double_ended_contiguous_container(
+    const void *storage_beg, const void *container_beg,
+    const void *container_end, const void *storage_end);
+SANITIZER_INTERFACE_ATTRIBUTE
 const void *__sanitizer_contiguous_container_find_bad_address(const void *beg,
                                                               const void *mid,
                                                               const void *end);
+SANITIZER_INTERFACE_ATTRIBUTE
+const void *__sanitizer_double_ended_contiguous_container_find_bad_address(
+    const void *storage_beg, const void *container_beg,
+    const void *container_end, const void *storage_end);
 
 SANITIZER_INTERFACE_ATTRIBUTE
 int __sanitizer_get_module_and_offset_for_pc(void *pc, char *module_path,
                                              __sanitizer::uptr module_path_len,
                                              void **pc_offset);
-
 SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
 __sanitizer_cov_trace_cmp();
 SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
index 2bd31f4..feb7063 100644 (file)
@@ -79,6 +79,38 @@ void TestContainer(size_t capacity, size_t off_begin, bool poison_buffer) {
   delete[] buffer;
 }
 
+void TestDoubleEndedContainer(size_t capacity) {
+  char *st_beg = new char[capacity];
+  char *st_end = st_beg + capacity;
+  char *beg = st_beg;
+  char *end = st_beg + capacity;
+
+  for (int i = 0; i < 10000; i++) {
+    size_t size = rand() % (capacity + 1);
+    size_t skipped = rand() % (capacity - size + 1);
+    assert(size <= capacity);
+    char *old_beg = beg;
+    char *old_end = end;
+    beg = st_beg + skipped;
+    end = beg + size;
+
+    __sanitizer_annotate_double_ended_contiguous_container(
+        st_beg, st_end, old_beg, old_end, beg, end);
+    for (size_t idx = 0; idx < RoundDown(skipped); idx++)
+      assert(__asan_address_is_poisoned(st_beg + idx));
+    for (size_t idx = 0; idx < size; idx++)
+      assert(!__asan_address_is_poisoned(st_beg + skipped + idx));
+    for (size_t idx = skipped + size; idx < capacity; idx++)
+      assert(__asan_address_is_poisoned(st_beg + idx));
+
+    assert(__sanitizer_verify_double_ended_contiguous_container(st_beg, beg,
+                                                                end, st_end));
+  }
+
+  __asan_unpoison_memory_region(st_beg, st_end - st_beg);
+  delete[] st_beg;
+}
+
 __attribute__((noinline)) void Throw() { throw 1; }
 
 __attribute__((noinline)) void ThrowAndCatch() {
@@ -103,9 +135,13 @@ void TestThrow() {
 
 int main(int argc, char **argv) {
   int n = argc == 1 ? 64 : atoi(argv[1]);
-  for (int i = 0; i <= n; i++)
-    for (int j = 0; j < kGranularity * 2; j++)
-      for (int poison = 0; poison < 2; ++poison)
+  for (int i = 0; i <= n; i++) {
+    for (int j = 0; j < kGranularity * 2; j++) {
+      for (int poison = 0; poison < 2; ++poison) {
         TestContainer(i, j, poison);
+      }
+    }
+    TestDoubleEndedContainer(i);
+  }
   TestThrow();
 }