Why does HWAddressSanitizer miss a heap over-read that AddressSanitizer reports?


I'm fuzzing a C parser on arm64 Android and hit a case where the two sanitizers disagree. ASan reports a heap-buffer-overflow READ; HWASan on the same source, same input, reports nothing at all.

Reduced to this:




#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    for (size_t n = 1; n <= 8; n++) {
        unsigned char *p = malloc(n);
        memset(p, 'A', n);
        /* 4-byte read starting one byte before the end: crosses the boundary */
        volatile int r = memcmp(p + n - 1, "FILE", 4);
        printf("size=%zu end_offset_in_granule=%zu memcmp=%d\n",
               n, (size_t)(((uintptr_t)(p + n)) % 16), r);
        free(p);
    }
}


Built with NDK 27 / clang 18:

# ASan
clang --target=aarch64-linux-android30 -O1 -g \
      -fsanitize=address -fsanitize-recover=address t.c -o t_asan

# HWASan
clang --target=aarch64-linux-android35 -O1 -g \
      -fsanitize=hwaddress -fsanitize-recover=hwaddress -static-libsan t.c -o t_hwasan

Results on a physical arm64 device:

build reports ASan 2 × heap-buffer-overflow HWASan 0

Every allocation ends at granule offset 1–8:

size=1 end_offset_in_granule=1 memcmp=-1 ... size=8 end_offset_in_granule=8 memcmp=-1

My hypothesis: HWASan tags memory in 16-byte granules, so for a malloc(n) where n isn't a multiple of 16, the bytes between the end of the allocation and the end of its granule carry the same tag. A 4-byte read starting at p + n - 1 therefore stays inside that granule, the pointer tag still matches the memory tag, and there is no mismatch to detect. ASan uses byte-granular redzones, so it sees it immediately.

Question: Is that the correct explanation, and is it documented as an accepted limitation rather than a bug? Specifically:

Is 16 bytes the tag granule on Android arm64, and is it configurable? Is there an option that makes HWASan catch intra-granule over-reads — something analogous to ASan's redzones? Is the guidance for this to run both sanitizers, or is there a reason to prefer one for boundary over-reads specifically?

I'm asking because the two are often described as interchangeable with HWASan as the strictly better one, and this looks like a class of bug where that isn't true.

https://github.com/infectedcoffee/MISC - holds my bug hunter report (more info) :)

4
Sep 27 at 2:46 PM
User AvatarAkira Patafio
#best-practices#android#c++#c#linux

Accepted Answer

Detailed explanation can be found at Hardware-assisted AddressSanitizer Design Documentation #Comparison with AddressSanitizer. Basically HWASan inflicts less overhead but detection is probablistic as a tradeoff. >* Does not require redzones to detect buffer overflows, but the buffer overflow detection is probabilistic, with roughly 1/(2**TS) chance of missing a bug (6.25% or 0.39% with 4 and 8-bit TS respectively). > >* Does not require quarantine to detect heap-use-after-free, or stack-use-after-return. The detection is similarly probabilistic. > >* The memory overhead of HWASAN is expected to be much smaller than that of AddressSanitizer: 1/TG extra memory for the shadow and some overhead due to TG-aligning all objects.

User Avataruser7860670
Sep 27 at 7:01 PM
2