blob: 27feaf4bb797a5778ec7f5436687fb22a84ba7c8 [file] [log] [blame]
// Copyright (c) 2010 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/browser/enumerate_modules_model_win.h"
#include <Tlhelp32.h>
#include <wintrust.h>
#include "app/l10n_util.h"
#include "app/win_util.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/file_path.h"
#include "base/file_version_info_win.h"
#include "base/scoped_handle.h"
#include "base/sha2.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/browser/net/service_providers_win.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_service.h"
#include "grit/generated_resources.h"
// The period of time (in milliseconds) to wait until checking to see if any
// incompatible modules exist.
static const int kModuleCheckDelayMs = 60 * 1000;
// A sort method that sorts by ModuleType ordinal (loaded module at the top),
// then by full name (including path).
static bool ModuleSort(const ModuleEnumerator::Module& a,
const ModuleEnumerator::Module& b) {
if (a.type != b.type)
return a.type < b.type;
if (a.location == b.location)
return a.name < b.name;
return a.location < b.location;
}
namespace {
// Used to protect the LoadedModuleVector which is accessed
// from both the UI thread and the FILE thread.
Lock* lock = NULL;
}
// The browser process module blacklist. This lists all modules that are known
// to cause compatibility issues within the browser process. When adding to this
// list, make sure that all paths are lower-case, in long pathname form, end
// with a slash and use environments variables (or just look at one of the
// comments below and keep it consistent with that). When adding an entry with
// an environment variable not currently used in the list below, make sure to
// update the list in PreparePathMappings. Filename, Description/Signer, and
// Location must be entered as hashes (see GenerateHash). Filename is mandatory.
// Entries without any Description, Signer info, or Location will never be
// marked as confirmed bad (only as suspicious).
const ModuleEnumerator::BlacklistEntry ModuleEnumerator::kModuleBlacklist[] = {
// NOTE: Please keep this list sorted by dll name, then location.
// foldersizecolumn.dll.
{"5ec91bd7", "", "", "", "", NONE},
// idmmbc.dll, "%programfiles%\\internet download manager\\", "Tonec Inc.".
// See: http://crbug.com/26892/.
{"b8dce5c3", "94541bf5", "d33ad640", "", "", NONE},
// imon.dll. See: http://crbug.com/21715.
{"8f42f22e", "", "", "", "", NONE},
// is3lsp.dll. See: http://crbug.com/26892.
{"7ffbdce9", "", "", "", "", NONE},
// nvlsp.dll. See: http://crbug.com/22083.
{"37f907e2", "", "", "", "", NONE},
// nvshell.dll. See: http://crbug.com/3269.
{"9290318f", "", "", "", "", NONE},
// securenet.dll. See: http://crbug.com/5165.
{"9b266e1c", "", "", "", "", NONE},
// sgprxy.dll.
{"005965ea", "", "", "", "", NONE},
// vaproxyd.dll. See: http://crbug.com/42445.
{"0a1c7f81", "", "", "", "", NONE},
// vlsp.dll. See: http://crbug.com/22826.
{"2e4eb93d", "", "", "", "", NONE},
};
// Generates an 8 digit hash from the input given.
static void GenerateHash(const std::string& input, std::string* output) {
if (input.empty()) {
*output = "";
return;
}
uint8 hash[4];
base::SHA256HashString(input, hash, sizeof(hash));
*output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
}
// -----------------------------------------------------------------------------
// static
void ModuleEnumerator::NormalizeModule(Module* module) {
string16 path = module->location;
if (!win_util::ConvertToLongPath(path, &module->location))
module->location = path;
module->location = l10n_util::ToLower(module->location);
// Location contains the filename, so the last slash is where the path
// ends.
size_t last_slash = module->location.find_last_of(L"\\");
if (last_slash != string16::npos) {
module->name = module->location.substr(last_slash + 1);
module->location = module->location.substr(0, last_slash + 1);
} else {
module->name = module->location;
module->location.clear();
}
// Some version strings have things like (win7_rtm.090713-1255) appended
// to them. Remove that.
size_t first_space = module->version.find_first_of(L" ");
if (first_space != string16::npos)
module->version = module->version.substr(0, first_space);
module->normalized = true;
}
// static
ModuleEnumerator::ModuleStatus ModuleEnumerator::Match(
const ModuleEnumerator::Module& module,
const ModuleEnumerator::BlacklistEntry& blacklisted) {
// All modules must be normalized before matching against blacklist.
DCHECK(module.normalized);
// Filename is mandatory and version should not contain spaces.
DCHECK(strlen(blacklisted.filename) > 0);
DCHECK(!strstr(blacklisted.version_from, " "));
DCHECK(!strstr(blacklisted.version_to, " "));
std::string filename_hash, location_hash;
GenerateHash(WideToUTF8(module.name), &filename_hash);
GenerateHash(WideToUTF8(module.location), &location_hash);
// Filenames are mandatory. Location is mandatory if given.
if (filename_hash == blacklisted.filename &&
(std::string(blacklisted.location).empty() ||
location_hash == blacklisted.location)) {
// We have a name match against the blacklist (and possibly location match
// also), so check version.
scoped_ptr<Version> module_version(
Version::GetVersionFromString(module.version));
scoped_ptr<Version> version_min(
Version::GetVersionFromString(blacklisted.version_from));
scoped_ptr<Version> version_max(
Version::GetVersionFromString(blacklisted.version_to));
bool version_ok = !version_min.get() && !version_max.get();
if (!version_ok) {
bool too_low = version_min.get() &&
(!module_version.get() ||
module_version->CompareTo(*version_min.get()) < 0);
bool too_high = version_max.get() &&
(!module_version.get() ||
module_version->CompareTo(*version_max.get()) > 0);
version_ok = !too_low && !too_high;
}
if (version_ok) {
// At this point, the names match and there is no version specified
// or the versions also match.
std::string desc_or_signer(blacklisted.desc_or_signer);
std::string signer_hash, description_hash;
GenerateHash(WideToUTF8(module.digital_signer), &signer_hash);
GenerateHash(WideToUTF8(module.description), &description_hash);
// If signatures match, we have a winner.
if (!desc_or_signer.empty() && signer_hash == desc_or_signer)
return CONFIRMED_BAD;
// If description matches and location, then we also have a match.
if (!desc_or_signer.empty() && description_hash == desc_or_signer &&
!location_hash.empty() && location_hash == blacklisted.location) {
return CONFIRMED_BAD;
}
// We are not sure, but it is likely bad.
return SUSPECTED_BAD;
}
}
return NOT_MATCHED;
}
ModuleEnumerator::ModuleEnumerator(EnumerateModulesModel* observer)
: observer_(observer) {
CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_));
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
}
ModuleEnumerator::~ModuleEnumerator() {
}
void ModuleEnumerator::ScanNow(ModulesVector* list) {
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
enumerated_modules_ = list;
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(this, &ModuleEnumerator::ScanOnFileThread));
}
void ModuleEnumerator::ScanOnFileThread() {
enumerated_modules_->clear();
// Make sure the path mapping vector is setup so we can collapse paths.
PreparePathMappings();
// Get all modules in the current process.
ScopedHandle snap(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
::GetCurrentProcessId()));
if (!snap.Get())
return;
// Walk the module list.
MODULEENTRY32 module = { sizeof(module) };
if (!::Module32First(snap.Get(), &module))
return;
do {
// It would be weird to present chrome.exe as a loaded module.
if (_wcsicmp(chrome::kBrowserProcessExecutableName, module.szModule) == 0)
continue;
Module entry;
entry.type = LOADED_MODULE;
entry.status = NOT_MATCHED;
entry.normalized = false;
entry.location = module.szExePath;
entry.digital_signer =
GetSubjectNameFromDigitalSignature(FilePath(entry.location));
entry.recommended_action = NONE;
scoped_ptr<FileVersionInfo> version_info(
FileVersionInfo::CreateFileVersionInfo(FilePath(entry.location)));
if (version_info.get()) {
FileVersionInfoWin* version_info_win =
static_cast<FileVersionInfoWin*>(version_info.get());
VS_FIXEDFILEINFO* fixed_file_info = version_info_win->fixed_file_info();
if (fixed_file_info) {
entry.description = version_info_win->file_description();
entry.version = version_info_win->file_version();
entry.product_name = version_info_win->product_name();
}
}
NormalizeModule(&entry);
CollapsePath(&entry);
enumerated_modules_->push_back(entry);
} while (::Module32Next(snap.Get(), &module));
// Add to this list the Winsock LSP DLLs.
WinsockLayeredServiceProviderList layered_providers;
GetWinsockLayeredServiceProviders(&layered_providers);
for (size_t i = 0; i < layered_providers.size(); ++i) {
Module entry;
entry.type = WINSOCK_MODULE_REGISTRATION;
entry.status = NOT_MATCHED;
entry.normalized = false;
entry.location = layered_providers[i].path;
entry.description = layered_providers[i].name;
entry.recommended_action = NONE;
wchar_t expanded[MAX_PATH];
DWORD size = ExpandEnvironmentStrings(
entry.location.c_str(), expanded, MAX_PATH);
if (size != 0 && size <= MAX_PATH) {
entry.digital_signer =
GetSubjectNameFromDigitalSignature(FilePath(expanded));
}
entry.version = base::IntToString16(layered_providers[i].version);
// Paths have already been collapsed.
NormalizeModule(&entry);
enumerated_modules_->push_back(entry);
}
MatchAgainstBlacklist();
std::sort(enumerated_modules_->begin(),
enumerated_modules_->end(), ModuleSort);
// Send a reply back on the UI thread.
BrowserThread::PostTask(
callback_thread_id_, FROM_HERE,
NewRunnableMethod(this, &ModuleEnumerator::ReportBack));
}
void ModuleEnumerator::PreparePathMappings() {
path_mapping_.clear();
scoped_ptr<base::Environment> environment(base::Environment::Create());
std::vector<string16> env_vars;
env_vars.push_back(L"LOCALAPPDATA");
env_vars.push_back(L"ProgramFiles");
env_vars.push_back(L"USERPROFILE");
env_vars.push_back(L"SystemRoot");
env_vars.push_back(L"TEMP");
for (std::vector<string16>::const_iterator variable = env_vars.begin();
variable != env_vars.end(); ++variable) {
std::string path;
if (environment->GetVar(WideToASCII(*variable).c_str(), &path)) {
path_mapping_.push_back(
std::make_pair(l10n_util::ToLower(UTF8ToWide(path)) + L"\\",
L"%" + l10n_util::ToLower(*variable) + L"%"));
}
}
}
void ModuleEnumerator::CollapsePath(Module* entry) {
// Take the path and see if we can use any of the substitution values
// from the vector constructed above to replace c:\windows with, for
// example, %systemroot%.
for (PathMapping::const_iterator mapping = path_mapping_.begin();
mapping != path_mapping_.end(); ++mapping) {
string16 prefix = mapping->first;
if (StartsWith(entry->location, prefix, false)) {
entry->location = mapping->second +
entry->location.substr(prefix.length() - 1);
return;
}
}
}
void ModuleEnumerator::MatchAgainstBlacklist() {
for (size_t m = 0; m < enumerated_modules_->size(); ++m) {
// Match this module against the blacklist.
Module* module = &(*enumerated_modules_)[m];
module->status = GOOD; // We change this below potentially.
for (size_t i = 0; i < arraysize(kModuleBlacklist); ++i) {
#if !defined(NDEBUG)
// This saves time when constructing the blacklist.
std::string hashes(kModuleBlacklist[i].filename);
std::string hash1, hash2, hash3;
GenerateHash(kModuleBlacklist[i].filename, &hash1);
hashes += " - " + hash1;
GenerateHash(kModuleBlacklist[i].location, &hash2);
hashes += " - " + hash2;
GenerateHash(kModuleBlacklist[i].desc_or_signer, &hash3);
hashes += " - " + hash3;
#endif
ModuleStatus status = Match(*module, kModuleBlacklist[i]);
if (status != NOT_MATCHED) {
// We have a match against the blacklist. Mark it as such.
module->status = status;
module->recommended_action = kModuleBlacklist[i].help_tip;
break;
}
}
}
}
void ModuleEnumerator::ReportBack() {
DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));
observer_->DoneScanning();
}
string16 ModuleEnumerator::GetSubjectNameFromDigitalSignature(
const FilePath& filename) {
HCERTSTORE store = NULL;
HCRYPTMSG message = NULL;
// Find the crypto message for this filename.
bool result = !!CryptQueryObject(CERT_QUERY_OBJECT_FILE,
filename.value().c_str(),
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
NULL,
NULL,
NULL,
&store,
&message,
NULL);
if (!result)
return string16();
// Determine the size of the signer info data.
DWORD signer_info_size = 0;
result = !!CryptMsgGetParam(message,
CMSG_SIGNER_INFO_PARAM,
0,
NULL,
&signer_info_size);
if (!result)
return string16();
// Allocate enough space to hold the signer info.
scoped_array<BYTE> signer_info_buffer(new BYTE[signer_info_size]);
CMSG_SIGNER_INFO* signer_info =
reinterpret_cast<CMSG_SIGNER_INFO*>(signer_info_buffer.get());
// Obtain the signer info.
result = !!CryptMsgGetParam(message,
CMSG_SIGNER_INFO_PARAM,
0,
signer_info,
&signer_info_size);
if (!result)
return string16();
// Search for the signer certificate.
CERT_INFO CertInfo = {0};
PCCERT_CONTEXT cert_context = NULL;
CertInfo.Issuer = signer_info->Issuer;
CertInfo.SerialNumber = signer_info->SerialNumber;
cert_context = CertFindCertificateInStore(
store,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
&CertInfo,
NULL);
if (!cert_context)
return string16();
// Determine the size of the Subject name.
DWORD subject_name_size = 0;
if (!(subject_name_size = CertGetNameString(cert_context,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
NULL,
0))) {
return string16();
}
string16 subject_name;
subject_name.resize(subject_name_size);
// Get subject name.
if (!(CertGetNameString(cert_context,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
const_cast<LPWSTR>(subject_name.c_str()),
subject_name_size))) {
return string16();
}
return subject_name;
}
// ----------------------------------------------------------------------------
void EnumerateModulesModel::ScanNow() {
if (scanning_)
return; // A scan is already in progress.
lock->Acquire(); // Balanced in DoneScanning();
scanning_ = true;
// Instruct the ModuleEnumerator class to load this on the File thread.
// ScanNow does not block.
if (!module_enumerator_)
module_enumerator_ = new ModuleEnumerator(this);
module_enumerator_->ScanNow(&enumerated_modules_);
}
ListValue* EnumerateModulesModel::GetModuleList() {
if (scanning_)
return NULL;
lock->Acquire();
if (enumerated_modules_.empty()) {
lock->Release();
return NULL;
}
ListValue* list = new ListValue();
for (ModuleEnumerator::ModulesVector::const_iterator module =
enumerated_modules_.begin();
module != enumerated_modules_.end(); ++module) {
DictionaryValue* data = new DictionaryValue();
data->SetInteger("type", module->type);
data->SetString("type_description",
(module->type == ModuleEnumerator::WINSOCK_MODULE_REGISTRATION) ?
ASCIIToWide("Winsock") : ASCIIToWide(""));
data->SetInteger("status", module->status);
data->SetString("location", module->location);
data->SetString("name", module->name);
data->SetString("product_name", module->product_name);
data->SetString("description", module->description);
data->SetString("version", module->version.empty() ? ASCIIToWide("") :
l10n_util::GetStringF(IDS_CONFLICTS_CHECK_VERSION_STRING,
module->version));
data->SetString("digital_signer", module->digital_signer);
// Figure out the possible resolution help string.
string16 actions;
string16 separator = ASCIIToWide(" ") + l10n_util::GetStringUTF16(
IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_SEPERATOR) +
ASCIIToWide(" ");
if (module->recommended_action & ModuleEnumerator::NONE) {
actions = l10n_util::GetStringUTF16(
IDS_CONFLICTS_CHECK_INVESTIGATING);
}
if (module->recommended_action & ModuleEnumerator::UNINSTALL) {
if (!actions.empty())
actions += separator;
actions = l10n_util::GetStringUTF16(
IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_UNINSTALL);
}
if (module->recommended_action & ModuleEnumerator::UPDATE) {
if (!actions.empty())
actions += separator;
actions += l10n_util::GetStringUTF16(
IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_UPDATE);
}
if (module->recommended_action & ModuleEnumerator::DISABLE) {
if (!actions.empty())
actions += separator;
actions += l10n_util::GetStringUTF16(
IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_DISABLE);
}
string16 possible_resolution = actions.empty() ? ASCIIToWide("") :
l10n_util::GetStringUTF16(IDS_CONFLICTS_CHECK_POSSIBLE_ACTIONS) +
ASCIIToWide(" ") +
actions;
data->SetString("possibleResolution", possible_resolution);
data->SetString("help_url", ConstructHelpCenterUrl(*module).spec().c_str());
list->Append(data);
}
lock->Release();
return list;
}
EnumerateModulesModel::EnumerateModulesModel()
: scanning_(false),
confirmed_bad_modules_detected_(0),
suspected_bad_modules_detected_(0) {
const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
if (cmd_line.HasSwitch(switches::kConflictingModulesCheck)) {
check_modules_timer_.Start(
base::TimeDelta::FromMilliseconds(kModuleCheckDelayMs),
this, &EnumerateModulesModel::ScanNow);
}
lock = new Lock();
}
EnumerateModulesModel::~EnumerateModulesModel() {
delete lock;
}
void EnumerateModulesModel::DoneScanning() {
confirmed_bad_modules_detected_ = 0;
suspected_bad_modules_detected_ = 0;
for (ModuleEnumerator::ModulesVector::const_iterator module =
enumerated_modules_.begin();
module != enumerated_modules_.end(); ++module) {
if (module->status == ModuleEnumerator::CONFIRMED_BAD)
++confirmed_bad_modules_detected_;
if (module->status == ModuleEnumerator::SUSPECTED_BAD)
++suspected_bad_modules_detected_;
}
scanning_ = false;
lock->Release();
NotificationService::current()->Notify(
NotificationType::MODULE_LIST_ENUMERATED,
Source<EnumerateModulesModel>(this),
NotificationService::NoDetails());
if (suspected_bad_modules_detected_ || confirmed_bad_modules_detected_) {
bool found_confirmed_bad_modules = confirmed_bad_modules_detected_ > 0;
NotificationService::current()->Notify(
NotificationType::MODULE_INCOMPATIBILITY_DETECTED,
Source<EnumerateModulesModel>(this),
Details<bool>(&found_confirmed_bad_modules));
}
}
GURL EnumerateModulesModel::ConstructHelpCenterUrl(
const ModuleEnumerator::Module& module) {
if (!(module.recommended_action & ModuleEnumerator::SEE_LINK))
return GURL();
// Construct the needed hashes.
std::string filename, location, description, signer;
GenerateHash(WideToUTF8(module.name), &filename);
GenerateHash(WideToUTF8(module.location), &location);
GenerateHash(WideToUTF8(module.description), &description);
GenerateHash(WideToUTF8(module.digital_signer), &signer);
string16 url = l10n_util::GetStringF(IDS_HELP_CENTER_VIEW_CONFLICTS,
ASCIIToWide(filename), ASCIIToWide(location),
ASCIIToWide(description), ASCIIToWide(signer));
return GURL(WideToUTF8(url));
}