enumerate_modules_model_win.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/enumerate_modules_model_win.h" 6 7#include <Tlhelp32.h> 8#include <wintrust.h> 9 10#include "app/l10n_util.h" 11#include "app/win_util.h" 12#include "base/command_line.h" 13#include "base/environment.h" 14#include "base/file_path.h" 15#include "base/file_version_info_win.h" 16#include "base/scoped_handle.h" 17#include "base/sha2.h" 18#include "base/string_number_conversions.h" 19#include "base/string_util.h" 20#include "base/utf_string_conversions.h" 21#include "base/values.h" 22#include "base/version.h" 23#include "chrome/browser/net/service_providers_win.h" 24#include "chrome/common/chrome_constants.h" 25#include "chrome/common/chrome_switches.h" 26#include "chrome/common/notification_service.h" 27#include "grit/generated_resources.h" 28 29// The period of time (in milliseconds) to wait until checking to see if any 30// incompatible modules exist. 31static const int kModuleCheckDelayMs = 60 * 1000; 32 33// A sort method that sorts by ModuleType ordinal (loaded module at the top), 34// then by full name (including path). 35static bool ModuleSort(const ModuleEnumerator::Module& a, 36 const ModuleEnumerator::Module& b) { 37 if (a.type != b.type) 38 return a.type < b.type; 39 if (a.location == b.location) 40 return a.name < b.name; 41 42 return a.location < b.location; 43} 44 45namespace { 46 47// Used to protect the LoadedModuleVector which is accessed 48// from both the UI thread and the FILE thread. 49Lock* lock = NULL; 50 51} 52 53// The browser process module blacklist. This lists all modules that are known 54// to cause compatibility issues within the browser process. When adding to this 55// list, make sure that all paths are lower-case, in long pathname form, end 56// with a slash and use environments variables (or just look at one of the 57// comments below and keep it consistent with that). When adding an entry with 58// an environment variable not currently used in the list below, make sure to 59// update the list in PreparePathMappings. Filename, Description/Signer, and 60// Location must be entered as hashes (see GenerateHash). Filename is mandatory. 61// Entries without any Description, Signer info, or Location will never be 62// marked as confirmed bad (only as suspicious). 63const ModuleEnumerator::BlacklistEntry ModuleEnumerator::kModuleBlacklist[] = { 64 // Test DLLs, to demonstrate the feature. Will be removed soon. 65 { // apphelp.dll, "%systemroot%\\system32\\" 66 "f5fda581", "23d01d5b", "", "", "", NONE 67 }, { // rsaenh.dll, "%systemroot%\\system32\\", "Microsoft Windows" 68 "6af212cb", "23d01d5b", "7b47bf79", "", "", 69 static_cast<RecommendedAction>(UPDATE | DISABLE | SEE_LINK) 70 }, 71 72 // NOTE: Please keep this list sorted by dll name, then location. 73 74 // foldersizecolumn.dll. 75 {"5ec91bd7", "", "", "", "", NONE}, 76 77 // idmmbc.dll, "%programfiles%\\internet download manager\\", "Tonec Inc.". 78 // See: http://crbug.com/26892/. 79 {"b8dce5c3", "94541bf5", "d33ad640", "", "", NONE}, 80 81 // imon.dll. See: http://crbug.com/21715. 82 {"8f42f22e", "", "", "", "", NONE}, 83 84 // is3lsp.dll. See: http://crbug.com/26892. 85 {"7ffbdce9", "", "", "", "", NONE}, 86 87 // nvlsp.dll. See: http://crbug.com/22083. 88 {"37f907e2", "", "", "", "", NONE}, 89 90 // nvshell.dll. See: http://crbug.com/3269. 91 {"9290318f", "", "", "", "", NONE}, 92 93 // securenet.dll. See: http://crbug.com/5165. 94 {"9b266e1c", "", "", "", "", NONE}, 95 96 // sgprxy.dll. 97 {"005965ea", "", "", "", "", NONE}, 98 99 // vaproxyd.dll. See: http://crbug.com/42445. 100 {"0a1c7f81", "", "", "", "", NONE}, 101 102 // vlsp.dll. See: http://crbug.com/22826. 103 {"2e4eb93d", "", "", "", "", NONE}, 104}; 105 106// Generates an 8 digit hash from the input given. 107static void GenerateHash(const std::string& input, std::string* output) { 108 if (input.empty()) { 109 *output = ""; 110 return; 111 } 112 113 uint8 hash[4]; 114 base::SHA256HashString(input, hash, sizeof(hash)); 115 *output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash))); 116} 117 118// ----------------------------------------------------------------------------- 119 120// static 121void ModuleEnumerator::NormalizeModule(Module* module) { 122 string16 path = module->location; 123 if (!win_util::ConvertToLongPath(path, &module->location)) 124 module->location = path; 125 126 module->location = l10n_util::ToLower(module->location); 127 128 // Location contains the filename, so the last slash is where the path 129 // ends. 130 size_t last_slash = module->location.find_last_of(L"\\"); 131 if (last_slash != string16::npos) { 132 module->name = module->location.substr(last_slash + 1); 133 module->location = module->location.substr(0, last_slash + 1); 134 } else { 135 module->name = module->location; 136 module->location.clear(); 137 } 138 139 // Some version strings have things like (win7_rtm.090713-1255) appended 140 // to them. Remove that. 141 size_t first_space = module->version.find_first_of(L" "); 142 if (first_space != string16::npos) 143 module->version = module->version.substr(0, first_space); 144 145 module->normalized = true; 146} 147 148// static 149ModuleEnumerator::ModuleStatus ModuleEnumerator::Match( 150 const ModuleEnumerator::Module& module, 151 const ModuleEnumerator::BlacklistEntry& blacklisted) { 152 // All modules must be normalized before matching against blacklist. 153 DCHECK(module.normalized); 154 // Filename is mandatory and version should not contain spaces. 155 DCHECK(strlen(blacklisted.filename) > 0); 156 DCHECK(!strstr(blacklisted.version_from, " ")); 157 DCHECK(!strstr(blacklisted.version_to, " ")); 158 159 std::string filename_hash, location_hash; 160 GenerateHash(WideToUTF8(module.name), &filename_hash); 161 GenerateHash(WideToUTF8(module.location), &location_hash); 162 163 // Filenames are mandatory. Location is mandatory if given. 164 if (filename_hash == blacklisted.filename && 165 (std::string(blacklisted.location).empty() || 166 location_hash == blacklisted.location)) { 167 // We have a name match against the blacklist (and possibly location match 168 // also), so check version. 169 scoped_ptr<Version> module_version( 170 Version::GetVersionFromString(module.version)); 171 scoped_ptr<Version> version_min( 172 Version::GetVersionFromString(blacklisted.version_from)); 173 scoped_ptr<Version> version_max( 174 Version::GetVersionFromString(blacklisted.version_to)); 175 bool version_ok = !version_min.get() && !version_max.get(); 176 if (!version_ok) { 177 bool too_low = version_min.get() && 178 (!module_version.get() || 179 module_version->CompareTo(*version_min.get()) < 0); 180 bool too_high = version_max.get() && 181 (!module_version.get() || 182 module_version->CompareTo(*version_max.get()) > 0); 183 version_ok = !too_low && !too_high; 184 } 185 186 if (version_ok) { 187 // At this point, the names match and there is no version specified 188 // or the versions also match. 189 190 std::string desc_or_signer(blacklisted.desc_or_signer); 191 std::string signer_hash, description_hash; 192 GenerateHash(WideToUTF8(module.digital_signer), &signer_hash); 193 GenerateHash(WideToUTF8(module.description), &description_hash); 194 195 // If signatures match, we have a winner. 196 if (!desc_or_signer.empty() && signer_hash == desc_or_signer) 197 return CONFIRMED_BAD; 198 199 // If description matches and location, then we also have a match. 200 if (!desc_or_signer.empty() && description_hash == desc_or_signer && 201 !location_hash.empty() && location_hash == blacklisted.location) { 202 return CONFIRMED_BAD; 203 } 204 205 // We are not sure, but it is likely bad. 206 return SUSPECTED_BAD; 207 } 208 } 209 210 return NOT_MATCHED; 211} 212 213ModuleEnumerator::ModuleEnumerator(EnumerateModulesModel* observer) 214: observer_(observer) { 215 CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_)); 216 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 217} 218 219ModuleEnumerator::~ModuleEnumerator() { 220} 221 222void ModuleEnumerator::ScanNow(ModulesVector* list) { 223 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 224 enumerated_modules_ = list; 225 BrowserThread::PostTask( 226 BrowserThread::FILE, FROM_HERE, 227 NewRunnableMethod(this, &ModuleEnumerator::ScanOnFileThread)); 228} 229 230void ModuleEnumerator::ScanOnFileThread() { 231 enumerated_modules_->clear(); 232 233 // Make sure the path mapping vector is setup so we can collapse paths. 234 PreparePathMappings(); 235 236 // Get all modules in the current process. 237 ScopedHandle snap(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 238 ::GetCurrentProcessId())); 239 if (!snap.Get()) 240 return; 241 242 // Walk the module list. 243 MODULEENTRY32 module = { sizeof(module) }; 244 if (!::Module32First(snap.Get(), &module)) 245 return; 246 247 do { 248 // It would be weird to present chrome.exe as a loaded module. 249 if (_wcsicmp(chrome::kBrowserProcessExecutableName, module.szModule) == 0) 250 continue; 251 252 Module entry; 253 entry.type = LOADED_MODULE; 254 entry.status = NOT_MATCHED; 255 entry.normalized = false; 256 entry.location = module.szExePath; 257 entry.digital_signer = 258 GetSubjectNameFromDigitalSignature(FilePath(entry.location)); 259 entry.recommended_action = NONE; 260 scoped_ptr<FileVersionInfo> version_info( 261 FileVersionInfo::CreateFileVersionInfo(FilePath(entry.location))); 262 if (version_info.get()) { 263 FileVersionInfoWin* version_info_win = 264 static_cast<FileVersionInfoWin*>(version_info.get()); 265 266 VS_FIXEDFILEINFO* fixed_file_info = version_info_win->fixed_file_info(); 267 if (fixed_file_info) { 268 entry.description = version_info_win->file_description(); 269 entry.version = version_info_win->file_version(); 270 entry.product_name = version_info_win->product_name(); 271 } 272 } 273 274 NormalizeModule(&entry); 275 CollapsePath(&entry); 276 enumerated_modules_->push_back(entry); 277 } while (::Module32Next(snap.Get(), &module)); 278 279 // Add to this list the Winsock LSP DLLs. 280 WinsockLayeredServiceProviderList layered_providers; 281 GetWinsockLayeredServiceProviders(&layered_providers); 282 for (size_t i = 0; i < layered_providers.size(); ++i) { 283 Module entry; 284 entry.type = WINSOCK_MODULE_REGISTRATION; 285 entry.status = NOT_MATCHED; 286 entry.normalized = false; 287 entry.location = layered_providers[i].path; 288 entry.description = layered_providers[i].name; 289 entry.recommended_action = NONE; 290 291 wchar_t expanded[MAX_PATH]; 292 DWORD size = ExpandEnvironmentStrings( 293 entry.location.c_str(), expanded, MAX_PATH); 294 if (size != 0 && size <= MAX_PATH) { 295 entry.digital_signer = 296 GetSubjectNameFromDigitalSignature(FilePath(expanded)); 297 } 298 entry.version = base::IntToString16(layered_providers[i].version); 299 300 // Paths have already been collapsed. 301 NormalizeModule(&entry); 302 enumerated_modules_->push_back(entry); 303 } 304 305 MatchAgainstBlacklist(); 306 307 std::sort(enumerated_modules_->begin(), 308 enumerated_modules_->end(), ModuleSort); 309 310 // Send a reply back on the UI thread. 311 BrowserThread::PostTask( 312 callback_thread_id_, FROM_HERE, 313 NewRunnableMethod(this, &ModuleEnumerator::ReportBack)); 314} 315 316void ModuleEnumerator::PreparePathMappings() { 317 path_mapping_.clear(); 318 319 scoped_ptr<base::Environment> environment(base::Environment::Create()); 320 std::vector<string16> env_vars; 321 env_vars.push_back(L"LOCALAPPDATA"); 322 env_vars.push_back(L"ProgramFiles"); 323 env_vars.push_back(L"USERPROFILE"); 324 env_vars.push_back(L"SystemRoot"); 325 env_vars.push_back(L"TEMP"); 326 for (std::vector<string16>::const_iterator variable = env_vars.begin(); 327 variable != env_vars.end(); ++variable) { 328 std::string path; 329 if (environment->GetVar(WideToASCII(*variable).c_str(), &path)) { 330 path_mapping_.push_back( 331 std::make_pair(l10n_util::ToLower(UTF8ToWide(path)) + L"\\", 332 L"%" + l10n_util::ToLower(*variable) + L"%")); 333 } 334 } 335} 336 337void ModuleEnumerator::CollapsePath(Module* entry) { 338 // Take the path and see if we can use any of the substitution values 339 // from the vector constructed above to replace c:\windows with, for 340 // example, %systemroot%. 341 for (PathMapping::const_iterator mapping = path_mapping_.begin(); 342 mapping != path_mapping_.end(); ++mapping) { 343 string16 prefix = mapping->first; 344 if (StartsWith(entry->location, prefix, false)) { 345 entry->location = mapping->second + 346 entry->location.substr(prefix.length() - 1); 347 return; 348 } 349 } 350} 351 352void ModuleEnumerator::MatchAgainstBlacklist() { 353 for (size_t m = 0; m < enumerated_modules_->size(); ++m) { 354 // Match this module against the blacklist. 355 Module* module = &(*enumerated_modules_)[m]; 356 module->status = GOOD; // We change this below potentially. 357 for (size_t i = 0; i < arraysize(kModuleBlacklist); ++i) { 358 #if !defined(NDEBUG) 359 // This saves time when constructing the blacklist. 360 std::string hashes(kModuleBlacklist[i].filename); 361 std::string hash1, hash2, hash3; 362 GenerateHash(kModuleBlacklist[i].filename, &hash1); 363 hashes += " - " + hash1; 364 GenerateHash(kModuleBlacklist[i].location, &hash2); 365 hashes += " - " + hash2; 366 GenerateHash(kModuleBlacklist[i].desc_or_signer, &hash3); 367 hashes += " - " + hash3; 368 #endif 369 370 ModuleStatus status = Match(*module, kModuleBlacklist[i]); 371 if (status != NOT_MATCHED) { 372 // We have a match against the blacklist. Mark it as such. 373 module->status = status; 374 module->recommended_action = kModuleBlacklist[i].help_tip; 375 break; 376 } 377 } 378 } 379} 380 381void ModuleEnumerator::ReportBack() { 382 DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_)); 383 observer_->DoneScanning(); 384} 385 386string16 ModuleEnumerator::GetSubjectNameFromDigitalSignature( 387 const FilePath& filename) { 388 HCERTSTORE store = NULL; 389 HCRYPTMSG message = NULL; 390 391 // Find the crypto message for this filename. 392 bool result = !!CryptQueryObject(CERT_QUERY_OBJECT_FILE, 393 filename.value().c_str(), 394 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, 395 CERT_QUERY_FORMAT_FLAG_BINARY, 396 0, 397 NULL, 398 NULL, 399 NULL, 400 &store, 401 &message, 402 NULL); 403 if (!result) 404 return string16(); 405 406 // Determine the size of the signer info data. 407 DWORD signer_info_size = 0; 408 result = !!CryptMsgGetParam(message, 409 CMSG_SIGNER_INFO_PARAM, 410 0, 411 NULL, 412 &signer_info_size); 413 if (!result) 414 return string16(); 415 416 // Allocate enough space to hold the signer info. 417 scoped_array<BYTE> signer_info_buffer(new BYTE[signer_info_size]); 418 CMSG_SIGNER_INFO* signer_info = 419 reinterpret_cast<CMSG_SIGNER_INFO*>(signer_info_buffer.get()); 420 421 // Obtain the signer info. 422 result = !!CryptMsgGetParam(message, 423 CMSG_SIGNER_INFO_PARAM, 424 0, 425 signer_info, 426 &signer_info_size); 427 if (!result) 428 return string16(); 429 430 // Search for the signer certificate. 431 CERT_INFO CertInfo = {0}; 432 PCCERT_CONTEXT cert_context = NULL; 433 CertInfo.Issuer = signer_info->Issuer; 434 CertInfo.SerialNumber = signer_info->SerialNumber; 435 436 cert_context = CertFindCertificateInStore( 437 store, 438 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 439 0, 440 CERT_FIND_SUBJECT_CERT, 441 &CertInfo, 442 NULL); 443 if (!cert_context) 444 return string16(); 445 446 // Determine the size of the Subject name. 447 DWORD subject_name_size = 0; 448 if (!(subject_name_size = CertGetNameString(cert_context, 449 CERT_NAME_SIMPLE_DISPLAY_TYPE, 450 0, 451 NULL, 452 NULL, 453 0))) { 454 return string16(); 455 } 456 457 string16 subject_name; 458 subject_name.resize(subject_name_size); 459 460 // Get subject name. 461 if (!(CertGetNameString(cert_context, 462 CERT_NAME_SIMPLE_DISPLAY_TYPE, 463 0, 464 NULL, 465 const_cast<LPWSTR>(subject_name.c_str()), 466 subject_name_size))) { 467 return string16(); 468 } 469 470 return subject_name; 471} 472 473// ---------------------------------------------------------------------------- 474 475void EnumerateModulesModel::ScanNow() { 476 if (scanning_) 477 return; // A scan is already in progress. 478 479 lock->Acquire(); // Balanced in DoneScanning(); 480 481 scanning_ = true; 482 483 // Instruct the ModuleEnumerator class to load this on the File thread. 484 // ScanNow does not block. 485 if (!module_enumerator_) 486 module_enumerator_ = new ModuleEnumerator(this); 487 module_enumerator_->ScanNow(&enumerated_modules_); 488} 489 490ListValue* EnumerateModulesModel::GetModuleList() { 491 if (scanning_) 492 return NULL; 493 494 lock->Acquire(); 495 496 if (enumerated_modules_.empty()) { 497 lock->Release(); 498 return NULL; 499 } 500 501 ListValue* list = new ListValue(); 502 503 for (ModuleEnumerator::ModulesVector::const_iterator module = 504 enumerated_modules_.begin(); 505 module != enumerated_modules_.end(); ++module) { 506 DictionaryValue* data = new DictionaryValue(); 507 data->SetInteger("type", module->type); 508 data->SetString("type_description", 509 (module->type == ModuleEnumerator::WINSOCK_MODULE_REGISTRATION) ? 510 ASCIIToWide("Winsock") : ASCIIToWide("")); 511 data->SetInteger("status", module->status); 512 data->SetString("location", module->location); 513 data->SetString("name", module->name); 514 data->SetString("product_name", module->product_name); 515 data->SetString("description", module->description); 516 data->SetString("version", module->version.empty() ? ASCIIToWide("") : 517 l10n_util::GetStringF(IDS_CONFLICTS_CHECK_VERSION_STRING, 518 module->version)); 519 data->SetString("digital_signer", module->digital_signer); 520 521 // Figure out the possible resolution help string. 522 string16 actions; 523 string16 separator = ASCIIToWide(" ") + l10n_util::GetStringUTF16( 524 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_SEPERATOR) + 525 ASCIIToWide(" "); 526 527 if (module->recommended_action & ModuleEnumerator::NONE) { 528 actions = l10n_util::GetStringUTF16( 529 IDS_CONFLICTS_CHECK_INVESTIGATING); 530 } 531 if (module->recommended_action & ModuleEnumerator::UNINSTALL) { 532 if (!actions.empty()) 533 actions += separator; 534 actions = l10n_util::GetStringUTF16( 535 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_UNINSTALL); 536 } 537 if (module->recommended_action & ModuleEnumerator::UPDATE) { 538 if (!actions.empty()) 539 actions += separator; 540 actions += l10n_util::GetStringUTF16( 541 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_UPDATE); 542 } 543 if (module->recommended_action & ModuleEnumerator::DISABLE) { 544 if (!actions.empty()) 545 actions += separator; 546 actions += l10n_util::GetStringUTF16( 547 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_DISABLE); 548 } 549 string16 possible_resolution = actions.empty() ? ASCIIToWide("") : 550 l10n_util::GetStringUTF16(IDS_CONFLICTS_CHECK_POSSIBLE_ACTIONS) + 551 ASCIIToWide(" ") + 552 actions; 553 data->SetString("possibleResolution", possible_resolution); 554 data->SetString("help_url", ConstructHelpCenterUrl(*module).spec().c_str()); 555 556 list->Append(data); 557 } 558 559 lock->Release(); 560 return list; 561} 562 563EnumerateModulesModel::EnumerateModulesModel() 564: scanning_(false), 565confirmed_bad_modules_detected_(0), 566suspected_bad_modules_detected_(0) { 567 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 568 if (cmd_line.HasSwitch(switches::kConflictingModulesCheck)) { 569 check_modules_timer_.Start( 570 base::TimeDelta::FromMilliseconds(kModuleCheckDelayMs), 571 this, &EnumerateModulesModel::ScanNow); 572 } 573 574 lock = new Lock(); 575} 576 577EnumerateModulesModel::~EnumerateModulesModel() { 578 delete lock; 579} 580 581void EnumerateModulesModel::DoneScanning() { 582 confirmed_bad_modules_detected_ = 0; 583 suspected_bad_modules_detected_ = 0; 584 for (ModuleEnumerator::ModulesVector::const_iterator module = 585 enumerated_modules_.begin(); 586 module != enumerated_modules_.end(); ++module) { 587 if (module->status == ModuleEnumerator::CONFIRMED_BAD) 588 ++confirmed_bad_modules_detected_; 589 if (module->status == ModuleEnumerator::SUSPECTED_BAD) 590 ++suspected_bad_modules_detected_; 591 } 592 593 scanning_ = false; 594 lock->Release(); 595 596 NotificationService::current()->Notify( 597 NotificationType::MODULE_LIST_ENUMERATED, 598 Source<EnumerateModulesModel>(this), 599 NotificationService::NoDetails()); 600 601 if (suspected_bad_modules_detected_ || confirmed_bad_modules_detected_) { 602 bool found_confirmed_bad_modules = confirmed_bad_modules_detected_ > 0; 603 NotificationService::current()->Notify( 604 NotificationType::MODULE_INCOMPATIBILITY_DETECTED, 605 Source<EnumerateModulesModel>(this), 606 Details<bool>(&found_confirmed_bad_modules)); 607 } 608} 609 610GURL EnumerateModulesModel::ConstructHelpCenterUrl( 611 const ModuleEnumerator::Module& module) { 612 if (!(module.recommended_action & ModuleEnumerator::SEE_LINK)) 613 return GURL(); 614 615 // Construct the needed hashes. 616 std::string filename, location, description, signer; 617 GenerateHash(WideToUTF8(module.name), &filename); 618 GenerateHash(WideToUTF8(module.location), &location); 619 GenerateHash(WideToUTF8(module.description), &description); 620 GenerateHash(WideToUTF8(module.digital_signer), &signer); 621 622 string16 url = l10n_util::GetStringF(IDS_HELP_CENTER_VIEW_CONFLICTS, 623 ASCIIToWide(filename), ASCIIToWide(location), 624 ASCIIToWide(description), ASCIIToWide(signer)); 625 return GURL(WideToUTF8(url)); 626} 627