// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome_frame/buggy_bho_handling.h" #include #include "base/logging.h" #include "base/process/memory.h" #include "base/win/scoped_comptr.h" #include "chrome_frame/exception_barrier.h" #include "chrome_frame/function_stub.h" #include "chrome_frame/pin_module.h" #include "chrome_frame/utils.h" #include "chrome_frame/vtable_patch_manager.h" namespace buggy_bho { base::ThreadLocalPointer BuggyBhoTls::s_bad_object_tls_; struct ModuleAndVersion { const char* module_name_; const uint32 major_version_; const uint32 minor_version_; }; const ModuleAndVersion kBuggyModules[] = { { "askbar.dll", 4, 1 }, // troublemaker: 4.1.0.5. { "gbieh.dll", 3, 8 }, // troublemaker: 3.8.14.12 { "gbiehcef.dll", 3, 8 }, // troublemaker: 3.8.11.23 { "alot.dll", 2, 5 }, // troublemaker: 2.5.12000.509 { "ctbr.dll", 5, 1 }, // troublemaker: 5.1.0.95 { "srchbxex.dll", 1, 2 }, // troublemaker: 1.2.123.0 { "iedvtool32.dll", 8, 0 }, // troublemaker: 8.0.0.4 { "mst164.dll", 9, 1 }, // troublemaker: 9.1.3700.1 { "deposit_ie_com.dll", 0, 1 }, // troublemaker: 0.1.0.72 { "rpshell32.dll", 6, 0 }, // troublemaker: 6.0.6000.1389 { "msgsres.dll", 6, 0 }, // troublemaker: 6.0.6000.1389 { "limewireinttb.dll", 4, 1 }, // troublemaker: 4.1.1.1000 { "pxsecure.dll", 3, 0 }, // troublemaker: 3.0.5.220 // These BHOs seem to be out of the same buggy BHO factory { "tbabso.dll", 4, 5 }, // troublemaker: 4.5.156.0 { "tbabs0.dll.dll", 4, 5 }, // troublemaker: 4.5.156.0 { "tbbes0.dll", 4, 5 }, // troublemaker: 4.5.153.0 { "tbfre0.dll", 4, 5 }, // troublemaker: 4.5.181.1 { "tbmypl.dll", 4, 5 }, // troublemaker: 4.5.181.3 { "tbmul1.dll", 4, 5 }, // troublemaker: 4.5.181.1 { "tbdow1.dll", 4, 5 }, // troublemaker: 4.5.167.0 { "tbfree.dll", 4, 5 }, // troublemaker: 4.5.178.0 // Viruses? { "msgsc2.dll", 0xffff, 0xffff }, // troublemaker: 1.2.3000.1 { "essclis.dll", 0xffff, 0xffff }, // troublemaker: 4.3.1800.2 { "snagwb.dll", 0xffff, 0xffff }, // troublemaker: 2.6.0.28 }; bool IsBuggyBho(HMODULE mod) { DCHECK(mod); char path[MAX_PATH * 2] = {0}; ::GetModuleFileNameA(mod, path, arraysize(path)); const char* file_name = ::PathFindFileNameA(path); for (size_t i = 0; i < arraysize(kBuggyModules); ++i) { if (lstrcmpiA(file_name, kBuggyModules[i].module_name_) == 0) { uint32 version = 0; GetModuleVersion(mod, &version, NULL); const ModuleAndVersion& buggy = kBuggyModules[i]; if (HIWORD(version) < buggy.major_version_ || (HIWORD(version) == buggy.major_version_ && LOWORD(version) <= buggy.minor_version_)) { return true; } } } return false; } BuggyBhoTls::BuggyBhoTls() : patched_(false) { DCHECK(s_bad_object_tls_.Get() == NULL); s_bad_object_tls_.Set(this); } BuggyBhoTls::~BuggyBhoTls() { DCHECK(BuggyBhoTls::GetInstance() == this); s_bad_object_tls_.Set(NULL); } void BuggyBhoTls::AddBuggyObject(IDispatch* obj) { bad_objects_.push_back(obj); } bool BuggyBhoTls::ShouldSkipInvoke(IDispatch* obj) const { DCHECK(web_browser2_ != NULL); if (IsChromeFrameDocument(web_browser2_)) { return std::find(bad_objects_.begin(), bad_objects_.end(), obj) != bad_objects_.end(); } return false; } // static BuggyBhoTls* BuggyBhoTls::GetInstance() { BuggyBhoTls* tls_instance = s_bad_object_tls_.Get(); if (!tls_instance) { tls_instance = new BuggyBhoTls(); DCHECK(s_bad_object_tls_.Get() != NULL); } return tls_instance; } // static void BuggyBhoTls::DestroyInstance() { BuggyBhoTls* tls_instance = s_bad_object_tls_.Get(); if (tls_instance) { delete tls_instance; DCHECK(s_bad_object_tls_.Get() == NULL); } } HRESULT BuggyBhoTls::PatchBuggyBHOs(IWebBrowser2* browser) { if (patched_) return S_FALSE; DCHECK(browser); DCHECK(web_browser2_ == NULL); base::win::ScopedComPtr cpc; HRESULT hr = cpc.QueryFrom(browser); if (SUCCEEDED(hr)) { const GUID sinks[] = { DIID_DWebBrowserEvents2, DIID_DWebBrowserEvents }; for (size_t i = 0; i < arraysize(sinks); ++i) { base::win::ScopedComPtr cp; cpc->FindConnectionPoint(sinks[i], cp.Receive()); if (cp) { base::win::ScopedComPtr connections; cp->EnumConnections(connections.Receive()); if (connections) { CONNECTDATA cd = {0}; DWORD fetched = 0; while (connections->Next(1, &cd, &fetched) == S_OK && fetched) { PatchIfBuggy(cd.pUnk, sinks[i]); cd.pUnk->Release(); fetched = 0; } } } } } patched_ = true; web_browser2_ = browser; return hr; } bool BuggyBhoTls::PatchIfBuggy(IUnknown* unk, const IID& diid) { DCHECK(unk); PROC* methods = *reinterpret_cast(unk); HMODULE mod = base::GetModuleFromAddress(methods[0]); if (!IsBuggyBho(mod)) return false; base::win::ScopedComPtr disp; HRESULT hr = unk->QueryInterface(diid, reinterpret_cast(disp.Receive())); if (FAILED(hr)) // Sometimes only IDispatch QI is supported hr = disp.QueryFrom(unk); DCHECK(SUCCEEDED(hr)); if (SUCCEEDED(hr)) { const int kInvokeIndex = 6; DCHECK(static_cast(disp) == unk); if (SUCCEEDED(PatchInvokeMethod(&methods[kInvokeIndex]))) { AddBuggyObject(disp); } } return false; } // static STDMETHODIMP BuggyBhoTls::BuggyBhoInvoke(InvokeFunc original, IDispatch* me, DISPID dispid, REFIID riid, LCID lcid, WORD flags, DISPPARAMS* params, VARIANT* result, EXCEPINFO* ei, UINT* err) { DVLOG(1) << __FUNCTION__; DCHECK(BuggyBhoTls::GetInstance()) << "You must first have an instance of BuggyBhoTls on this thread"; if (BuggyBhoTls::GetInstance() && BuggyBhoTls::GetInstance()->ShouldSkipInvoke(me)) { // Ignore this call and avoid the bug. // TODO(tommi): Maybe we should check a specific list of DISPIDs too? return S_OK; } // No need to report crashes in those known-to-be-buggy DLLs. ExceptionBarrierReportOnlyModule barrier; return original(me, dispid, riid, lcid, flags, params, result, ei, err); } // static HRESULT BuggyBhoTls::PatchInvokeMethod(PROC* invoke) { CCritSecLock lock(_pAtlModule->m_csStaticDataInitAndTypeInfo.m_sec, true); FunctionStub* stub = FunctionStub::FromCode(*invoke); if (stub) return S_FALSE; DWORD flags = 0; if (!::VirtualProtect(invoke, sizeof(PROC), PAGE_EXECUTE_READWRITE, &flags)) return AtlHresultFromLastError(); HRESULT hr = S_OK; stub = FunctionStub::Create(reinterpret_cast(*invoke), BuggyBhoInvoke); if (!stub) { hr = E_OUTOFMEMORY; } else { if (!vtable_patch::internal::ReplaceFunctionPointer( reinterpret_cast(invoke), stub->code(), reinterpret_cast(stub->argument()))) { hr = E_UNEXPECTED; FunctionStub::Destroy(stub); } else { chrome_frame::PinModule(); // No backing out now. ::FlushInstructionCache(::GetCurrentProcess(), invoke, sizeof(PROC)); } } ::VirtualProtect(invoke, sizeof(PROC), flags, &flags); return hr; } } // end namespace buggy_bho