Skip to content

[libc++] Fix vector sanitization annotations on destruction #121031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 20, 2025

Conversation

ddcc
Copy link
Member

@ddcc ddcc commented Dec 24, 2024

In D136765/D144155, the asan annotations for std::vector were modified to unpoison freed backing memory on destruction, instead of leaving it poisoned. However, calling __clear() instead of clear() skips informing the asan runtime of this decrease in the accessible container size, which breaks the invariant that the value of old_mid should match the value of new_mid from the previous call to __sanitizer_annotate_contiguous_container, which can trip the sanity checks for the partial poison between [d1, d2) and the container redzone between [d2, c), if enabled. To fix this, ensure that clear() is called instead, as is already done by __vdeallocate(). Also remove __clear(), since it is no longer called.

@ddcc ddcc requested a review from a team as a code owner December 24, 2024 06:12
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Dec 24, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 24, 2024

@llvm/pr-subscribers-libcxx

Author: Dominic Chen (ddcc)

Changes

In D144155/D136765, the asan annotations for std::vector were modified to unpoison freed backing memory on destruction, instead of leaving it poisoned. However, calling __clear() instead of clear() skips informing the asan runtime of this decrease in the accessible container size, which breaks the invariant that the value of old_mid should match the value of new_mid from the previous call to _sanitizer_annotate_contiguous_container(), which can trip the sanity checks for the partial poison between [d1, d2) and the container redzone between [d2, c), if enabled. To fix this, ensure that clear() is called instead, as is already done by __vdeallocate().


Full diff: https://github.com/llvm/llvm-project/pull/121031.diff

1 Files Affected:

  • (modified) libcxx/include/__vector/vector.h (+2-2)
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 6ba7ba7bcf724b..fe87e825ce6d04 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -241,7 +241,7 @@ class _LIBCPP_TEMPLATE_VIS vector {
 
     _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void operator()() {
       if (__vec_.__begin_ != nullptr) {
-        __vec_.__clear();
+        __vec_.clear();
         __vec_.__annotate_delete();
         __alloc_traits::deallocate(__vec_.__alloc_, __vec_.__begin_, __vec_.capacity());
       }
@@ -759,7 +759,7 @@ class _LIBCPP_TEMPLATE_VIS vector {
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __copy_assign_alloc(const vector& __c, true_type) {
     if (this->__alloc_ != __c.__alloc_) {
-      __clear();
+      clear();
       __annotate_delete();
       __alloc_traits::deallocate(this->__alloc_, this->__begin_, capacity());
       this->__begin_ = this->__end_ = this->__cap_ = nullptr;

@ddcc ddcc added the compiler-rt:asan Address sanitizer label Dec 24, 2024
Copy link
Collaborator

@vitalybuka vitalybuka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but libc++ maintainers need to check

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is fixing a bug, this needs a test.

@ddcc ddcc force-pushed the libcxx_asan_vector branch from f92677d to 6efac08 Compare January 10, 2025 21:23
@ddcc
Copy link
Member Author

ddcc commented Jan 10, 2025

I'm not too familiar with the libcxx testing infrastructure, and I'm not sure what the best way to test this would be. Part of the problem is that the two codepaths being changed call __annotate_delete() immediately after the clear(), so by the time control flow returns to the caller, the intermediate asan container state has been lost and we can't assert on it. The other factor is that the two sanity checks in the compiler-rt asan runtime for the asan shadow state are currently disabled: https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/asan/asan_poisoning.cpp#L448 (lines 448, 455), and I'm hesitant to touch that because it would have broader scope and seems deliberately not fixed.

Copy link
Member

@AdvenamTacet AdvenamTacet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for noticing it!

Let's make sure that the commit message mentions removal of void __clear().

Please wait for a green light from @philnik777 before merging.

breaks the invariant that the value of old_mid should match the value of new_mid from the previous call to __sanitizer_annotate_contiguous_container()

It shouldn't be observed by a user as we actually do not want to enforce that "invariant" (we want to keep it only to easier ensure correctness of the implementation while testing).

Reasoning is that there are situations where users may want to (un)poison containers memory. We worked on making sure that annotation functions work with modified annotations (including over-unpoisoned memory), so it should not result in any error.

I support that change as keeping that "invariant" helps to avoid annotation errors in future, but unless I missed something, it's not necessary change. Users using public vectors API won't notice that.
Unpoisoning memory from D144155 is necessary, but poisoning here is not.

Part of the problem is that the two codepaths being changed call __annotate_delete() immediately after the clear()

I don't think we can test it in a reasonable way as it shouldn't be observable by using public vector functions. But if someone has an idea, I'm happy to learn how to do it (without cluttering vectors implementation or recompiling compiler-rt).

@philnik777
Copy link
Contributor

@AdvenamTacet

which can trip the sanity checks for the partial poison between [d1, d2) and the container redzone between [d2, c), if enabled

sounds to me a lot like this is oberservable somehow. I don't know exactly what they are referring to, but that doesn't make it not observable (even if it's not observable to the average user). If the observable behaviour is only that there is some ASan mode which trips, we should enable that.

@AdvenamTacet
Copy link
Member

I could misunderstood and thought that commented sanity checks are not satisfied. If there is an example which hits sanity check error, I would love to see it and we should definitely add it.

@ddcc do you have an example tripping a sanity check?

In D144155/D136765, the asan annotations for `std::vector` were modified to unpoison freed backing memory on destruction, instead of leaving it
poisoned. However, calling `__clear()` instead of `clear()` skips informing the asan runtime of this decrease in the accessible container size, which
breaks the invariant that the value of `old_mid` should match the value of `new_mid` from the previous call to `__sanitizer_annotate_contiguous_container`,
which can trip the sanity checks for the partial poison between [d1, d2) and the container redzone between [d2, c), if enabled. To fix this, ensure that
`clear()` is called instead, as is already done by `__vdeallocate()`. Also remove `__clear()`, since it is no longer called.
@ddcc ddcc force-pushed the libcxx_asan_vector branch from 6efac08 to 39c17f4 Compare January 13, 2025 19:07
@ddcc
Copy link
Member Author

ddcc commented Jan 13, 2025

Yeah, I should clarify that the relevant sanity checks are currently commented out in the upstream asan runtime. However, we are using an internal asan runtime that does have these sanity checks enabled, and that is where we see the failures.

@ddcc
Copy link
Member Author

ddcc commented Jan 15, 2025

@ldionne @philnik777 Is this PR ok now?

@ldionne ldionne merged commit 9b853f6 into llvm:main Jan 20, 2025
71 of 76 checks passed
@ddcc ddcc deleted the libcxx_asan_vector branch January 28, 2025 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler-rt:asan Address sanitizer libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants