Skip to content

Commit eb87c79

Browse files
rlyerlymeta-codesync[bot]
authored andcommitted
Allow disabling free memory poisoning in ASAN builds
Summary: There's a lot of overhead when adding ASAN to every cachelib memory allocate/free. Allow disabling memory poisoning through the config, even in ASAN builds (this lets us keep existing ASAN environments). Reviewed By: AlnisM Differential Revision: D101399129 fbshipit-source-id: 1d36cfe302b6cd280ab0305ff2e3174f5d3d18d3
1 parent bcbdd45 commit eb87c79

13 files changed

Lines changed: 201 additions & 66 deletions

cachelib/allocator/CacheAllocator.h

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
#include <folly/logging/xlog.h>
2626
#include <folly/synchronization/SanitizeThread.h>
2727
#include <gtest/gtest.h>
28-
#include <sanitizer/asan_interface.h>
2928

3029
#include <chrono>
3130
#include <functional>
@@ -1631,23 +1630,25 @@ class CacheAllocator : public CacheBase {
16311630
return {};
16321631
}
16331632
#if FOLLY_SANITIZE_ADDRESS
1634-
// Copy the key out so we can use it without disabling instrumentation for
1635-
// the whole find path. Don't use std utilities (like std::string or
1636-
// std::memcpy) because they still have ASAN enabled. If it's a small key,
1637-
// store on the stack. Otherwise malloc on the heap.
16381633
char stackBuf[KAllocation::kKeyMaxLenSmall];
16391634
std::unique_ptr<char[]> heapBuf;
1640-
char* buffer;
1641-
if (key.size() <= KAllocation::kKeyMaxLenSmall) {
1642-
buffer = stackBuf;
1643-
} else {
1644-
heapBuf = std::unique_ptr<char[]>(new char[key.size()]);
1645-
buffer = heapBuf.get();
1646-
}
1647-
for (size_t i = 0; i < key.size(); i++) {
1648-
buffer[i] = key[i];
1635+
if (config_.isSlabAsanPoisoningEnabled()) {
1636+
// Copy the key out so we can use it without disabling instrumentation for
1637+
// the whole find path. Don't use std utilities (like std::string or
1638+
// std::memcpy) because they still have ASAN enabled. If it's a small key,
1639+
// store on the stack. Otherwise malloc on the heap.
1640+
char* buffer;
1641+
if (key.size() <= KAllocation::kKeyMaxLenSmall) {
1642+
buffer = stackBuf;
1643+
} else {
1644+
heapBuf = std::unique_ptr<char[]>(new char[key.size()]);
1645+
buffer = heapBuf.get();
1646+
}
1647+
for (size_t i = 0; i < key.size(); i++) {
1648+
buffer[i] = key[i];
1649+
}
1650+
key = Key(buffer, key.size());
16491651
}
1650-
key = Key(buffer, key.size());
16511652
#endif
16521653
if (Item::getRequiredSize(key, /* size */ 0) > allocSize) {
16531654
return {};
@@ -2182,7 +2183,7 @@ class CacheAllocator : public CacheBase {
21822183
config.reduceFragmentationInAllocationClass)
21832184
: config.defaultAllocSizes,
21842185
config.enableZeroedSlabAllocs, config.disableFullCoredump,
2185-
config.lockMemory};
2186+
config.lockMemory, config.isSlabAsanPoisoningEnabled()};
21862187
}
21872188

21882189
// starts one of the cache workers passing the current instance and the args
@@ -2766,8 +2767,8 @@ CacheAllocator<CacheTrait>::restoreMemoryAllocator() {
27662767
->attachShm(detail::kShmCacheName, config_.slabMemoryBaseAddr,
27672768
createShmCacheOpts())
27682769
.addr,
2769-
config_.getCacheSize(),
2770-
config_.disableFullCoredump);
2770+
config_.getCacheSize(), config_.disableFullCoredump,
2771+
config_.isSlabAsanPoisoningEnabled());
27712772
}
27722773

27732774
template <typename CacheTrait>

cachelib/allocator/CacheAllocatorConfig.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#pragma once
1818

19+
#include <folly/CPortability.h>
1920
#include <folly/Optional.h>
2021
#include <folly/json/DynamicConverter.h>
2122
#include <folly/json/json.h>
@@ -370,6 +371,21 @@ class CacheAllocatorConfig {
370371
return skipPromoteChildrenWhenParentFailed;
371372
}
372373

374+
// Enable or disable ASAN slab poisoning. When enabled, freed slab memory is
375+
// poisoned so that ASAN can detect use-after-free bugs. This is disabled by
376+
// default to avoid overhead in ASAN builds. Enable this for testing or when
377+
// actively debugging memory issues. This is a no-op in non-ASAN builds.
378+
CacheAllocatorConfig& setSlabAsanPoisoning(bool enable);
379+
380+
// @return whether slab ASAN poisoning is enabled
381+
bool isSlabAsanPoisoningEnabled() const noexcept {
382+
#if FOLLY_SANITIZE_ADDRESS
383+
return enableSlabAsanPoisoning;
384+
#else
385+
return false;
386+
#endif
387+
}
388+
373389
// Enable aggregating pool stats to a single stat
374390
//
375391
// When enabled, pool stats from all pools will be aggregated into a single
@@ -718,6 +734,13 @@ class CacheAllocatorConfig {
718734
// If true, aggregate pool stats into a single stat before exporting
719735
bool aggregatePoolStats{false};
720736

737+
#if FOLLY_SANITIZE_ADDRESS
738+
// If true, slab memory is ASAN-poisoned on free and unpoisoned on allocation.
739+
// Disabled by default to avoid overhead in ASAN builds. Enable for testing
740+
// or when actively debugging memory issues.
741+
bool enableSlabAsanPoisoning{false};
742+
#endif
743+
721744
friend CacheT;
722745

723746
private:
@@ -1214,6 +1237,15 @@ CacheAllocatorConfig<T>& CacheAllocatorConfig<T>::setNumShards(size_t shards) {
12141237
return *this;
12151238
}
12161239

1240+
template <typename T>
1241+
CacheAllocatorConfig<T>& CacheAllocatorConfig<T>::setSlabAsanPoisoning(
1242+
[[maybe_unused]] bool enable) {
1243+
#if FOLLY_SANITIZE_ADDRESS
1244+
enableSlabAsanPoisoning = enable;
1245+
#endif
1246+
return *this;
1247+
}
1248+
12171249
template <typename T>
12181250
CacheAllocatorConfig<T>& CacheAllocatorConfig<T>::enableAggregatePoolStats() {
12191251
aggregatePoolStats = true;

cachelib/allocator/memory/AllocationClass.cpp

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
#include <folly/Try.h>
2020
#include <folly/logging/xlog.h>
21-
#include <sanitizer/asan_interface.h>
2221

2322
#include "cachelib/allocator/memory/SlabAllocator.h"
2423
#include "cachelib/common/Exceptions.h"
@@ -52,7 +51,8 @@ AllocationClass::AllocationClass(ClassId classId,
5251
slabAlloc_.createPtrCompressor<FreeAlloc, CompressedPtr5B>()
5352
#if FOLLY_SANITIZE_ADDRESS
5453
,
55-
allocationSize_
54+
// Only enable ASAN poisoning in the free list if configured
55+
slabAlloc_.isAsanPoisoningEnabled() ? allocationSize_ : 0
5656
#endif
5757
} {
5858
checkState();
@@ -110,7 +110,8 @@ AllocationClass::AllocationClass(
110110
slabAlloc_.createPtrCompressor<FreeAlloc, CompressedPtr5B>()
111111
#if FOLLY_SANITIZE_ADDRESS
112112
,
113-
allocationSize_
113+
// Only enable ASAN poisoning in the free list if configured
114+
slabAlloc_.isAsanPoisoningEnabled() ? allocationSize_ : 0
114115
#endif
115116
),
116117
canAllocate_(*object.canAllocate()) {
@@ -133,22 +134,22 @@ AllocationClass::AllocationClass(
133134

134135
// Free slabs are entirely unused
135136
for (auto* slab : freeSlabs_) {
136-
ASAN_POISON_MEMORY_REGION(slab->memoryAtOffset(0), Slab::kSize);
137+
slabAlloc_.asanPoisonMemoryRegion(slab->memoryAtOffset(0), Slab::kSize);
137138
}
138139

139140
// Poison the internal fragmentation at the tail of fully allocated slabs
140141
const uint32_t usableSize = getAllocsPerSlab() * allocationSize_;
141142
if (usableSize < Slab::kSize) {
142143
for (auto* slab : allocatedSlabs_) {
143-
ASAN_POISON_MEMORY_REGION(slab->memoryAtOffset(usableSize),
144-
Slab::kSize - usableSize);
144+
slabAlloc_.asanPoisonMemoryRegion(slab->memoryAtOffset(usableSize),
145+
Slab::kSize - usableSize);
145146
}
146147
}
147148

148149
// Poison the uncarved region of currSlab_
149150
if (currSlab_ != nullptr && currOffset_ < Slab::kSize) {
150-
ASAN_POISON_MEMORY_REGION(currSlab_->memoryAtOffset(currOffset_),
151-
Slab::kSize - currOffset_);
151+
slabAlloc_.asanPoisonMemoryRegion(currSlab_->memoryAtOffset(currOffset_),
152+
Slab::kSize - currOffset_);
152153
}
153154
}
154155

@@ -177,7 +178,7 @@ void* AllocationClass::allocateFromCurrentSlabLocked() noexcept {
177178
XDCHECK(canAllocateFromCurrentSlabLocked());
178179
void* ret = currSlab_->memoryAtOffset(currOffset_);
179180
currOffset_ += allocationSize_;
180-
ASAN_UNPOISON_MEMORY_REGION(ret, allocationSize_);
181+
slabAlloc_.asanUnpoisonMemoryRegion(ret, allocationSize_);
181182
return ret;
182183
}
183184

@@ -208,7 +209,7 @@ void* AllocationClass::allocateLocked() {
208209
FreeAlloc* ret = freedAllocations_.getHead();
209210
XDCHECK(ret != nullptr);
210211
freedAllocations_.pop();
211-
ASAN_UNPOISON_MEMORY_REGION(ret, allocationSize_);
212+
slabAlloc_.asanUnpoisonMemoryRegion(ret, allocationSize_);
212213
return reinterpret_cast<void*>(ret);
213214
}
214215

@@ -687,7 +688,7 @@ void AllocationClass::free(void* memory) {
687688
throw std::invalid_argument(
688689
folly::sformat("Allocation {} is already marked as free", memory));
689690
}
690-
ASAN_POISON_MEMORY_REGION(memory, allocationSize_);
691+
slabAlloc_.asanPoisonMemoryRegion(memory, allocationSize_);
691692
allocState[idx] = true;
692693
return;
693694
}

cachelib/allocator/memory/MemoryAllocator.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,17 @@ MemoryAllocator::MemoryAllocator(Config config,
6565
: config_(std::move(config)),
6666
slabAllocator_(memoryStart,
6767
memSize,
68-
{config_.disableFullCoredump, config_.lockMemory}),
68+
{config_.disableFullCoredump, config_.lockMemory,
69+
config_.enableAsanPoisoning}),
6970
memoryPoolManager_(slabAllocator_) {
7071
checkConfig(config_);
7172
}
7273

7374
MemoryAllocator::MemoryAllocator(Config config, size_t memSize)
7475
: config_(std::move(config)),
7576
slabAllocator_(memSize,
76-
{config_.disableFullCoredump, config_.lockMemory}),
77+
{config_.disableFullCoredump, config_.lockMemory,
78+
config_.enableAsanPoisoning}),
7779
memoryPoolManager_(slabAllocator_) {
7880
checkConfig(config_);
7981
}
@@ -82,16 +84,19 @@ MemoryAllocator::MemoryAllocator(
8284
const serialization::MemoryAllocatorObject& object,
8385
void* memoryStart,
8486
size_t memSize,
85-
bool disableCoredump)
87+
bool disableCoredump,
88+
bool enableAsanPoisoning)
8689
: config_(std::set<uint32_t>{object.allocSizes()->begin(),
8790
object.allocSizes()->end()},
8891
*object.enableZeroedSlabAllocs(),
8992
disableCoredump,
90-
*object.lockMemory()),
93+
*object.lockMemory(),
94+
enableAsanPoisoning),
9195
slabAllocator_(*object.slabAllocator(),
9296
memoryStart,
9397
memSize,
94-
{config_.disableFullCoredump, config_.lockMemory}),
98+
{config_.disableFullCoredump, config_.lockMemory,
99+
config_.enableAsanPoisoning}),
95100
memoryPoolManager_(*object.memoryPoolManager(), slabAllocator_) {
96101
checkConfig(config_);
97102
}

cachelib/allocator/memory/MemoryAllocator.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,13 @@ class MemoryAllocator {
8888
Config(std::set<uint32_t> sizes,
8989
bool zeroOnRelease,
9090
bool disableCoredump,
91-
bool _lockMemory)
91+
bool _lockMemory,
92+
bool _enableAsanPoisoning)
9293
: allocSizes(std::move(sizes)),
9394
enableZeroedSlabAllocs(zeroOnRelease),
9495
disableFullCoredump(disableCoredump),
95-
lockMemory(_lockMemory) {}
96+
lockMemory(_lockMemory),
97+
enableAsanPoisoning(_enableAsanPoisoning) {}
9698

9799
// Hint to determine the allocation class sizes
98100
std::set<uint32_t> allocSizes;
@@ -110,6 +112,10 @@ class MemoryAllocator {
110112
// allocator is not shared, user needs to ensure there are appropriate
111113
// rlimits setup to lock the memory.
112114
bool lockMemory{false};
115+
116+
// When true, slab memory is ASAN-poisoned on free and unpoisoned on
117+
// allocation so ASAN can detect use-after-free bugs.
118+
bool enableAsanPoisoning{false};
113119
};
114120

115121
// Creates a memory allocator out of the caller allocated memory region. The
@@ -147,10 +153,12 @@ class MemoryAllocator {
147153
// @param memSize the size of the memory region that was originally
148154
// used to create this memory allocator
149155
// @param disableCoredump exclude mapped region from core dumps
156+
// @param enableAsanPoisoning When true, slab memory is ASAN-poisoned
150157
MemoryAllocator(const serialization::MemoryAllocatorObject& object,
151158
void* memoryStart,
152159
size_t memSize,
153-
bool disableCoredump);
160+
bool disableCoredump,
161+
bool enableAsanPoisoning);
154162

155163
MemoryAllocator(const MemoryAllocator&) = delete;
156164
MemoryAllocator& operator=(const MemoryAllocator&) = delete;

cachelib/allocator/memory/MemoryPool.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
#include "cachelib/allocator/memory/MemoryPool.h"
1818

19-
#include <sanitizer/asan_interface.h>
20-
2119
#include <algorithm>
2220
#include <numeric>
2321
#include <utility>
@@ -86,7 +84,7 @@ MemoryPool::MemoryPool(const serialization::MemoryPoolObject& object,
8684

8785
for (auto freeSlabIdx : *object.freeSlabIdxs()) {
8886
auto* slab = slabAllocator_.getSlabForIdx(freeSlabIdx);
89-
ASAN_POISON_MEMORY_REGION(slab->memoryAtOffset(0), Slab::kSize);
87+
slabAllocator_.asanPoisonMemoryRegion(slab->memoryAtOffset(0), Slab::kSize);
9088
freeSlabs_.push_back(slab);
9189
}
9290
checkState();
@@ -406,7 +404,11 @@ void MemoryPool::releaseSlab(SlabReleaseMode mode,
406404
ClassId receiverClassId) {
407405
if (zeroOnRelease) {
408406
#if FOLLY_SANITIZE_ADDRESS
409-
memsetNoAsan(slab->memoryAtOffset(0), 0, Slab::kSize);
407+
if (slabAllocator_.isAsanPoisoningEnabled()) {
408+
memsetNoAsan(slab->memoryAtOffset(0), 0, Slab::kSize);
409+
} else {
410+
memset(slab->memoryAtOffset(0), 0, Slab::kSize);
411+
}
410412
#else
411413
memset(slab->memoryAtOffset(0), 0, Slab::kSize);
412414
#endif

cachelib/allocator/memory/SlabAllocator.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
#include <folly/Random.h>
2121
#include <folly/logging/xlog.h>
2222
#include <folly/synchronization/SanitizeThread.h>
23-
#include <sanitizer/asan_interface.h>
2423
#include <sys/mman.h>
2524

2625
#include <chrono>
@@ -118,7 +117,12 @@ SlabAllocator::SlabAllocator(void* memoryStart,
118117
memorySize_(roundDownToSlabSize(memorySize)),
119118
slabMemoryStart_(computeSlabMemoryStart(memoryStart_, memorySize_)),
120119
nextSlabAllocation_(slabMemoryStart_),
121-
ownsMemory_(ownsMemory) {
120+
ownsMemory_(ownsMemory)
121+
#if FOLLY_SANITIZE_ADDRESS
122+
,
123+
asanPoisoningEnabled_(config.enableAsanPoisoning)
124+
#endif
125+
{
122126
checkState();
123127

124128
static_assert(!(sizeof(Slab) & (sizeof(Slab) - 1)),
@@ -132,7 +136,7 @@ SlabAllocator::SlabAllocator(void* memoryStart,
132136
memoryLocker_ = std::thread{[this]() { lockMemoryAsync(); }};
133137
}
134138

135-
ASAN_POISON_MEMORY_REGION(
139+
asanPoisonMemoryRegion(
136140
slabMemoryStart_,
137141
reinterpret_cast<const uint8_t*>(getSlabMemoryEnd()) -
138142
reinterpret_cast<const uint8_t*>(slabMemoryStart_));
@@ -153,7 +157,12 @@ SlabAllocator::SlabAllocator(const serialization::SlabAllocatorObject& object,
153157
slabMemoryStart_(computeSlabMemoryStart(memoryStart_, memorySize_)),
154158
nextSlabAllocation_(getSlabForIdx(*object.nextSlabIdx())),
155159
canAllocate_(*object.canAllocate()),
156-
ownsMemory_(false) {
160+
ownsMemory_(false)
161+
#if FOLLY_SANITIZE_ADDRESS
162+
,
163+
asanPoisoningEnabled_(config.enableAsanPoisoning)
164+
#endif
165+
{
157166
if (Slab::kSize != *object.slabSize()) {
158167
throw std::invalid_argument(folly::sformat(
159168
"current slab size {} does not match the previous one {}",
@@ -217,17 +226,17 @@ SlabAllocator::SlabAllocator(const serialization::SlabAllocatorObject& object,
217226
// Poison slabs that are not allocated. Tests depend on this running after
218227
// checkState().
219228

220-
ASAN_POISON_MEMORY_REGION(
229+
asanPoisonMemoryRegion(
221230
nextSlabAllocation_,
222231
reinterpret_cast<const uint8_t*>(getSlabMemoryEnd()) -
223232
reinterpret_cast<const uint8_t*>(nextSlabAllocation_));
224233

225234
for (const auto* slab : freeSlabs_) {
226-
ASAN_POISON_MEMORY_REGION(slab, Slab::kSize);
235+
asanPoisonMemoryRegion(slab, Slab::kSize);
227236
}
228237

229238
for (const auto* slab : advisedSlabs_) {
230-
ASAN_POISON_MEMORY_REGION(slab, Slab::kSize);
239+
asanPoisonMemoryRegion(slab, Slab::kSize);
231240
}
232241
}
233242

0 commit comments

Comments
 (0)