-
Notifications
You must be signed in to change notification settings - Fork 14k
[TSan] Make Shadow/Meta region inclusive-exclusive #144647
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
base: main
Are you sure you want to change the base?
Conversation
This commit changes the interval shadow/meta address check from inclusive-inclusive ( [start, end] ) to inclusive-exclusive ( [start, end) ), to resolve the ambiguity of the end point address. This also aligns the logic with the check for `isAppMem`, ensuring consistent behavior across all memory classifications. 1. The `isShadowMem` and `isMetaMem` checks previously used an inclusive-inclusive interval, i.e., [start, end], which could lead to a boundary address being incorrectly classified as both Shadow and Meta memory, e.g., 0x3000_0000_0000 in `Mapping48AddressSpace`. - What's more, even when Shadow doesn't border Meta, `ShadowMem::end` cannot be considered a legal shadow address, as TSan protects the gap, i.e., `ProtectRange(ShadowEnd(), MetaShadowBeg());` 2. `ShadowMem`/`MetaMem` addresses are derived from `AppMem` using an affine-like transformation (`* factor + bias`). This transformation includes two extra modifications: high- and low-order bits are masked out, and for Shadow Memory, an optional XOR operation may be applied to prevent conflicts with certain AppMem regions. - Given that all AppMem regions are defined as inclusive-exclusive intervals, $[\mathrm{start}, \mathrm{end})$, the resulting Shadow/Meta regions should logically also be inclusive-exclusive. Note: This change is purely for improving code consistency and should have no functional impact. In practice, the exact endpoint addresses of the Shadow/Meta regions are generally not reached.
@llvm/pr-subscribers-compiler-rt-sanitizer Author: Kunqiu Chen (Camsyn) ChangesThis commit changes the interval shadow/meta address check from inclusive-inclusive (
Note: This change is purely for improving code consistency and should have no functional impact. In practice, the exact endpoint addresses of the Shadow/Meta regions are generally not reached. Full diff: https://github.com/llvm/llvm-project/pull/144647.diff 2 Files Affected:
diff --git a/compiler-rt/lib/tsan/rtl/tsan_platform.h b/compiler-rt/lib/tsan/rtl/tsan_platform.h
index 354f6da6a64a1..ada594bc11fc7 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_platform.h
+++ b/compiler-rt/lib/tsan/rtl/tsan_platform.h
@@ -931,7 +931,7 @@ bool IsAppMem(uptr mem) { return SelectMapping<IsAppMemImpl>(mem); }
struct IsShadowMemImpl {
template <typename Mapping>
static bool Apply(uptr mem) {
- return mem >= Mapping::kShadowBeg && mem <= Mapping::kShadowEnd;
+ return mem >= Mapping::kShadowBeg && mem < Mapping::kShadowEnd;
}
};
@@ -943,7 +943,7 @@ bool IsShadowMem(RawShadow *p) {
struct IsMetaMemImpl {
template <typename Mapping>
static bool Apply(uptr mem) {
- return mem >= Mapping::kMetaShadowBeg && mem <= Mapping::kMetaShadowEnd;
+ return mem >= Mapping::kMetaShadowBeg && mem < Mapping::kMetaShadowEnd;
}
};
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp
index cf07686d968dc..b0ce5680c95c1 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp
@@ -525,7 +525,7 @@ ALWAYS_INLINE USED void UnalignedMemoryAccess(ThreadState* thr, uptr pc,
void ShadowSet(RawShadow* p, RawShadow* end, RawShadow v) {
DCHECK_LE(p, end);
DCHECK(IsShadowMem(p));
- DCHECK(IsShadowMem(end));
+ DCHECK(p == end || IsShadowMem(end - 1));
UNUSED const uptr kAlign = kShadowCnt * kShadowSize;
DCHECK_EQ(reinterpret_cast<uptr>(p) % kAlign, 0);
DCHECK_EQ(reinterpret_cast<uptr>(end) % kAlign, 0);
@@ -675,7 +675,7 @@ void MemoryAccessRangeT(ThreadState* thr, uptr pc, uptr addr, uptr size) {
Printf("Access to non app mem start: %p\n", (void*)addr);
DCHECK(IsAppMem(addr));
}
- if (!IsAppMem(addr + size - 1)) {
+ if (size > 0 && !IsAppMem(addr + size - 1)) {
Printf("Access to non app mem end: %p\n", (void*)(addr + size - 1));
DCHECK(IsAppMem(addr + size - 1));
}
@@ -686,7 +686,7 @@ void MemoryAccessRangeT(ThreadState* thr, uptr pc, uptr addr, uptr size) {
RawShadow* shadow_mem_end = reinterpret_cast<RawShadow*>(
reinterpret_cast<uptr>(shadow_mem) + size * kShadowMultiplier - 1);
- if (!IsShadowMem(shadow_mem_end)) {
+ if (size > 0 && !IsShadowMem(shadow_mem_end)) {
Printf("Bad shadow end addr: %p (%p)\n", shadow_mem_end,
(void*)(addr + size - 1));
Printf(
|
@@ -675,7 +675,7 @@ void MemoryAccessRangeT(ThreadState* thr, uptr pc, uptr addr, uptr size) { | |||
Printf("Access to non app mem start: %p\n", (void*)addr); | |||
DCHECK(IsAppMem(addr)); | |||
} | |||
if (!IsAppMem(addr + size - 1)) { | |||
if (size > 0 && !IsAppMem(addr + size - 1)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is called from MemoryAccessRange, which checks if size==0, and since size is uptr size will always be > 0. Therefore this check is pointless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand this.
But MemoryAccessRangeT
did not guarantee size != 0
in its context -- is it possible for MemoryAccessRangeT
to be called in the future without checking for size == 0?
It just so happens that these checks are wrapped in SANITIZER_DEBUG
macro, so I think adding this check is better than adding an assertion DCHECK(size != 0)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But MemoryAccessRangeT did not guarantee size != 0 in its context -- is it possible for MemoryAccessRangeT to be called in the future without checking for size == 0?
We don't know.
But it's clear currently an invariant here is size != 0
, so why not add a DCHECK and catch if that assumption changes in future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it's clear currently an invariant here is
size != 0
, so why not add a DCHECK and catch if that assumption changes in future?
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the patch!
This commit changes the interval shadow/meta address check from inclusive-inclusive ($[\mathrm{start}, \mathrm{end}]$ ) to inclusive-exclusive ( $[\mathrm{start}, \mathrm{end})$ ), to resolve the ambiguity of the end point address. This also aligns the logic with the check for
isAppMem
(i.e., inclusive-exclusive), ensuring consistent behavior across all memory classifications.The$[\mathrm{start}, \mathrm{end}]$ , which could lead to a boundary address being incorrectly classified as both Shadow and Meta memory, e.g., 0x3000_0000_0000 in
isShadowMem
andisMetaMem
checks previously used an inclusive-inclusive interval, i.e.,Mapping48AddressSpace
.ShadowMem::end
cannot be considered a legal shadow address, as TSan protects the gap, i.e.,ProtectRange(ShadowEnd(), MetaShadowBeg());
ShadowMem
/MetaMem
addresses are derived fromAppMem
using an affine-like transformation (* factor + bias
). This transformation includes two extra modifications: high- and low-order bits are masked out, and for Shadow Memory, an optional XOR operation may be applied to prevent conflicts with certain AppMem regions.Note: This change is purely for improving code consistency and should have no functional impact. In practice, the exact endpoint addresses of the Shadow/Meta regions are generally not reached.