[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.
This commit is contained in:
Roman Vinogradov 2026-03-20 18:05:19 +01:00 committed by GitHub
parent d7dbba55bf
commit ca54948d0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 8 deletions

View File

@ -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);

View File

@ -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));