1 : // Copyright 2012 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/agent/asan/asan_runtime.h"
16 :
17 : #include "base/bind.h"
18 : #include "base/command_line.h"
19 : #include "base/environment.h"
20 : #include "base/logging.h"
21 : #include "base/string_number_conversions.h"
22 : #include "base/utf_string_conversions.h"
23 : #include "base/strings/string_tokenizer.h"
24 : #include "base/strings/sys_string_conversions.h"
25 : #include "base/win/pe_image.h"
26 : #include "base/win/wrapped_window_proc.h"
27 : #include "syzygy/agent/asan/asan_logger.h"
28 : #include "syzygy/agent/asan/asan_shadow.h"
29 : #include "syzygy/agent/asan/stack_capture_cache.h"
30 : #include "syzygy/trace/client/client_utils.h"
31 : #include "syzygy/trace/protocol/call_trace_defs.h"
32 :
33 : namespace agent {
34 : namespace asan {
35 :
36 : namespace {
37 :
38 : using agent::asan::AsanLogger;
39 : using agent::asan::HeapProxy;
40 : using agent::asan::StackCaptureCache;
41 : using base::win::WinProcExceptionFilter;
42 :
43 : typedef void (__cdecl * SetCrashKeyValueFuncPtr)(const char*, const char*);
44 :
45 : // The default error handler. It is expected that this will be bound in a
46 : // callback in the ASAN runtime.
47 : // @param context The context when the error has been reported.
48 : // @param error_info The information about this error.
49 i : void DefaultErrorHandler(CONTEXT* context, AsanErrorInfo* error_info) {
50 i : DCHECK(context != NULL);
51 i : DCHECK(error_info != NULL);
52 :
53 : ULONG_PTR arguments[] = {
54 i : reinterpret_cast<ULONG_PTR>(context),
55 : reinterpret_cast<ULONG_PTR>(error_info)
56 i : };
57 :
58 i : ::DebugBreak();
59 : ::RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED,
60 : 0,
61 : ARRAYSIZE(arguments),
62 i : &arguments[0]);
63 i : }
64 :
65 : // Returns the breakpad crash reporting functions if breakpad is enabled for
66 : // the current executable.
67 : //
68 : // @param crash_func A pointer to a crash reporting function will be returned
69 : // here, or NULL.
70 : // @param key_value_func A pointer to a function to set additional key/value
71 : // attributes for the crash before calling crash_func, or NULL. This may
72 : // return NULL even if crash_func returns non-NULL.
73 : //
74 : // If we're running in the context of a breakpad enabled binary we can
75 : // report errors directly via that breakpad entry-point. This allows us
76 : // to report the exact context of the error without including the asan_rtl
77 : // in crash context, depending on where and when we capture the context.
78 : void GetBreakpadFunctions(WinProcExceptionFilter* crash_func,
79 E : SetCrashKeyValueFuncPtr* key_value_func ) {
80 E : DCHECK(crash_func != NULL);
81 E : DCHECK(key_value_func != NULL);
82 :
83 : // The named entry-point exposed to report a crash.
84 : static const char kCrashHandlerSymbol[] = "CrashForException";
85 :
86 : // The named entry-point exposed to annotate a crash with a key/value pair.
87 : static const char kSetCrashKeyValueSymbol[] = "SetCrashKeyValuePair";
88 :
89 : // Get a handle to the current executable image.
90 E : HMODULE exe_hmodule = ::GetModuleHandle(NULL);
91 :
92 : // Lookup the crash handler symbol.
93 : *crash_func = reinterpret_cast<WinProcExceptionFilter>(
94 E : ::GetProcAddress(exe_hmodule, kCrashHandlerSymbol));
95 :
96 : // Lookup the crash annotation symbol.
97 : *key_value_func = reinterpret_cast<SetCrashKeyValueFuncPtr>(
98 E : ::GetProcAddress(exe_hmodule, kSetCrashKeyValueSymbol));
99 E : }
100 :
101 : // The breakpad error handler. It is expected that this will be bound in a
102 : // callback in the ASAN runtime.
103 : // @param func_ptr A pointer to the breakpad error reporting function. This
104 : // will be used to perform the error reporting.
105 : // @param crash_func_ptr A pointer to the breakpad crash reporting function.
106 : // @param key_value_func A pointer to a function to set additional key/value
107 : // attributes for the crash before calling crash_func. For backwards
108 : // compatibility, with older breakpad clients, this parameter is optional
109 : // (it may be NULL).
110 : // @param context The context when the error has been reported.
111 : // @param error_info The information about this error.
112 : void BreakpadErrorHandler(WinProcExceptionFilter crash_func_ptr,
113 : SetCrashKeyValueFuncPtr set_key_value_func_ptr,
114 : CONTEXT* context,
115 i : AsanErrorInfo* error_info) {
116 i : DCHECK(crash_func_ptr != NULL);
117 i : DCHECK(context != NULL);
118 i : DCHECK(error_info != NULL);
119 :
120 i : if (set_key_value_func_ptr != NULL) {
121 : set_key_value_func_ptr(
122 : "asan-error-type",
123 i : HeapProxy::AccessTypeToStr(error_info->error_type));
124 i : if (error_info->shadow_info[0] != '\0') {
125 : set_key_value_func_ptr(
126 i : "asan-error-message", error_info->shadow_info);
127 : }
128 : }
129 :
130 i : EXCEPTION_RECORD exception = {};
131 i : exception.ExceptionCode = EXCEPTION_ARRAY_BOUNDS_EXCEEDED;
132 i : exception.ExceptionAddress = reinterpret_cast<PVOID>(context->Eip);
133 i : exception.NumberParameters = 2;
134 i : exception.ExceptionInformation[0] = reinterpret_cast<ULONG_PTR>(context);
135 i : exception.ExceptionInformation[1] = reinterpret_cast<ULONG_PTR>(error_info);
136 :
137 i : EXCEPTION_POINTERS pointers = { &exception, context };
138 i : crash_func_ptr(&pointers);
139 i : NOTREACHED();
140 i : }
141 :
142 : // Try to update the value of a size_t variable from a command-line.
143 : // @param cmd_line The command line who might contain a given parameter.
144 : // @param param_name The parameter that we want to read.
145 : // @param value Will receive the value of the parameter if it's present.
146 : // @returns true on success, false otherwise.
147 : bool UpdateSizetFromCommandLine(const CommandLine& cmd_line,
148 : const std::string& param_name,
149 E : size_t* value) {
150 E : DCHECK(value != NULL);
151 E : if (!cmd_line.HasSwitch(param_name))
152 E : return true;
153 E : std::string value_str = cmd_line.GetSwitchValueASCII(param_name);
154 E : size_t new_value = 0;
155 E : if (!base::StringToSizeT(value_str, &new_value))
156 i : return false;
157 E : *value = new_value;
158 :
159 E : return true;
160 E : }
161 :
162 : // Try to update the value of an array of ignored stack ids from a command-line.
163 : // We expect the values to be in hexadecimal format and separated by a
164 : // semi-colon.
165 : // @param cmd_line The command line to parse.
166 : // @param param_name The parameter that we want to read.
167 : // @param values Will receive the set of parsed values.
168 : // @returns true on success, false otherwise.
169 : bool ReadIgnoredStackIdsFromCommandLine(const CommandLine& cmd_line,
170 : const std::string& param_name,
171 E : AsanRuntime::StackIdSet* values) {
172 E : DCHECK(values != NULL);
173 E : if (!cmd_line.HasSwitch(param_name))
174 E : return true;
175 E : std::string value_str = cmd_line.GetSwitchValueASCII(param_name);
176 E : base::StringTokenizer string_tokenizer(value_str, ";");
177 E : while (string_tokenizer.GetNext()) {
178 E : int64 new_value = 0;
179 E : if (!base::HexStringToInt64(string_tokenizer.token(), &new_value))
180 i : return false;
181 E : values->insert(static_cast<StackCapture::StackId>(new_value));
182 E : }
183 E : return true;
184 E : }
185 :
186 : // A helper function to find if an intrusive list contains a given entry.
187 : // @param list The list in which we want to look for the entry.
188 : // @param item The entry we want to look for.
189 : // @returns true if the list contains this entry, false otherwise.
190 E : bool HeapListContainsEntry(const LIST_ENTRY* list, const LIST_ENTRY* item) {
191 E : LIST_ENTRY* current = list->Flink;
192 E : while (current != NULL) {
193 E : LIST_ENTRY* next_item = NULL;
194 E : if (current->Flink != list) {
195 E : next_item = current->Flink;
196 : }
197 :
198 E : if (current == item) {
199 E : return true;
200 : }
201 :
202 E : current = next_item;
203 E : }
204 i : return false;
205 E : }
206 :
207 : // Check if the current process is large address aware.
208 : // @returns true if it is, false otherwise.
209 E : bool CurrentProcessIsLargeAddressAware() {
210 E : const base::win::PEImage image(::GetModuleHandle(NULL));
211 :
212 : bool process_is_large_address_aware =
213 : (image.GetNTHeaders()->FileHeader.Characteristics &
214 E : IMAGE_FILE_LARGE_ADDRESS_AWARE) != 0;
215 :
216 E : return process_is_large_address_aware;
217 E : }
218 :
219 : } // namespace
220 :
221 : const char AsanRuntime::kSyzyAsanEnvVar[] = "SYZYGY_ASAN_OPTIONS";
222 :
223 : const char AsanRuntime::kBottomFramesToSkip[] =
224 : "bottom_frames_to_skip";
225 : const char AsanRuntime::kCompressionReportingPeriod[] =
226 : "compression_reporting_period";
227 : const char AsanRuntime::kExitOnFailure[] = "exit_on_failure";
228 : const char AsanRuntime::kIgnoredStackIds[] = "ignored_stack_ids";
229 : const char AsanRuntime::kMaxNumberOfFrames[] = "max_num_frames";
230 : const char AsanRuntime::kMiniDumpOnFailure[] = "minidump_on_failure";
231 : const char AsanRuntime::kNoLogAsText[] = "no_log_as_text";
232 : const char AsanRuntime::kQuarantineSize[] = "quarantine_size";
233 : const wchar_t AsanRuntime::kSyzyAsanDll[] = L"asan_rtl.dll";
234 :
235 : AsanRuntime::AsanRuntime()
236 : : logger_(NULL), stack_cache_(NULL), asan_error_callback_(), flags_(),
237 E : heap_proxy_dlist_lock_(), heap_proxy_dlist_() {
238 E : }
239 :
240 E : AsanRuntime::~AsanRuntime() {
241 E : }
242 :
243 E : void AsanRuntime::SetUp(const std::wstring& flags_command_line) {
244 : // Ensure that the current process is not large address aware. It shouldn't be
245 : // because the shadow memory assume that the process will only be able to use
246 : // 2GB of address space.
247 E : CHECK(!CurrentProcessIsLargeAddressAware());
248 :
249 : // Initialize the command-line structures. This is needed so that
250 : // SetUpLogger() can include the command-line in the message announcing
251 : // this process. Note: this is mostly for debugging purposes.
252 E : CommandLine::Init(0, NULL);
253 :
254 E : Shadow::SetUp();
255 :
256 E : InitializeListHead(&heap_proxy_dlist_);
257 :
258 : // Setup the "global" state.
259 E : HeapProxy::Init();
260 E : StackCapture::Init();
261 E : StackCaptureCache::Init();
262 E : SetUpLogger();
263 E : SetUpStackCache();
264 E : if (!ParseFlagsFromString(flags_command_line)) {
265 i : LOG(ERROR) << "Unable to parse the flags from the input string (\""
266 : << flags_command_line.c_str() << "\").";
267 : }
268 : // Propagates the flags values to the different modules.
269 E : PropagateFlagsValues();
270 :
271 : // Register the error reporting callback to use if/when an ASAN error is
272 : // detected. If we're able to resolve a breakpad error reporting function
273 : // then use that; otherwise, fall back to the default error handler.
274 E : WinProcExceptionFilter crash_func_ptr = NULL;
275 E : SetCrashKeyValueFuncPtr set_key_value_func_ptr = NULL;
276 E : GetBreakpadFunctions(&crash_func_ptr, &set_key_value_func_ptr);
277 E : if (crash_func_ptr != NULL) {
278 i : LOG(INFO) << "SyzyASAN: Using Breakpad for error reporting.";
279 : SetErrorCallBack(base::Bind(
280 i : &BreakpadErrorHandler, crash_func_ptr, set_key_value_func_ptr));
281 i : } else {
282 E : LOG(INFO) << "SyzyASAN: Using default error reporting handler.";
283 E : SetErrorCallBack(base::Bind(&DefaultErrorHandler));
284 : }
285 E : }
286 :
287 E : void AsanRuntime::TearDown() {
288 E : TearDownStackCache();
289 E : TearDownLogger();
290 E : DCHECK(asan_error_callback_.is_null() == FALSE);
291 E : asan_error_callback_.Reset();
292 E : Shadow::TearDown();
293 : // In principle, we should also check that all the heaps have been destroyed
294 : // but this is not guaranteed to be the case in Chrome, so the heap list may
295 : // not be empty here.
296 E : }
297 :
298 E : void AsanRuntime::OnError(CONTEXT* context, AsanErrorInfo* error_info) {
299 E : DCHECK(context != NULL);
300 :
301 E : if (flags_.minidump_on_failure) {
302 i : DCHECK(logger_.get() != NULL);
303 i : logger_->SaveMiniDump(context, error_info);
304 : }
305 :
306 E : if (flags_.exit_on_failure) {
307 E : DCHECK(logger_.get() != NULL);
308 E : logger_->Stop();
309 E : exit(EXIT_FAILURE);
310 : }
311 :
312 : // Call the callback to handle this error.
313 E : DCHECK(!asan_error_callback_.is_null());
314 E : asan_error_callback_.Run(context, error_info);
315 E : }
316 :
317 E : void AsanRuntime::SetErrorCallBack(const AsanOnErrorCallBack& callback) {
318 E : asan_error_callback_ = callback;
319 E : }
320 :
321 E : void AsanRuntime::SetUpLogger() {
322 : // Setup variables we're going to use.
323 E : scoped_ptr<base::Environment> env(base::Environment::Create());
324 E : scoped_ptr<AsanLogger> client(new AsanLogger);
325 E : CHECK(env.get() != NULL);
326 E : CHECK(client.get() != NULL);
327 :
328 : // Initialize the client.
329 : client->set_instance_id(
330 E : UTF8ToWide(trace::client::GetInstanceIdForThisModule()));
331 E : client->Init();
332 :
333 : // Register the client singleton instance.
334 E : logger_.reset(client.release());
335 E : }
336 :
337 E : void AsanRuntime::TearDownLogger() {
338 E : logger_.reset();
339 E : }
340 :
341 E : void AsanRuntime::SetUpStackCache() {
342 E : DCHECK(stack_cache_.get() == NULL);
343 E : DCHECK(logger_.get() != NULL);
344 E : stack_cache_.reset(new StackCaptureCache(logger_.get()));
345 E : }
346 :
347 E : void AsanRuntime::TearDownStackCache() {
348 E : DCHECK(stack_cache_.get() != NULL);
349 E : stack_cache_->LogStatistics();
350 E : stack_cache_.reset();
351 E : }
352 :
353 E : bool AsanRuntime::ParseFlagsFromString(std::wstring str) {
354 : // Prepends the flags with the agent name. We need to do this because the
355 : // command-line constructor expect the process name to be the first value of
356 : // the command-line string.
357 : // Start by inserting a space at the beginning of the flags to separate the
358 : // flags from the agent name.
359 E : str.insert(0, L" ");
360 : // Insert the agent name.
361 E : str.insert(0, kSyzyAsanDll);
362 :
363 E : CommandLine cmd_line = CommandLine::FromString(str);
364 :
365 : // Parse the quarantine size flag.
366 E : flags_.quarantine_size = HeapProxy::default_quarantine_max_size();
367 : if (!UpdateSizetFromCommandLine(cmd_line, kQuarantineSize,
368 E : &flags_.quarantine_size)) {
369 i : LOG(ERROR) << "Unable to read " << kQuarantineSize << " from the argument "
370 : << "list.";
371 i : return false;
372 : }
373 :
374 : // Parse the reporting period flag.
375 : flags_.reporting_period =
376 E : StackCaptureCache::GetDefaultCompressionReportingPeriod();
377 : if (!UpdateSizetFromCommandLine(cmd_line, kCompressionReportingPeriod,
378 E : &flags_.reporting_period)) {
379 i : LOG(ERROR) << "Unable to read " << kCompressionReportingPeriod
380 : << " from the argument list.";
381 i : return false;
382 : }
383 :
384 : // Parse the bottom frames to skip flag.
385 E : flags_.bottom_frames_to_skip = StackCapture::bottom_frames_to_skip();
386 : if (!UpdateSizetFromCommandLine(cmd_line, kBottomFramesToSkip,
387 E : &flags_.bottom_frames_to_skip)) {
388 i : LOG(ERROR) << "Unable to read " << kBottomFramesToSkip << " from the "
389 : << "argument list.";
390 i : return false;
391 : }
392 :
393 : // Parse the max number of frames flag.
394 E : flags_.max_num_frames = stack_cache_->max_num_frames();
395 : if (!UpdateSizetFromCommandLine(cmd_line, kMaxNumberOfFrames,
396 E : &flags_.max_num_frames)) {
397 i : LOG(ERROR) << "Unable to read " << kMaxNumberOfFrames << " from the "
398 : << "argument list.";
399 i : return false;
400 : }
401 :
402 : // Parse the ignored stack ids.
403 : if (!ReadIgnoredStackIdsFromCommandLine(cmd_line, kIgnoredStackIds,
404 E : &flags_.ignored_stack_ids)) {
405 i : LOG(ERROR) << "Unable to read " << kIgnoredStackIds << " from the "
406 : << "argument list.";
407 i : return false;
408 : }
409 :
410 : // Parse the other (boolean) flags.
411 E : flags_.exit_on_failure = cmd_line.HasSwitch(kExitOnFailure);
412 E : flags_.minidump_on_failure = cmd_line.HasSwitch(kMiniDumpOnFailure);
413 E : flags_.log_as_text = !cmd_line.HasSwitch(kNoLogAsText);
414 :
415 E : return true;
416 E : }
417 :
418 E : bool AsanRuntime::GetAsanFlagsEnvVar(std::wstring* env_var_wstr) {
419 E : scoped_ptr<base::Environment> env(base::Environment::Create());
420 E : if (env.get() == NULL) {
421 i : LOG(ERROR) << "base::Environment::Create returned NULL.";
422 i : return false;
423 : }
424 :
425 : // If this fails, the environment variable simply does not exist.
426 E : std::string env_var_str;
427 E : if (!env->GetVar(kSyzyAsanEnvVar, &env_var_str)) {
428 E : return true;
429 : }
430 :
431 i : *env_var_wstr = base::SysUTF8ToWide(env_var_str);
432 :
433 i : return true;
434 E : }
435 :
436 E : void AsanRuntime::PropagateFlagsValues() const {
437 : // TODO(sebmarchand): Look into edit-free ways to expose new flags to the
438 : // different modules.
439 E : HeapProxy::set_default_quarantine_max_size(flags_.quarantine_size);
440 E : StackCapture::set_bottom_frames_to_skip(flags_.bottom_frames_to_skip);
441 E : StackCaptureCache::set_compression_reporting_period(flags_.reporting_period);
442 E : stack_cache_->set_max_num_frames(flags_.max_num_frames);
443 E : logger_->set_log_as_text(flags_.log_as_text);
444 E : logger_->set_minidump_on_failure(flags_.minidump_on_failure);
445 E : }
446 :
447 E : void AsanRuntime::set_flags(const AsanFlags* flags) {
448 E : DCHECK(flags != NULL);
449 E : flags_ = *flags;
450 E : }
451 :
452 E : void AsanRuntime::AddHeap(HeapProxy* heap) {
453 E : base::AutoLock lock(heap_proxy_dlist_lock_);
454 E : InsertTailList(&heap_proxy_dlist_, HeapProxy::ToListEntry(heap));
455 E : }
456 :
457 E : void AsanRuntime::RemoveHeap(HeapProxy* heap) {
458 E : base::AutoLock lock(heap_proxy_dlist_lock_);
459 : DCHECK(HeapListContainsEntry(&heap_proxy_dlist_,
460 E : HeapProxy::ToListEntry(heap)));
461 E : RemoveEntryList(HeapProxy::ToListEntry(heap));
462 E : }
463 :
464 : void AsanRuntime::ReportAsanErrorDetails(const void* addr,
465 : const CONTEXT& context,
466 : const StackCapture& stack,
467 : HeapProxy::AccessMode access_mode,
468 : size_t access_size,
469 E : AsanErrorInfo* bad_access_info) {
470 E : DCHECK(bad_access_info != NULL);
471 E : base::AutoLock lock(heap_proxy_dlist_lock_);
472 :
473 : // Checks if this is an access to an internal structure or if it's an access
474 : // in the upper region of the memory (over the 2 GB limit).
475 : if ((reinterpret_cast<size_t>(addr) & (1 << 31)) != 0 ||
476 E : Shadow::GetShadowMarkerForAddress(addr) == Shadow::kAsanMemoryByte) {
477 E : LIST_ENTRY* item = heap_proxy_dlist_.Flink;
478 E : CHECK(item != NULL);
479 E : HeapProxy* proxy = HeapProxy::FromListEntry(item);
480 E : CHECK(proxy != NULL);
481 E : bad_access_info->error_type = HeapProxy::WILD_ACCESS;
482 : proxy->ReportWildAccess(addr, context, stack, access_mode,
483 E : access_size);
484 E : return;
485 : }
486 :
487 : // Iterates over the HeapProxy list to find the memory block containing this
488 : // address. We expect that there is at least one heap proxy extant.
489 E : HeapProxy* proxy = NULL;
490 E : LIST_ENTRY* item = heap_proxy_dlist_.Flink;
491 E : CHECK(item != NULL);
492 E : while (item != NULL) {
493 E : LIST_ENTRY* next_item = NULL;
494 E : if (item->Flink != &heap_proxy_dlist_) {
495 i : next_item = item->Flink;
496 : }
497 :
498 E : proxy = HeapProxy::FromListEntry(item);
499 : if (proxy->OnBadAccess(addr,
500 : context,
501 : stack,
502 : access_mode,
503 : access_size,
504 E : bad_access_info)) {
505 E : break;
506 : }
507 :
508 i : item = next_item;
509 i : }
510 :
511 : // If item is NULL then we went through the list without finding the heap
512 : // from which this address was allocated. We can just reuse the logger of
513 : // the last heap proxy we saw to report an "unknown" error. As every poisoned
514 : // block of the shadow memory should point to a memory block or to an internal
515 : // structure this case should never happen.
516 E : if (item == NULL) {
517 i : NOTREACHED();
518 i : bad_access_info->error_type = HeapProxy::UNKNOWN_BAD_ACCESS;
519 i : CHECK(proxy != NULL);
520 i : proxy->ReportWildAccess(addr, context, stack, access_mode, access_size);
521 : }
522 E : }
523 :
524 : } // namespace asan
525 : } // namespace agent
|