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