Skip to content

Commit c76045d

Browse files
[compiler-rt][ASan] Add function copying annotations (#91702)
This PR adds a `__sanitizer_copy_contiguous_container_annotations` function, which copies annotations from one memory area to another. New area is annotated in the same way as the old region at the beginning (within limitations of ASan). Overlapping case: The function supports overlapping containers, however no assumptions should be made outside of no false positives in new buffer area. (It doesn't modify old container annotations where it's not necessary, false negatives may happen in edge granules of the new container area.) I don't expect this function to be used with overlapping buffers, but it's designed to work with them and not result in incorrect ASan errors (false positives). If buffers have granularity-aligned distance between them (`old_beg % granularity == new_beg % granularity`), copying algorithm works faster. If the distance is not granularity-aligned, annotations are copied byte after byte. ```cpp void __sanitizer_copy_contiguous_container_annotations( const void *old_storage_beg_p, const void *old_storage_end_p, const void *new_storage_beg_p, const void *new_storage_end_p) { ``` This function aims to help with short string annotations and similar container annotations. Right now we change trait types of `std::basic_string` when compiling with ASan and this function purpose is reverting that change as soon as possible. https://github.com/llvm/llvm-project/blob/87f3407856e61a73798af4e41b28bc33b5bf4ce6/libcxx/include/string#L738-L751 The goal is to not change `__trivially_relocatable` when compiling with ASan. If this function is accepted and upstreamed, the next step is creating a function like `__memcpy_with_asan` moving memory with ASan. And then using this function instead of `__builtin__memcpy` while moving trivially relocatable objects. https://github.com/llvm/llvm-project/blob/11a6799740f824282650aa9ec249b55dcf1a8aae/libcxx/include/__memory/uninitialized_algorithms.h#L644-L646 --- I'm thinking if there is a good way to address fact that in a container the new buffer is usually bigger than the previous one. We may add two more arguments to the functions to address it (the beginning and the end of the whole buffer. Another potential change is removing `new_storage_end_p` as it's redundant, because we require the same size. Potential future work is creating a function `__asan_unsafe_memmove`, which will be basically memmove, but with turned off instrumentation (therefore it will allow copy data from poisoned area). --------- Co-authored-by: Vitaly Buka <[email protected]>
1 parent f314e12 commit c76045d

File tree

9 files changed

+441
-0
lines changed

9 files changed

+441
-0
lines changed

compiler-rt/include/sanitizer/common_interface_defs.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,43 @@ void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container(
193193
const void *old_container_beg, const void *old_container_end,
194194
const void *new_container_beg, const void *new_container_end);
195195

196+
/// Copies memory annotations from a source storage region to a destination
197+
/// storage region. After the operation, the destination region has the same
198+
/// memory annotations as the source region, as long as sanitizer limitations
199+
/// allow it (more bytes may be unpoisoned than in the source region, resulting
200+
/// in more false negatives, but never false positives). If the source and
201+
/// destination regions overlap, only the minimal required changes are made to
202+
/// preserve the correct annotations. Old storage bytes that are not in the new
203+
/// storage should have the same annotations, as long as sanitizer limitations
204+
/// allow it.
205+
///
206+
/// This function is primarily designed to be used when moving trivially
207+
/// relocatable objects that may have poisoned memory, making direct copying
208+
/// problematic under sanitizer. However, this function does not move memory
209+
/// content itself, only annotations.
210+
///
211+
/// A contiguous container is a container that keeps all of its elements in a
212+
/// contiguous region of memory. The container owns the region of memory
213+
/// <c>[src_begin, src_end)</c> and <c>[dst_begin, dst_end)</c>. The memory
214+
/// within these regions may be alternately poisoned and non-poisoned, with
215+
/// possibly smaller poisoned and unpoisoned regions.
216+
///
217+
/// If this function fully poisons a granule, it is marked as "container
218+
/// overflow".
219+
///
220+
/// Argument requirements: The destination container must have the same size as
221+
/// the source container, which is inferred from the beginning and end of the
222+
/// source region. Addresses may be granule-unaligned, but this may affect
223+
/// performance.
224+
///
225+
/// \param src_begin Begin of the source container region.
226+
/// \param src_end End of the source container region.
227+
/// \param dst_begin Begin of the destination container region.
228+
/// \param dst_end End of the destination container region.
229+
void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations(
230+
const void *src_begin, const void *src_end, const void *dst_begin,
231+
const void *dst_end);
232+
196233
/// Returns true if the contiguous container <c>[beg, end)</c> is properly
197234
/// poisoned.
198235
///

compiler-rt/lib/asan/asan_errors.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,20 @@ void ErrorBadParamsToAnnotateDoubleEndedContiguousContainer::Print() {
348348
ReportErrorSummary(scariness.GetDescription(), stack);
349349
}
350350

351+
void ErrorBadParamsToCopyContiguousContainerAnnotations::Print() {
352+
Report(
353+
"ERROR: AddressSanitizer: bad parameters to "
354+
"__sanitizer_copy_contiguous_container_annotations:\n"
355+
" src_storage_beg : %p\n"
356+
" src_storage_end : %p\n"
357+
" dst_storage_beg : %p\n"
358+
" new_storage_end : %p\n",
359+
(void *)old_storage_beg, (void *)old_storage_end, (void *)new_storage_beg,
360+
(void *)new_storage_end);
361+
stack->Print();
362+
ReportErrorSummary(scariness.GetDescription(), stack);
363+
}
364+
351365
void ErrorODRViolation::Print() {
352366
Decorator d;
353367
Printf("%s", d.Error());

compiler-rt/lib/asan/asan_errors.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,24 @@ struct ErrorBadParamsToAnnotateDoubleEndedContiguousContainer : ErrorBase {
353353
void Print();
354354
};
355355

356+
struct ErrorBadParamsToCopyContiguousContainerAnnotations : ErrorBase {
357+
const BufferedStackTrace *stack;
358+
uptr old_storage_beg, old_storage_end, new_storage_beg, new_storage_end;
359+
360+
ErrorBadParamsToCopyContiguousContainerAnnotations() = default; // (*)
361+
ErrorBadParamsToCopyContiguousContainerAnnotations(
362+
u32 tid, BufferedStackTrace *stack_, uptr old_storage_beg_,
363+
uptr old_storage_end_, uptr new_storage_beg_, uptr new_storage_end_)
364+
: ErrorBase(tid, 10,
365+
"bad-__sanitizer_annotate_double_ended_contiguous_container"),
366+
stack(stack_),
367+
old_storage_beg(old_storage_beg_),
368+
old_storage_end(old_storage_end_),
369+
new_storage_beg(new_storage_beg_),
370+
new_storage_end(new_storage_end_) {}
371+
void Print();
372+
};
373+
356374
struct ErrorODRViolation : ErrorBase {
357375
__asan_global global1, global2;
358376
u32 stack_id1, stack_id2;
@@ -421,6 +439,7 @@ struct ErrorGeneric : ErrorBase {
421439
macro(StringFunctionSizeOverflow) \
422440
macro(BadParamsToAnnotateContiguousContainer) \
423441
macro(BadParamsToAnnotateDoubleEndedContiguousContainer) \
442+
macro(BadParamsToCopyContiguousContainerAnnotations) \
424443
macro(ODRViolation) \
425444
macro(InvalidPointerPair) \
426445
macro(Generic)

compiler-rt/lib/asan/asan_poisoning.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "asan_report.h"
1717
#include "asan_stack.h"
1818
#include "sanitizer_common/sanitizer_atomic.h"
19+
#include "sanitizer_common/sanitizer_common.h"
1920
#include "sanitizer_common/sanitizer_flags.h"
2021
#include "sanitizer_common/sanitizer_interface_internal.h"
2122
#include "sanitizer_common/sanitizer_libc.h"
@@ -576,6 +577,185 @@ void __sanitizer_annotate_double_ended_contiguous_container(
576577
}
577578
}
578579

580+
// Marks the specified number of bytes in a granule as accessible or
581+
// poisones the whole granule with kAsanContiguousContainerOOBMagic value.
582+
static void SetContainerGranule(uptr ptr, u8 n) {
583+
constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
584+
u8 s = (n == granularity) ? 0 : (n ? n : kAsanContiguousContainerOOBMagic);
585+
*(u8 *)MemToShadow(ptr) = s;
586+
}
587+
588+
// Performs a byte-by-byte copy of ASan annotations (shadow memory values).
589+
// Result may be different due to ASan limitations, but result cannot lead
590+
// to false positives (more memory than requested may get unpoisoned).
591+
static void SlowCopyContainerAnnotations(uptr src_beg, uptr src_end,
592+
uptr dst_beg, uptr dst_end) {
593+
constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
594+
uptr dst_end_down = RoundDownTo(dst_end, granularity);
595+
uptr src_ptr = src_beg;
596+
uptr dst_ptr = dst_beg;
597+
598+
while (dst_ptr < dst_end) {
599+
uptr granule_beg = RoundDownTo(dst_ptr, granularity);
600+
uptr granule_end = granule_beg + granularity;
601+
uptr unpoisoned_bytes = 0;
602+
603+
uptr end = Min(granule_end, dst_end);
604+
for (; dst_ptr != end; ++dst_ptr, ++src_ptr)
605+
if (!AddressIsPoisoned(src_ptr))
606+
unpoisoned_bytes = dst_ptr - granule_beg + 1;
607+
608+
if (dst_ptr == dst_end && dst_end != dst_end_down &&
609+
!AddressIsPoisoned(dst_end))
610+
continue;
611+
612+
if (unpoisoned_bytes != 0 || granule_beg >= dst_beg)
613+
SetContainerGranule(granule_beg, unpoisoned_bytes);
614+
else if (!AddressIsPoisoned(dst_beg))
615+
SetContainerGranule(granule_beg, dst_beg - granule_beg);
616+
}
617+
}
618+
619+
// Performs a byte-by-byte copy of ASan annotations (shadow memory values),
620+
// going through bytes in reversed order, but not reversing annotations.
621+
// Result may be different due to ASan limitations, but result cannot lead
622+
// to false positives (more memory than requested may get unpoisoned).
623+
static void SlowReversedCopyContainerAnnotations(uptr src_beg, uptr src_end,
624+
uptr dst_beg, uptr dst_end) {
625+
constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
626+
uptr dst_end_down = RoundDownTo(dst_end, granularity);
627+
uptr src_ptr = src_end;
628+
uptr dst_ptr = dst_end;
629+
630+
while (dst_ptr > dst_beg) {
631+
uptr granule_beg = RoundDownTo(dst_ptr - 1, granularity);
632+
uptr unpoisoned_bytes = 0;
633+
634+
uptr end = Max(granule_beg, dst_beg);
635+
for (; dst_ptr != end; --dst_ptr, --src_ptr)
636+
if (unpoisoned_bytes == 0 && !AddressIsPoisoned(src_ptr - 1))
637+
unpoisoned_bytes = dst_ptr - granule_beg;
638+
639+
if (dst_ptr >= dst_end_down && !AddressIsPoisoned(dst_end))
640+
continue;
641+
642+
if (granule_beg == dst_ptr || unpoisoned_bytes != 0)
643+
SetContainerGranule(granule_beg, unpoisoned_bytes);
644+
else if (!AddressIsPoisoned(dst_beg))
645+
SetContainerGranule(granule_beg, dst_beg - granule_beg);
646+
}
647+
}
648+
649+
// A helper function for __sanitizer_copy_contiguous_container_annotations,
650+
// has assumption about begin and end of the container.
651+
// Should not be used stand alone.
652+
static void CopyContainerFirstGranuleAnnotation(uptr src_beg, uptr dst_beg) {
653+
constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
654+
// First granule
655+
uptr src_beg_down = RoundDownTo(src_beg, granularity);
656+
uptr dst_beg_down = RoundDownTo(dst_beg, granularity);
657+
if (dst_beg_down == dst_beg)
658+
return;
659+
if (!AddressIsPoisoned(src_beg))
660+
*(u8 *)MemToShadow(dst_beg_down) = *(u8 *)MemToShadow(src_beg_down);
661+
else if (!AddressIsPoisoned(dst_beg))
662+
SetContainerGranule(dst_beg_down, dst_beg - dst_beg_down);
663+
}
664+
665+
// A helper function for __sanitizer_copy_contiguous_container_annotations,
666+
// has assumption about begin and end of the container.
667+
// Should not be used stand alone.
668+
static void CopyContainerLastGranuleAnnotation(uptr src_end, uptr dst_end) {
669+
constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
670+
// Last granule
671+
uptr src_end_down = RoundDownTo(src_end, granularity);
672+
uptr dst_end_down = RoundDownTo(dst_end, granularity);
673+
if (dst_end_down == dst_end || !AddressIsPoisoned(dst_end))
674+
return;
675+
if (AddressIsPoisoned(src_end))
676+
*(u8 *)MemToShadow(dst_end_down) = *(u8 *)MemToShadow(src_end_down);
677+
else
678+
SetContainerGranule(dst_end_down, src_end - src_end_down);
679+
}
680+
681+
// This function copies ASan memory annotations (poisoned/unpoisoned states)
682+
// from one buffer to another.
683+
// It's main purpose is to help with relocating trivially relocatable objects,
684+
// which memory may be poisoned, without calling copy constructor.
685+
// However, it does not move memory content itself, only annotations.
686+
// If the buffers aren't aligned (the distance between buffers isn't
687+
// granule-aligned)
688+
// // src_beg % granularity != dst_beg % granularity
689+
// the function handles this by going byte by byte, slowing down performance.
690+
// The old buffer annotations are not removed. If necessary,
691+
// user can unpoison old buffer with __asan_unpoison_memory_region.
692+
void __sanitizer_copy_contiguous_container_annotations(const void *src_beg_p,
693+
const void *src_end_p,
694+
const void *dst_beg_p,
695+
const void *dst_end_p) {
696+
if (!flags()->detect_container_overflow)
697+
return;
698+
699+
VPrintf(3, "contiguous_container_src: %p %p\n", src_beg_p, src_end_p);
700+
VPrintf(3, "contiguous_container_dst: %p %p\n", dst_beg_p, dst_end_p);
701+
702+
uptr src_beg = reinterpret_cast<uptr>(src_beg_p);
703+
uptr src_end = reinterpret_cast<uptr>(src_end_p);
704+
uptr dst_beg = reinterpret_cast<uptr>(dst_beg_p);
705+
uptr dst_end = reinterpret_cast<uptr>(dst_end_p);
706+
707+
constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
708+
709+
if (src_beg > src_end || (dst_end - dst_beg) != (src_end - src_beg)) {
710+
GET_STACK_TRACE_FATAL_HERE;
711+
ReportBadParamsToCopyContiguousContainerAnnotations(
712+
src_beg, src_end, dst_beg, dst_end, &stack);
713+
}
714+
715+
if (src_beg == src_end || src_beg == dst_beg)
716+
return;
717+
// Due to support for overlapping buffers, we may have to copy elements
718+
// in reversed order, when destination buffer starts in the middle of
719+
// the source buffer (or shares first granule with it).
720+
//
721+
// When buffers are not granule-aligned (or distance between them,
722+
// to be specific), annotatios have to be copied byte by byte.
723+
//
724+
// The only remaining edge cases involve edge granules,
725+
// when the container starts or ends within a granule.
726+
uptr src_beg_up = RoundUpTo(src_beg, granularity);
727+
uptr src_end_up = RoundUpTo(src_end, granularity);
728+
bool copy_in_reversed_order = src_beg < dst_beg && dst_beg <= src_end_up;
729+
if (src_beg % granularity != dst_beg % granularity ||
730+
RoundDownTo(dst_end - 1, granularity) <= dst_beg) {
731+
if (copy_in_reversed_order)
732+
SlowReversedCopyContainerAnnotations(src_beg, src_end, dst_beg, dst_end);
733+
else
734+
SlowCopyContainerAnnotations(src_beg, src_end, dst_beg, dst_end);
735+
return;
736+
}
737+
738+
// As buffers are granule-aligned, we can just copy annotations of granules
739+
// from the middle.
740+
uptr dst_beg_up = RoundUpTo(dst_beg, granularity);
741+
uptr dst_end_down = RoundDownTo(dst_end, granularity);
742+
if (copy_in_reversed_order)
743+
CopyContainerLastGranuleAnnotation(src_end, dst_end);
744+
else
745+
CopyContainerFirstGranuleAnnotation(src_beg, dst_beg);
746+
747+
if (dst_beg_up < dst_end_down) {
748+
internal_memmove((u8 *)MemToShadow(dst_beg_up),
749+
(u8 *)MemToShadow(src_beg_up),
750+
(dst_end_down - dst_beg_up) / granularity);
751+
}
752+
753+
if (copy_in_reversed_order)
754+
CopyContainerFirstGranuleAnnotation(src_beg, dst_beg);
755+
else
756+
CopyContainerLastGranuleAnnotation(src_end, dst_end);
757+
}
758+
579759
static const void *FindBadAddress(uptr begin, uptr end, bool poisoned) {
580760
CHECK_LE(begin, end);
581761
constexpr uptr kMaxRangeToCheck = 32;

compiler-rt/lib/asan/asan_report.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,16 @@ void ReportBadParamsToAnnotateDoubleEndedContiguousContainer(
367367
in_report.ReportError(error);
368368
}
369369

370+
void ReportBadParamsToCopyContiguousContainerAnnotations(
371+
uptr old_storage_beg, uptr old_storage_end, uptr new_storage_beg,
372+
uptr new_storage_end, BufferedStackTrace *stack) {
373+
ScopedInErrorReport in_report;
374+
ErrorBadParamsToCopyContiguousContainerAnnotations error(
375+
GetCurrentTidOrInvalid(), stack, old_storage_beg, old_storage_end,
376+
new_storage_beg, new_storage_end);
377+
in_report.ReportError(error);
378+
}
379+
370380
void ReportODRViolation(const __asan_global *g1, u32 stack_id1,
371381
const __asan_global *g2, u32 stack_id2) {
372382
ScopedInErrorReport in_report;

compiler-rt/lib/asan/asan_report.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ void ReportBadParamsToAnnotateDoubleEndedContiguousContainer(
8888
uptr storage_beg, uptr storage_end, uptr old_container_beg,
8989
uptr old_container_end, uptr new_container_beg, uptr new_container_end,
9090
BufferedStackTrace *stack);
91+
void ReportBadParamsToCopyContiguousContainerAnnotations(
92+
uptr old_storage_beg, uptr old_storage_end, uptr new_storage_beg,
93+
uptr new_storage_end, BufferedStackTrace *stack);
9194

9295
void ReportODRViolation(const __asan_global *g1, u32 stack_id1,
9396
const __asan_global *g2, u32 stack_id2);

compiler-rt/lib/sanitizer_common/sanitizer_common_interface.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
INTERFACE_FUNCTION(__sanitizer_acquire_crash_state)
1111
INTERFACE_FUNCTION(__sanitizer_annotate_contiguous_container)
1212
INTERFACE_FUNCTION(__sanitizer_annotate_double_ended_contiguous_container)
13+
INTERFACE_FUNCTION(__sanitizer_copy_contiguous_container_annotations)
1314
INTERFACE_FUNCTION(__sanitizer_contiguous_container_find_bad_address)
1415
INTERFACE_FUNCTION(
1516
__sanitizer_double_ended_contiguous_container_find_bad_address)

compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ void __sanitizer_annotate_double_ended_contiguous_container(
7676
const void *old_container_beg, const void *old_container_end,
7777
const void *new_container_beg, const void *new_container_end);
7878
SANITIZER_INTERFACE_ATTRIBUTE
79+
void __sanitizer_copy_contiguous_container_annotations(const void *src_begin,
80+
const void *src_end,
81+
const void *dst_begin,
82+
const void *dst_end);
83+
SANITIZER_INTERFACE_ATTRIBUTE
7984
int __sanitizer_verify_contiguous_container(const void *beg, const void *mid,
8085
const void *end);
8186
SANITIZER_INTERFACE_ATTRIBUTE

0 commit comments

Comments
 (0)