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 <memory>
8 #include <ostream>
9
10 #include "build/build_config.h"
11 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13
14 #if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
15
16 #if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86) || defined(ARCH_CPU_X86_64))
17 #include <xmmintrin.h>
18 #endif
19
20 namespace partition_alloc::internal {
21
22 namespace {
23
24 class PartitionAllocStackTest : public ::testing::Test {
25 protected:
PartitionAllocStackTest()26 PartitionAllocStackTest() : stack_(std::make_unique<Stack>(GetStackTop())) {}
27
GetStack() const28 Stack* GetStack() const { return stack_.get(); }
29
30 private:
31 std::unique_ptr<Stack> stack_;
32 };
33
34 class StackScanner final : public StackVisitor {
35 public:
36 struct Container {
37 std::unique_ptr<int> value;
38 };
39
StackScanner()40 StackScanner() : container_(std::make_unique<Container>()) {
41 container_->value = std::make_unique<int>();
42 }
43
VisitStack(uintptr_t * stack_ptr,uintptr_t * stack_top)44 void VisitStack(uintptr_t* stack_ptr, uintptr_t* stack_top) final {
45 for (; stack_ptr != stack_top; ++stack_ptr) {
46 if (*stack_ptr == reinterpret_cast<uintptr_t>(container_->value.get())) {
47 found_ = true;
48 }
49 }
50 }
51
Reset()52 void Reset() { found_ = false; }
found() const53 bool found() const { return found_; }
needle() const54 int* needle() const { return container_->value.get(); }
55
56 private:
57 std::unique_ptr<Container> container_;
58 bool found_ = false;
59 };
60
61 } // namespace
62
TEST_F(PartitionAllocStackTest,IteratePointersFindsOnStackValue)63 TEST_F(PartitionAllocStackTest, IteratePointersFindsOnStackValue) {
64 auto scanner = std::make_unique<StackScanner>();
65
66 // No check that the needle is initially not found as on some platforms it
67 // may be part of temporaries after setting it up through StackScanner.
68 {
69 [[maybe_unused]] int* volatile tmp = scanner->needle();
70 GetStack()->IteratePointers(scanner.get());
71 EXPECT_TRUE(scanner->found());
72 }
73 }
74
TEST_F(PartitionAllocStackTest,IteratePointersFindsOnStackValuePotentiallyUnaligned)75 TEST_F(PartitionAllocStackTest,
76 IteratePointersFindsOnStackValuePotentiallyUnaligned) {
77 auto scanner = std::make_unique<StackScanner>();
78
79 // No check that the needle is initially not found as on some platforms it
80 // may be part of temporaries after setting it up through StackScanner.
81 {
82 [[maybe_unused]] char a = 'c';
83 [[maybe_unused]] int* volatile tmp = scanner->needle();
84 GetStack()->IteratePointers(scanner.get());
85 EXPECT_TRUE(scanner->found());
86 }
87 }
88
89 namespace {
90
91 // Prevent inlining as that would allow the compiler to prove that the parameter
92 // must not actually be materialized.
93 //
94 // Parameter positiosn are explicit to test various calling conventions.
RecursivelyPassOnParameterImpl(void * p1,void * p2,void * p3,void * p4,void * p5,void * p6,void * p7,void * p8,Stack * stack,StackVisitor * visitor)95 PA_NOINLINE void* RecursivelyPassOnParameterImpl(void* p1,
96 void* p2,
97 void* p3,
98 void* p4,
99 void* p5,
100 void* p6,
101 void* p7,
102 void* p8,
103 Stack* stack,
104 StackVisitor* visitor) {
105 if (p1) {
106 return RecursivelyPassOnParameterImpl(nullptr, p1, nullptr, nullptr,
107 nullptr, nullptr, nullptr, nullptr,
108 stack, visitor);
109 } else if (p2) {
110 return RecursivelyPassOnParameterImpl(nullptr, nullptr, p2, nullptr,
111 nullptr, nullptr, nullptr, nullptr,
112 stack, visitor);
113 } else if (p3) {
114 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, p3,
115 nullptr, nullptr, nullptr, nullptr,
116 stack, visitor);
117 } else if (p4) {
118 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
119 p4, nullptr, nullptr, nullptr, stack,
120 visitor);
121 } else if (p5) {
122 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
123 nullptr, p5, nullptr, nullptr, stack,
124 visitor);
125 } else if (p6) {
126 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
127 nullptr, nullptr, p6, nullptr, stack,
128 visitor);
129 } else if (p7) {
130 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
131 nullptr, nullptr, nullptr, p7, stack,
132 visitor);
133 } else if (p8) {
134 stack->IteratePointers(visitor);
135 return p8;
136 }
137 return nullptr;
138 }
139
RecursivelyPassOnParameter(size_t num,void * parameter,Stack * stack,StackVisitor * visitor)140 PA_NOINLINE void* RecursivelyPassOnParameter(size_t num,
141 void* parameter,
142 Stack* stack,
143 StackVisitor* visitor) {
144 switch (num) {
145 case 0:
146 stack->IteratePointers(visitor);
147 return parameter;
148 case 1:
149 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
150 nullptr, nullptr, nullptr,
151 parameter, stack, visitor);
152 case 2:
153 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
154 nullptr, nullptr, parameter,
155 nullptr, stack, visitor);
156 case 3:
157 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
158 nullptr, parameter, nullptr,
159 nullptr, stack, visitor);
160 case 4:
161 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
162 parameter, nullptr, nullptr,
163 nullptr, stack, visitor);
164 case 5:
165 return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr,
166 parameter, nullptr, nullptr,
167 nullptr, nullptr, stack, visitor);
168 case 6:
169 return RecursivelyPassOnParameterImpl(nullptr, nullptr, parameter,
170 nullptr, nullptr, nullptr, nullptr,
171 nullptr, stack, visitor);
172 case 7:
173 return RecursivelyPassOnParameterImpl(nullptr, parameter, nullptr,
174 nullptr, nullptr, nullptr, nullptr,
175 nullptr, stack, visitor);
176 case 8:
177 return RecursivelyPassOnParameterImpl(parameter, nullptr, nullptr,
178 nullptr, nullptr, nullptr, nullptr,
179 nullptr, stack, visitor);
180 default:
181 __builtin_unreachable();
182 }
183 __builtin_unreachable();
184 }
185
186 } // namespace
187
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting0)188 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting0) {
189 auto scanner = std::make_unique<StackScanner>();
190 void* needle = RecursivelyPassOnParameter(0, scanner->needle(), GetStack(),
191 scanner.get());
192 EXPECT_EQ(scanner->needle(), needle);
193 EXPECT_TRUE(scanner->found());
194 }
195
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting1)196 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting1) {
197 auto scanner = std::make_unique<StackScanner>();
198 void* needle = RecursivelyPassOnParameter(1, scanner->needle(), GetStack(),
199 scanner.get());
200 EXPECT_EQ(scanner->needle(), needle);
201 EXPECT_TRUE(scanner->found());
202 }
203
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting2)204 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting2) {
205 auto scanner = std::make_unique<StackScanner>();
206 void* needle = RecursivelyPassOnParameter(2, scanner->needle(), GetStack(),
207 scanner.get());
208 EXPECT_EQ(scanner->needle(), needle);
209 EXPECT_TRUE(scanner->found());
210 }
211
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting3)212 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting3) {
213 auto scanner = std::make_unique<StackScanner>();
214 void* needle = RecursivelyPassOnParameter(3, scanner->needle(), GetStack(),
215 scanner.get());
216 EXPECT_EQ(scanner->needle(), needle);
217 EXPECT_TRUE(scanner->found());
218 }
219
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting4)220 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting4) {
221 auto scanner = std::make_unique<StackScanner>();
222 void* needle = RecursivelyPassOnParameter(4, scanner->needle(), GetStack(),
223 scanner.get());
224 EXPECT_EQ(scanner->needle(), needle);
225 EXPECT_TRUE(scanner->found());
226 }
227
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting5)228 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting5) {
229 auto scanner = std::make_unique<StackScanner>();
230 void* needle = RecursivelyPassOnParameter(5, scanner->needle(), GetStack(),
231 scanner.get());
232 EXPECT_EQ(scanner->needle(), needle);
233 EXPECT_TRUE(scanner->found());
234 }
235
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting6)236 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting6) {
237 auto scanner = std::make_unique<StackScanner>();
238 void* needle = RecursivelyPassOnParameter(6, scanner->needle(), GetStack(),
239 scanner.get());
240 EXPECT_EQ(scanner->needle(), needle);
241 EXPECT_TRUE(scanner->found());
242 }
243
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting7)244 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting7) {
245 auto scanner = std::make_unique<StackScanner>();
246 void* needle = RecursivelyPassOnParameter(7, scanner->needle(), GetStack(),
247 scanner.get());
248 EXPECT_EQ(scanner->needle(), needle);
249 EXPECT_TRUE(scanner->found());
250 }
251
TEST_F(PartitionAllocStackTest,IteratePointersFindsParameterNesting8)252 TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting8) {
253 auto scanner = std::make_unique<StackScanner>();
254 void* needle = RecursivelyPassOnParameter(8, scanner->needle(), GetStack(),
255 scanner.get());
256 EXPECT_EQ(scanner->needle(), needle);
257 EXPECT_TRUE(scanner->found());
258 }
259
260 // The following test uses inline assembly and has been checked to work on clang
261 // to verify that the stack-scanning trampoline pushes callee-saved registers.
262 //
263 // The test uses a macro loop as asm() can only be passed string literals.
264 #if defined(__clang__) && defined(ARCH_CPU_X86_64) && !BUILDFLAG(IS_WIN)
265
266 // Excluded from test: rbp
267 #define FOR_ALL_CALLEE_SAVED_REGS(V) \
268 V(rbx) \
269 V(r12) \
270 V(r13) \
271 V(r14) \
272 V(r15)
273
274 namespace {
275
IteratePointersNoMangling(Stack * stack,StackVisitor * visitor)276 extern "C" void IteratePointersNoMangling(Stack* stack, StackVisitor* visitor) {
277 stack->IteratePointers(visitor);
278 }
279
280 #define DEFINE_MOVE_INTO(reg) \
281 PA_NOINLINE void MoveInto##reg(Stack* local_stack, \
282 StackScanner* local_scanner) { \
283 asm volatile(" mov %0, %%" #reg \
284 "\n mov %1, %%rdi" \
285 "\n mov %2, %%rsi" \
286 "\n call %P3" \
287 "\n mov $0, %%" #reg \
288 : \
289 : "r"(local_scanner->needle()), "r"(local_stack), \
290 "r"(local_scanner), "i"(IteratePointersNoMangling) \
291 : "memory", #reg, "rdi", "rsi", "cc"); \
292 }
293
294 FOR_ALL_CALLEE_SAVED_REGS(DEFINE_MOVE_INTO)
295
296 } // namespace
297
TEST_F(PartitionAllocStackTest,IteratePointersFindsCalleeSavedRegisters)298 TEST_F(PartitionAllocStackTest, IteratePointersFindsCalleeSavedRegisters) {
299 auto scanner = std::make_unique<StackScanner>();
300
301 // No check that the needle is initially not found as on some platforms it
302 // may be part of temporaries after setting it up through StackScanner.
303
304 // First, clear all callee-saved registers.
305 #define CLEAR_REGISTER(reg) asm("mov $0, %%" #reg : : : #reg);
306
307 FOR_ALL_CALLEE_SAVED_REGS(CLEAR_REGISTER)
308 #undef CLEAR_REGISTER
309
310 // Keep local raw pointers to keep instruction sequences small below.
311 auto* local_stack = GetStack();
312 auto* local_scanner = scanner.get();
313
314 // Moves |local_scanner->needle()| into a callee-saved register, leaving the
315 // callee-saved register as the only register referencing the needle.
316 // (Ignoring implementation-dependent dirty registers/stack.)
317 #define KEEP_ALIVE_FROM_CALLEE_SAVED(reg) \
318 local_scanner->Reset(); \
319 MoveInto##reg(local_stack, local_scanner); \
320 EXPECT_TRUE(local_scanner->found()) \
321 << "pointer in callee-saved register not found. register: " << #reg \
322 << std::endl;
323
324 FOR_ALL_CALLEE_SAVED_REGS(KEEP_ALIVE_FROM_CALLEE_SAVED)
325 #undef KEEP_ALIVE_FROM_CALLEE_SAVED
326 #undef FOR_ALL_CALLEE_SAVED_REGS
327 }
328
329 #endif // defined(__clang__) && defined(ARCH_CPU_X86_64) && !BUILDFLAG(IS_WIN)
330
331 #if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86) || defined(ARCH_CPU_X86_64))
332 class CheckStackAlignmentVisitor final : public StackVisitor {
333 public:
VisitStack(uintptr_t *,uintptr_t *)334 void VisitStack(uintptr_t*, uintptr_t*) final {
335 // Check that the stack doesn't get misaligned by asm trampolines.
336 float f[4] = {0.};
337 [[maybe_unused]] volatile auto xmm = ::_mm_load_ps(f);
338 }
339 };
340
TEST_F(PartitionAllocStackTest,StackAlignment)341 TEST_F(PartitionAllocStackTest, StackAlignment) {
342 auto checker = std::make_unique<CheckStackAlignmentVisitor>();
343 GetStack()->IteratePointers(checker.get());
344 }
345 #endif // BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86) ||
346 // defined(ARCH_CPU_X86_64))
347
348 } // namespace partition_alloc::internal
349
350 #endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
351