def fReportCPUUsageBug(oExcessiveCPUUsageDetector): if bDebugOutput: print "@@@ Reporting excessive CPU usage bug..."; oCdbWrapper = oExcessiveCPUUsageDetector.oCdbWrapper; sBugTypeId = "CPUUsage"; sBugDescription = "The application was using %d%% CPU for %d seconds, which is considered excessive." % \ (oExcessiveCPUUsageDetector.nTotalCPUPercent, dxBugIdConfig["nExcessiveCPUUsageCheckInterval"]); sSecurityImpact = None; oCdbWrapper.oBugReport = cBugReport.foCreate(oCdbWrapper, sBugTypeId, sBugDescription, sSecurityImpact); if not oCdbWrapper.bCdbRunning: return; oCdbWrapper.fRemoveBreakpoint(oExcessiveCPUUsageDetector.uBugBreakpointId); oExcessiveCPUUsageDetector.uBugBreakpointId = None; if not oCdbWrapper.bCdbRunning: return;
def fReportCPUUsageBug(oExcessiveCPUUsageDetector): oCdbWrapper = oExcessiveCPUUsageDetector.oCdbWrapper oExcessiveCPUUsageDetector.oLock.acquire() try: uStackPointer = oCdbWrapper.fuGetValue("@$csp") if not oCdbWrapper.bCdbRunning: return uInstructionPointer = oCdbWrapper.fuGetValue("@$ip") if not oCdbWrapper.bCdbRunning: return # This is a sanity check: the instruction pointer should be equal to the address at which we set the breakpoint. assert uInstructionPointer == oExcessiveCPUUsageDetector.uNextBreakpointAddress, \ "Expected to hit breakpoint at 0x%X, but got 0x%X instead !?" % (oExcessiveCPUUsageDetector.uNextBreakpointAddress, uInstructionPointer) # The code we're expecting to return to may actually be *called* in recursive code. We can detect this by checking # if the stackpointer has increased or not. If not, we have not yet returned and will ignore this breakpoint. if uStackPointer < oExcessiveCPUUsageDetector.uLastStackPointer: oExcessiveCPUUsageDetector.fWormDebugOutput( "Ignored bug breakpoint at IP=%p, SP=%p: SP but must be >=%p", uInstructionPointer, uStackPointer, oExcessiveCPUUsageDetector.uLastStackPointer) if not oCdbWrapper.bCdbRunning: return return if bDebugOutput: print "@@@ Reporting excessive CPU usage bug..." oExcessiveCPUUsageDetector.fWormDebugOutput( "Bug breakpoint at IP=%p, SP=%p is hit, removing breakpoint and reporting bug...", uInstructionPointer, uStackPointer, ) # Remove the breakpoint oCdbWrapper.fRemoveBreakpoint( oExcessiveCPUUsageDetector.uBugBreakpointId) if not oCdbWrapper.bCdbRunning: return oExcessiveCPUUsageDetector.uBugBreakpointId = None # Report a bug sBugTypeId = "CPUUsage" sBugDescription = "The application was using %d%% CPU for %d seconds, which is considered excessive." % \ (oExcessiveCPUUsageDetector.nTotalCPUUsagePercent, dxBugIdConfig["nExcessiveCPUUsageCheckInterval"]) sSecurityImpact = None if not oCdbWrapper.bCdbRunning: return oCdbWrapper.oBugReport = cBugReport.foCreate( oCdbWrapper, sBugTypeId, sBugDescription, sSecurityImpact) if not oCdbWrapper.bCdbRunning: return oCdbWrapper.oBugReport.bRegistersRelevant = False finally: oExcessiveCPUUsageDetector.oLock.release()
def fReportCPUUsageBug(oExcessiveCPUUsageDetector): oCdbWrapper = oExcessiveCPUUsageDetector.oCdbWrapper; oExcessiveCPUUsageDetector.oLock.acquire(); try: oCdbWrapper.fSelectProcessAndThread(oExcessiveCPUUsageDetector.uProcessId, oExcessiveCPUUsageDetector.uThreadId); oWormProcess = oCdbWrapper.oCurrentProcess; uStackPointer = oWormProcess.fuGetValueForRegister("$csp", "Get stack pointer"); uInstructionPointer = oWormProcess.fuGetValueForRegister("$ip", "Get instruction pointer"); # This is a sanity check: the instruction pointer should be equal to the address at which we set the breakpoint. assert uInstructionPointer == oExcessiveCPUUsageDetector.uNextBreakpointAddress, \ "Expected to hit breakpoint at 0x%X, but got 0x%X instead !?" % (oExcessiveCPUUsageDetector.uNextBreakpointAddress, uInstructionPointer); # The code we're expecting to return to may actually be *called* in recursive code. We can detect this by checking # if the stackpointer has increased or not. If not, we have not yet returned and will ignore this breakpoint. if uStackPointer < oExcessiveCPUUsageDetector.uLastStackPointer: oExcessiveCPUUsageDetector.fWormDebugOutput( "Ignored bug breakpoint at IP=%p, SP=%p: SP but must be >=%p", uInstructionPointer, uStackPointer, oExcessiveCPUUsageDetector.uLastStackPointer ); return; if bDebugOutput: print "@@@ Reporting excessive CPU usage bug..."; oExcessiveCPUUsageDetector.fWormDebugOutput( "Bug breakpoint at IP=%p, SP=%p is hit, removing breakpoint and reporting bug...", uInstructionPointer, uStackPointer, ); # Remove the breakpoint oCdbWrapper.fRemoveBreakpoint(oExcessiveCPUUsageDetector.uBugBreakpointId); oExcessiveCPUUsageDetector.uBugBreakpointId = None; # Report a bug sBugTypeId = "CPUUsage"; sBugDescription = "The application was using %d%% CPU for %d seconds, which is considered excessive." % \ (oExcessiveCPUUsageDetector.nTotalCPUUsagePercent, dxConfig["nExcessiveCPUUsageCheckInterval"]); sSecurityImpact = None; oBugReport = cBugReport.foCreate(oWormProcess, sBugTypeId, sBugDescription, sSecurityImpact); oBugReport.bRegistersRelevant = False; oBugReport.fReport(oCdbWrapper); oCdbWrapper.bFatalBugDetected = True; finally: oExcessiveCPUUsageDetector.oLock.release();
def foDetectAndCreateBugReportForASan(oCdbWrapper, uExceptionCode): # Sample ASan outputs # |================================================================= # |==1796:1960==ERROR: AddressSanitizer: use-after-poison on address 0x0dd092c4 at pc 0x1aaa90d1 bp 0x0103b8dc sp 0x0103b8d0 # |READ of size 4 at 0x0dd092c4 thread T0 # |==1796:1960==WARNING: Failed to use and restart external symbolizer! # |==1796:1960==*** WARNING: Failed to initialize DbgHelp! *** # |==1796:1960==*** Most likely this means that the app is already *** # |==1796:1960==*** using DbgHelp, possibly with incompatible flags. *** # |==1796:1960==*** Due to technical reasons, symbolization might crash *** # |==1796:1960==*** or produce wrong results. *** # | #0 0x1aaa90d0 in blink::Element::setAttribute C:\b\c\b\win_asan_release\src\third_party\WebKit\Source\core\dom\Element.cpp:1326 # | #1 0x19dc7c9a in blink::V8Element::idAttributeSetterCallback C:\b\c\b\Win_ASan_Release\src\out\Release\gen\blink\bindings\core\v8\V8Element.cpp:2097 # | # |Address 0x0dd092c4 is a wild pointer. # |SUMMARY: AddressSanitizer: use-after-poison C:\b\c\b\win_asan_release\src\third_party\WebKit\Source\core\dom\Element.cpp:1326 in blink::Element::setAttribute # |Shadow bytes around the buggy address: # | 0x31ba1200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # | 0x31ba1210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # | 0x31ba1220: 00 00 00 00 00 00 04 f7 f7 f7 f7 f7 f7 f7 f7 f7 # | 0x31ba1230: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 # | 0x31ba1240: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 # |=>0x31ba1250: f7 f7 f7 00 00 00 00 04[f7]f7 f7 f7 f7 f7 f7 f7 # | 0x31ba1260: f7 f7 00 00 00 00 00 00 04 00 00 00 00 04 00 00 # | 0x31ba1270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # | 0x31ba1280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # | 0x31ba1290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # | 0x31ba12a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # |Shadow byte legend (one shadow byte represents 8 application bytes): # | Addressable: 00 # | Partially addressable: 01 02 03 04 05 06 07 # | Heap left redzone: fa # | Freed heap region: fd # | Stack left redzone: f1 # | Stack mid redzone: f2 # | Stack right redzone: f3 # | Stack after return: f5 # | Stack use after scope: f8 # | Global redzone: f9 # |(704.7a8): Break instruction exception - code 80000003 (first chance) # | Global init order: f6 # | Poisoned by user: f7 # | Container overflow: fc # | Array cookie: ac # | Intra object redzone: bb # | ASan internal: fe # | Left alloca redzone: ca # | Right alloca redzone: cb # |==1796:1960==ABORTING ############################################################################# # |================================================================= # |==4720:4264==ERROR: AddressSanitizer: global-buffer-overflow on address 0x21a74f3c at pc 0x1601151a bp 0x00efafdc sp 0x00efafd0 # |READ of size 4 at 0x21a74f3c thread T0 # |==4720:4264==WARNING: Failed to use and restart external symbolizer! # |==4720:4264==*** WARNING: Failed to initialize DbgHelp! *** # |==4720:4264==*** Most likely this means that the app is already *** # |==4720:4264==*** using DbgHelp, possibly with incompatible flags. *** # |==4720:4264==*** Due to technical reasons, symbolization might crash *** # |==4720:4264==*** or produce wrong results. *** # | #0 0x16011519 in blink::ElementData::Attributes C:\b\c\b\win_asan_release\src\third_party\WebKit\Source\core\dom\ElementData.h:200 # | #1 0x1a9a8bcd in blink::Element::setAttribute C:\b\c\b\win_asan_release\src\third_party\WebKit\Source\core\dom\Element.cpp:1322 # | #2 0x19cc7c9a in blink::V8Element::idAttributeSetterCallback C:\b\c\b\Win_ASan_Release\src\out\Release\gen\blink\bindings\core\v8\V8Element.cpp:2097 # | # |0x21a74f3c is located 36 bytes to the left of global variable 'WTF::g_global_empty16_bitStorage' defined in '../../third_party/WebKit/Source/platform/wtf/text/StringImpl.cpp:166:1' (0x21a74f60) of size 12 # |0x21a74f3c is located 16 bytes to the right of global variable 'WTF::g_global_emptyStorage' defined in '../../third_party/WebKit/Source/platform/wtf/text/StringImpl.cpp:165:1' (0x21a74f20) of size 12 # |SUMMARY: AddressSanitizer: global-buffer-overflow C:\b\c\b\win_asan_release\src\third_party\WebKit\Source\core\dom\ElementData.h:200 in blink::ElementData::Attributes # |Shadow bytes around the buggy address: # | 0x3434e990: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # | 0x3434e9a0: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # | 0x3434e9b0: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 # | 0x3434e9c0: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 00 04 f9 f9 # | 0x3434e9d0: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # |=>0x3434e9e0: f9 f9 f9 f9 00 04 f9[f9]f9 f9 f9 f9 00 04 f9 f9 # | 0x3434e9f0: f9 f9 f9 f9 00 00 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # | 0x3434ea00: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # | 0x3434ea10: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # | 0x3434ea20: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # | 0x3434ea30: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 # |Shadow byte legend (one shadow byte represents 8 application bytes): # | Addressable: 00 # | Partially addressable: 01 02 03 04 05 06 07 # | Heap left redzone: fa # | Freed heap region: fd # | Stack left redzone: f1 # | Stack mid redzone: f2 # | Stack right redzone: f3 # | Stack after return: f5 # | Stack use after scope: f8 # | Global redzone: f9 # | Global init order: f6 # | Poisoned by user: f7 # | Container overflow: fc # | Array cookie: ac # | Intra object redzone: bb # | ASan internal: fe # | Left alloca redzone: ca # | Right alloca redzone: cb # |==4720:4264==ABORTING ############################################################################## # |================================================================= # |==3012:3984==ERROR: AddressSanitizer: heap-use-after-free on address 0x0636d770 at pc 0x14e45df9 bp 0x00c9a7c8 sp 0x00c9a7bc # |READ of size 1 at 0x0636d770 thread T0 # | #0 0x14e45df8 in std::_Hash<std::_Uset_traits<device::BluetoothAdapter::Observer *,std::_Uhash_compare<device::BluetoothAdapter::Observer *,std::hash<device::BluetoothAdapter::Observer *>,std::equal_to<device::BluetoothAdapter::Observer *> >,std::allocator<device::BluetoothAdapter::Observer *>,0> >::equal_range c:\b\c\win_toolchain\vs_files\f53e4598951162bad6330f7a167486c7ae5db1e5\vc\include\xhash:636 # | #1 0x15142422 in std::_Hash<std::_Umap_traits<int,ui::AXPlatformNode *,std::_Uhash_compare<int,base_hash::hash<int>,std::equal_to<int> >,std::allocator<std::pair<const int,ui::AXPlatformNode *> >,0> >::erase c:\b\c\win_toolchain\vs_files\f53e4598951162bad6330f7a167486c7ae5db1e5\vc\include\xhash:563 # | #2 0x15147750 in ui::AXPlatformNodeWin::Destroy C:\b\c\b\win_asan_release\src\ui\accessibility\platform\ax_platform_node_win.cc:556 # | #3 0x10529055 in content::BrowserAccessibilityWin::~BrowserAccessibilityWin C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_win.h:29 # | #4 0x104c9fba in content::BrowserAccessibility::NativeReleaseReference C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility.cc:556 # | #5 0x1051014c in content::BrowserAccessibilityManager::OnNodeWillBeDeleted C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager.cc:1134 # | #6 0x15116137 in ui::AXTree::DestroyNodeAndSubtree C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:501 # | #7 0x15119afb in ui::AXTree::DestroySubtree C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:494 # | #8 0x1511a03b in ui::AXTree::UpdateNode C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:406 # | #9 0x15117cb9 in ui::AXTree::Unserialize C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:258 # | #10 0x1050c39b in content::BrowserAccessibilityManager::OnAccessibilityEvents C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager.cc:364 # | #11 0x10b436b5 in content::RenderFrameHostImpl::OnAccessibilityEvents C:\b\c\b\win_asan_release\src\content\browser\frame_host\render_frame_host_impl.cc:2388 # | #12 0x10b427af in IPC::MessageT<AccessibilityHostMsg_Events_Meta,std::tuple<std::vector<AccessibilityHostMsg_EventParams,std::allocator<AccessibilityHostMsg_EventParams> >,int,int>,void>::Dispatch<content::RenderFrameHostImpl,content::RenderFrameHostImpl,void,void (content::RenderFrameHostImpl::*)(const std::vector<AccessibilityHostMsg_EventParams,std::allocator<AccessibilityHostMsg_EventParams> > &, int, int) __attribute__((thiscall))> C:\b\c\b\win_asan_release\src\ipc\ipc_message_templates.h:120 # | #13 0x10b23853 in content::RenderFrameHostImpl::OnMessageReceived C:\b\c\b\win_asan_release\src\content\browser\frame_host\render_frame_host_impl.cc:890 # | #14 0x11183345 in content::RenderProcessHostImpl::OnMessageReceived C:\b\c\b\win_asan_release\src\content\browser\renderer_host\render_process_host_impl.cc:2831 # | #15 0x145f0109 in IPC::ChannelProxy::Context::OnDispatchMessage C:\b\c\b\win_asan_release\src\ipc\ipc_channel_proxy.cc:329 # | #16 0x1153d5fe in base::internal::Invoker<base::internal::BindState<base::internal::IgnoreResultHelper<bool (content::UtilityProcessHostClient::*)(const IPC::Message &) __attribute__((thiscall))>,scoped_refptr<content::UtilityProcessHostClient>,IPC::Message>,void ()>::Run C:\b\c\b\win_asan_release\src\base\bind_internal.h:317 # | #17 0x1291b01a in base::debug::TaskAnnotator::RunTask C:\b\c\b\win_asan_release\src\base\debug\task_annotator.cc:57 # | #18 0x12762259 in base::MessageLoop::RunTask C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:422 # | #19 0x1276363b in base::MessageLoop::DeferOrRunPendingTask C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:433 # | #20 0x127642f3 in base::MessageLoop::DoWork C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:540 # | #21 0x12920256 in base::MessagePumpForUI::DoRunLoop C:\b\c\b\win_asan_release\src\base\message_loop\message_pump_win.cc:173 # | #22 0x1291f155 in base::MessagePumpWin::Run C:\b\c\b\win_asan_release\src\base\message_loop\message_pump_win.cc:56 # | #23 0x12760ec4 in base::MessageLoop::Run C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:369 # | #24 0x1283d5ed in base::RunLoop::Run C:\b\c\b\win_asan_release\src\base\run_loop.cc:111 # | #25 0x123ef3d1 in ChromeBrowserMainParts::MainMessageLoopRun C:\b\c\b\win_asan_release\src\chrome\browser\chrome_browser_main.cc:1967 # | #26 0x107205e4 in content::BrowserMainLoop::RunMainMessageLoopParts C:\b\c\b\win_asan_release\src\content\browser\browser_main_loop.cc:1170 # | #27 0x10729dfd in content::BrowserMainRunnerImpl::Run C:\b\c\b\win_asan_release\src\content\browser\browser_main_runner.cc:142 # | #28 0x107133b9 in content::BrowserMain C:\b\c\b\win_asan_release\src\content\browser\browser_main.cc:46 # | #29 0x1211d5d6 in content::RunNamedProcessTypeMain C:\b\c\b\win_asan_release\src\content\app\content_main_runner.cc:408 # | #30 0x1211e95d in content::ContentMainRunnerImpl::Run C:\b\c\b\win_asan_release\src\content\app\content_main_runner.cc:687 # | #31 0x12189d05 in service_manager::Main C:\b\c\b\win_asan_release\src\services\service_manager\embedder\main.cc:469 # | #32 0x1211d2cc in content::ContentMain C:\b\c\b\win_asan_release\src\content\app\content_main.cc:19 # | #33 0xf961320 in ChromeMain C:\b\c\b\win_asan_release\src\chrome\app\chrome_main.cc:139 # | #34 0xdb9f24 in MainDllLoader::Launch C:\b\c\b\win_asan_release\src\chrome\app\main_dll_loader_win.cc:199 # | #35 0xdb1c53 in main C:\b\c\b\win_asan_release\src\chrome\app\chrome_exe_main_win.cc:268 # | #36 0x111f72a in __scrt_common_main_seh f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:253 # | #37 0x74b39ba3 in BaseThreadInitThunk+0x23 (C:\Windows\System32\KERNEL32.DLL+0x68919ba3) # | #38 0x76fcac9a in RtlCheckRegistryKey+0xfba (C:\Windows\SYSTEM32\ntdll.dll+0x6a26ac9a) # | #39 0x76fcac6e in RtlCheckRegistryKey+0xf8e (C:\Windows\SYSTEM32\ntdll.dll+0x6a26ac6e) # | # |0x0636d770 is located 64 bytes inside of 140-byte region [0x0636d730,0x0636d7bc) # |freed by thread T0 here: # | #0 0x110e378 in free e:\b\build\slave\win_upload_clang\build\src\third_party\llvm\projects\compiler-rt\lib\asan\asan_malloc_win.cc:44 # | #1 0x105296f9 in ATL::CComObject<content::BrowserAccessibilityComWin>::`vector deleting destructor' c:\b\c\win_toolchain\vs_files\f53e4598951162bad6330f7a167486c7ae5db1e5\vc\atlmfc\include\atlcom.h # | #2 0x1515b6fb in ATL::CComObject<ui::AXPlatformNodeWin>::Release c:\b\c\win_toolchain\vs_files\f53e4598951162bad6330f7a167486c7ae5db1e5\vc\atlmfc\include\atlcom.h:2934 # | #3 0x1514765e in ui::AXPlatformNodeWin::Dispose C:\b\c\b\win_asan_release\src\ui\accessibility\platform\ax_platform_node_win.cc:550 # | #4 0x15147707 in ui::AXPlatformNodeWin::Destroy C:\b\c\b\win_asan_release\src\ui\accessibility\platform\ax_platform_node_win.cc:555 # | #5 0x10529055 in content::BrowserAccessibilityWin::~BrowserAccessibilityWin C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_win.h:29 # | #6 0x104c9fba in content::BrowserAccessibility::NativeReleaseReference C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility.cc:556 # | #7 0x1051014c in content::BrowserAccessibilityManager::OnNodeWillBeDeleted C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager.cc:1134 # | #8 0x15116137 in ui::AXTree::DestroyNodeAndSubtree C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:501 # | #9 0x15119afb in ui::AXTree::DestroySubtree C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:494 # | #10 0x1511a03b in ui::AXTree::UpdateNode C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:406 # | #11 0x15117cb9 in ui::AXTree::Unserialize C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:258 # | #12 0x1050c39b in content::BrowserAccessibilityManager::OnAccessibilityEvents C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager.cc:364 # | #13 0x10b436b5 in content::RenderFrameHostImpl::OnAccessibilityEvents C:\b\c\b\win_asan_release\src\content\browser\frame_host\render_frame_host_impl.cc:2388 # | #14 0x10b427af in IPC::MessageT<AccessibilityHostMsg_Events_Meta,std::tuple<std::vector<AccessibilityHostMsg_EventParams,std::allocator<AccessibilityHostMsg_EventParams> >,int,int>,void>::Dispatch<content::RenderFrameHostImpl,content::RenderFrameHostImpl,void,void (content::RenderFrameHostImpl::*)(const std::vector<AccessibilityHostMsg_EventParams,std::allocator<AccessibilityHostMsg_EventParams> > &, int, int) __attribute__((thiscall))> C:\b\c\b\win_asan_release\src\ipc\ipc_message_templates.h:120 # | #15 0x10b23853 in content::RenderFrameHostImpl::OnMessageReceived C:\b\c\b\win_asan_release\src\content\browser\frame_host\render_frame_host_impl.cc:890 # | #16 0x11183345 in content::RenderProcessHostImpl::OnMessageReceived C:\b\c\b\win_asan_release\src\content\browser\renderer_host\render_process_host_impl.cc:2831 # | #17 0x145f0109 in IPC::ChannelProxy::Context::OnDispatchMessage C:\b\c\b\win_asan_release\src\ipc\ipc_channel_proxy.cc:329 # | #18 0x1153d5fe in base::internal::Invoker<base::internal::BindState<base::internal::IgnoreResultHelper<bool (content::UtilityProcessHostClient::*)(const IPC::Message &) __attribute__((thiscall))>,scoped_refptr<content::UtilityProcessHostClient>,IPC::Message>,void ()>::Run C:\b\c\b\win_asan_release\src\base\bind_internal.h:317 # | #19 0x1291b01a in base::debug::TaskAnnotator::RunTask C:\b\c\b\win_asan_release\src\base\debug\task_annotator.cc:57 # | #20 0x12762259 in base::MessageLoop::RunTask C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:422 # | #21 0x1276363b in base::MessageLoop::DeferOrRunPendingTask C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:433 # | #22 0x127642f3 in base::MessageLoop::DoWork C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:540 # | #23 0x12920256 in base::MessagePumpForUI::DoRunLoop C:\b\c\b\win_asan_release\src\base\message_loop\message_pump_win.cc:173 # | #24 0x1291f155 in base::MessagePumpWin::Run C:\b\c\b\win_asan_release\src\base\message_loop\message_pump_win.cc:56 # | #25 0x12760ec4 in base::MessageLoop::Run C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:369 # | #26 0x1283d5ed in base::RunLoop::Run C:\b\c\b\win_asan_release\src\base\run_loop.cc:111 # | #27 0x123ef3d1 in ChromeBrowserMainParts::MainMessageLoopRun C:\b\c\b\win_asan_release\src\chrome\browser\chrome_browser_main.cc:1967 # | #28 0x107205e4 in content::BrowserMainLoop::RunMainMessageLoopParts C:\b\c\b\win_asan_release\src\content\browser\browser_main_loop.cc:1170 # | # |previously allocated by thread T0 here: # | #0 0x110e45c in malloc e:\b\build\slave\win_upload_clang\build\src\third_party\llvm\projects\compiler-rt\lib\asan\asan_malloc_win.cc:60 # | #1 0x19f475bb in operator new f:\dd\vctools\crt\vcstartup\src\heap\new_scalar.cpp:19 # | #2 0x19f47cae in operator new f:\dd\vctools\crt\vcstartup\src\heap\new_scalar_nothrow.cpp:17 # | #3 0x10528bf2 in ATL::CComObject<content::BrowserAccessibilityComWin>::CreateInstance c:\b\c\win_toolchain\vs_files\f53e4598951162bad6330f7a167486c7ae5db1e5\vc\atlmfc\include\atlcom.h:2966 # | #4 0x10528a2f in content::BrowserAccessibilityWin::BrowserAccessibilityWin C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_win.cc:19 # | #5 0x10528962 in content::BrowserAccessibility::Create C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_win.cc:14 # | #6 0x10510613 in content::BrowserAccessibilityManager::OnNodeCreated C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager.cc:1166 # | #7 0x105261e3 in content::BrowserAccessibilityManagerWin::OnNodeCreated C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager_win.cc:249 # | #8 0x1511ac76 in ui::AXTree::CreateNode C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:330 # | #9 0x15119dda in ui::AXTree::UpdateNode C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:360 # | #10 0x15117cb9 in ui::AXTree::Unserialize C:\b\c\b\win_asan_release\src\ui\accessibility\ax_tree.cc:258 # | #11 0x1050a873 in content::BrowserAccessibilityManager::Initialize C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager.cc:177 # | #12 0x105250e4 in content::BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin C:\b\c\b\win_asan_release\src\content\browser\accessibility\browser_accessibility_manager_win.cc:44 # | #13 0x11234704 in content::RenderWidgetHostViewAura::CreateBrowserAccessibilityManager C:\b\c\b\win_asan_release\src\content\browser\renderer_host\render_widget_host_view_aura.cc:1144 # | #14 0x10b5a82a in content::RenderFrameHostImpl::GetOrCreateBrowserAccessibilityManager C:\b\c\b\win_asan_release\src\content\browser\frame_host\render_frame_host_impl.cc:3595 # |(bc4.f90): Break instruction exception - code 80000003 (first chance) # | #15 0x10b42c8a in content::RenderFrameHostImpl::OnAccessibilityEvents C:\b\c\b\win_asan_release\src\content\browser\frame_host\render_frame_host_impl.cc:2360 # | #16 0x10b427af in IPC::MessageT<AccessibilityHostMsg_Events_Meta,std::tuple<std::vector<AccessibilityHostMsg_EventParams,std::allocator<AccessibilityHostMsg_EventParams> >,int,int>,void>::Dispatch<content::RenderFrameHostImpl,content::RenderFrameHostImpl,void,void (content::RenderFrameHostImpl::*)(const std::vector<AccessibilityHostMsg_EventParams,std::allocator<AccessibilityHostMsg_EventParams> > &, int, int) __attribute__((thiscall))> C:\b\c\b\win_asan_release\src\ipc\ipc_message_templates.h:120 # | #17 0x10b23853 in content::RenderFrameHostImpl::OnMessageReceived C:\b\c\b\win_asan_release\src\content\browser\frame_host\render_frame_host_impl.cc:890 # | #18 0x11183345 in content::RenderProcessHostImpl::OnMessageReceived C:\b\c\b\win_asan_release\src\content\browser\renderer_host\render_process_host_impl.cc:2831 # | #19 0x145f0109 in IPC::ChannelProxy::Context::OnDispatchMessage C:\b\c\b\win_asan_release\src\ipc\ipc_channel_proxy.cc:329 # | #20 0x1153d5fe in base::internal::Invoker<base::internal::BindState<base::internal::IgnoreResultHelper<bool (content::UtilityProcessHostClient::*)(const IPC::Message &) __attribute__((thiscall))>,scoped_refptr<content::UtilityProcessHostClient>,IPC::Message>,void ()>::Run C:\b\c\b\win_asan_release\src\base\bind_internal.h:317 # | #21 0x1291b01a in base::debug::TaskAnnotator::RunTask C:\b\c\b\win_asan_release\src\base\debug\task_annotator.cc:57 # | #22 0x12762259 in base::MessageLoop::RunTask C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:422 # | #23 0x1276363b in base::MessageLoop::DeferOrRunPendingTask C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:433 # | #24 0x127642f3 in base::MessageLoop::DoWork C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:540 # | #25 0x12920256 in base::MessagePumpForUI::DoRunLoop C:\b\c\b\win_asan_release\src\base\message_loop\message_pump_win.cc:173 # | #26 0x1291f155 in base::MessagePumpWin::Run C:\b\c\b\win_asan_release\src\base\message_loop\message_pump_win.cc:56 # | #27 0x12760ec4 in base::MessageLoop::Run C:\b\c\b\win_asan_release\src\base\message_loop\message_loop.cc:369 # | #28 0x1283d5ed in base::RunLoop::Run C:\b\c\b\win_asan_release\src\base\run_loop.cc:111 # | # |SUMMARY: AddressSanitizer: heap-use-after-free c:\b\c\win_toolchain\vs_files\f53e4598951162bad6330f7a167486c7ae5db1e5\vc\include\xhash:636 in std::_Hash<std::_Uset_traits<device::BluetoothAdapter::Observer *,std::_Uhash_compare<device::BluetoothAdapter::Observer *,std::hash<device::BluetoothAdapter::Observer *>,std::equal_to<device::BluetoothAdapter::Observer *> >,std::allocator<device::BluetoothAdapter::Observer *>,0> >::equal_range # |Shadow bytes around the buggy address: # | 0x30c6da90: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 # | 0x30c6daa0: 00 00 00 00 00 00 00 00 00 04 fa fa fa fa fa fa # | 0x30c6dab0: fa fa fd fd fd fd fd fd fd fd fd fd fd fd fd fd # | 0x30c6dac0: fd fd fd fd fa fa fa fa fa fa fa fa 00 00 00 00 # | 0x30c6dad0: 00 00 00 00 00 00 00 00 00 00 00 00 00 04 fa fa # |=>0x30c6dae0: fa fa fa fa fa fa fd fd fd fd fd fd fd fd[fd]fd # | 0x30c6daf0: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa # | 0x30c6db00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd # | 0x30c6db10: fd fd fa fa fa fa fa fa fa fa fd fd fd fd fd fd # | 0x30c6db20: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa # | 0x30c6db30: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00 # |Shadow byte legend (one shadow byte represents 8 application bytes): # | Addressable: 00 # | Partially addressable: 01 02 03 04 05 06 07 # | Heap left redzone: fa # | Freed heap region: fd # | Stack left redzone: f1 # | Stack mid redzone: f2 # | Stack right redzone: f3 # | Stack after return: f5 # | Stack use after scope: f8 # | Global redzone: f9 # | Global init order: f6 # | Poisoned by user: f7 # | Container overflow: fc # | Array cookie: ac # | Intra object redzone: bb # | ASan internal: fe # | Left alloca redzone: ca # | Right alloca redzone: cb # |==3012:3984==ABORTING asAsanBugReport = [] uMemoryDumpStartAddress = None uMemoryDumpEndAddress = None atxMemoryRemarks = [] for sLine in oCdbWrapper.asStdErrOutput: if not asAsanBugReport: oSummaryMatch = re.match( r"^==(\d+):(\d+)==ERROR: AddressSanitizer: (.*) on address 0x([0-9`a-f]+) at pc 0x([0-9`a-f]+) bp 0x([0-9`a-f]+) sp 0x([0-9`a-f]+)$", sLine, re.I) if not oSummaryMatch: continue sProcessId, sThreadId, sASanBugType, sAddressHex, sIPHex, sBPHex, sSPHex = oSummaryMatch.groups( ) uProcessId = long(sProcessId) uThreadId = long(sThreadId) uProblemAddress = long(sAddressHex, 16) atxMemoryRemarks.append( ("Address for which ASan reported this problem", uProblemAddress, None)) elif sLine == "==%d:%d==ABORTING" % (uProcessId, uThreadId): break else: oHeapInfoMatch = re.match( r"0x([0-9`a-f]+) is located (\d+) bytes (?:inside|to the (?:left|right)) of (\d+)\-byte region \[0x([0-9`a-f]+),0x([0-9`a-f]+)\)", sLine, re.I) if oHeapInfoMatch: sAddressHex, sOffset, sBlockSize, sBlockStartAddressHex, sBlockEndAddressHex = oHeapInfoMatch.groups( ) assert uProblemAddress == long(sAddressHex, 16), \ "Problem reported at address 0x%X, but information provided for address 0x%s" % (uProblemAddress, sAddressHex) uBlockStartAddress = long(sBlockStartAddressHex, 16) uBlockEndAddress = long(sBlockEndAddressHex, 16) uBlockSize = long(sBlockSize) assert uBlockEndAddress - uBlockStartAddress == uBlockSize, \ "The memory block start (0x%X) and end (0x%X) address suggest a size (0x%X) that do not agree with the reported size (0x%X)" % \ (uBlockStartAddress, uBlockEndAddress, uBlockEndAddress - uBlockStartAddress, uBlockSize) if uMemoryDumpStartAddress is None or uBlockStartAddress < uMemoryDumpStartAddress: uMemoryDumpStartAddress = uBlockStartAddress if uMemoryDumpEndAddress is None or uBlockEndAddress > uMemoryDumpEndAddress: uMemoryDumpEndAddress = uBlockEndAddress atxMemoryRemarks.append(("Memory block according to ASan", uBlockStartAddress, uBlockSize), ) oHeapInfoMatch = re.match( r"0x([0-9`a-f]+) is located (\d+) bytes (?:inside|to the (?:left|right)) of global variable '(.+)' defined in '.+' \(0x([0-9`a-f]+)\) of size (\d+)", sLine, re.I) if oHeapInfoMatch: sAddressHex, sOffset, sVariableName, sVariableStartAddressHex, sVariableSize = oHeapInfoMatch.groups( ) assert uProblemAddress == long(sAddressHex, 16), \ "Problem reported at address 0x%X, but information provided for address 0x%s" % (uProblemAddress, sAddressHex) uVariableStartAddress = long(sVariableStartAddressHex, 16) uVariableSize = long(sVariableSize) if uMemoryDumpStartAddress is None or uVariableStartAddress < uMemoryDumpStartAddress: uMemoryDumpStartAddress = uVariableStartAddress if uMemoryDumpEndAddress is None or uVariableStartAddress + uVariableSize > uMemoryDumpEndAddress: uMemoryDumpEndAddress = uVariableStartAddress + uVariableSize atxMemoryRemarks.append( ("global variable %s" % sVariableName, uMemoryDumpStartAddress, uVariableSize), ) oHeapInfoMatch = re.match( r"(READ|WRITE) of size (\d+) at 0x([0-9`a-f]+) thread T\d+", sLine, re.I) if oHeapInfoMatch: sAction, sSize, sAddressHex = oHeapInfoMatch.groups() uSize = long(sSize) uAddress = long(sAddressHex, 16) atxMemoryRemarks.append( ("Attempt to %s %d bytes from 0x%X" % (sAction.lower(), uSize, uAddress), uAddress, uSize), ) asAsanBugReport.append(sLine) else: if asAsanBugReport: # We found the start of a bug report, but not the end. It may be the application was interrupted while ASan was # busy collecting and outputting information. We will ignore this exception, as there should be another one when # ASan is finished, at which point all relevant information is available for us in our report. return # We did not find a bug report. return oCdbWrapper.fSelectProcess(uProcessId) oProcess = oCdbWrapper.oCurrentProcess sBugTypeId = "ASan:%s" % sASanBugType sBugDescription = "AddressSanitizer reported a %s on address 0x%X." % ( sASanBugType, uProblemAddress) sSecurityImpact = dsSecurityImpact_by_sASanBugType.get( sASanBugType, "Unknown: this type of bug has not been analyzed before") oHeapManagerData = oProcess.foGetHeapManagerDataForAddress(uProblemAddress) if oHeapManagerData: atxMemoryRemarks.extend(oHeapManagerData.fatxMemoryRemarks()) # Make sure entire page heap block is included in the memory dump. if oHeapManagerData.uMemoryDumpStartAddress < uMemoryDumpStartAddress: uMemoryDumpStartAddress = oHeapManagerData.uMemoryDumpStartAddress assert uMemoryDumpEndAddress - uMemoryDumpStartAddress == uMemoryDumpSize, \ "Something, somewhere went wrong because 0x%X - 0x%X == 0x%X and not 0x%X" % \ (uMemoryDumpEndAddress, uMemoryDumpStartAddress, uMemoryDumpEndAddress - uMemoryDumpStartAddress, uMemoryDumpSize) if oHeapManagerData.uMemoryDumpEndAddress > uMemoryDumpEndAddress: uMemoryDumpEndAddress = ooHeapManagerData.uMemoryDumpEndAddress oBugReport = cBugReport.foCreate(oProcess, sBugTypeId, sBugDescription, sSecurityImpact) if oCdbWrapper.bGenerateReportHTML: if uMemoryDumpStartAddress is None: # We know nothing about the layout of the memory region for which the problem was reported, but we do want to # dump it in the report, so we will output a region of a size that will hopefully be useful, but not too large # so as to bloat the report with irrelevant data. uMemoryDumpStartAddress = uProblemAddress - 0x100 uMemoryDumpSize = 0x200 else: # Make sure the problem address is in the memory dump if uMemoryDumpStartAddress > uProblemAddress: uMemoryDumpStartAddress = uProblemAddress elif uMemoryDumpEndAddress < uProblemAddress: uMemoryDumpEndAddress = uProblemAddress uMemoryDumpStartAddress, uMemoryDumpSize = ftuLimitedAndAlignedMemoryDumpStartAddressAndSize( uProblemAddress, oProcess.uPointerSize, uMemoryDumpStartAddress, uMemoryDumpEndAddress - uMemoryDumpStartAddress, ) # Dump memory oBugReport.fAddMemoryDump( uMemoryDumpStartAddress, uMemoryDumpStartAddress + uMemoryDumpSize, "Memory near heap block at 0x%X" % uMemoryDumpStartAddress, ) for (sDescription, uAddress, uSize) in atxMemoryRemarks: print "$$$ 0x%08X+%s %s" % (uAddress, uSize is None and "????" or "%04X" % uSize, sDescription) oBugReport.atxMemoryRemarks.extend(atxMemoryRemarks) # Add ASan output to file sASanOutputHTML = sBlockHTMLTemplate % { "sName": "ASan bug report", "sCollapsed": "Collapsed", "sContent": "<pre>%s</pre>" % "\r\n".join([ oCdbWrapper.fsHTMLEncode(s, uTabStop=8) for s in asAsanBugReport ]) } oBugReport.asExceptionSpecificBlocksHTML.append(sASanOutputHTML) return oBugReport
def foDetectAndCreateBugReportForVERIFIER_STOP(oCdbWrapper, uExceptionCode, asCdbOutput): if uExceptionCode not in [STATUS_BREAKPOINT, STATUS_WX86_BREAKPOINT]: return None # Not a VERIFIER STOP uErrorNumber = None uProcessId = None sMessage = None uVerifierStopHeapBlockAddress = None uVerifierStopHeapBlockSize = None uVerifierStopHeapBlockHandle = None uVerifierStopHeapHandle = None # This is what is being used, which may differ from the actual heap handle of the block uCorruptedStamp = None uCorruptionAddress = None asRelevantLines = [] for sLine in asCdbOutput: # Ignore exmpty lines if not sLine: continue # Look for the first VERIFIER STOP message and gather information if uErrorNumber is None: oErrorMessageMatch = re.match( r"^VERIFIER STOP ([0-9A-F]+): pid 0x([0-9A-F]+): (.*?)\s*$", sLine) if oErrorMessageMatch: sErrorNumber, sProcessId, sMessage = oErrorMessageMatch.groups( ) uErrorNumber = long(sErrorNumber, 16) uProcessId = long(sProcessId, 16) asRelevantLines.append(sLine) oCdbWrapper.fSelectProcess(uProcessId) assert uProcessId == oCdbWrapper.oCurrentProcess.uId, \ "VERIFIER STOP in process with id %d/0x%X, but current process id is %d/0x%X" % \ (uProcessId, uProcessId, oCdbWrapper.oCurrentProcess.uId, oCdbWrapper.oCurrentProcess.uId) continue # A VERIFIER STOP message has been detected, gather what information verifier provides: oInformationMatch = re.match(r"^\t([0-9A-F]+) : (.*?)\s*$", sLine) if oInformationMatch: asRelevantLines.append(sLine) sValue, sDescription = oInformationMatch.groups() uValue = long(sValue, 16) sDescription = sDescription.lower() # Both "Corruption address" and "corruption address" are used :( if sDescription == "heap block": uVerifierStopHeapBlockAddress = uValue elif sDescription == "block size": uVerifierStopHeapBlockSize = uValue elif sDescription == "corrupted stamp": uCorruptedStamp = uValue elif sDescription == "corruption address": uCorruptionAddress = uValue elif sDescription == "corruption address": uCorruptionAddress = uValue elif sDescription == "heap handle": uVerifierStopHeapBlockHandle = uValue uVerifierStopHeapHandle = uValue elif sDescription == "heap used in the call": uVerifierStopHeapHandle = uValue elif sDescription == "heap owning the block": uVerifierStopHeapBlockHandle = uValue else: assert sLine.strip().replace("=", "") == "", \ "Unknown VERIFIER STOP message line: %s\r\n%s" % (repr(sLine), "\r\n".join(asCdbOutput)) break else: assert uErrorNumber is None, \ "Detected the start of a VERIFIER STOP message but not the end\r\n%s" % "\r\n".join(asCdbOutput) return None # No VERIFIER STOP in the output. if uErrorNumber == 0x303: # |======================================= # |VERIFIER STOP 0000000000000303: pid 0xB2C: NULL handle passed as parameter. A valid handle must be used. # | # |0000000000000000 : Not used. # |0000000000000000 : Not used. # |0000000000000000 : Not used. # |0000000000000000 : Not used. # | # |======================================= return None # This is a VERIFIER STOP, but not an interesting one and we can continue the application. assert uVerifierStopHeapBlockAddress is not None, \ "The heap block start address was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines) assert uVerifierStopHeapBlockSize is not None, \ "The heap block size was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines) if sMessage == "block already freed": # It appears there is a bug in verifier that causes it to report the wrong address: rather than the address # of the block that was already freed, it reports the address of some struct. Luckily it appears that this struct # does contain the address of the heap block: # PVOID pUnknown_0 # PVOID pUnknown_1 # PVOID pUnknown_2 # PVOID pUnknown_3 # PVOID pHeapBlock; // A pointer to the start of the heap block. # PVOID pVitualAllocation; // A pointer to the virtual allocation that contains the heap block and page heap data. # ULONG uVirtualAllocationSize; // The size of the virtual allocations for the heap block and guard page. # PVOID pUnknown_7; # ULONG uHeapBlockSize; // The size of the heap block # This means we can read the address of the heap block by reading a pointer sized value at offset 4 * sizeof(PVOID). puHeapBlockAddress = uVerifierStopHeapBlockAddress + 4 * oCdbWrapper.oCurrentProcess.uPointerSize uHeapBlockAddress = oCdbWrapper.oCurrentProcess.fuGetValue( "poi(0x%X)" % puHeapBlockAddress, "Get heap block address from verifier data") else: # In all other cases, we trust verifier to provide the correct value: uHeapBlockAddress = uVerifierStopHeapBlockAddress oPageHeapAllocation = cPageHeapAllocation.foGetForAddress( oCdbWrapper, uHeapBlockAddress) assert oPageHeapAllocation, \ "No page heap report avaiable for address 0x%X" % uHeapBlockAddress uMemoryDumpStartAddress = None atxMemoryRemarks = [] if sMessage in ["corrupted start stamp", "corrupted end stamp"] \ and oPageHeapAllocation.uBlockStartAddress != uVerifierStopHeapBlockAddress: # When the application attempts to free (heap pointer + offset), Verifier does not detect this and will assume # the application provided pointer is correct. This causes it to look for the start and end stamp in the wrong # location and report this bug as memory corruption. When the page heap data shows no signs of corruption, we # can special case it. sHeapBlockAndOffsetId, sHeapBlockAndOffsetDescription = ftsGetHeapBlockAndOffsetIdAndDescription( uVerifierStopHeapBlockAddress, oPageHeapAllocation) sBugTypeId = "MisalignedFree%s" % sHeapBlockAndOffsetId sBugDescription = "The application attempted to free memory using a pointer that was %s." % sHeapBlockAndOffsetDescription sSecurityImpact = "Unknown: this type of bug has not been analyzed before" if oCdbWrapper.bGenerateReportHTML and oPageHeapAllocation: sPageHeapHTMLOutputHeader = "Page heap output for heap block near 0x%X" % uVerifierStopHeapBlockAddress elif sMessage == "block already freed": # |=========================================================== # |VERIFIER STOP 00000007: pid 0x1358: block already freed # | # | 02F71000 : Heap handle # | 152F016C : Heap block # | 000004C8 : Block size # | 00000000 : # |=========================================================== # |This verifier stop is not continuable. Process will be terminated # |when you use the `go' debugger command. # |=========================================================== if oPageHeapAllocation.uBlockSize is None: sBugTypeId = "DoubleFree[?]" sBugDescription = "The application attempted to free a heap block of unknown size at address 0x%X twice" % \ (uVerifierStopHeapBlockAddress) if oCdbWrapper.bGenerateReportHTML and oPageHeapAllocation: sPageHeapHTMLOutputHeader = "Page heap output for heap block near 0x%X" % uVerifierStopHeapBlockAddress else: sBugTypeId = "DoubleFree[%s]" % fsGetNumberDescription( oPageHeapAllocation.uBlockSize) sBugDescription = "The application attempted to free a %d/0x%X byte heap block at address 0x%X twice" % \ (oPageHeapAllocation.uBlockSize, oPageHeapAllocation.uBlockSize, oPageHeapAllocation.uBlockStartAddress) if oCdbWrapper.bGenerateReportHTML and oPageHeapAllocation: sPageHeapHTMLOutputHeader = "Page heap output for heap block at 0x%X" % oPageHeapAllocation.uBlockStartAddress sSecurityImpact = "Potentially exploitable security issue, if the attacker can force the application to " \ "allocate memory between the two frees" elif sMessage == "corrupted heap pointer or using wrong heap": # |=========================================================== # |VERIFIER STOP 00000006: pid 0x144C: corrupted heap pointer or using wrong heap # | # | 077F1000 : Heap used in the call # | 43742FE0 : Heap block # | 00000020 : Block size # | 277F1000 : Heap owning the block # |=========================================================== # |This verifier stop is not continuable. Process will be terminated # |when you use the `go' debugger command. # |=========================================================== assert uVerifierStopHeapHandle is not None, \ "Missing 'Heap used in the call' value in the VERIFIER STOP message.\r\n%s" % "\r\n".join(asCdbOutput) assert uVerifierStopHeapBlockHandle is not None, \ "Missing 'Heap owning the block' value in the VERIFIER STOP message.\r\n%s" % "\r\n".join(asCdbOutput) if oPageHeapAllocation.uBlockSize is None: sBugTypeId = "IncorrectHeap[?]" sBugDescription = "The application provided an incorrect heap handle (0x%X) for a heap block of unknown size " \ "near address 0x%X which belongs to another heap (handle 0x%X)" % \ (uVerifierStopHeapHandle, uVerifierStopHeapBlockAddress, uVerifierStopHeapBlockHandle) if oCdbWrapper.bGenerateReportHTML and oPageHeapAllocation: sPageHeapHTMLOutputHeader = "Page heap output for heap block near 0x%X" % uVerifierStopHeapBlockAddress else: sBugTypeId = "IncorrectHeap[%s]" % (fsGetNumberDescription( oPageHeapAllocation.uBlockSize)) sBugDescription = "The application provided an incorrect heap handle (0x%X) for a %d/0x%X byte heap block at " \ "address 0x%X which belongs to another heap (handle 0x%X)" % \ (uVerifierStopHeapHandle, oPageHeapAllocation.uBlockSize, oPageHeapAllocation.uBlockSize, \ oPageHeapAllocation.uBlockStartAddress, uVerifierStopHeapBlockHandle) if oCdbWrapper.bGenerateReportHTML and oPageHeapAllocation: sPageHeapHTMLOutputHeader = "Page heap output for heap block at 0x%X" % oPageHeapAllocation.uBlockStartAddress sSecurityImpact = "Unknown: this type of bug has not been analyzed before" else: # Check the page heap data near the heap block for signs of corruption: oCorruptionDetector = cCorruptionDetector.foCreateForPageHeapAllocation( oPageHeapAllocation) if oCdbWrapper.bGenerateReportHTML: uMemoryDumpStartAddress = oPageHeapAllocation.uBlockStartAddress uMemoryDumpSize = oPageHeapAllocation.uBlockSize if oPageHeapAllocation: atxMemoryRemarks.extend( oPageHeapAllocation.fatxMemoryRemarks()) # Handle various VERIFIER STOP messages. if sMessage in ["corrupted start stamp", "corrupted end stamp"]: assert uCorruptionAddress is None, \ "We do not expect the corruption address to be provided in this VERIFIER STOP message\r\n%s" % \ "\r\n".join(asRelevantLines) sBugTypeId = "OOBW" uCorruptionStartAddress = sMessage == "corrupted start stamp" \ and oPageHeapAllocation.uStartStampAddress \ or oPageHeapAllocation.uEndStampAddress uCorruptionEndAddress = uCorruptionStartAddress + 4 # Stamps are 4 bytes elif sMessage in ["corrupted suffix pattern", "corrupted header"]: assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in this VERIFIER STOP message:\r\n%s" % \ "\r\n".join(asRelevantLines) # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start of the # heap block must be properly aligned. Bytes between the heap block and the end of the page are initialized to # 0xD0. Verifier has detected that one of the bytes changed value, which indicates an out-of-bounds write. BugId # will try to find all bytes that were changed: sBugTypeId = "OOBW" uCorruptionStartAddress = uCorruptionAddress uCorruptionEndAddress = uCorruptionStartAddress + 1 # We do not know the size, so assume one byte. elif sMessage == "corrupted infix pattern": assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \ "\r\n".join(asRelevantLines) # Page heap sometimes does not free a heap block immediately, but overwrites the bytes with 0xF0. Verifier has # detected that one of the bytes changed value, which indicates a write-after-free. BugId will try to find all # bytes that were changed: oCorruptionDetector.fbDetectCorruption( oPageHeapAllocation.uBlockStartAddress, [0xF0 for x in xrange(oPageHeapAllocation.uBlockSize)]) uCorruptionStartAddress = uCorruptionAddress uCorruptionEndAddress = uCorruptionStartAddress + 1 # We do not know the size, so assume one byte. sBugTypeId = "UAFW" else: raise AssertionError("Unhandled VERIFIER STOP message: %s" % sMessage) # If we detected the reported corruption ourselves, we can get a better idea of the start and end address: # (One might expect that when a VERIFIER STOP reported memory corruption, we can always detect this as well, but # unfortunately, this is not always true. I do not know why a VERIFIER STOP would report corruption when there is # no sign of it, but by trusting it is correct, we at least get a report so I can find out what is going on). if oCorruptionDetector.bCorruptionDetected: uCorruptionStartAddress = oCorruptionDetector.uCorruptionStartAddress uCorruptionEndAddress = oCorruptionDetector.uCorruptionEndAddress if oCdbWrapper.bGenerateReportHTML: atxMemoryRemarks.extend( oCorruptionDetector.fatxMemoryRemarks()) uCorruptionSize = uCorruptionEndAddress - uCorruptionStartAddress # If the corruption starts before or ends after the heap block, expand the memory dump to include the entire # corrupted region. if oCdbWrapper.bGenerateReportHTML: if uCorruptionStartAddress < uMemoryDumpStartAddress: uMemoryDumpSize += uMemoryDumpStartAddress - uCorruptionStartAddress uMemoryDumpStartAddress = uCorruptionStartAddress if uCorruptionEndAddress < uMemoryDumpStartAddress + uMemoryDumpSize: uMemoryDumpSize += uCorruptionEndAddress - ( uMemoryDumpStartAddress + uMemoryDumpSize) # Get a human readable description of the start offset of corruption relative to the heap block, where corruption # starting before or inside the heap block will be relative to the start, and corruption after it to the end. sHeapBlockAndOffsetId, sHeapBlockAndOffsetDescription = \ ftsGetHeapBlockAndOffsetIdAndDescription(uCorruptionStartAddress, oPageHeapAllocation) sBugTypeId += sHeapBlockAndOffsetId sBugDescription = "Page heap detected heap corruption at 0x%X; %s." % ( uCorruptionStartAddress, sHeapBlockAndOffsetDescription) if uCorruptionStartAddress == oPageHeapAllocation.uBlockEndAddress: sBugDescription += " This appears to be a classic buffer-overrun vulnerability." sSecurityImpact = "Potentially highly exploitable security issue." else: sSecurityImpact = "Potentially exploitable security issue, if the corruption is attacker controlled." # If we detected corruption by scanning certain bytes in the applications memory, make sure this matches what # verifier reported and save all bytes that were affected: so far, we only saved the bytes that had an unexpected # value, but there is a chance that a byte was overwritten with the same value it has before, in which case it was # not saved. This can be detect if it is surrounded by bytes that did change value. This code reads the value of all # bytes between the first and last byte that we detected was corrupted: if oCorruptionDetector.bCorruptionDetected: asCorruptedBytes = oCorruptionDetector.fasCorruptedBytes() sBugDescription += " The following byte values were written to the corrupted area: %s." % ", ".join( asCorruptedBytes) sBugTypeId += oCorruptionDetector.fsCorruptionId() or "" if oCdbWrapper.bGenerateReportHTML and oPageHeapAllocation: sPageHeapHTMLOutputHeader = "Page heap output for heap block near 0x%X" % uCorruptionStartAddress oBugReport = cBugReport.foCreate(oCdbWrapper, sBugTypeId, sBugDescription, sSecurityImpact) if oCdbWrapper.bGenerateReportHTML: if uMemoryDumpStartAddress: oBugReport.fAddMemoryDump( uMemoryDumpStartAddress, uMemoryDumpStartAddress + uMemoryDumpSize, "Memory near heap block at 0x%X" % uMemoryDumpStartAddress, ) oBugReport.atxMemoryRemarks.extend(atxMemoryRemarks) # Output the VERIFIER STOP message for reference if oCdbWrapper.bGenerateReportHTML: sVerifierStopMessageHTML = sBlockHTMLTemplate % { "sName": "VERIFIER STOP message", "sCollapsed": "Collapsed", "sContent": "<pre>%s</pre>" % "\r\n".join([ oCdbWrapper.fsHTMLEncode(s, uTabStop=8) for s in asRelevantLines ]) } oBugReport.asExceptionSpecificBlocksHTML.append( sVerifierStopMessageHTML) # Output the page heap information for reference if oPageHeapAllocation: sPageHeapOutputHTML = sBlockHTMLTemplate % { "sName": sPageHeapHTMLOutputHeader, "sCollapsed": "Collapsed", "sContent": "<pre>%s</pre>" % "\r\n".join([ oCdbWrapper.fsHTMLEncode(s, uTabStop=8) for s in oPageHeapAllocation.asPageHeapOutput ]) } oBugReport.asExceptionSpecificBlocksHTML.append( sPageHeapOutputHTML) oBugReport.bRegistersRelevant = False return oBugReport
def foDetectAndCreateBugReportForVERIFIER_STOP(oCdbWrapper, uExceptionCode, asCdbOutput): uErrorNumber = None uProcessId = None sMessage = None uVerifierStopHeapBlockAddress = None uVerifierStopHeapBlockSize = None uVerifierStopHeapBlockHandle = None uVerifierStopHeapHandle = None # This is what is being used, which may differ from the actual heap handle of the block uCorruptedStamp = None uCorruptionAddress = None asRelevantLines = [] for sLine in asCdbOutput: # Ignore exmpty lines if not sLine: continue # Look for the first VERIFIER STOP message and gather information if uErrorNumber is None: oErrorMessageMatch = re.match( r"^VERIFIER STOP ([0-9A-F]+): pid 0x([0-9A-F]+): (.*?)\s*$", sLine) if oErrorMessageMatch: sErrorNumber, sProcessId, sMessage = oErrorMessageMatch.groups( ) uErrorNumber = long(sErrorNumber, 16) uProcessId = long(sProcessId, 16) asRelevantLines.append(sLine) oCdbWrapper.fSelectProcess(uProcessId) assert uProcessId == oCdbWrapper.oCurrentProcess.uId, \ "VERIFIER STOP in process with id %d/0x%X, but current process id is %d/0x%X" % \ (uProcessId, uProcessId, oCdbWrapper.oCurrentProcess.uId, oCdbWrapper.oCurrentProcess.uId) continue # A VERIFIER STOP message has been detected, gather what information verifier provides: oInformationMatch = re.match(r"^\t([0-9A-F]+) : (.*?)\s*$", sLine) if oInformationMatch: asRelevantLines.append(sLine) sValue, sDescription = oInformationMatch.groups() uValue = long(sValue, 16) sDescription = sDescription.lower() # Both "Corruption address" and "corruption address" are used :( if sDescription == "heap block": uVerifierStopHeapBlockAddress = uValue elif sDescription == "block size": uVerifierStopHeapBlockSize = uValue elif sDescription == "corrupted stamp": uCorruptedStamp = uValue elif sDescription == "corruption address": uCorruptionAddress = uValue elif sDescription == "corruption address": uCorruptionAddress = uValue elif sDescription == "heap handle": uVerifierStopHeapBlockHandle = uValue uVerifierStopHeapHandle = uValue elif sDescription == "heap used in the call": uVerifierStopHeapHandle = uValue elif sDescription == "heap owning the block": uVerifierStopHeapBlockHandle = uValue else: assert sLine.strip().replace("=", "") == "", \ "Unknown VERIFIER STOP message line: %s\r\n%s" % (repr(sLine), "\r\n".join(asCdbOutput)) break else: assert uErrorNumber is None, \ "Detected the start of a VERIFIER STOP message but not the end\r\n%s" % "\r\n".join(asCdbOutput) return None # No VERIFIER STOP in the output. oCdbWrapper.fSelectProcess(uProcessId) oProcess = oCdbWrapper.oCurrentProcess if uErrorNumber == 0x303: # |======================================= # |VERIFIER STOP 0000000000000303: pid 0xB2C: NULL handle passed as parameter. A valid handle must be used. # | # |0000000000000000 : Not used. # |0000000000000000 : Not used. # |0000000000000000 : Not used. # |0000000000000000 : Not used. # | # |======================================= return None # This is a VERIFIER STOP, but not an interesting one and we can continue the application. assert uVerifierStopHeapBlockAddress is not None, \ "The heap block start address was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines) assert uVerifierStopHeapBlockSize is not None, \ "The heap block size was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines) oPageHeapManagerData = oProcess.foGetHeapManagerDataForAddress( uVerifierStopHeapBlockAddress, sType="page heap") if oPageHeapManagerData is None and sMessage == "block already freed": # There is a bug in application verifier where it reports the address of the DPH_HEAP_BLOCK structure instead of # the heap block in certain situations. In this case, "!heap" will not return any information and # oPageHeapManagerData will be None. We can still find the correct information though: oPageHeapManagerData = cPageHeapManagerData.foGetForProcessAndAllocationInformationStartAddress( oProcess, uVerifierStopHeapBlockAddress, ) uMemoryDumpStartAddress = None atxMemoryRemarks = [] if sMessage == "corrupted start stamp" and uCorruptedStamp == 0xABCDBBBA and not oPageHeapManagerData.oVirtualAllocation.bReserved: # Verifier sometimes reports this and I am not sure what is going on. Page heap will report that the heap block was # freed by the current function on the stack, while verifier reports this error almost immediately after that (from # that same stack). However, the start stamp reported in the verifier error is a valid value indicating the memory # was freed, so it makes no sense that it is reported as being corrupt. Also, there is no memory allocated at the # given address; it is only reserved, so it cannot actually contain a value. # I cannot think of any situation in which all this information is correct, and I suspect that verifier is # reporting the wrong kind of error. However, I have been unable to find a reliable repro for this to debug, so I # do not know exactly what is going on and what to report... (sHeapBlockAndOffsetId, sHeapBlockAndOffsetDescription) = \ oPageHeapManagerData.ftsGetIdAndDescriptionForAddress(uVerifierStopHeapBlockAddress) sBugTypeId = "UnknownVerifierError%s" % sHeapBlockAndOffsetId sBugDescription = "Application verifier reported a corrupt memory block, but no memory is allocated at the " \ "address provided. This suggests that the verfier report is incorrect. However, it is likely that the " \ "application does have some kind of memory corruption bug that triggers this report, but it is unknown " \ "what kind of bug that might be. If you can reliably reproduce this, please contact the author of BugId " \ "so this situation can be analyzed and BugId can be improved to report something more helpful here." sSecurityImpact = "Unknown: this type of bug has not been analyzed before" elif sMessage in ["corrupted start stamp", "corrupted end stamp"] \ and oPageHeapManagerData.uHeapBlockStartAddress != uVerifierStopHeapBlockAddress: # When the application attempts to free (heap pointer + offset), Verifier does not detect this and will assume # the application provided pointer is correct. This causes it to look for the start and end stamp in the wrong # location and report this bug as memory corruption. When the page heap data shows no signs of corruption, we # can special case it. (sHeapBlockAndOffsetId, sHeapBlockAndOffsetDescription) = \ oPageHeapManagerData.ftsGetIdAndDescriptionForAddress(uVerifierStopHeapBlockAddress) sBugTypeId = "MisalignedFree%s" % sHeapBlockAndOffsetId sBugDescription = "The application attempted to free memory using a pointer that was %s." % sHeapBlockAndOffsetDescription sSecurityImpact = "Unknown: this type of bug has not been analyzed before" elif sMessage == "block already freed": # |=========================================================== # |VERIFIER STOP 00000007: pid 0x1358: block already freed # | # | 02F71000 : Heap handle # | 152F016C : Heap block # | 000004C8 : Block size # | 00000000 : # |=========================================================== # |This verifier stop is not continuable. Process will be terminated # |when you use the `go' debugger command. # |=========================================================== sBugTypeId = "DoubleFree[%s]" % fsGetNumberDescription( oPageHeapManagerData.uHeapBlockSize) sBugDescription = "The application attempted to free a %d/0x%X byte heap block at address 0x%X twice" % \ (oPageHeapManagerData.uHeapBlockSize, oPageHeapManagerData.uHeapBlockSize, oPageHeapManagerData.uHeapBlockStartAddress) sSecurityImpact = "Potentially exploitable security issue, if the attacker can force the application to " \ "allocate memory between the two frees" elif sMessage == "corrupted heap pointer or using wrong heap": # |=========================================================== # |VERIFIER STOP 00000006: pid 0x144C: corrupted heap pointer or using wrong heap # | # | 077F1000 : Heap used in the call # | 43742FE0 : Heap block # | 00000020 : Block size # | 277F1000 : Heap owning the block # |=========================================================== # |This verifier stop is not continuable. Process will be terminated # |when you use the `go' debugger command. # |=========================================================== assert uVerifierStopHeapHandle is not None, \ "Missing 'Heap used in the call' value in the VERIFIER STOP message.\r\n%s" % "\r\n".join(asCdbOutput) assert uVerifierStopHeapBlockHandle is not None, \ "Missing 'Heap owning the block' value in the VERIFIER STOP message.\r\n%s" % "\r\n".join(asCdbOutput) if oPageHeapManagerData.uHeapBlockSize is None: sBugTypeId = "IncorrectHeap[?]" sBugDescription = "The application provided an incorrect heap handle (0x%X) for a heap block of unknown size " \ "near address 0x%X which belongs to another heap (handle 0x%X)" % \ (uVerifierStopHeapHandle, uVerifierStopHeapBlockAddress, uVerifierStopHeapBlockHandle) else: sBugTypeId = "IncorrectHeap[%s]" % (fsGetNumberDescription( oPageHeapManagerData.uHeapBlockSize)) sBugDescription = "The application provided an incorrect heap handle (0x%X) for a %d/0x%X byte heap block at " \ "address 0x%X which belongs to another heap (handle 0x%X)" % \ (uVerifierStopHeapHandle, oPageHeapManagerData.uHeapBlockSize, oPageHeapManagerData.uHeapBlockSize, \ oPageHeapManagerData.uHeapBlockStartAddress, uVerifierStopHeapBlockHandle) sSecurityImpact = "Unknown: this type of bug has not been analyzed before" else: if oCdbWrapper.bGenerateReportHTML: uMemoryDumpStartAddress = oPageHeapManagerData.uMemoryDumpStartAddress uMemoryDumpEndAddress = oPageHeapManagerData.uMemoryDumpEndAddress if oPageHeapManagerData: atxMemoryRemarks.extend( oPageHeapManagerData.fatxMemoryRemarks()) # Handle various VERIFIER STOP messages. if sMessage in ["corrupted start stamp", "corrupted end stamp"]: assert uCorruptionAddress is None, \ "We do not expect the corruption address to be provided in this VERIFIER STOP message\r\n%s" % \ "\r\n".join(asRelevantLines) if not oPageHeapManagerData.oHeapBlockHeader: # This makes no sense: there is no heap block header because the memory is really freed by verifier but left # reserved to prevent it from being reallocated. So the start and end stamp no longer exist... assert oPageHeapManagerData.oVirtualAllocation.bReserved, \ "This is even more unexpected." sBugTypeId = "UnknownVerifierError" uCorruptionStartAddress = None uCorruptionEndAddress = None else: sBugTypeId = "OOBW" if sMessage == "corrupted start stamp": uCorruptionStartAddress = oPageHeapManagerData.fuHeapBlockHeaderFieldAddress( "StartStamp") uCorruptionEndAddress = uCorruptionStartAddress + oPageHeapManagerData.fuHeapBlockHeaderFieldSize( "StartStamp") else: uCorruptionStartAddress = oPageHeapManagerData.fuHeapBlockHeaderFieldAddress( "EndStamp") uCorruptionEndAddress = uCorruptionStartAddress + oPageHeapManagerData.fuHeapBlockHeaderFieldSize( "EndStamp") elif sMessage in ["corrupted suffix pattern", "corrupted header"]: assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in this VERIFIER STOP message:\r\n%s" % \ "\r\n".join(asRelevantLines) # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start of the # heap block must be properly aligned. Bytes between the heap block and the end of the page are initialized to # 0xD0. Verifier has detected that one of the bytes changed value, which indicates an out-of-bounds write. BugId # will try to find all bytes that were changed: sBugTypeId = "OOBW" uCorruptionStartAddress = uCorruptionAddress uCorruptionEndAddress = uCorruptionStartAddress + 1 # We do not know the size, so assume one byte. elif sMessage == "corrupted infix pattern": assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \ "\r\n".join(asRelevantLines) # Page heap sometimes does not free a heap block immediately, but overwrites the bytes with 0xF0. Verifier has # detected that one of the bytes changed value, which indicates a write-after-free. BugId will try to find all # bytes that were changed: uCorruptionStartAddress = uCorruptionAddress uCorruptionEndAddress = uCorruptionStartAddress + 1 # We do not know the size, so assume one byte. sBugTypeId = "UAFW" else: raise AssertionError("Unhandled VERIFIER STOP message: %s" % sMessage) # We cannot trust the information in the VERIFIER STOP message: if you run the test "OutOfBounds Heap Write 1 -1 1" # (meaning you allocate 1 byte, write 1 byte at offset -1) you would expect it to report that the "end stamp" of # the heap block header was corrupted. HOWEVER IT INCORRECTLY REPORTS THAT THE START STAMP WAS CORRUPTED. So, only # if we cannot detect any corruption ourselves will we use the information we got from the VERIFIER STOP message. if oPageHeapManagerData.bCorruptionDetected: uCorruptionStartAddress = oPageHeapManagerData.uCorruptionStartAddress uCorruptionEndAddress = oPageHeapManagerData.uCorruptionEndAddress # If the corruption starts before or ends after the heap block, expand the memory dump to include the entire # corrupted region. if uCorruptionStartAddress and oCdbWrapper.bGenerateReportHTML: # Limit memory dump size uMemoryDumpStartAddress, uMemoryDumpSize = ftuLimitedAndAlignedMemoryDumpStartAddressAndSize( uCorruptionStartAddress, oProcess.uPointerSize, uCorruptionStartAddress, uMemoryDumpEndAddress - uMemoryDumpStartAddress) # Get a human readable description of the start offset of corruption relative to the heap block, where corruption # starting before or inside the heap block will be relative to the start, and corruption after it to the end. if uCorruptionStartAddress is None: (sHeapBlockId, sHeapBlockDescription) = \ oPageHeapManagerData.ftsGetIdAndDescription() sBugTypeId += sHeapBlockId sBugDescription = "Heap corruption reported but not detected in %s." % sHeapBlockDescription sSecurityImpact = "Unknown - Application Verifier reported this but it could not be confirmed." else: (sHeapBlockAndOffsetId, sHeapBlockAndOffsetDescription) = \ oPageHeapManagerData.ftsGetIdAndDescriptionForAddress(uCorruptionStartAddress) sBugTypeId += sHeapBlockAndOffsetId sBugDescription = "Heap corruption detected at 0x%X; %s." % ( uCorruptionStartAddress, sHeapBlockAndOffsetDescription) if uCorruptionStartAddress == oPageHeapManagerData.uHeapBlockEndAddress: sBugDescription += " This appears to be a classic buffer-overrun vulnerability." sSecurityImpact = "Potentially highly exploitable security issue." else: sSecurityImpact = "Potentially exploitable security issue, if the corruption is attacker controlled." if oPageHeapManagerData.bCorruptionDetected: sBugTypeId += oPageHeapManagerData.sCorruptionId oBugReport = cBugReport.foCreate(oProcess, sBugTypeId, sBugDescription, sSecurityImpact) if oCdbWrapper.bGenerateReportHTML and uMemoryDumpStartAddress: oBugReport.fAddMemoryDump( uMemoryDumpStartAddress, uMemoryDumpStartAddress + uMemoryDumpSize, "Memory at 0x%X" % uMemoryDumpStartAddress, ) oBugReport.atxMemoryRemarks.extend(atxMemoryRemarks) # Output the VERIFIER STOP message for reference if oCdbWrapper.bGenerateReportHTML: sVerifierStopMessageHTML = sBlockHTMLTemplate % { "sName": "VERIFIER STOP message", "sCollapsed": "Collapsed", "sContent": "<pre>%s</pre>" % "\r\n".join([ oCdbWrapper.fsHTMLEncode(s, uTabStop=8) for s in asRelevantLines ]) } oBugReport.asExceptionSpecificBlocksHTML.append( sVerifierStopMessageHTML) oBugReport.bRegistersRelevant = False return oBugReport
def cCdbWrapper_fCdbStdInOutThread(oCdbWrapper): # Get a lock on cdb so the "interrupt on timeout" thread does not attempt to interrupt cdb while we execute commands. oCdbWrapper.oCdbLock.acquire(); try: # Create a list of commands to set up event handling. The default for any exception not explicitly mentioned is to # be handled as a second chance exception. asExceptionHandlingCommands = ["sxd *"]; # request second chance debugger break for certain exceptions that indicate the application has a bug. for sCommand, axExceptions in daxExceptionHandling.items(): for xException in axExceptions: sException = isinstance(xException, str) and xException or ("0x%08X" % xException); asExceptionHandlingCommands.append("%s %s" % (sCommand, sException)); sExceptionHandlingCommands = ";".join(asExceptionHandlingCommands); # Read the initial cdb output related to starting/attaching to the first process. asIntialCdbOutput = oCdbWrapper.fasReadOutput(); if not oCdbWrapper.bCdbRunning: return; # Turn off prompt information as it is not useful most of the time, but can clutter output. oCdbWrapper.fasSendCommandAndReadOutput(".prompt_allow -dis -ea -reg -src -sym", bIsRelevantIO = False); if not oCdbWrapper.bCdbRunning: return; # Exception handlers need to be set up. oCdbWrapper.bExceptionHandlersHaveBeenSet = False; # Only fire fApplicationRunningCallback if the application was started for the first time or resumed after it was # paused to analyze an exception. bInitialApplicationRunningCallbackFired = False; bDebuggerNeedsToResumeAttachedProcesses = len(oCdbWrapper.auProcessIdsPendingAttach) > 0; bApplicationWasPausedToAnalyzeAnException = False; # An bug report will be created when needed; it is returned at the end oBugReport = None; # Memory can be allocated to be freed later in case the system has run low on memory when an analysis needs to be # performed. This is done only if dxBugIdConfig["uReserveRAM"] > 0. The memory is allocated at the start of # debugging, freed right before an analysis is performed and reallocated if the exception was not fatal. bReserveRAMAllocated = False; while asIntialCdbOutput or len(oCdbWrapper.auProcessIdsPendingAttach) + len(oCdbWrapper.auProcessIds) > 0: # If requested, reserve some memory in cdb that can be released later to make analysis under low memory conditions # more likely to succeed. if dxBugIdConfig["uReserveRAM"] and not bReserveRAMAllocated: uBitMask = 2 ** 31; while uBitMask >= 1: sBit = dxBugIdConfig["uReserveRAM"] & uBitMask and "A" or ""; if bReserveRAMAllocated: oCdbWrapper.fasSendCommandAndReadOutput("aS /c RAM .printf \"${RAM}{$RAM}%s\";" % sBit, bIsRelevantIO = False); elif sBit: oCdbWrapper.fasSendCommandAndReadOutput("aS RAM \"%s\";" % sBit, bIsRelevantIO = False); bReserveRAMAllocated = True; if not oCdbWrapper.bCdbRunning: return; uBitMask /= 2; # Discard any cached information about modules loaded in the current process, as this may be about to change # during execution of the application. oCdbWrapper.doModules_by_sCdbId = None; if asIntialCdbOutput: # First parse the intial output asCdbOutput = asIntialCdbOutput; asIntialCdbOutput = None; else: # Then attach to a process, or start or resume the application if ( # If we've just started the application or we've attached to all processes and are about to resume them... not bInitialApplicationRunningCallbackFired and len(oCdbWrapper.auProcessIdsPendingAttach) == 0 ) or ( # ... or if we've paused the application and are about to resume it ... bApplicationWasPausedToAnalyzeAnException ): # ...report that the application is about to start running. oCdbWrapper.fApplicationRunningCallback and oCdbWrapper.fApplicationRunningCallback(); bInitialApplicationRunningCallbackFired = True; # Mark the time when the application was resumed. oCdbWrapper.nApplicationResumeTime = time.clock(); # Release the lock on cdb so the "interrupt on timeout" thread can attempt to interrupt cdb while the # application is running. if len(oCdbWrapper.auProcessIdsPendingAttach) == 0: oCdbWrapper.oCdbLock.release(); try: asCdbOutput = oCdbWrapper.fasSendCommandAndReadOutput("g", bMayContainApplicationOutput = True); if not oCdbWrapper.bCdbRunning: return; finally: # Get a lock on cdb so the "interrupt on timeout" thread does not attempt to interrupt cdb while we execute # commands. if len(oCdbWrapper.auProcessIdsPendingAttach) == 0: oCdbWrapper.oCdbLock.acquire(); # Let the interrupt-on-timeout thread know that the application has been interrupted, so that when it detects # another timeout should be fired, it will try to interrupt the application again. oCdbWrapper.bInterruptPending = False; # Add the time since the application was resumed to the total application run time, and mark the application as # suspended by setting nApplicationResumeTime to None. TODO there is a race condition between here and in # oCdbWrapper.fxSetTimeout that must be addressed. oCdbWrapper.nApplicationRunTime += time.clock() - oCdbWrapper.nApplicationResumeTime; oCdbWrapper.nApplicationResumeTime = None; # Execute any pending timeout callbacks for xTimeout in oCdbWrapper.axTimeouts: (nTimeoutTime, fTimeoutCallback) = xTimeout; if nTimeoutTime <= oCdbWrapper.nApplicationRunTime: # This timeout should be fired. oCdbWrapper.axTimeouts.remove(xTimeout); fTimeoutCallback(); if oCdbWrapper.bGetDetailsHTML: # Save the current number of blocks of StdIO; if this exception is not relevant it can be used to remove all # blocks added while analyzing it. These blocks are not considered to contain useful information and removing # them can reduce the risk of OOM when irrelevant exceptions happens very often. The last block contains a # prompt, which will become the first analysis command's block, so it is not saved. uOriginalHTMLCdbStdIOBlocks = len(oCdbWrapper.asCdbStdIOBlocksHTML) - 1; # If cdb is attaching to a process, make sure it worked. for sLine in asCdbOutput: oFailedAttachMatch = re.match(r"^Cannot debug pid \d+, Win32 error 0n\d+\s*$", sLine); assert not oFailedAttachMatch, "Failed to attach to process!\r\n%s" % "\r\n".join(asCdbOutput); # Find out what event caused the debugger break asLastEventOutput = oCdbWrapper.fasSendCommandAndReadOutput(".lastevent"); if not oCdbWrapper.bCdbRunning: return; # Sample output: # |Last event: 3d8.1348: Create process 3:3d8 # | debugger time: Tue Aug 25 00:06:07.311 2015 (UTC + 2:00) # - or - # |Last event: c74.10e8: Exit process 4:c74, code 0 # | debugger time: Tue Aug 25 00:06:07.311 2015 (UTC + 2:00) bValidLastEventOutput = len(asLastEventOutput) == 2 and re.match(r"^\s*debugger time: .*$", asLastEventOutput[1]); oEventMatch = bValidLastEventOutput and re.match( "".join([ r"^Last event: ([0-9a-f]+)\.[0-9a-f]+: ", r"(?:", r"(Create|Exit) process [0-9a-f]+\:([0-9a-f]+)(?:, code [0-9a-f]+)?", r"|", r"(.*?) \- code ([0-9a-f]+) \(!*\s*(first|second) chance\s*!*\)", r"|", r"Hit breakpoint (\d+)", r")\s*$", ]), asLastEventOutput[0], re.I ); assert oEventMatch, "Invalid .lastevent output:\r\n%s" % "\r\n".join(asLastEventOutput); ( sProcessIdHex, sCreateExitProcess, sCreateExitProcessIdHex, sExceptionDescription, sExceptionCode, sChance, sBreakpointId, ) = oEventMatch.groups(); uProcessId = long(sProcessIdHex, 16); uExceptionCode = sExceptionCode and long(sExceptionCode, 16); uBreakpointId = sBreakpointId and long(sBreakpointId); if ( uExceptionCode in (STATUS_BREAKPOINT, STATUS_WAKE_SYSTEM_DEBUGGER) and uProcessId not in oCdbWrapper.auProcessIds ): # This is assumed to be the initial breakpoint after starting/attaching to the first process or after a new # process was created by the application. This assumption may not be correct, in which case the code needs to # be modifed to check the stack to determine if this really is the initial breakpoint. But that comes at a # performance cost, so until proven otherwise, the code is based on this assumption. sCreateExitProcess = "Create"; sCreateExitProcessIdHex = sProcessIdHex; if sCreateExitProcess: # Make sure the created/exited process is the current process. assert sProcessIdHex == sCreateExitProcessIdHex, "%s vs %s" % (sProcessIdHex, sCreateExitProcessIdHex); oCdbWrapper.fHandleCreateExitProcess(sCreateExitProcess, uProcessId); # If there are more processes to attach to, do so: if len(oCdbWrapper.auProcessIdsPendingAttach) > 0: asAttachToProcess = oCdbWrapper.fasSendCommandAndReadOutput(".attach 0n%d" % oCdbWrapper.auProcessIdsPendingAttach[0]); if not oCdbWrapper.bCdbRunning: return; else: # Set up exception handling if this has not been done yet. if not oCdbWrapper.bExceptionHandlersHaveBeenSet: # Note to self: when rewriting the code, make sure not to set up exception handling before the debugger has # attached to all processes. But do so before resuming the threads. Otherwise one or more of the processes # can end up having only one thread that has a suspend count of 2 and no amount of resuming will cause the # process to run. The reason for this is unknown, but if things are done in the correct order, this problem # is avoided. oCdbWrapper.bExceptionHandlersHaveBeenSet = True; oCdbWrapper.fasSendCommandAndReadOutput(sExceptionHandlingCommands, bIsRelevantIO = False); if not oCdbWrapper.bCdbRunning: return; # If the debugger attached to processes, mark that as done and resume threads in all processes. if bDebuggerNeedsToResumeAttachedProcesses: bDebuggerNeedsToResumeAttachedProcesses = False; for uProcessId in oCdbWrapper.auProcessIds: oCdbWrapper.fSelectProcessAndThread(uProcessId = uProcessId); if not oCdbWrapper.bCdbRunning: return; oCdbWrapper.fasSendCommandAndReadOutput("~*m", bIsRelevantIO = False); if not oCdbWrapper.bCdbRunning: return; if oCdbWrapper.bGetDetailsHTML: # As this is not relevant to the bug, remove the commands and their output from cdb IO and replace with a # description of the exception to remove clutter and reduce memory usage. oCdbWrapper.asCdbStdIOBlocksHTML = ( oCdbWrapper.asCdbStdIOBlocksHTML[0:uOriginalHTMLCdbStdIOBlocks] + # IO before analysis commands ["<span class=\"CDBIgnoredException\">%s process %d breakpoint.</span>" % (sCreateExitProcess, uProcessId)] + # Replacement for analysis commands oCdbWrapper.asCdbStdIOBlocksHTML[-1:] # Last block contains prompt and must be conserved. ); elif uExceptionCode == DBG_CONTROL_BREAK: # Debugging the application was interrupted and the application suspended to fire some timeouts. This exception # is not a bug and should be ignored. pass; elif ( uExceptionCode in [STATUS_WX86_BREAKPOINT, STATUS_BREAKPOINT] and dxBugIdConfig["bIgnoreFirstChanceBreakpoints"] and sChance == "first" ): pass; else: # Report that the application is paused for analysis... if oCdbWrapper.fExceptionDetectedCallback: if sBreakpointId: oCdbWrapper.fExceptionDetectedCallback(STATUS_BREAKPOINT, "A BugId breakpoint"); else: oCdbWrapper.fExceptionDetectedCallback(uExceptionCode, sExceptionDescription); # And potentially report that the application is resumed later... bApplicationWasPausedToAnalyzeAnException = True; # If available, free previously allocated memory to allow analysis in low memory conditions. if bReserveRAMAllocated: # This command is not relevant to the bug, so it is hidden in the cdb IO to prevent OOM. oCdbWrapper.fasSendCommandAndReadOutput("ad RAM;", bIsRelevantIO = False); bReserveRAMAllocated = False; # Create a bug report, if the exception is fatal. oCdbWrapper.oBugReport = cBugReport.foCreate(oCdbWrapper, uExceptionCode, sExceptionDescription, uBreakpointId); if not oCdbWrapper.bCdbRunning: return; if oCdbWrapper.oBugReport is not None: if dxBugIdConfig["bSaveDump"]: sDumpFileName = fsCreateFileName(oCdbWrapper.oBugReport.sId); sOverwrite = dxBugIdConfig["bOverwriteDump"] and "/o" or ""; oCdbWrapper.fasSendCommandAndReadOutput(".dump %s /ma \"%s.dmp\"" % (sOverwrite, sDumpFileName)); if not oCdbWrapper.bCdbRunning: return; break; if oCdbWrapper.bGetDetailsHTML: # Otherwise remove the analysis commands from the cdb IO and replace with a description of the exception to # remove clutter and reduce memory usage. oCdbWrapper.asCdbStdIOBlocksHTML = ( oCdbWrapper.asCdbStdIOBlocksHTML[0:uOriginalHTMLCdbStdIOBlocks] + # IO before analysis commands ["<span class=\"CDBIgnoredException\">Exception 0x%08X in process %d.</span>" % (uExceptionCode, uProcessId)] + oCdbWrapper.asCdbStdIOBlocksHTML[-1:] # Last block contains prompt and must be conserved. ); # Terminate cdb. oCdbWrapper.bCdbWasTerminatedOnPurpose = True; oCdbWrapper.fasSendCommandAndReadOutput("q"); finally: # Release the lock on cdb so the "interrupt on timeout" thread can notice cdb has terminated oCdbWrapper.oCdbLock.release(); assert not oCdbWrapper.bCdbRunning, "Debugger did not terminate when requested";
def cCdbWrapper_fbDetectAndReportVerifierErrors(oCdbWrapper, asCdbOutput): uErrorNumber = None uProcessId = None sMessage = None uVerifierStopHeapBlockStartAddress = None uVerifierStopHeapBlockSize = None uCorruptedStamp = None uCorruptionAddress = None asRelevantLines = [] for sLine in asCdbOutput: # Ignore exmpty lines if not sLine: continue # Look for the first VERIFIER STOP message and gather information if uErrorNumber is None: oErrorMessageMatch = re.match( r"^VERIFIER STOP ([0-9A-F]+): pid 0x([0-9A-F]+): (.*?)\s*$", sLine) if oErrorMessageMatch: sErrorNumber, sProcessId, sMessage = oErrorMessageMatch.groups( ) uErrorNumber = long(sErrorNumber, 16) uProcessId = long(sProcessId, 16) asRelevantLines.append(sLine) continue # A VERIFIER STOP message has been detected, gather what information verifier provides: oInformationMatch = re.match(r"^\t([0-9A-F]+) : (.*?)\s*$", sLine) if oInformationMatch: asRelevantLines.append(sLine) sValue, sDescription = oInformationMatch.groups() uValue = long(sValue, 16) sDescription = sDescription.lower() # Both "Corruption address" and "corruption address" are used :( if sDescription == "heap block": uVerifierStopHeapBlockStartAddress = uValue elif sDescription == "block size": uVerifierStopHeapBlockSize = uValue elif sDescription == "corrupted stamp": uCorruptedStamp = uValue elif sDescription == "corruption address": uCorruptionAddress = uValue else: assert sLine.strip().replace("=", "") == "", \ "Unknown VERIFIER STOP message line: %s\r\n%s" % (repr(sLine), "\r\n".join(asCdbOutput)) break else: assert uErrorNumber is None, \ "Detected the start of a VERIFIER STOP message but not the end\r\n%s" % "\r\n".join(asCdbOutput) return False if uErrorNumber == 0x303: # ======================================= # VERIFIER STOP 0000000000000303: pid 0xB2C: NULL handle passed as parameter. A valid handle must be used. # # 0000000000000000 : Not used. # 0000000000000000 : Not used. # 0000000000000000 : Not used. # 0000000000000000 : Not used. # # ======================================= # This is not interesting; do not report an error. return True assert uVerifierStopHeapBlockStartAddress is not None, \ "The heap block start address was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines) assert uVerifierStopHeapBlockSize is not None, \ "The heap block size was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines) oCorruptionDetector = cCorruptionDetector(oCdbWrapper) oPageHeapReport = cPageHeapReport.foCreate( oCdbWrapper, uVerifierStopHeapBlockStartAddress) if not oCdbWrapper.bCdbRunning: return None if oPageHeapReport: # Prefer page heap information over VERIFIER STOP info - the later has been known to be incorrect sometimes, for # instance when the application frees (heap pointer + offset), the VERIFIER STOP info will use that as the heap # block base, whereas the page heap report will correctly report (heap pointer) as the heap block base. uAllocationStartAddress = oPageHeapReport.uAllocationStartAddress uAllocationEndAddress = oPageHeapReport.uAllocationEndAddress # Check the page heap data near the heap block for signs of corruption: oPageHeapReport.fbCheckForCorruption(oCorruptionDetector) if not oCdbWrapper.bCdbRunning: return None if oPageHeapReport and oPageHeapReport.uBlockStartAddress: uHeapBlockStartAddress = oPageHeapReport.uBlockStartAddress uHeapBlockSize = oPageHeapReport.uBlockSize uHeapBlockEndAddress = uHeapBlockStartAddress + uHeapBlockSize else: uHeapBlockStartAddress = uVerifierStopHeapBlockStartAddress uHeapBlockSize = uVerifierStopHeapBlockSize uHeapBlockEndAddress = uVerifierStopHeapBlockStartAddress + uVerifierStopHeapBlockSize atxMemoryRemarks = [] if oCdbWrapper.bGenerateReportHTML: uMemoryDumpStartAddress = uHeapBlockStartAddress uMemoryDumpSize = uVerifierStopHeapBlockSize if oPageHeapReport: atxMemoryRemarks.extend(oPageHeapReport.fatxMemoryRemarks()) # Handle various VERIFIER STOP messages. sBugDescription = None if sMessage in ["corrupted start stamp", "corrupted end stamp"]: assert uCorruptionAddress is None, \ "We do not expect the corruption address to be provided in this VERIFIER STOP message\r\n%s" % \ "\r\n".join(asRelevantLines) if not oCorruptionDetector.bCorruptionDetected and uHeapBlockStartAddress != uVerifierStopHeapBlockStartAddress: # When the application attempts to free (heap pointer + offset), Verifier does not detect this and will assume # the application provided pointer is correct. This causes it to look for the start and end stamp in the wrong # location and report this bug as memory corruption. When the page heap data shows no signs of corruption, we # can special case it. iFreeAtOffset = uVerifierStopHeapBlockStartAddress - uHeapBlockStartAddress sBugTypeId = "MisalignedFree[%s]%s" % (fsGetNumberDescription( uHeapBlockSize), fsGetOffsetDescription(iFreeAtOffset)) sOffsetBeforeOrAfter = iFreeAtOffset < 0 and "before" or "after" sBugDescription = "The application attempted to free memory using a pointer that was %d/0x%X bytes %s a " \ "%d/0x%X byte heap block at address 0x%X" % (abs(iFreeAtOffset), abs(iFreeAtOffset), \ sOffsetBeforeOrAfter, uHeapBlockSize, uHeapBlockSize, uHeapBlockStartAddress) sSecurityImpact = "Unknown: this type of bug has not been analyzed before" else: sBugTypeId = "OOBW[%s]" % (fsGetNumberDescription(uHeapBlockSize)) assert oCorruptionDetector.bCorruptionDetected, \ "Cannot find any sign of corruption" elif sMessage == "corrupted suffix pattern": assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in this VERIFIER STOP message:\r\n%s" % \ "\r\n".join(asRelevantLines) # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start of the # heap block must be properly aligned. Bytes between the heap block and the end of the page are initialized to # 0xD0. Verifier has detected that one of the bytes changed value, which indicates an out-of-bounds write. BugId # will try to find all bytes that were changed: sBugTypeId = "OOBW[%s]" % (fsGetNumberDescription(uHeapBlockSize)) assert oCorruptionDetector.bCorruptionDetected, \ "Cannot find any sign of corruption" elif sMessage == "corrupted infix pattern": assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \ "\r\n".join(asRelevantLines) # Page heap sometimes does not free a heap block immediately, but overwrites the bytes with 0xF0. Verifier has # detected that one of the bytes changed value, which indicates a write-after-free. BugId will try to find all # bytes that were changed: sBugTypeId = "UAFW[%s]" % (fsGetNumberDescription(uHeapBlockSize)) # TODO add these checks to cPaheHeapReport if possible. oCorruptionDetector.fbDetectCorruption( uHeapBlockStartAddress, [0xF0 for x in xrange(uHeapBlockSize)]) assert oCorruptionDetector.bCorruptionDetected, \ "Cannot find any sign of corruption" else: sBugTypeId = "HeapCorrupt[%s]" % ( fsGetNumberDescription(uHeapBlockSize)) # sBugDescription is not set if this is a memory corruption # See if we have a better idea of where the corruption started and ended: if oCorruptionDetector.bCorruptionDetected: uCorruptionStartAddress = oCorruptionDetector.uCorruptionStartAddress uCorruptionEndAddress = oCorruptionDetector.uCorruptionEndAddress uCorruptionSize = uCorruptionEndAddress - uCorruptionStartAddress if oCdbWrapper.bGenerateReportHTML: atxMemoryRemarks.extend(oCorruptionDetector.fatxMemoryRemarks()) # I believe verifier may not check various memory areas in order from lowest to highest address. So it may not report # the byte that got corrupted with the lowest address; therefore it may not make sense to compare what verifier # reported with what our corruption detector found: # assert uCorruptionAddress is None or uCorruptionAddress == oCorruptionDetector.uCorruptionStartAddress, \ # "Verifier reported corruption at address 0x%X but BugId detected corruption at address 0x%X\r\n%s" % \ # (uCorruptionAddress, oCorruptionDetector.uCorruptionStartAddress, "\r\n".join(asRelevantLines)); bCorruptionDetected = True elif uCorruptionAddress: if oCdbWrapper.bGenerateReportHTML: atxMemoryRemarks.append( ("Corrupted memory", uCorruptionAddress, None)) uCorruptionStartAddress = uCorruptionAddress uCorruptionEndAddress = uCorruptionAddress bCorruptionDetected = True else: bCorruptionDetected = False if bCorruptionDetected: # If the corruption starts before or ends after the heap block, expand the memory dump to include the entire # corrupted region. if oCdbWrapper.bGenerateReportHTML: if uCorruptionStartAddress < uMemoryDumpStartAddress: uMemoryDumpSize += uMemoryDumpStartAddress - uCorruptionStartAddress uMemoryDumpStartAddress = uCorruptionStartAddress if uCorruptionEndAddress < uMemoryDumpStartAddress + uMemoryDumpSize: uMemoryDumpSize += uCorruptionEndAddress - ( uMemoryDumpStartAddress + uMemoryDumpSize) # Get a human readable description of the start offset of corruption relative to the heap block, where corruption # starting before or inside the heap block will be relative to the start, and corruption after it to the end. uCorruptionStartOffset = uCorruptionStartAddress - uHeapBlockStartAddress if uCorruptionStartOffset >= uHeapBlockSize: uCorruptionStartOffset -= uHeapBlockSize sCorruptionStartOffsetDescription = "%d/0x%X bytes beyond" % ( uCorruptionStartOffset, uCorruptionStartOffset) sBugTypeId += fsGetOffsetDescription(uCorruptionStartOffset) elif uCorruptionStartOffset > 0: sCorruptionStartOffsetDescription = "%d/0x%X bytes into" % ( uCorruptionStartOffset, uCorruptionStartOffset) sBugTypeId += "@%s" % fsGetNumberDescription( uCorruptionStartOffset) else: sCorruptionStartOffsetDescription = "%d/0x%X bytes before" % ( -uCorruptionStartOffset, -uCorruptionStartOffset) sBugTypeId += fsGetOffsetDescription(uCorruptionStartOffset) sBugDescription = "Page heap detected heap corruption at 0x%X; %s a %d/0x%X byte heap block at address 0x%X" % \ (uCorruptionStartAddress, sCorruptionStartOffsetDescription, uHeapBlockSize, uHeapBlockSize, uHeapBlockStartAddress) # If we detected corruption by scanning certain bytes in the applications memory, make sure this matches what # verifier reported and save all bytes that were affected: so far, we only saved the bytes that had an unexpected # value, but there is a chance that a byte was overwritten with the same value it has before, in which case it was # not saved. This can be detect if it is surrounded by bytes that did change value. This code reads the value of all # bytes between the first and last byte that we detected was corrupted: if oCorruptionDetector.bCorruptionDetected: asCorruptedBytes = oCorruptionDetector.fasCorruptedBytes() sBugDescription += " The following byte values were written to the corrupted area: %s." % ", ".join( asCorruptedBytes) sBugTypeId += oCorruptionDetector.fsCorruptionId() or "" sSecurityImpact = "Potentially exploitable security issue, if the corruption is attacker controlled" else: assert sBugDescription, \ "sBugDescription should have been set" oBugReport = cBugReport.foCreate(oCdbWrapper, sBugTypeId, sBugDescription, sSecurityImpact) if not oCdbWrapper.bCdbRunning: return None if oCdbWrapper.bGenerateReportHTML: oBugReport.atxMemoryDumps.append(("Memory near heap block at 0x%X" % uMemoryDumpStartAddress, \ uMemoryDumpStartAddress, uMemoryDumpSize)) oBugReport.atxMemoryRemarks.extend(atxMemoryRemarks) # Output the VERIFIER STOP message for reference if oCdbWrapper.bGenerateReportHTML: sVerifierStopMessageHTML = sBlockHTMLTemplate % { "sName": "VERIFIER STOP message", "sCollapsed": "Collapsed", "sContent": "<pre>%s</pre>" % "\r\n".join([ oCdbWrapper.fsHTMLEncode(s, uTabStop=8) for s in asRelevantLines ]) } oBugReport.asExceptionSpecificBlocksHTML.append( sVerifierStopMessageHTML) # Output the page heap information for reference if oPageHeapReport: sPageHeapOutputHTML = sBlockHTMLTemplate % { "sName": "Page heap output for heap block at 0x%X" % uHeapBlockStartAddress, "sCollapsed": "Collapsed", "sContent": "<pre>%s</pre>" % "\r\n".join([ oCdbWrapper.fsHTMLEncode(s, uTabStop=8) for s in oPageHeapReport.asPageHeapOutput ]) } oBugReport.asExceptionSpecificBlocksHTML.append( sPageHeapOutputHTML) oBugReport.bRegistersRelevant = False oCdbWrapper.oBugReport = oBugReport return True
def cCdbWrapper_fbDetectAndReportVerifierErrors(oCdbWrapper, asCdbOutput): uErrorNumber = None uProcessId = None sMessage = None uHeapBlockAddress = None uHeapBlockSize = None uCorruptedStamp = None uCorruptionAddress = None asRelevantLines = [] for sLine in asCdbOutput: # Ignore exmpty lines if not sLine: continue # Look for the first VERIFIER STOP message and gather information if uErrorNumber is None: oErrorMessageMatch = re.match( r"^VERIFIER STOP ([0-9A-F]+): pid 0x([0-9A-F]+): (.*?)\s*$", sLine) if oErrorMessageMatch: sErrorNumber, sProcessId, sMessage = oErrorMessageMatch.groups( ) uErrorNumber = long(sErrorNumber, 16) uProcessId = long(sProcessId, 16) asRelevantLines.append(sLine) continue asRelevantLines.append(sLine) # A VERIFIER STOP message has been detected, gather what information verifier provides: oInformationMatch = re.match(r"\t([0-9A-F]+) : (.*?)\s*$", sLine) if oInformationMatch: sValue, sDescription = oInformationMatch.groups() uValue = long(sValue, 16) if sDescription == "Heap block": uHeapBlockAddress = uValue elif sDescription == "Block size": uHeapBlockSize = uValue elif sDescription == "Corrupted stamp": uCorruptedStamp = uValue elif sDescription == "corruption address": uCorruptionAddress = uValue else: assert sLine.strip().replace("=", "") == "", \ "Unknown VERIFIER STOP message line: %s" % repr(sLine) break else: assert uErrorNumber is None, \ "Detected the start of a VERIFIER STOP message but not the end\r\n%s" % "\r\n".join(asLines) return False uHeapBlockEndAddress = uHeapBlockAddress + uHeapBlockSize uHeapPageEndAddress = (uHeapBlockEndAddress | 0xFFF) + 1 assert uHeapPageEndAddress >= uHeapBlockEndAddress, \ "The heap block at 0x%X is expected to end at 0x%X, but the page is expected to end at 0x%X, which is impossible." % \ (uHeapBlockAddress, uHeapBlockEndAddress, uHeapPageEndAddress) oCorruptionDetector = cCorruptionDetector(oCdbWrapper) # End of VERIFIER STOP message; report a bug. if sMessage in ["corrupted start stamp", "corrupted end stamp"]: assert uCorruptionAddress is None, \ "We do not expect the corruption address to be provided in the VERIFIER STOP message" sBugTypeId = "OOBW" # Both the start and end stamp may have been corrupted and it appears that a bug in verifier causes a corruption # of the end stamp to be reported as a corruption of the start stamp, so we'll check both for unexpected values: uPointerSize = oCdbWrapper.fuGetValue("$ptrsize") if not oCdbWrapper.bCdbRunning: return # https://msdn.microsoft.com/en-us/library/ms220938(v=vs.90).aspx uEndStampAddress = uHeapBlockAddress - uPointerSize # ULONG with optional padding to pointer size if uPointerSize == 8: # End stamp comes immediately after other header values oCorruptionDetector.fDetectCorruption(uEndStampAddress, 0, 0, 0, 0, [0xBA, 0xBB], 0xBB, 0xBA, 0xDC) else: oCorruptionDetector.fDetectCorruption(uEndStampAddress, [0xBA, 0xBB], 0xBB, 0xBA, 0xDC) uStackTraceAddress = uEndStampAddress - uPointerSize # PVOID uFreeQueueAddress = uStackTraceAddress - 2 * uPointerSize # LIST_ENTRY uActualSizeAddress = uFreeQueueAddress - uPointerSize # size_t uRequestedSizeAddress = uActualSizeAddress - uPointerSize # size_t uHeapAddressAddress = uRequestedSizeAddress - uPointerSize # PVOID uStartStampAddress = uHeapAddressAddress - uPointerSize # ULONG with optional padding to pointer size if uPointerSize == 8: # End stamp comes immediately before other header values oCorruptionDetector.fDetectCorruption(uStartStampAddress, [0xBA, 0xBB], 0xBB, 0xCD, 0xAB, 0, 0, 0, 0) else: oCorruptionDetector.fDetectCorruption(uStartStampAddress, [0xBA, 0xBB], 0xBB, 0xCD, 0xAB) assert oCorruptionDetector.bCorruptionDetected, \ "Cannot find any sign of corruption" elif sMessage == "corrupted suffix pattern": assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \ "\r\n" % (asRelevantLines) # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start of the # heap block must be properly aligned. Bytes between the heap block and the end of the page are initialized to # 0xD0. Verifier has detected that one of the bytes changed value, which indicates an out-of-bounds write. BugId # will try to find all bytes that were changed: sBugTypeId = "OOBW" uPaddingSize = uHeapPageEndAddress - uHeapBlockEndAddress oCorruptionDetector.fDetectCorruption( uHeapBlockEndAddress, *[0xD0 for x in xrange(uPaddingSize)]) assert oCorruptionDetector.bCorruptionDetected, \ "Cannot find any sign of corruption" elif sMessage == "corrupted infix pattern": assert uCorruptionAddress is not None, \ "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \ "\r\n" % (asRelevantLines) # Page heap sometimes does not free a heap block immediately, but overwrites the bytes with 0xF0. Verifier has # detected that one of the bytes changed value, which indicates a write-after-free. BugId will try to find all # bytes that were changed: sBugTypeId = "UAFW" oCorruptionDetector.fDetectCorruption( uHeapBlockAddress, *[0xF0 for x in xrange(uHeapBlockSize)]) assert oCorruptionDetector.bCorruptionDetected, \ "Cannot find any sign of corruption" else: sBugTypeId = "HeapCorrupt" if uHeapBlockSize is not None: sBugTypeId += "[%s]" % (fsGetNumberDescription(uHeapBlockSize)) if oCorruptionDetector.bCorruptionDetected: if uCorruptionAddress is None: uCorruptionAddress = oCorruptionDetector.uCorruptionStartAddress else: assert uCorruptionAddress == oCorruptionDetector.uCorruptionStartAddress, \ "Verifier reported corruption at address 0x%X but BugId detected corruption at address 0x%X" % \ (uCorruptionAddress, oCorruptionDetector.uCorruptionStartAddress) if uCorruptionAddress is not None: sMessage = "heap corruption" uCorruptionOffset = uCorruptionAddress - uHeapBlockAddress if uCorruptionOffset >= uHeapBlockSize: uCorruptionOffset -= uHeapBlockSize sOffsetDescription = "%d/0x%X bytes beyond" % (uCorruptionOffset, uCorruptionOffset) sBugTypeId += fsGetOffsetDescription(uCorruptionOffset) elif uCorruptionOffset > 0: sOffsetDescription = "%d/0x%X bytes into" % (uCorruptionOffset, uCorruptionOffset) sBugTypeId += "@%s" % fsGetNumberDescription(uCorruptionOffset) else: sOffsetDescription = "%d/0x%X bytes before" % (-uCorruptionOffset, -uCorruptionOffset) sBugTypeId += fsGetOffsetDescription(uCorruptionOffset) sBugDescription = "Page heap detected %s at 0x%X; %s a %d/0x%X byte heap block at address 0x%X" % \ (sMessage, uCorruptionAddress, sOffsetDescription, uHeapBlockSize, uHeapBlockSize, uHeapBlockAddress) uRelevantAddress = uCorruptionAddress else: sBugDescription = "Page heap detected %s in a %d/0x%X byte heap block at address 0x%X." % \ (sMessage, uHeapBlockSize, uHeapBlockSize, uHeapBlockAddress) uRelevantAddress = uHeapBlockAddress # If we detected corruption by scanning certain bytes in the applications memory, make sure this matches what # verifier reported and save all bytes that were affected: so far, we only saved the bytes that had an unexpected # value, but there is a chance that a byte was overwritten with the same value it has before, in which case it was # not saved. This can be detect if it is surrounded by bytes that did change value. This code reads the value of all # bytes between the first and last byte that we detected was corrupted: asCorruptedBytes = oCorruptionDetector.fasCorruptedBytes() if asCorruptedBytes: sBugDescription += " The following byte values were written to the corrupted area: %s." % ", ".join( asCorruptedBytes) sBugTypeId += oCorruptionDetector.fsCorruptionId() or "" sSecurityImpact = "Potentially exploitable security issue, if the corruption is attacker controlled" oCdbWrapper.oBugReport = cBugReport.foCreate(oCdbWrapper, sBugTypeId, sBugDescription, sSecurityImpact) oCdbWrapper.oBugReport.duRelevantAddress_by_sDescription \ ["memory corruption at 0x%X" % uRelevantAddress] = uRelevantAddress oCdbWrapper.oBugReport.bRegistersRelevant = False return True