llvm / llvm/llvm-project

[asan] Possible off-by-one error on checking memset/memcpy

Open
#173,354 1 comment 0 reactions 0 assignees View on GitHub
compiler-rt:asan
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

### Summary

AddressSanitizer reports a false-positive out-of-bounds access when a large `memset`/`memcpy` writes to the last user-mappable page. The report points to `dst + size` (a one-past address), even though the accessed range is the valid half-open interval `[dst, dst+size)`.

### Reproducer

On my system `GetMaxUserVirtualAddress()` returns `0x7f7fffffffff`. I can map the last page at `0x7f7ffffff000` and then write exactly one page.

```c
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include

int main(void) {
const size_t page = (size_t)sysconf(_SC_PAGESIZE); // in my case is 0x1000

// In the reported case: GetMaxUserVirtualAddress() == 0x7f7fffffffff.
const uintptr_t max_user_va = 0x7f7fffffffffull;

// Start of the last page: [addr, addr+page) ends exactly at max_user_va+1.
const uintptr_t addr = 0x7f7ffffff000ull;

void *p = mmap((void *)addr, page, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
if (p == MAP_FAILED) {
fprintf(stderr, "mmap(%#" PRIxPTR ") failed: %s\n", addr, strerror(errno));
return 0;
}

fprintf(stderr, "mapped: %p .. %p\n", p, (void *)((uintptr_t)p + page));

// This triggers ASan false-positive before the patch:
// it reports an access at addr+page (one-by-one).
memset(p, 0xAB, page);

munmap(p, page);
return 0;
}
```

Build/run:
```shell
clang -O0 -g -fsanitize=address repro.c -o repro
./repro
```

### Actual behavior

ASan reports an invalid write access at 0x7f8000000000 (i.e. dst + size), even though the write stays within the mapped page.

### Expected behavior

No ASan report (writing the last mapped page should be valid).

### Root cause analysis

The runtime helper `__asan_region_is_poisoned(beg, size)` treats the region as a half-open interval [beg, end), but it has an early check:
```c
if (!AddrIsInMem(end))
return end;
```
This incorrectly rejects the valid case when `end == kHighMemEnd + 1 `(off-by-one error) and causes memintrinsic interceptors to report end as the faulty address.

Contributor guide

Open the contributing guide

Research direction

Start with the runtime helper __asan_region_is_poisoned(beg, size), focusing on the AddrIsInMem(end) early check described in the root-cause analysis. Build and run the provided C reproducer with Clang and AddressSanitizer; done means writing the last mapped page produces no ASan report.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
devtools, testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.