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