Skip to content

Commit 1c5ad6d

Browse files
Advenam Tacetvitalybuka
Advenam Tacet
authored andcommitted
[1a/3][ASan][compiler-rt] API for double ended containers
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: [email protected] [email protected] Differential Revision: https://reviews.llvm.org/D132090
1 parent 6c87dea commit 1c5ad6d

File tree

9 files changed

+397
-6
lines changed

9 files changed

+397
-6
lines changed

compiler-rt/include/sanitizer/common_interface_defs.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,40 @@ void __sanitizer_annotate_contiguous_container(const void *beg,
159159
const void *old_mid,
160160
const void *new_mid);
161161

162+
/// Similar to <c>__sanitizer_annotate_contiguous_container</c>.
163+
///
164+
/// Annotates the current state of a contiguous container memory,
165+
/// such as <c>std::deque</c>'s single chunk, when the boundries are moved.
166+
///
167+
/// A contiguous chunk is a chunk that keeps all of its elements
168+
/// in a contiguous region of memory. The container owns the region of memory
169+
/// <c>[storage_beg, storage_end)</c>; the memory <c>[container_beg,
170+
/// container_end)</c> is used to store the current elements, and the memory
171+
/// <c>[storage_beg, container_beg), [container_end, storage_end)</c> is
172+
/// reserved for future elements (<c>storage_beg <= container_beg <=
173+
/// container_end <= storage_end</c>). For example, in <c> std::deque </c>:
174+
/// - chunk with a frist deques element will have container_beg equal to address
175+
/// of the first element.
176+
/// - in every next chunk with elements, true is <c> container_beg ==
177+
/// storage_beg </c>.
178+
///
179+
/// Argument requirements:
180+
/// During unpoisoning memory of empty container (before first element is
181+
/// added):
182+
/// - old_container_beg_p == old_container_end_p
183+
/// During poisoning after last element was removed:
184+
/// - new_container_beg_p == new_container_end_p
185+
/// \param storage_beg Beginning of memory region.
186+
/// \param storage_end End of memory region.
187+
/// \param old_container_beg Old beginning of used region.
188+
/// \param old_container_end End of used region.
189+
/// \param new_container_beg New beginning of used region.
190+
/// \param new_container_end New end of used region.
191+
void __sanitizer_annotate_double_ended_contiguous_container(
192+
const void *storage_beg, const void *storage_end,
193+
const void *old_container_beg, const void *old_container_end,
194+
const void *new_container_beg, const void *new_container_end);
195+
162196
/// Returns true if the contiguous container <c>[beg, end)</c> is properly
163197
/// poisoned.
164198
///
@@ -178,6 +212,31 @@ void __sanitizer_annotate_contiguous_container(const void *beg,
178212
int __sanitizer_verify_contiguous_container(const void *beg, const void *mid,
179213
const void *end);
180214

215+
/// Returns true if the double ended contiguous
216+
/// container <c>[storage_beg, storage_end)</c> is properly poisoned.
217+
///
218+
/// Proper poisoning could occur, for example, with
219+
/// <c>__sanitizer_annotate_double_ended_contiguous_container</c>), that is, if
220+
/// <c>[storage_beg, container_beg)</c> is not addressable, <c>[container_beg,
221+
/// container_end)</c> is addressable and <c>[container_end, end)</c> is
222+
/// unaddressable. Full verification requires O (<c>storage_end -
223+
/// storage_beg</c>) time; this function tries to avoid such complexity by
224+
/// touching only parts of the container around <c><i>storage_beg</i></c>,
225+
/// <c><i>container_beg</i></c>, <c><i>container_end</i></c>, and
226+
/// <c><i>storage_end</i></c>.
227+
///
228+
/// \param storage_beg Beginning of memory region.
229+
/// \param container_beg Beginning of used region.
230+
/// \param container_end End of used region.
231+
/// \param storage_end End of memory region.
232+
///
233+
/// \returns True if the double-ended contiguous container <c>[storage_beg,
234+
/// container_beg, container_end, end)</c> is properly poisoned - only
235+
/// [container_beg; container_end) is addressable.
236+
int __sanitizer_verify_double_ended_contiguous_container(
237+
const void *storage_beg, const void *container_beg,
238+
const void *container_end, const void *storage_end);
239+
181240
/// Similar to <c>__sanitizer_verify_contiguous_container()</c> but also
182241
/// returns the address of the first improperly poisoned byte.
183242
///
@@ -192,6 +251,20 @@ const void *__sanitizer_contiguous_container_find_bad_address(const void *beg,
192251
const void *mid,
193252
const void *end);
194253

254+
/// returns the address of the first improperly poisoned byte.
255+
///
256+
/// Returns NULL if the area is poisoned properly.
257+
///
258+
/// \param storage_beg Beginning of memory region.
259+
/// \param container_beg Beginning of used region.
260+
/// \param container_end End of used region.
261+
/// \param storage_end End of memory region.
262+
///
263+
/// \returns The bad address or NULL.
264+
const void *__sanitizer_double_ended_contiguous_container_find_bad_address(
265+
const void *storage_beg, const void *container_beg,
266+
const void *container_end, const void *storage_end);
267+
195268
/// Prints the stack trace leading to this call (useful for calling from the
196269
/// debugger).
197270
void __sanitizer_print_stack_trace(void);

compiler-rt/lib/asan/asan_errors.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,26 @@ void ErrorBadParamsToAnnotateContiguousContainer::Print() {
334334
ReportErrorSummary(scariness.GetDescription(), stack);
335335
}
336336

337+
void ErrorBadParamsToAnnotateDoubleEndedContiguousContainer::Print() {
338+
Report(
339+
"ERROR: AddressSanitizer: bad parameters to "
340+
"__sanitizer_annotate_double_ended_contiguous_container:\n"
341+
" storage_beg : %p\n"
342+
" storage_end : %p\n"
343+
" old_container_beg : %p\n"
344+
" old_container_end : %p\n"
345+
" new_container_beg : %p\n"
346+
" new_container_end : %p\n",
347+
(void *)storage_beg, (void *)storage_end, (void *)old_container_beg,
348+
(void *)old_container_end, (void *)new_container_beg,
349+
(void *)new_container_end);
350+
uptr granularity = ASAN_SHADOW_GRANULARITY;
351+
if (!IsAligned(storage_beg, granularity))
352+
Report("ERROR: storage_beg is not aligned by %zu\n", granularity);
353+
stack->Print();
354+
ReportErrorSummary(scariness.GetDescription(), stack);
355+
}
356+
337357
void ErrorODRViolation::Print() {
338358
Decorator d;
339359
Printf("%s", d.Error());

compiler-rt/lib/asan/asan_errors.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,28 @@ struct ErrorBadParamsToAnnotateContiguousContainer : ErrorBase {
331331
void Print();
332332
};
333333

334+
struct ErrorBadParamsToAnnotateDoubleEndedContiguousContainer : ErrorBase {
335+
const BufferedStackTrace *stack;
336+
uptr storage_beg, storage_end, old_container_beg, old_container_end,
337+
new_container_beg, new_container_end;
338+
339+
ErrorBadParamsToAnnotateDoubleEndedContiguousContainer() = default; // (*)
340+
ErrorBadParamsToAnnotateDoubleEndedContiguousContainer(
341+
u32 tid, BufferedStackTrace *stack_, uptr storage_beg_, uptr storage_end_,
342+
uptr old_container_beg_, uptr old_container_end_, uptr new_container_beg_,
343+
uptr new_container_end_)
344+
: ErrorBase(tid, 10,
345+
"bad-__sanitizer_annotate_double_ended_contiguous_container"),
346+
stack(stack_),
347+
storage_beg(storage_beg_),
348+
storage_end(storage_end_),
349+
old_container_beg(old_container_beg_),
350+
old_container_end(old_container_end_),
351+
new_container_beg(new_container_beg_),
352+
new_container_end(new_container_end_) {}
353+
void Print();
354+
};
355+
334356
struct ErrorODRViolation : ErrorBase {
335357
__asan_global global1, global2;
336358
u32 stack_id1, stack_id2;
@@ -398,6 +420,7 @@ struct ErrorGeneric : ErrorBase {
398420
macro(StringFunctionMemoryRangesOverlap) \
399421
macro(StringFunctionSizeOverflow) \
400422
macro(BadParamsToAnnotateContiguousContainer) \
423+
macro(BadParamsToAnnotateDoubleEndedContiguousContainer) \
401424
macro(ODRViolation) \
402425
macro(InvalidPointerPair) \
403426
macro(Generic)

compiler-rt/lib/asan/asan_poisoning.cpp

Lines changed: 209 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,148 @@ void __sanitizer_annotate_contiguous_container(const void *beg_p,
472472
}
473473
}
474474

475+
// Annotates a double ended contiguous memory area like std::deque's chunk.
476+
// It allows detecting buggy accesses to allocated but not used begining
477+
// or end items of such a container.
478+
void __sanitizer_annotate_double_ended_contiguous_container(
479+
const void *storage_beg_p, const void *storage_end_p,
480+
const void *old_container_beg_p, const void *old_container_end_p,
481+
const void *new_container_beg_p, const void *new_container_end_p) {
482+
if (!flags()->detect_container_overflow)
483+
return;
484+
485+
VPrintf(2, "contiguous_container: %p %p %p %p %p %p\n", storage_beg_p,
486+
storage_end_p, old_container_beg_p, old_container_end_p,
487+
new_container_beg_p, new_container_end_p);
488+
489+
uptr storage_beg = reinterpret_cast<uptr>(storage_beg_p);
490+
uptr storage_end = reinterpret_cast<uptr>(storage_end_p);
491+
uptr old_beg = reinterpret_cast<uptr>(old_container_beg_p);
492+
uptr old_end = reinterpret_cast<uptr>(old_container_end_p);
493+
uptr new_beg = reinterpret_cast<uptr>(new_container_beg_p);
494+
uptr new_end = reinterpret_cast<uptr>(new_container_end_p);
495+
496+
constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
497+
498+
if (!(storage_beg <= new_beg && new_beg <= storage_end) ||
499+
!(storage_beg <= new_end && new_end <= storage_end) ||
500+
!(storage_beg <= old_beg && old_beg <= storage_end) ||
501+
!(storage_beg <= old_end && old_end <= storage_end) ||
502+
!(old_beg <= old_end && new_beg <= new_end)) {
503+
GET_STACK_TRACE_FATAL_HERE;
504+
ReportBadParamsToAnnotateDoubleEndedContiguousContainer(
505+
storage_beg, storage_end, old_beg, old_end, new_beg, new_end, &stack);
506+
}
507+
508+
// Right now, the function does not support:
509+
// - unaligned storage beginning
510+
// - situations when container ends in the middle of granule
511+
// (storage_end is unaligned by granularity)
512+
// and shares that granule with a different object.
513+
if (!AddrIsAlignedByGranularity(storage_beg))
514+
return;
515+
516+
if (old_beg == old_end) {
517+
old_beg = old_end = new_beg;
518+
} else if (new_end <= old_beg || old_end <= new_beg || new_beg == new_end) {
519+
// Poisoining whole memory.
520+
uptr a = RoundDownTo(old_beg, granularity);
521+
uptr b = RoundUpTo(old_end, granularity);
522+
PoisonShadow(a, b - a, kAsanContiguousContainerOOBMagic);
523+
524+
old_beg = old_end = new_beg;
525+
}
526+
527+
if (old_beg != new_beg) {
528+
CHECK_LE(storage_end - storage_beg,
529+
FIRST_32_SECOND_64(1UL << 30, 1ULL << 40)); // Sanity check.
530+
531+
// There are two situations: we are poisoning or unpoisoning.
532+
// WARNING: at the moment we do not poison prefixes of blocks described by
533+
// one byte in shadow memory, so we have to unpoison prefixes of blocks with
534+
// content. Up to (granularity - 1) bytes not-in-use may not be poisoned.
535+
536+
if (new_beg < old_beg) { // We are unpoisoning
537+
uptr a = RoundDownTo(new_beg, granularity);
538+
uptr c = RoundDownTo(old_beg, granularity);
539+
// State at the moment is:
540+
// [storage_beg, a] is poisoned and should remain like that.
541+
// [a, c] is poisoned as well (interval may be empty if new_beg
542+
// and old_beg are in the same block). If the container is not
543+
// empty, first element starts somewhere in [c, c+granularity]. Because we
544+
// do not poison prefixes, memory [c, container_end] is not poisoned and
545+
// we won't change it. If container is empty, we have to unpoison memory
546+
// for elements after c, so [c, container_end]
547+
PoisonShadow(a, c - a, 0);
548+
if (old_beg == old_end &&
549+
!AddrIsAlignedByGranularity(old_beg)) { // was empty && ends in the
550+
// middle of a block
551+
*(u8 *)MemToShadow(c) = static_cast<u8>(old_end - c);
552+
}
553+
// else: we cannot poison prefix of a block with elements or there is
554+
// nothing to poison.
555+
} else { // we are poisoning as beginning moved further in memory
556+
uptr a = RoundDownTo(old_beg, granularity);
557+
uptr c = RoundDownTo(new_beg, granularity);
558+
// State at the moment is:
559+
// [storage_beg, a] is poisoned and should remain like that.
560+
// [a, c] is not poisoned (interval may be empty if new_beg and
561+
// old_beg are in the same block) [c, container_end] is not
562+
// poisoned. If there are remaining elements in the container:
563+
// We have to poison [a, c], but because we do not poison prefixes, we
564+
// cannot poison memory after c (even that there are not elements of the
565+
// container). Up to granularity-1 unused bytes will not be poisoned.
566+
// Otherwise:
567+
// We have to poison the last byte as well.
568+
PoisonShadow(a, c - a, kAsanContiguousContainerOOBMagic);
569+
if (new_beg == old_end &&
570+
!AddrIsAlignedByGranularity(new_beg)) { // is empty && ends in the
571+
// middle of a block
572+
*(u8 *)MemToShadow(c) =
573+
static_cast<u8>(kAsanContiguousContainerOOBMagic);
574+
}
575+
}
576+
577+
old_beg = new_beg;
578+
}
579+
580+
if (old_end != new_end) {
581+
CHECK_LE(storage_end - storage_beg,
582+
FIRST_32_SECOND_64(1UL << 30, 1ULL << 40)); // Sanity check.
583+
584+
if (old_end < new_end) { // We are unpoisoning memory
585+
uptr a = RoundDownTo(old_end, granularity);
586+
uptr c = RoundDownTo(new_end, granularity);
587+
// State at the moment is:
588+
// if container_beg < a : [container_beg, a] is correct and we will not be
589+
// changing it. else [a, container_beg] cannot be poisoned, so we do not
590+
// have to think about it. we have to makr as unpoisoned [a, c]. [c, end]
591+
// is correctly poisoned.
592+
PoisonShadow(a, c - a, 0);
593+
if (!AddrIsAlignedByGranularity(
594+
new_end)) // ends in the middle of a block
595+
*(u8 *)MemToShadow(c) = static_cast<u8>(new_end - c);
596+
} else { // We are poisoning memory
597+
uptr a = RoundDownTo(new_end, granularity);
598+
// State at the moment is:
599+
// [storage_beg, a] is correctly annotated
600+
// if container is empty after the removal, then a < container_beg and we
601+
// will have to poison memory which is adressable only because we are not
602+
// poisoning prefixes.
603+
uptr a2 = RoundUpTo(new_end, granularity);
604+
uptr c2 = RoundUpTo(old_end, granularity);
605+
PoisonShadow(a2, c2 - a2, kAsanContiguousContainerOOBMagic);
606+
if (!AddrIsAlignedByGranularity(
607+
new_end)) { // Starts in the middle of the block
608+
if (new_end == old_beg) // empty
609+
*(u8 *)MemToShadow(a) = kAsanContiguousContainerOOBMagic;
610+
else // not empty
611+
*(u8 *)MemToShadow(a) = static_cast<u8>(new_end - a);
612+
}
613+
}
614+
}
615+
}
616+
475617
const void *__sanitizer_contiguous_container_find_bad_address(
476618
const void *beg_p, const void *mid_p, const void *end_p) {
477619
if (!flags()->detect_container_overflow)
@@ -486,8 +628,8 @@ const void *__sanitizer_contiguous_container_find_bad_address(
486628
uptr mid = reinterpret_cast<uptr>(mid_p);
487629
CHECK_LE(beg, mid);
488630
CHECK_LE(mid, end);
489-
// Check some bytes starting from beg, some bytes around mid, and some bytes
490-
// ending with end.
631+
// Check some bytes starting from storage_beg, some bytes around mid, and some
632+
// bytes ending with end.
491633
uptr kMaxRangeToCheck = 32;
492634
uptr r1_beg = beg;
493635
uptr r1_end = Min(beg + kMaxRangeToCheck, mid);
@@ -517,6 +659,71 @@ int __sanitizer_verify_contiguous_container(const void *beg_p,
517659
end_p) == nullptr;
518660
}
519661

662+
const void *__sanitizer_double_ended_contiguous_container_find_bad_address(
663+
const void *storage_beg_p, const void *container_beg_p,
664+
const void *container_end_p, const void *storage_end_p) {
665+
uptr granularity = ASAN_SHADOW_GRANULARITY;
666+
// This exists to verify double ended containers.
667+
// We assume that such collection's internal memory layout
668+
// consists of contiguous blocks:
669+
// [a; b) [b; c) [c; d)
670+
// where
671+
// a - beginning address of contiguous memory block,
672+
// b - beginning address of contiguous memory in use
673+
// (address of the first element in the block)
674+
// c - end address of contiguous memory in use
675+
// (address just after the last element in the block)
676+
// d - end address of contiguous memory block
677+
// [a; b) - poisoned
678+
// [b; c) - accessible
679+
// [c; d) - poisoned
680+
// WARNING: We can't poison [a; b) fully in all cases.
681+
// This is because the current shadow memory encoding
682+
// does not allow for marking/poisoning that a prefix
683+
// of an 8-byte block (or, ASAN_SHADOW_GRANULARITY sized block)
684+
// cannot be used by the instrumented program. It only has the
685+
// 01, 02, 03, 04, 05, 06, 07 and 00 encodings
686+
// for usable/addressable memory
687+
// (where 00 means that the whole 8-byte block can be used).
688+
//
689+
// This means that there are cases where not whole of the [a; b)
690+
// region is poisoned and instead only the [a; RoundDown(b))
691+
// region is poisoned and we may not detect invalid memory accesses on
692+
// [RegionDown(b), b).
693+
// This is an inherent design limitation of how AddressSanitizer granularity
694+
// and shadow memory encoding works at the moment.
695+
696+
// If empty, storage_beg_p == container_beg_p == container_end_p
697+
698+
const void *a = storage_beg_p;
699+
// We do not suport poisoning prefixes of blocks, so
700+
// memory in the first block with data in us,
701+
// just before container beginning cannot be poisoned, as described above.
702+
const void *b = reinterpret_cast<const void *>(
703+
RoundDownTo(reinterpret_cast<uptr>(container_beg_p), granularity));
704+
const void *c = container_end_p;
705+
const void *d = storage_end_p;
706+
if (container_beg_p == container_end_p)
707+
return __sanitizer_contiguous_container_find_bad_address(a, a, d);
708+
const void *result;
709+
if (a < b &&
710+
(result = __sanitizer_contiguous_container_find_bad_address(a, a, b)))
711+
return result;
712+
if (b < d &&
713+
(result = __sanitizer_contiguous_container_find_bad_address(b, c, d)))
714+
return result;
715+
716+
return nullptr;
717+
}
718+
719+
int __sanitizer_verify_double_ended_contiguous_container(
720+
const void *storage_beg_p, const void *container_beg_p,
721+
const void *container_end_p, const void *storage_end_p) {
722+
return __sanitizer_double_ended_contiguous_container_find_bad_address(
723+
storage_beg_p, container_beg_p, container_end_p, storage_end_p) ==
724+
nullptr;
725+
}
726+
520727
extern "C" SANITIZER_INTERFACE_ATTRIBUTE
521728
void __asan_poison_intra_object_redzone(uptr ptr, uptr size) {
522729
AsanPoisonOrUnpoisonIntraObjectRedzone(ptr, size, true);

0 commit comments

Comments
 (0)