1/*
2 * Copyright 2015-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#include <folly/fibers/GuardPageAllocator.h>
17
18#ifndef _WIN32
19#include <dlfcn.h>
20#endif
21#include <signal.h>
22
23#include <iostream>
24#include <mutex>
25
26#include <folly/Singleton.h>
27#include <folly/SpinLock.h>
28#include <folly/Synchronized.h>
29#include <folly/portability/SysMman.h>
30#include <folly/portability/Unistd.h>
31
32#include <glog/logging.h>
33
34namespace folly {
35namespace fibers {
36
37/**
38 * Each stack with a guard page creates two memory mappings.
39 * Since this is a limited resource, we don't want to create too many of these.
40 *
41 * The upper bound on total number of mappings created
42 * is kNumGuarded * kMaxInUse.
43 */
44
45/**
46 * Number of guarded stacks per allocator instance
47 */
48constexpr size_t kNumGuarded = 100;
49
50/**
51 * Maximum number of allocator instances with guarded stacks enabled
52 */
53constexpr size_t kMaxInUse = 100;
54
55/**
56 * A cache for kNumGuarded stacks of a given size
57 *
58 * Thread safe.
59 */
60class StackCache {
61 public:
62 explicit StackCache(size_t stackSize) : allocSize_(allocSize(stackSize)) {
63 auto p = ::mmap(
64 nullptr,
65 allocSize_ * kNumGuarded,
66 PROT_READ | PROT_WRITE,
67 MAP_PRIVATE | MAP_ANONYMOUS,
68 -1,
69 0);
70 PCHECK(p != (void*)(-1));
71 storage_ = reinterpret_cast<unsigned char*>(p);
72
73 /* Protect the bottommost page of every stack allocation */
74 for (size_t i = 0; i < kNumGuarded; ++i) {
75 auto allocBegin = storage_ + allocSize_ * i;
76 freeList_.emplace_back(allocBegin, /* protected= */ false);
77 }
78 }
79
80 unsigned char* borrow(size_t size) {
81 std::lock_guard<folly::SpinLock> lg(lock_);
82
83 assert(storage_);
84
85 auto as = allocSize(size);
86 if (as != allocSize_ || freeList_.empty()) {
87 return nullptr;
88 }
89
90 auto p = freeList_.back().first;
91 if (!freeList_.back().second) {
92 PCHECK(0 == ::mprotect(p, pagesize(), PROT_NONE));
93 protectedPages().wlock()->insert(reinterpret_cast<intptr_t>(p));
94 }
95 freeList_.pop_back();
96
97 /* We allocate minimum number of pages required, plus a guard page.
98 Since we use this for stack storage, requested allocation is aligned
99 at the top of the allocated pages, while the guard page is at the bottom.
100
101 -- increasing addresses -->
102 Guard page Normal pages
103 |xxxxxxxxxx|..........|..........|
104 <- allocSize_ ------------------->
105 p -^ <- size -------->
106 limit -^
107 */
108 auto limit = p + allocSize_ - size;
109 assert(limit >= p + pagesize());
110 return limit;
111 }
112
113 bool giveBack(unsigned char* limit, size_t size) {
114 std::lock_guard<folly::SpinLock> lg(lock_);
115
116 assert(storage_);
117
118 auto as = allocSize(size);
119 if (std::less_equal<void*>{}(limit, storage_) ||
120 std::less_equal<void*>{}(storage_ + allocSize_ * kNumGuarded, limit)) {
121 /* not mine */
122 return false;
123 }
124
125 auto p = limit + size - as;
126 assert(as == allocSize_);
127 assert((p - storage_) % allocSize_ == 0);
128 freeList_.emplace_back(p, /* protected= */ true);
129 return true;
130 }
131
132 ~StackCache() {
133 assert(storage_);
134 protectedPages().withWLock([&](auto& pages) {
135 for (const auto& item : freeList_) {
136 pages.erase(reinterpret_cast<intptr_t>(item.first));
137 }
138 });
139 PCHECK(0 == ::munmap(storage_, allocSize_ * kNumGuarded));
140 }
141
142 static bool isProtected(intptr_t addr) {
143 // Use a read lock for reading.
144 return protectedPages().withRLock([&](auto const& pages) {
145 for (const auto& page : pages) {
146 intptr_t pageEnd = intptr_t(page + pagesize());
147 if (page <= addr && addr < pageEnd) {
148 return true;
149 }
150 }
151 return false;
152 });
153 }
154
155 private:
156 folly::SpinLock lock_;
157 unsigned char* storage_{nullptr};
158 size_t allocSize_{0};
159
160 /**
161 * LIFO free list. Each pair contains stack pointer and protected flag.
162 */
163 std::vector<std::pair<unsigned char*, bool>> freeList_;
164
165 static size_t pagesize() {
166 static const size_t pagesize = size_t(sysconf(_SC_PAGESIZE));
167 return pagesize;
168 }
169
170 /* Returns a multiple of pagesize() enough to store size + one guard page */
171 static size_t allocSize(size_t size) {
172 return pagesize() * ((size + pagesize() - 1) / pagesize() + 1);
173 }
174
175 static folly::Synchronized<std::unordered_set<intptr_t>>& protectedPages() {
176 static auto instance =
177 new folly::Synchronized<std::unordered_set<intptr_t>>();
178 return *instance;
179 }
180};
181
182#ifndef _WIN32
183
184namespace {
185
186struct sigaction oldSigsegvAction;
187
188void sigsegvSignalHandler(int signum, siginfo_t* info, void*) {
189 if (signum != SIGSEGV) {
190 std::cerr << "GuardPageAllocator signal handler called for signal: "
191 << signum;
192 return;
193 }
194
195 if (info &&
196 StackCache::isProtected(reinterpret_cast<intptr_t>(info->si_addr))) {
197 std::cerr << "folly::fibers Fiber stack overflow detected." << std::endl;
198 }
199
200 // Restore old signal handler and let it handle the signal.
201 sigaction(signum, &oldSigsegvAction, nullptr);
202 raise(signum);
203}
204
205bool isInJVM() {
206 auto getCreated = dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs");
207 return getCreated;
208}
209
210void installSignalHandler() {
211 static std::once_flag onceFlag;
212 std::call_once(onceFlag, []() {
213 if (isInJVM()) {
214 // Don't install signal handler, since JVM internal signal handler doesn't
215 // work with SA_ONSTACK
216 return;
217 }
218
219 struct sigaction sa;
220 memset(&sa, 0, sizeof(sa));
221 sigemptyset(&sa.sa_mask);
222 // By default signal handlers are run on the signaled thread's stack.
223 // In case of stack overflow running the SIGSEGV signal handler on
224 // the same stack leads to another SIGSEGV and crashes the program.
225 // Use SA_ONSTACK, so alternate stack is used (only if configured via
226 // sigaltstack).
227 sa.sa_flags |= SA_SIGINFO | SA_ONSTACK;
228 sa.sa_sigaction = &sigsegvSignalHandler;
229 sigaction(SIGSEGV, &sa, &oldSigsegvAction);
230 });
231}
232} // namespace
233
234#endif
235
236class CacheManager {
237 public:
238 static CacheManager& instance() {
239 static auto inst = new CacheManager();
240 return *inst;
241 }
242
243 std::unique_ptr<StackCacheEntry> getStackCache(size_t stackSize) {
244 std::lock_guard<folly::SpinLock> lg(lock_);
245 if (inUse_ < kMaxInUse) {
246 ++inUse_;
247 return std::make_unique<StackCacheEntry>(stackSize);
248 }
249
250 return nullptr;
251 }
252
253 private:
254 folly::SpinLock lock_;
255 size_t inUse_{0};
256
257 friend class StackCacheEntry;
258
259 void giveBack(std::unique_ptr<StackCache> /* stackCache_ */) {
260 assert(inUse_ > 0);
261 --inUse_;
262 /* Note: we can add a free list for each size bucket
263 if stack re-use is important.
264 In this case this needs to be a folly::Singleton
265 to make sure the free list is cleaned up on fork.
266
267 TODO(t7351705): fix Singleton destruction order
268 */
269 }
270};
271
272/*
273 * RAII Wrapper around a StackCache that calls
274 * CacheManager::giveBack() on destruction.
275 */
276class StackCacheEntry {
277 public:
278 explicit StackCacheEntry(size_t stackSize)
279 : stackCache_(std::make_unique<StackCache>(stackSize)) {}
280
281 StackCache& cache() const noexcept {
282 return *stackCache_;
283 }
284
285 ~StackCacheEntry() {
286 CacheManager::instance().giveBack(std::move(stackCache_));
287 }
288
289 private:
290 std::unique_ptr<StackCache> stackCache_;
291};
292
293GuardPageAllocator::GuardPageAllocator(bool useGuardPages)
294 : useGuardPages_(useGuardPages) {
295#ifndef _WIN32
296 installSignalHandler();
297#endif
298}
299
300GuardPageAllocator::~GuardPageAllocator() = default;
301
302unsigned char* GuardPageAllocator::allocate(size_t size) {
303 if (useGuardPages_ && !stackCache_) {
304 stackCache_ = CacheManager::instance().getStackCache(size);
305 }
306
307 if (stackCache_) {
308 auto p = stackCache_->cache().borrow(size);
309 if (p != nullptr) {
310 return p;
311 }
312 }
313 return fallbackAllocator_.allocate(size);
314}
315
316void GuardPageAllocator::deallocate(unsigned char* limit, size_t size) {
317 if (!(stackCache_ && stackCache_->cache().giveBack(limit, size))) {
318 fallbackAllocator_.deallocate(limit, size);
319 }
320}
321} // namespace fibers
322} // namespace folly
323