From ca54948d0b7a914211ae3d8d95e309358576c3f1 Mon Sep 17 00:00:00 2001 From: Roman Vinogradov Date: Fri, 20 Mar 2026 18:05:19 +0100 Subject: [PATCH] [ASan] Fix missed poisoned suffix in first granule in __asan_region_is_poisoned (#187466) Align beg address down instead of up in __asan_region_is_poisoned(), so the shadow scan includes the first granule. This fixes a false negative when first granule has an unpoisoned prefix and poisoned suffix. Add test that covers this scenario. --- compiler-rt/lib/asan/asan_poisoning.cpp | 15 ++++---- .../lib/asan/tests/asan_interface_test.cpp | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/compiler-rt/lib/asan/asan_poisoning.cpp b/compiler-rt/lib/asan/asan_poisoning.cpp index 89f6ea85d605..79457dda1e7c 100644 --- a/compiler-rt/lib/asan/asan_poisoning.cpp +++ b/compiler-rt/lib/asan/asan_poisoning.cpp @@ -247,16 +247,15 @@ uptr __asan_region_is_poisoned(uptr beg, uptr size) { if (!AddrIsInMem(last)) return last; CHECK_LE(beg, last); - // First check the first and the last application bytes, - // then check the ASAN_SHADOW_GRANULARITY-aligned inner region by calling - // mem_is_zero on the corresponding shadow. - if (!__asan::AddressIsPoisoned(beg) && !__asan::AddressIsPoisoned(last)) { - uptr aligned_b = RoundUpTo(beg, ASAN_SHADOW_GRANULARITY); + // First check the last application byte, i.e. last granule, then check + // the ASAN_SHADOW_GRANULARITY-aligned region by calling mem_is_zero + // on the corresponding shadow (first granule is fully checked). + if (!__asan::AddressIsPoisoned(last)) { + uptr aligned_b = RoundDownTo(beg, ASAN_SHADOW_GRANULARITY); uptr aligned_e = RoundDownTo(last, ASAN_SHADOW_GRANULARITY); - if (aligned_e <= aligned_b) - return 0; - if (UNLIKELY(aligned_b < beg)) // address space overflow. + if (aligned_b == aligned_e) // one granule case => last check is enough. return 0; + CHECK_LT(aligned_b, aligned_e); uptr shadow_beg = MemToShadow(aligned_b); uptr shadow_end = MemToShadow(aligned_e); CHECK_LT(shadow_beg, shadow_end); diff --git a/compiler-rt/lib/asan/tests/asan_interface_test.cpp b/compiler-rt/lib/asan/tests/asan_interface_test.cpp index 021ebfb04b00..3b6e35ee374a 100644 --- a/compiler-rt/lib/asan/tests/asan_interface_test.cpp +++ b/compiler-rt/lib/asan/tests/asan_interface_test.cpp @@ -200,6 +200,40 @@ TEST(AddressSanitizerInterface, OverlappingPoisonMemoryRegionTest) { free(array); } +TEST(AddressSanitizerInterface, PoisonedSuffixOfFirstGranuleOfRegionTest) { + static const size_t granularity = 1ULL << 3; // shadow granularity + char* array = Ident((char*)malloc(118)); + // State: [0..117] - unpoisoned + char* first_granule = (char*)((uintptr_t)array & ~(granularity - 1)); + EXPECT_EQ(array, first_granule); + // Poison [4..7] (suffix of the first granule) + __asan_poison_memory_region(array + 4, 4); + // State: [uuuupppp][uuuuuuuu]...[uuuuuupp] + // i.e. [0..3] - unpoisoned; [4..7] - poisoned; [8..117] - unpoisoned + // Sanity checks: + GOOD_ACCESS(array, 3); + BAD_ACCESS(array, 4); + BAD_ACCESS(array, 7); + GOOD_ACCESS(array, 8); + GOOD_ACCESS(array, 117); + BAD_ACCESS(array, 118); + EXPECT_EQ(0, __asan_region_is_poisoned(array, 4)); // [0..3] + EXPECT_EQ(0, __asan_region_is_poisoned(array + 8, 110)); // [8..117] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array + 4, 4)); // [4..7] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array, 8)); // [0..7] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array, 16)); // [0..15] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array, 17)); // [0..16] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array + 4, 12)); // [4..15] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array + 4, 13)); // [4..16] + EXPECT_EQ(array + 5, __asan_region_is_poisoned(array + 5, 2)); // [5..6] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array + 2, 4)); // [2..5] + EXPECT_EQ(array + 6, __asan_region_is_poisoned(array + 6, 10)); // [6..15] + // Check that poisoned suffix of the first granule is not skipped: + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array + 2, 14)); // [2..15] + EXPECT_EQ(array + 4, __asan_region_is_poisoned(array + 2, 100)); // [2..101] + free(array); +} + TEST(AddressSanitizerInterface, PushAndPopWithPoisoningTest) { // Vector of capacity 20 char *vec = Ident((char*)malloc(20));