1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "partition_alloc/stack/stack.h"
6
7 #include <cstdint>
8 #include <limits>
9
10 #include "build/build_config.h"
11 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
12 #include "partition_alloc/partition_alloc_buildflags.h"
13 #include "partition_alloc/partition_alloc_check.h"
14
15 #if BUILDFLAG(IS_WIN)
16 #include <windows.h>
17 #else
18 #include <pthread.h>
19 #endif
20
21 #if defined(LIBC_GLIBC)
22 extern "C" void* __libc_stack_end;
23 #endif
24
25 namespace partition_alloc::internal {
26
27 #if BUILDFLAG(IS_WIN)
28
GetStackTop()29 void* GetStackTop() {
30 #if defined(ARCH_CPU_X86_64)
31 return reinterpret_cast<void*>(
32 reinterpret_cast<NT_TIB64*>(NtCurrentTeb())->StackBase);
33 #elif defined(ARCH_CPU_32_BITS)
34 return reinterpret_cast<void*>(
35 reinterpret_cast<NT_TIB*>(NtCurrentTeb())->StackBase);
36 #elif defined(ARCH_CPU_ARM64)
37 // Windows 8 and later, see
38 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadstacklimits
39 ULONG_PTR lowLimit, highLimit;
40 ::GetCurrentThreadStackLimits(&lowLimit, &highLimit);
41 return reinterpret_cast<void*>(highLimit);
42 #else
43 #error "Unsupported GetStackStart"
44 #endif
45 }
46
47 #elif BUILDFLAG(IS_APPLE)
48
49 void* GetStackTop() {
50 return pthread_get_stackaddr_np(pthread_self());
51 }
52
53 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
54
55 void* GetStackTop() {
56 pthread_attr_t attr;
57 int error = pthread_getattr_np(pthread_self(), &attr);
58 if (!error) {
59 void* base;
60 size_t size;
61 error = pthread_attr_getstack(&attr, &base, &size);
62 PA_CHECK(!error);
63 pthread_attr_destroy(&attr);
64 return reinterpret_cast<uint8_t*>(base) + size;
65 }
66
67 #if defined(LIBC_GLIBC)
68 // pthread_getattr_np can fail for the main thread. In this case
69 // just like NaCl we rely on the __libc_stack_end to give us
70 // the start of the stack.
71 // See https://code.google.com/p/nativeclient/issues/detail?id=3431.
72 return __libc_stack_end;
73 #else
74 return nullptr;
75 #endif // defined(LIBC_GLIBC)
76 }
77
78 #else // BUILDFLAG(IS_WIN)
79 #error "Unsupported GetStackTop"
80 #endif // BUILDFLAG(IS_WIN)
81
82 using IterateStackCallback = void (*)(const Stack*, StackVisitor*, uintptr_t*);
83 extern "C" void PAPushAllRegistersAndIterateStack(const Stack*,
84 StackVisitor*,
85 IterateStackCallback);
86
Stack(void * stack_top)87 Stack::Stack(void* stack_top) : stack_top_(stack_top) {
88 PA_DCHECK(stack_top);
89 }
90
GetStackPointer()91 PA_NOINLINE uintptr_t* GetStackPointer() {
92 return reinterpret_cast<uintptr_t*>(__builtin_frame_address(0));
93 }
94
95 namespace {
96
IterateSafeStackIfNecessary(StackVisitor * visitor)97 [[maybe_unused]] void IterateSafeStackIfNecessary(StackVisitor* visitor) {
98 #if defined(__has_feature)
99 #if __has_feature(safe_stack)
100 // Source:
101 // https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/safestack/safestack.cpp
102 constexpr size_t kSafeStackAlignmentBytes = 16;
103 void* stack_ptr = __builtin___get_unsafe_stack_ptr();
104 void* stack_top = __builtin___get_unsafe_stack_top();
105 PA_CHECK(stack_top > stack_ptr);
106 PA_CHECK(0u == (reinterpret_cast<uintptr_t>(stack_ptr) &
107 (kSafeStackAlignmentBytes - 1)));
108 PA_CHECK(0u == (reinterpret_cast<uintptr_t>(stack_top) &
109 (kSafeStackAlignmentBytes - 1)));
110 visitor->VisitStack(reinterpret_cast<uintptr_t*>(stack_ptr),
111 reinterpret_cast<uintptr_t*>(stack_top));
112 #endif // __has_feature(safe_stack)
113 #endif // defined(__has_feature)
114 }
115
116 // Called by the trampoline that pushes registers on the stack. This method
117 // should never be inlined to ensure that a possible redzone cannot contain
118 // any data that needs to be scanned.
119 // No ASAN support as method accesses redzones while walking the stack.
IteratePointersImpl(const Stack * stack,StackVisitor * visitor,uintptr_t * stack_ptr)120 [[maybe_unused]] PA_NOINLINE PA_NO_SANITIZE("address") void IteratePointersImpl(
121 const Stack* stack,
122 StackVisitor* visitor,
123 uintptr_t* stack_ptr) {
124 PA_DCHECK(stack);
125 PA_DCHECK(visitor);
126 PA_CHECK(nullptr != stack->stack_top());
127 // All supported platforms should have their stack aligned to at least
128 // sizeof(void*).
129 constexpr size_t kMinStackAlignment = sizeof(void*);
130 PA_CHECK(0u ==
131 (reinterpret_cast<uintptr_t>(stack_ptr) & (kMinStackAlignment - 1)));
132 visitor->VisitStack(stack_ptr,
133 reinterpret_cast<uintptr_t*>(stack->stack_top()));
134 }
135
136 } // namespace
137
IteratePointers(StackVisitor * visitor) const138 void Stack::IteratePointers(StackVisitor* visitor) const {
139 #if BUILDFLAG(STACK_SCAN_SUPPORTED)
140 PAPushAllRegistersAndIterateStack(this, visitor, &IteratePointersImpl);
141 // No need to deal with callee-saved registers as they will be kept alive by
142 // the regular conservative stack iteration.
143 IterateSafeStackIfNecessary(visitor);
144 #endif // BUILDFLAG(STACK_SCAN_SUPPORTED)
145 }
146
147 StackTopRegistry::StackTopRegistry() = default;
148 StackTopRegistry::~StackTopRegistry() = default;
149
150 // static
Get()151 StackTopRegistry& StackTopRegistry::Get() {
152 static base::NoDestructor<StackTopRegistry> instance;
153 return *instance.get();
154 }
155
NotifyThreadCreated(void * stack_top)156 void StackTopRegistry::NotifyThreadCreated(void* stack_top) {
157 const auto tid = base::PlatformThread::CurrentId();
158 ScopedGuard guard(lock_);
159 stack_tops_.insert({tid, stack_top});
160 // Insertion may fail due to an existing entry
161 // (i.e. `insert(...).second == false`), but we allow it instead of `CHECK`ing
162 // it. Guaranteeing this function to be called exactly once is quite hard and
163 // we aim to guarantee "at least once".
164 }
165
NotifyThreadDestroyed()166 void StackTopRegistry::NotifyThreadDestroyed() {
167 const auto tid = base::PlatformThread::CurrentId();
168 ScopedGuard guard(lock_);
169 PA_DCHECK(1 == stack_tops_.count(tid));
170 stack_tops_.erase(tid);
171 }
172
GetCurrentThreadStackTop() const173 void* StackTopRegistry::GetCurrentThreadStackTop() const {
174 const auto tid = base::PlatformThread::CurrentId();
175 ScopedGuard guard(lock_);
176 auto it = stack_tops_.find(tid);
177 return it != stack_tops_.end() ? it->second : nullptr;
178 }
179
180 } // namespace partition_alloc::internal
181