[compiler-rt] [compiler-rt][ASan] Add function moving annotations (PR #91702)
via llvm-commits
llvm-commits at lists.llvm.org
Thu May 9 23:32:30 PDT 2024
https://github.com/AdvenamTacet updated https://github.com/llvm/llvm-project/pull/91702
>From e7c5b88dcbd7e72280ce8bdabb9ae8f2a837fdb8 Mon Sep 17 00:00:00 2001
From: Advenam Tacet <advenam.tacet at trailofbits.com>
Date: Fri, 10 May 2024 07:27:17 +0200
Subject: [PATCH] [compiler-rt][ASan] Add function moving annotations
This PR adds a `__sanitizer_move_contiguous_container_annotations` function, which moves annotations from one memory area to another.
Old area is unpoisoned at the end. New area is annotated in the same way as the old region at the beginning (within limitations of ASan).
```cpp
void __sanitizer_move_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.
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 accpeted and upstreamed, the next step is creating function like `__memcpy_with_asan` moving memory with ASan.
And then using this function instead of `__builtin__memcpy` while moving trivially relocatable objects.
NOTICE: I did not test it yet, so it's probably not compiling, but I don't expect big changes. PR is WIP until I test it.
---
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.
---
.../include/sanitizer/common_interface_defs.h | 24 +++
compiler-rt/lib/asan/asan_errors.cpp | 14 ++
compiler-rt/lib/asan/asan_errors.h | 19 +++
compiler-rt/lib/asan/asan_poisoning.cpp | 116 +++++++++++++
compiler-rt/lib/asan/asan_report.cpp | 10 ++
compiler-rt/lib/asan/asan_report.h | 3 +
.../sanitizer_common_interface.inc | 1 +
.../sanitizer_interface_internal.h | 4 +
.../TestCases/move_container_annotations.cpp | 155 ++++++++++++++++++
9 files changed, 346 insertions(+)
create mode 100644 compiler-rt/test/asan/TestCases/move_container_annotations.cpp
diff --git a/compiler-rt/include/sanitizer/common_interface_defs.h b/compiler-rt/include/sanitizer/common_interface_defs.h
index f9fce595b37bb..a3c91a9be6439 100644
--- a/compiler-rt/include/sanitizer/common_interface_defs.h
+++ b/compiler-rt/include/sanitizer/common_interface_defs.h
@@ -193,6 +193,30 @@ void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container(
const void *old_container_beg, const void *old_container_end,
const void *new_container_beg, const void *new_container_end);
+/// Moves annotation from one storage to another.
+/// At the end, new buffer is annotated in the same way as old buffer at
+/// the very beginning. Old buffer is fully unpoisoned.
+/// Main purpose of that function is use while moving trivially relocatable
+/// objects, which memory may be poisoned (therefore not trivially with ASan).
+///
+/// A contiguous container is a container that keeps all of its elements
+/// in a contiguous region of memory. The container owns the region of memory
+/// <c>[old_storage_beg, old_storage_end)</c> and
+/// <c>[new_storage_beg, new_storage_end)</c>;
+/// There is no requirement where objects are kept.
+/// Poisoned and non-poisoned memory areas can alternate,
+/// there are no shadow memory restrictions.
+///
+/// Argument requirements:
+/// New containert has to have the same size as the old container.
+/// \param old_storage_beg Beginning of the old container region.
+/// \param old_storage_end End of the old container region.
+/// \param new_storage_beg Beginning of the new container region.
+/// \param new_storage_end End of the new container region.
+void SANITIZER_CDECL __sanitizer_move_contiguous_container_annotations(
+ const void *old_storage_beg, const void *old_storage_end,
+ const void *new_storage_beg, const void *new_storage_end);
+
/// Returns true if the contiguous container <c>[beg, end)</c> is properly
/// poisoned.
///
diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index 3f2d13e314640..59f2f31cafe38 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -354,6 +354,20 @@ void ErrorBadParamsToAnnotateDoubleEndedContiguousContainer::Print() {
ReportErrorSummary(scariness.GetDescription(), stack);
}
+void ErrorBadParamsToMoveContiguousContainerAnnotations::Print() {
+ Report(
+ "ERROR: AddressSanitizer: bad parameters to "
+ "__sanitizer_move_contiguous_container_annotations:\n"
+ " old_storage_beg : %p\n"
+ " old_storage_end : %p\n"
+ " new_storage_beg : %p\n"
+ " new_storage_end : %p\n",
+ (void *)old_storage_beg, (void *)old_storage_end, (void *)new_storage_beg,
+ (void *)new_storage_end);
+ stack->Print();
+ ReportErrorSummary(scariness.GetDescription(), stack);
+}
+
void ErrorODRViolation::Print() {
Decorator d;
Printf("%s", d.Error());
diff --git a/compiler-rt/lib/asan/asan_errors.h b/compiler-rt/lib/asan/asan_errors.h
index 634f6da544355..40526039fbc76 100644
--- a/compiler-rt/lib/asan/asan_errors.h
+++ b/compiler-rt/lib/asan/asan_errors.h
@@ -353,6 +353,24 @@ struct ErrorBadParamsToAnnotateDoubleEndedContiguousContainer : ErrorBase {
void Print();
};
+struct ErrorBadParamsToMoveContiguousContainerAnnotations : ErrorBase {
+ const BufferedStackTrace *stack;
+ uptr old_storage_beg, old_storage_end, new_storage_beg, new_storage_end;
+
+ ErrorBadParamsToMoveContiguousContainerAnnotations() = default; // (*)
+ ErrorBadParamsToMoveContiguousContainerAnnotations(
+ u32 tid, BufferedStackTrace *stack_, uptr old_storage_beg_,
+ uptr old_storage_end_, uptr new_storage_beg_, uptr new_storage_end_)
+ : ErrorBase(tid, 10,
+ "bad-__sanitizer_annotate_double_ended_contiguous_container"),
+ stack(stack_),
+ old_storage_beg(old_storage_beg_),
+ old_storage_end(old_storage_end_),
+ new_storage_beg(new_storage_beg_),
+ new_storage_end(new_storage_end_) {}
+ void Print();
+};
+
struct ErrorODRViolation : ErrorBase {
__asan_global global1, global2;
u32 stack_id1, stack_id2;
@@ -421,6 +439,7 @@ struct ErrorGeneric : ErrorBase {
macro(StringFunctionSizeOverflow) \
macro(BadParamsToAnnotateContiguousContainer) \
macro(BadParamsToAnnotateDoubleEndedContiguousContainer) \
+ macro(BadParamsToMoveContiguousContainerAnnotations) \
macro(ODRViolation) \
macro(InvalidPointerPair) \
macro(Generic)
diff --git a/compiler-rt/lib/asan/asan_poisoning.cpp b/compiler-rt/lib/asan/asan_poisoning.cpp
index 746ad61813c65..5bf089486bdde 100644
--- a/compiler-rt/lib/asan/asan_poisoning.cpp
+++ b/compiler-rt/lib/asan/asan_poisoning.cpp
@@ -576,6 +576,122 @@ void __sanitizer_annotate_double_ended_contiguous_container(
}
}
+// This function moves annotation from one buffer to another.
+// Old buffer is unpoisoned at the end.
+void __sanitizer_move_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) {
+ if (!flags()->detect_container_overflow)
+ return;
+
+ VPrintf(2, "contiguous_container_old: %p %p\n", old_storage_beg_p,
+ old_storage_end_p);
+ VPrintf(2, "contiguous_container_new: %p %p\n", new_storage_beg_p,
+ new_storage_end_p);
+
+ uptr old_storage_beg = reinterpret_cast<uptr>(old_storage_beg_p);
+ uptr old_storage_end = reinterpret_cast<uptr>(old_storage_end_p);
+ uptr new_storage_beg = reinterpret_cast<uptr>(new_storage_beg_p);
+ uptr new_storage_end = reinterpret_cast<uptr>(new_storage_end_p);
+
+ constexpr uptr granularity = ASAN_SHADOW_GRANULARITY;
+
+ if (!(old_storage_beg <= old_storage_end) ||
+ !(new_storage_beg <= new_storage_end) ||
+ (old_storage_end - old_storage_beg) !=
+ (new_storage_end - new_storage_beg)) {
+ GET_STACK_TRACE_FATAL_HERE;
+ ReportBadParamsToMoveContiguousContainerAnnotations(
+ old_storage_beg, old_storage_end, new_storage_beg, new_storage_end,
+ &stack);
+ }
+
+ // At the very beginning we poison the whole buffer.
+ // Later we unpoison what is necessary.
+ PoisonShadow(new_storage_beg, new_storage_end - new_storage_beg,
+ kAsanContiguousContainerOOBMagic);
+
+ // There are two cases.
+ // 1) Distance between buffers is granule-aligned.
+ // 2) It's not aligned, that case is slower.
+ if (old_storage_beg % granularity == new_storage_beg % granularity) {
+ // When buffers are aligned in the same way, we can just copy shadow memory,
+ // except first and last granule.
+ uptr new_internal_beg = RoundUpTo(new_storage_beg, granularity);
+ uptr old_internal_beg = RoundUpTo(old_storage_beg, granularity);
+
+ uptr new_internal_end = RoundDownTo(new_storage_end, granularity);
+ uptr old_internal_end = RoundDownTo(old_storage_end, granularity);
+
+ __builtin_memcpy((u8 *)MemToShadow(new_internal_beg),
+ (u8 *)MemToShadow(old_internal_beg),
+ (new_internal_end - new_internal_beg) / granularity);
+ // In first granule we cannot poison anything before beginning of the
+ // container.
+ if (new_internal_beg != new_storage_beg) {
+ uptr new_external_beg = RoundDownTo(new_storage_beg, granularity);
+ uptr old_external_beg = RoundDownTo(old_storage_beg, granularity);
+
+ uptr old_unpoisoned = *(u8 *)MemToShadow(old_external_beg);
+ uptr new_unpoisoned = *(u8 *)MemToShadow(new_external_beg);
+
+ if (old_unpoisoned > old_storage_beg - old_external_beg) {
+ *(u8 *)MemToShadow(new_external_beg) = old_unpoisoned;
+ } else if (new_unpoisoned > new_storage_beg - new_external_beg) {
+ *(u8 *)MemToShadow(new_external_beg) =
+ new_storage_beg - new_external_beg;
+ }
+ }
+ // In last granule we cannot poison anything after the end of the container.
+ if (new_internal_end != new_storage_end) {
+ uptr old_unpoisoned = *(u8 *)MemToShadow(old_internal_end);
+ uptr new_unpoisoned = *(u8 *)MemToShadow(new_internal_end);
+ if (new_unpoisoned <= new_storage_end - new_internal_end &&
+ old_unpoisoned < new_unpoisoned) {
+ *(u8 *)MemToShadow(new_internal_end) = old_unpoisoned;
+ }
+ }
+ } else {
+ // If buffers are not aligned, we have to go byte by byte.
+ uptr old_ptr = old_storage_beg;
+ uptr new_ptr = new_storage_beg;
+ uptr next_new;
+ for (; new_ptr + granularity <= new_storage_end;) {
+ next_new = RoundUpTo(new_ptr + 1, granularity);
+ uptr unpoison_to = 0;
+ for (; new_ptr != next_new; ++new_ptr, ++old_ptr) {
+ if (!AddressIsPoisoned(old_ptr)) {
+ unpoison_to = new_ptr + 1;
+ }
+ }
+ if (unpoison_to != 0) {
+ uptr granule_beg = new_ptr - granularity;
+ uptr value = unpoison_to - granule_beg;
+ *(u8 *)MemToShadow(granule_beg) = static_cast<u8>(value);
+ }
+ }
+ // Only case left is the end of the container in the middle of a granule.
+ // If memory after the end is unpoisoned, we cannot change anything.
+ // But if it's poisoned, we should unpoison as little as possible.
+ if (new_ptr != new_storage_end && AddressIsPoisoned(new_storage_end)) {
+ uptr unpoison_to = 0;
+ for (; new_ptr != new_storage_end; ++new_ptr, ++old_ptr) {
+ if (!AddressIsPoisoned(old_ptr)) {
+ unpoison_to = new_ptr + 1;
+ }
+ }
+ if (unpoison_to != 0) {
+ uptr granule_beg = RoundDownTo(new_storage_end, granularity);
+ uptr value = unpoison_to - granule_beg;
+ *(u8 *)MemToShadow(granule_beg) = static_cast<u8>(value);
+ }
+ }
+ }
+
+ __asan_unpoison_memory_region((void *)old_storage_beg,
+ old_storage_end - old_storage_beg);
+}
+
static const void *FindBadAddress(uptr begin, uptr end, bool poisoned) {
CHECK_LE(begin, end);
constexpr uptr kMaxRangeToCheck = 32;
diff --git a/compiler-rt/lib/asan/asan_report.cpp b/compiler-rt/lib/asan/asan_report.cpp
index c9730dd368cb4..83a9222a13830 100644
--- a/compiler-rt/lib/asan/asan_report.cpp
+++ b/compiler-rt/lib/asan/asan_report.cpp
@@ -367,6 +367,16 @@ void ReportBadParamsToAnnotateDoubleEndedContiguousContainer(
in_report.ReportError(error);
}
+void ReportBadParamsToMoveContiguousContainerAnnotations(
+ uptr old_storage_beg, uptr old_storage_end, uptr new_storage_beg,
+ uptr new_storage_end, BufferedStackTrace *stack) {
+ ScopedInErrorReport in_report;
+ ErrorBadParamsToMoveContiguousContainerAnnotations error(
+ GetCurrentTidOrInvalid(), stack, old_storage_beg, old_storage_end,
+ new_storage_beg, new_storage_end);
+ in_report.ReportError(error);
+}
+
void ReportODRViolation(const __asan_global *g1, u32 stack_id1,
const __asan_global *g2, u32 stack_id2) {
ScopedInErrorReport in_report;
diff --git a/compiler-rt/lib/asan/asan_report.h b/compiler-rt/lib/asan/asan_report.h
index 3540b3b4b1bfe..d98c87e5a6930 100644
--- a/compiler-rt/lib/asan/asan_report.h
+++ b/compiler-rt/lib/asan/asan_report.h
@@ -88,6 +88,9 @@ 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 ReportBadParamsToMoveContiguousContainerAnnotations(
+ uptr old_storage_beg, uptr old_storage_end, uptr new_storage_beg,
+ uptr new_storage_end, BufferedStackTrace *stack);
void ReportODRViolation(const __asan_global *g1, u32 stack_id1,
const __asan_global *g2, u32 stack_id2);
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interface.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interface.inc
index 557207fe62ac6..826976dc52af6 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interface.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interface.inc
@@ -10,6 +10,7 @@
INTERFACE_FUNCTION(__sanitizer_acquire_crash_state)
INTERFACE_FUNCTION(__sanitizer_annotate_contiguous_container)
INTERFACE_FUNCTION(__sanitizer_annotate_double_ended_contiguous_container)
+INTERFACE_FUNCTION(__sanitizer_move_contiguous_container_annotations)
INTERFACE_FUNCTION(__sanitizer_contiguous_container_find_bad_address)
INTERFACE_FUNCTION(
__sanitizer_double_ended_contiguous_container_find_bad_address)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h b/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h
index cd0d45e2f3fab..fa1d2b562f33f 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h
@@ -71,6 +71,10 @@ void __sanitizer_annotate_double_ended_contiguous_container(
const void *old_container_beg, const void *old_container_end,
const void *new_container_beg, const void *new_container_end);
SANITIZER_INTERFACE_ATTRIBUTE
+void __sanitizer_move_contiguous_container_annotations(
+ const void *old_storage_beg, const void *old_storage_end,
+ const void *new_storage_beg, const void *new_storage_end);
+SANITIZER_INTERFACE_ATTRIBUTE
int __sanitizer_verify_contiguous_container(const void *beg, const void *mid,
const void *end);
SANITIZER_INTERFACE_ATTRIBUTE
diff --git a/compiler-rt/test/asan/TestCases/move_container_annotations.cpp b/compiler-rt/test/asan/TestCases/move_container_annotations.cpp
new file mode 100644
index 0000000000000..f21826ca0b793
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/move_container_annotations.cpp
@@ -0,0 +1,155 @@
+// RUN: %clangxx_asan -fexceptions -O %s -o %t && %env_asan_opts=detect_stack_use_after_return=0 %run %t
+//
+// Test __sanitizer_move_contiguous_container_annotations.
+
+#include <algorithm>
+#include <deque>
+#include <numeric>
+
+#include <assert.h>
+#include <sanitizer/asan_interface.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static constexpr size_t kGranularity = 8;
+
+template <class T> static constexpr T RoundDown(T x) {
+ return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(x) &
+ ~(kGranularity - 1));
+}
+template <class T> static constexpr T RoundUp(T x) {
+ return (x == RoundDown(x))
+ ? x
+ : reinterpret_cast<T>(reinterpret_cast<uintptr_t>(RoundDown(x)) +
+ kGranularity);
+}
+
+static std::deque<int> GetPoisonedState(char *begin, char *end) {
+ std::deque<int> result;
+ for (; begin != end; ++begin) {
+ result.push_back(__asan_address_is_poisoned(begin));
+ }
+ return result;
+}
+
+static void RandomPoison(char *beg, char *end) {
+ if (beg != RoundDown(beg) && (rand() % 2 == 1)) {
+ __asan_poison_memory_region(beg, RoundUp(beg) - beg);
+ __asan_unpoison_memory_region(beg, rand() % (RoundUp(beg) - beg + 1));
+ }
+ for (beg = RoundUp(beg); beg + kGranularity <= end; beg += kGranularity) {
+ __asan_poison_memory_region(beg, kGranularity);
+ __asan_unpoison_memory_region(beg, rand() % (kGranularity + 1));
+ }
+ if (end > beg && __asan_address_is_poisoned(end)) {
+ __asan_poison_memory_region(beg, kGranularity);
+ __asan_unpoison_memory_region(beg, rand() % (end - beg + 1));
+ }
+}
+
+static size_t count_unpoisoned(std::deque<int> &poison_states, size_t n) {
+ size_t result = 0;
+ for (size_t i = 0; i < n && !poison_states.empty(); ++i) {
+ if (!poison_states.front()) {
+ result = i + 1;
+ }
+ poison_states.pop_front();
+ }
+
+ return result;
+}
+
+void TestMove(size_t capacity, size_t off_old, size_t off_new,
+ int poison_buffers) {
+ size_t old_buffer_size = capacity + off_old + kGranularity * 2;
+ size_t new_buffer_size = capacity + off_new + kGranularity * 2;
+ char *old_buffer = new char[old_buffer_size];
+ char *new_buffer = new char[new_buffer_size];
+ char *old_buffer_end = old_buffer + old_buffer_size;
+ char *new_buffer_end = new_buffer + new_buffer_size;
+ bool poison_old = poison_buffers % 2 == 1;
+ bool poison_new = poison_buffers / 2 == 1;
+ if (poison_old)
+ __asan_poison_memory_region(old_buffer, old_buffer_size);
+ if (poison_new)
+ __asan_poison_memory_region(new_buffer, new_buffer_size);
+ char *old_beg = old_buffer + off_old;
+ char *new_beg = new_buffer + off_new;
+ char *old_end = old_beg + capacity;
+ char *new_end = new_beg + capacity;
+
+ for (int i = 0; i < 1000; i++) {
+ RandomPoison(old_beg, old_end);
+ std::deque<int> poison_states(old_beg, old_end);
+ __sanitizer_move_contiguous_container_annotations(old_beg, old_end, new_beg,
+ new_end);
+
+ // If old_buffer were poisoned, expected state of memory before old_beg
+ // is undetermined.
+ // If old buffer were not poisoned, that memory should still be unpoisoned.
+ // Area between old_beg and old_end should never be poisoned.
+ char *cur = poison_old ? old_beg : old_buffer;
+ for (; cur < old_end; ++cur) {
+ assert(!__asan_address_is_poisoned(cur));
+ }
+ // Memory after old_beg should be the same as at the beginning.
+ for (; cur < old_buffer_end; ++cur) {
+ assert(__asan_address_is_poisoned(cur) == poison_old);
+ }
+
+ // If new_buffer were not poisoned, memory before new_beg should never
+ // be poisoned. Otherwise, its state is undetermined.
+ if (!poison_new) {
+ for (cur = new_buffer; cur < new_beg; ++cur) {
+ assert(!__asan_address_is_poisoned(cur));
+ }
+ }
+ //In every granule, poisoned memory should be after last expected unpoisoned.
+ char *next;
+ for (cur = new_beg; cur + kGranularity <= new_end; cur = next) {
+ next = RoundUp(cur + 1);
+ size_t unpoisoned = count_unpoisoned(poison_states, next - cur);
+ if (unpoisoned > 0) {
+ assert(!__asan_address_is_poisoned(cur + unpoisoned - 1));
+ }
+ if (cur + unpoisoned < next) {
+ assert(__asan_address_is_poisoned(cur + unpoisoned));
+ }
+ }
+ // [cur; new_end) is not checked yet.
+ // If new_buffer were not poisoned, it cannot be poisoned and we can ignore check.
+ // If new_buffer were poisoned, it should be same as earlier.
+ if (cur < new_end && poison_new) {
+ size_t unpoisoned = count_unpoisoned(poison_states, new_end - cur);
+ if (unpoisoned > 0) {
+ assert(!__asan_address_is_poisoned(cur + unpoisoned - 1));
+ }
+ if (cur + unpoisoned < new_end) {
+ assert(__asan_address_is_poisoned(cur + unpoisoned));
+ }
+ }
+ // Memory annotations after new_end should be unchanged.
+ for (cur = new_end; cur < new_buffer_end; ++cur) {
+ assert(__asan_address_is_poisoned(cur) == poison_new);
+ }
+ }
+
+ __asan_unpoison_memory_region(old_buffer, old_buffer_size);
+ __asan_unpoison_memory_region(new_buffer, new_buffer_size);
+ delete[] old_buffer;
+ delete[] new_buffer;
+}
+
+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 k = 0; k < kGranularity * 2; k++) {
+ for (int poison = 0; poison < 4; ++poison) {
+ TestMove(i, j, k, poison);
+ }
+ }
+ }
+ }
+}
More information about the llvm-commits
mailing list