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