• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/memory/raw_ptr_asan_service.h"
11 
12 #if PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
13 
14 #include <sanitizer/allocator_interface.h>
15 #include <sanitizer/asan_interface.h>
16 #include <stdarg.h>
17 #include <string.h>
18 
19 #include "base/check_op.h"
20 #include "base/compiler_specific.h"
21 #include "base/debug/asan_service.h"
22 #include "base/immediate_crash.h"
23 #include "base/logging.h"
24 #include "base/memory/raw_ptr.h"
25 #include "base/memory/raw_ptr_asan_bound_arg_tracker.h"
26 #include "base/memory/raw_ptr_asan_hooks.h"
27 #include "base/process/process.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/task/thread_pool/thread_group.h"
30 
31 namespace base {
32 
33 RawPtrAsanService RawPtrAsanService::instance_;
34 
35 namespace {
36 
37 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_mapping.h#L154
38 constexpr size_t kShadowScale = 3;
39 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_allocator.cpp#L143
40 constexpr size_t kChunkHeaderSize = 16;
41 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_internal.h#L138
42 constexpr uint8_t kAsanHeapLeftRedzoneMagic = 0xfa;
43 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_internal.h#L145
44 constexpr uint8_t kAsanUserPoisonedMemoryMagic = 0xf7;
45 
46 // Intentionally use thread-local-storage here. Making this sequence-local
47 // doesn't prevent sharing of PendingReport contents between unrelated tasks, so
48 // we keep this at a lower-level and avoid introducing additional assumptions
49 // about Chrome's sequence model.
50 constinit thread_local RawPtrAsanService::PendingReport pending_report;
51 
52 }  // namespace
53 
54 // Mark the first eight bytes of every allocation's header as "user poisoned".
55 // This allows us to filter out allocations made before BRP-ASan is activated.
56 // The change shouldn't reduce the regular ASan coverage.
57 
58 // static
59 NO_SANITIZE("address")
MallocHook(const volatile void * ptr,size_t size)60 void RawPtrAsanService::MallocHook(const volatile void* ptr, size_t size) {
61   uint8_t* header =
62       static_cast<uint8_t*>(const_cast<void*>(ptr)) - kChunkHeaderSize;
63   *RawPtrAsanService::GetInstance().GetShadow(header) =
64       kAsanUserPoisonedMemoryMagic;
65 }
66 
67 NO_SANITIZE("address")
IsSupportedAllocation(void * allocation_start) const68 bool RawPtrAsanService::IsSupportedAllocation(void* allocation_start) const {
69   uint8_t* header = static_cast<uint8_t*>(allocation_start) - kChunkHeaderSize;
70   return *GetShadow(header) == kAsanUserPoisonedMemoryMagic;
71 }
72 
73 NO_SANITIZE("address")
Configure(EnableDereferenceCheck enable_dereference_check,EnableExtractionCheck enable_extraction_check,EnableInstantiationCheck enable_instantiation_check)74 void RawPtrAsanService::Configure(
75     EnableDereferenceCheck enable_dereference_check,
76     EnableExtractionCheck enable_extraction_check,
77     EnableInstantiationCheck enable_instantiation_check) {
78   CHECK_EQ(mode_, Mode::kUninitialized);
79 
80   Mode new_mode = enable_dereference_check || enable_extraction_check ||
81                           enable_instantiation_check
82                       ? Mode::kEnabled
83                       : Mode::kDisabled;
84   if (new_mode == Mode::kEnabled) {
85     // The constants we use aren't directly exposed by the API, so
86     // validate them at runtime as carefully as possible.
87     size_t shadow_scale;
88     __asan_get_shadow_mapping(&shadow_scale, &shadow_offset_);
89     CHECK_EQ(shadow_scale, kShadowScale);
90 
91     uint8_t* dummy_alloc = new uint8_t;
92     CHECK_EQ(*GetShadow(dummy_alloc - kChunkHeaderSize),
93              kAsanHeapLeftRedzoneMagic);
94 
95     __asan_poison_memory_region(dummy_alloc, 1);
96     CHECK_EQ(*GetShadow(dummy_alloc), kAsanUserPoisonedMemoryMagic);
97     delete dummy_alloc;
98 
99     __sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook);
100     debug::AsanService::GetInstance()->AddErrorCallback(ErrorReportCallback);
101     internal::InstallRawPtrHooks(base::internal::GetRawPtrAsanHooks());
102 
103     is_dereference_check_enabled_ = !!enable_dereference_check;
104     is_extraction_check_enabled_ = !!enable_extraction_check;
105     is_instantiation_check_enabled_ = !!enable_instantiation_check;
106   }
107 
108   mode_ = new_mode;
109 }
110 
GetShadow(void * ptr) const111 uint8_t* RawPtrAsanService::GetShadow(void* ptr) const {
112   return reinterpret_cast<uint8_t*>(
113       (reinterpret_cast<uintptr_t>(ptr) >> kShadowScale) + shadow_offset_);
114 }
115 
116 // static
SetPendingReport(ReportType type,const volatile void * ptr)117 void RawPtrAsanService::SetPendingReport(ReportType type,
118                                          const volatile void* ptr) {
119   // The actual ASan crash may occur at an offset from the pointer passed
120   // here, so track the whole region.
121   void* region_base;
122   size_t region_size;
123   __asan_locate_address(const_cast<void*>(ptr), nullptr, 0, &region_base,
124                         &region_size);
125 
126   pending_report = {type, reinterpret_cast<uintptr_t>(region_base),
127                     region_size};
128 }
129 
130 namespace {
131 enum class ProtectionStatus {
132   kNotProtected,
133   kManualAnalysisRequired,
134   kProtected,
135 };
136 
ProtectionStatusToString(ProtectionStatus status)137 const char* ProtectionStatusToString(ProtectionStatus status) {
138   switch (status) {
139     case ProtectionStatus::kNotProtected:
140       return "NOT PROTECTED";
141     case ProtectionStatus::kManualAnalysisRequired:
142       return "MANUAL ANALYSIS REQUIRED";
143     case ProtectionStatus::kProtected:
144       return "PROTECTED";
145   }
146 }
147 
148 // ASan doesn't have an API to get the current thread's identifier.
149 // We have to create a dummy allocation to determine it.
GetCurrentThreadId()150 int GetCurrentThreadId() {
151   int* dummy = new int;
152   int id = -1;
153   __asan_get_alloc_stack(dummy, nullptr, 0, &id);
154   delete dummy;
155   return id;
156 }
157 }  // namespace
158 
159 // static
ErrorReportCallback(const char * report,bool *)160 void RawPtrAsanService::ErrorReportCallback(const char* report, bool*) {
161   if (strcmp(__asan_get_report_description(), "heap-use-after-free") != 0) {
162     return;
163   }
164 
165   struct {
166     ProtectionStatus protection_status;
167     const char* crash_details;
168     const char* protection_details;
169   } crash_info;
170 
171   uintptr_t ptr = reinterpret_cast<uintptr_t>(__asan_get_report_address());
172   uintptr_t bound_arg_ptr = RawPtrAsanBoundArgTracker::GetProtectedArgPtr(ptr);
173   if (pending_report.allocation_base <= ptr &&
174       ptr < pending_report.allocation_base + pending_report.allocation_size) {
175     bool is_supported_allocation =
176         RawPtrAsanService::GetInstance().IsSupportedAllocation(
177             reinterpret_cast<void*>(pending_report.allocation_base));
178     switch (pending_report.type) {
179       case ReportType::kDereference: {
180         if (is_supported_allocation) {
181           crash_info = {ProtectionStatus::kProtected,
182                         "This crash occurred while a raw_ptr<T> object "
183                         "containing a dangling pointer was being dereferenced.",
184                         "MiraclePtr is expected to make this crash "
185                         "non-exploitable once fully enabled."};
186         } else {
187           crash_info = {ProtectionStatus::kNotProtected,
188                         "This crash occurred while accessing a region that was "
189                         "allocated before MiraclePtr was activated.",
190                         "This crash is still exploitable with MiraclePtr."};
191         }
192         break;
193       }
194       case ReportType::kExtraction: {
195         if (is_supported_allocation && bound_arg_ptr) {
196           crash_info = {
197               ProtectionStatus::kProtected,
198               "This crash occurred inside a callback where a raw_ptr<T> "
199               "pointing to the same region was bound to one of the arguments.",
200               "MiraclePtr is expected to make this crash non-exploitable once "
201               "fully enabled."};
202         } else if (is_supported_allocation) {
203           crash_info = {
204               ProtectionStatus::kManualAnalysisRequired,
205               "A pointer to the same region was extracted from a raw_ptr<T> "
206               "object prior to this crash.",
207               "To determine the protection status, enable extraction warnings "
208               "and check whether the raw_ptr<T> object can be destroyed or "
209               "overwritten between the extraction and use."};
210         } else {
211           crash_info = {ProtectionStatus::kNotProtected,
212                         "This crash occurred while accessing a region that was "
213                         "allocated before MiraclePtr was activated.",
214                         "This crash is still exploitable with MiraclePtr."};
215         }
216         break;
217       }
218       case ReportType::kInstantiation: {
219         crash_info = {ProtectionStatus::kNotProtected,
220                       "A pointer to an already freed region was assigned to a "
221                       "raw_ptr<T> object, which may lead to memory corruption.",
222                       "This crash is still exploitable with MiraclePtr."};
223       }
224     }
225   } else if (bound_arg_ptr) {
226     // Note - this branch comes second to avoid hiding invalid instantiations,
227     // as we still consider it to be an error to instantiate a raw_ptr<T> from
228     // an invalid T* even if that T* is guaranteed to be quarantined.
229     bool is_supported_allocation =
230         RawPtrAsanService::GetInstance().IsSupportedAllocation(
231             reinterpret_cast<void*>(bound_arg_ptr));
232     if (is_supported_allocation) {
233       crash_info = {
234           ProtectionStatus::kProtected,
235           "This crash occurred inside a callback where a raw_ptr<T> pointing "
236           "to the same region was bound to one of the arguments.",
237           "MiraclePtr is expected to make this crash non-exploitable once "
238           "fully enabled."};
239     } else {
240       crash_info = {ProtectionStatus::kNotProtected,
241                     "This crash occurred while accessing a region that was "
242                     "allocated before MiraclePtr was activated.",
243                     "This crash is still exploitable with MiraclePtr."};
244     }
245   } else {
246     crash_info = {
247         ProtectionStatus::kNotProtected,
248         "No raw_ptr<T> access to this region was detected prior to this crash.",
249         "This crash is still exploitable with MiraclePtr."};
250   }
251 
252   // The race condition check below may override the protection status.
253   if (crash_info.protection_status != ProtectionStatus::kNotProtected) {
254     int free_thread_id = -1;
255     __asan_get_free_stack(reinterpret_cast<void*>(ptr), nullptr, 0,
256                           &free_thread_id);
257     if (free_thread_id != GetCurrentThreadId()) {
258       crash_info.protection_status = ProtectionStatus::kManualAnalysisRequired;
259       crash_info.protection_details =
260           "The \"use\" and \"free\" threads don't match. This crash is likely "
261           "to have been caused by a race condition that is mislabeled as a "
262           "use-after-free. Make sure that the \"free\" is sequenced after the "
263           "\"use\" (e.g. both are on the same sequence, or the \"free\" is in "
264           "a task posted after the \"use\"). Otherwise, the crash is still "
265           "exploitable with MiraclePtr.";
266     } else if (internal::ThreadGroup::CurrentThreadHasGroup()) {
267       // We need to be especially careful with ThreadPool threads. Otherwise,
268       // we might miss false-positives where the "use" and "free" happen on
269       // different sequences but the same thread by chance.
270       crash_info.protection_status = ProtectionStatus::kManualAnalysisRequired;
271       crash_info.protection_details =
272           "This crash occurred in the thread pool. The sequence which invoked "
273           "the \"free\" is unknown, so the crash may have been caused by a "
274           "race condition that is mislabeled as a use-after-free. Make sure "
275           "that the \"free\" is sequenced after the \"use\" (e.g. both are on "
276           "the same sequence, or the \"free\" is in a task posted after the "
277           "\"use\"). Otherwise, the crash is still exploitable with "
278           "MiraclePtr.";
279     }
280   }
281 
282   debug::AsanService::GetInstance()->Log(
283       "\nMiraclePtr Status: %s\n%s\n%s\n"
284       "Refer to "
285       "https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
286       "raw_ptr.md for details.",
287       ProtectionStatusToString(crash_info.protection_status),
288       crash_info.crash_details, crash_info.protection_details);
289 }
290 
291 namespace {
292 enum class MessageLevel {
293   kWarning,
294   kError,
295 };
296 
LevelToString(MessageLevel level)297 const char* LevelToString(MessageLevel level) {
298   switch (level) {
299     case MessageLevel::kWarning:
300       return "WARNING";
301     case MessageLevel::kError:
302       return "ERROR";
303   }
304 }
305 
306 // Prints AddressSanitizer-like custom error messages.
Log(MessageLevel level,uintptr_t address,const char * type,const char * description)307 void Log(MessageLevel level,
308          uintptr_t address,
309          const char* type,
310          const char* description) {
311 #if __has_builtin(__builtin_extract_return_addr) && \
312     __has_builtin(__builtin_return_address)
313   void* pc = __builtin_extract_return_addr(__builtin_return_address(0));
314 #else
315   void* pc = nullptr;
316 #endif
317 
318 #if __has_builtin(__builtin_frame_address)
319   void* bp = __builtin_frame_address(0);
320 #else
321   void* bp = nullptr;
322 #endif
323 
324   void* local_stack;
325   void* sp = &local_stack;
326 
327   debug::AsanService::GetInstance()->Log(
328       "=================================================================\n"
329       "==%d==%s: MiraclePtr: %s on address %p at pc %p bp %p sp %p",
330       Process::Current().Pid(), LevelToString(level), type, address, pc, bp,
331       sp);
332   __sanitizer_print_stack_trace();
333   __asan_describe_address(reinterpret_cast<void*>(address));
334   debug::AsanService::GetInstance()->Log(
335       "%s\n"
336       "=================================================================",
337       description);
338 }
339 }  // namespace
340 
WarnOnDanglingExtraction(const volatile void * ptr) const341 void RawPtrAsanService::WarnOnDanglingExtraction(
342     const volatile void* ptr) const {
343   Log(MessageLevel::kWarning, reinterpret_cast<uintptr_t>(ptr),
344       "dangling-pointer-extraction",
345       "A regular ASan report will follow if the extracted pointer is "
346       "dereferenced later.\n"
347       "Otherwise, it is still likely a bug to rely on the address of an "
348       "already freed allocation.\n"
349       "Refer to "
350       "https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
351       "raw_ptr.md for details.");
352 }
353 
CrashOnDanglingInstantiation(const volatile void * ptr) const354 void RawPtrAsanService::CrashOnDanglingInstantiation(
355     const volatile void* ptr) const {
356   Log(MessageLevel::kError, reinterpret_cast<uintptr_t>(ptr),
357       "dangling-pointer-instantiation",
358       "This crash occurred due to an attempt to assign a dangling pointer to a "
359       "raw_ptr<T> variable, which might lead to use-after-free.\n"
360       "Note that this report might be a false positive if at the moment of the "
361       "crash another raw_ptr<T> is guaranteed to keep the allocation alive.\n"
362       "Refer to "
363       "https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
364       "raw_ptr.md for details.");
365   base::ImmediateCrash();
366 }
367 
368 }  // namespace base
369 
370 #endif  // PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
371