Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(936)

Side by Side Diff: chrome/browser/win/enumerate_modules_model.cc

Issue 2420133002: Make module enumeration a low priority background task. (Closed)
Patch Set: Moar comments! Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/win/enumerate_modules_model.h" 5 #include "chrome/browser/win/enumerate_modules_model.h"
6 6
7 #include <softpub.h> 7 #include <softpub.h>
8 #include <stddef.h> 8 #include <stddef.h>
9 #include <stdint.h> 9 #include <stdint.h>
10 #include <tlhelp32.h> 10 #include <tlhelp32.h>
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
53 return a.status > b.status; 53 return a.status > b.status;
54 54
55 if (a.location == b.location) 55 if (a.location == b.location)
56 return a.name < b.name; 56 return a.name < b.name;
57 57
58 return a.location < b.location; 58 return a.location < b.location;
59 } 59 }
60 60
61 namespace { 61 namespace {
62 62
63 // The default amount of time between module inspections. This prevents
64 // certificate inspection and validation from using a large amount of CPU and
65 // battery immediately after startup.
66 const base::TimeDelta kDefaultPerModuleDelay = base::TimeDelta::FromSeconds(1);
Patrick Monette 2016/10/14 22:45:55 constexpr
chrisha 2016/10/17 15:32:21 Done.
67
63 // A struct to help de-duping modules before adding them to the enumerated 68 // A struct to help de-duping modules before adding them to the enumerated
64 // modules vector. 69 // modules vector.
65 struct FindModule { 70 struct FindModule {
66 public: 71 public:
67 explicit FindModule(const ModuleEnumerator::Module& x) 72 explicit FindModule(const ModuleEnumerator::Module& x)
68 : module(x) {} 73 : module(x) {}
69 bool operator()(const ModuleEnumerator::Module& module_in) const { 74 bool operator()(const ModuleEnumerator::Module& module_in) const {
70 return (module.location == module_in.location) && 75 return (module.location == module_in.location) &&
71 (module.name == module_in.name); 76 (module.name == module_in.name);
72 } 77 }
(...skipping 313 matching lines...) Expand 10 before | Expand all | Expand 10 after
386 // to them. Remove that. 391 // to them. Remove that.
387 size_t first_space = module->version.find_first_of(L" "); 392 size_t first_space = module->version.find_first_of(L" ");
388 if (first_space != base::string16::npos) 393 if (first_space != base::string16::npos)
389 module->version = module->version.substr(0, first_space); 394 module->version = module->version.substr(0, first_space);
390 395
391 module->normalized = true; 396 module->normalized = true;
392 } 397 }
393 398
394 ModuleEnumerator::ModuleEnumerator(EnumerateModulesModel* observer) 399 ModuleEnumerator::ModuleEnumerator(EnumerateModulesModel* observer)
395 : enumerated_modules_(nullptr), 400 : enumerated_modules_(nullptr),
396 observer_(observer) { 401 observer_(observer),
402 per_module_delay_(kDefaultPerModuleDelay) {
397 } 403 }
398 404
399 ModuleEnumerator::~ModuleEnumerator() { 405 ModuleEnumerator::~ModuleEnumerator() {
400 } 406 }
401 407
402 void ModuleEnumerator::ScanNow(ModulesVector* list) { 408 void ModuleEnumerator::ScanNow(ModulesVector* list) {
403 DCHECK_CURRENTLY_ON(BrowserThread::UI); 409 DCHECK_CURRENTLY_ON(BrowserThread::UI);
404 enumerated_modules_ = list; 410 enumerated_modules_ = list;
405 411
406 // This object can't be reaped until it has finished scanning, so its safe 412 // This object can't be reaped until it has finished scanning, so its safe
407 // to post a raw pointer to another thread. It will simply be leaked if the 413 // to post a raw pointer to another thread. It will simply be leaked if the
408 // scanning has not been finished before shutdown. 414 // scanning has not been finished before shutdown.
409 BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior( 415 BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
410 FROM_HERE, 416 FROM_HERE,
411 base::Bind(&ModuleEnumerator::ScanImpl, 417 base::Bind(&ModuleEnumerator::ScanImplStart,
412 base::Unretained(this)), 418 base::Unretained(this)),
413 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 419 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
414 } 420 }
415 421
416 void ModuleEnumerator::ScanImpl() { 422 void ModuleEnumerator::SetPerModuleDelayToZero() {
423 // Set the delay to zero so the modules enumerate as quickly as possible.
424 per_module_delay_ = base::TimeDelta::FromSeconds(0);
425 }
426
427 void ModuleEnumerator::ScanImplStart() {
417 base::TimeTicks start_time = base::TimeTicks::Now(); 428 base::TimeTicks start_time = base::TimeTicks::Now();
418 429
419 // The provided destination for the enumerated modules should be empty, as it 430 // The provided destination for the enumerated modules should be empty, as it
420 // should only be populated once, by a single ModuleEnumerator instance. See 431 // should only be populated once, by a single ModuleEnumerator instance. See
421 // EnumerateModulesModel::ScanNow for details. 432 // EnumerateModulesModel::ScanNow for details.
422 DCHECK(enumerated_modules_->empty()); 433 DCHECK(enumerated_modules_->empty());
423 434
424 // Make sure the path mapping vector is setup so we can collapse paths. 435 // Make sure the path mapping vector is setup so we can collapse paths.
425 PreparePathMappings(); 436 PreparePathMappings();
426 437
(...skipping 10 matching lines...) Expand all
437 checkpoint2 = base::TimeTicks::Now(); 448 checkpoint2 = base::TimeTicks::Now();
438 UMA_HISTOGRAM_TIMES("Conflicts.EnumerateShellExtensions", 449 UMA_HISTOGRAM_TIMES("Conflicts.EnumerateShellExtensions",
439 checkpoint2 - checkpoint); 450 checkpoint2 - checkpoint);
440 451
441 checkpoint = checkpoint2; 452 checkpoint = checkpoint2;
442 EnumerateWinsockModules(); 453 EnumerateWinsockModules();
443 checkpoint2 = base::TimeTicks::Now(); 454 checkpoint2 = base::TimeTicks::Now();
444 UMA_HISTOGRAM_TIMES("Conflicts.EnumerateWinsockModules", 455 UMA_HISTOGRAM_TIMES("Conflicts.EnumerateWinsockModules",
445 checkpoint2 - checkpoint); 456 checkpoint2 - checkpoint);
446 457
458 enumeration_total_time_ = base::TimeTicks::Now() - start_time;
459
460 // Post a delayed task to scan the first module. This forwards directly to
461 // ScanImplFinish if there are no modules to scan.
462 BrowserThread::GetBlockingPool()->PostDelayedWorkerTask(
463 FROM_HERE,
464 base::Bind(&ModuleEnumerator::ScanImplModule,
465 base::Unretained(this),
466 0),
467 per_module_delay_);
468 }
469
470 void ModuleEnumerator::ScanImplDelay(size_t index) {
471 // Bounce this over to a CONTINUE_ON_SHUTDOWN task in the same pool. This is
472 // necessary to prevent shutdown hangs while inspecting a module.
473 BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
474 FROM_HERE,
475 base::Bind(&ModuleEnumerator::ScanImplModule,
476 base::Unretained(this),
477 index),
478 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
479 }
480
481 void ModuleEnumerator::ScanImplModule(size_t index) {
482 while (index < enumerated_modules_->size()) {
483 base::TimeTicks start_time = base::TimeTicks::Now();
484 Module& entry = enumerated_modules_->at(index);
485 PopulateModuleInformation(&entry);
486 NormalizeModule(&entry);
487 CollapsePath(&entry);
488 base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
489 enumeration_inspection_time_ += elapsed;
490 enumeration_total_time_ += elapsed;
491
492 // If the delay has been set to zero then simply finish the rest of the
493 // enumeration in this already started task.
494 if (per_module_delay_.is_zero()) {
Patrick Monette 2016/10/14 22:45:55 Reverse the conditional for simpler code? (Let you
chrisha 2016/10/17 15:32:21 Done.
495 ++index;
496 continue;
497 } else {
498 // Bounce back over to ScanImplDelay, which will bounce back to this
499 // function and inspect the next module.
500 BrowserThread::GetBlockingPool()->PostDelayedWorkerTask(
501 FROM_HERE,
502 base::Bind(&ModuleEnumerator::ScanImplDelay,
503 base::Unretained(this),
504 index + 1),
505 per_module_delay_);
506 return;
507 }
508 }
509
510 // Getting here means that all of the modules have been inspected.
511 return ScanImplFinish();
512 }
513
514 void ModuleEnumerator::ScanImplFinish() {
447 // TODO(chrisha): Annotate any modules that are suspicious/bad. 515 // TODO(chrisha): Annotate any modules that are suspicious/bad.
448 516
449 ReportThirdPartyMetrics(); 517 ReportThirdPartyMetrics();
450 518
451 std::sort(enumerated_modules_->begin(), 519 std::sort(enumerated_modules_->begin(),
452 enumerated_modules_->end(), ModuleSort); 520 enumerated_modules_->end(), ModuleSort);
453 521
522 UMA_HISTOGRAM_TIMES("Conflicts.EnumerationInspectionTime",
523 enumeration_inspection_time_);
454 UMA_HISTOGRAM_TIMES("Conflicts.EnumerationTotalTime", 524 UMA_HISTOGRAM_TIMES("Conflicts.EnumerationTotalTime",
455 base::TimeTicks::Now() - start_time); 525 enumeration_total_time_);
456 526
457 // Send a reply back on the UI thread. The |observer_| outlives this 527 // Send a reply back on the UI thread. The |observer_| outlives this
458 // enumerator, so posting a raw pointer is safe. This is done last as 528 // enumerator, so posting a raw pointer is safe. This is done last as
459 // DoneScanning will then reap this ModuleEnumerator. 529 // DoneScanning will then reap this ModuleEnumerator.
460 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 530 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
461 base::Bind(&EnumerateModulesModel::DoneScanning, 531 base::Bind(&EnumerateModulesModel::DoneScanning,
462 base::Unretained(observer_))); 532 base::Unretained(observer_)));
463 } 533 }
464 534
465 void ModuleEnumerator::EnumerateLoadedModules() { 535 void ModuleEnumerator::EnumerateLoadedModules() {
466 // Get all modules in the current process. 536 // Get all modules in the current process.
467 base::win::ScopedHandle snap(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 537 base::win::ScopedHandle snap(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
468 ::GetCurrentProcessId())); 538 ::GetCurrentProcessId()));
469 if (!snap.Get()) 539 if (!snap.Get())
470 return; 540 return;
471 541
472 // Walk the module list. 542 // Walk the module list.
473 MODULEENTRY32 module = { sizeof(module) }; 543 MODULEENTRY32 module = { sizeof(module) };
474 if (!::Module32First(snap.Get(), &module)) 544 if (!::Module32First(snap.Get(), &module))
475 return; 545 return;
476 546
477 do { 547 do {
478 // It would be weird to present chrome.exe as a loaded module. 548 // It would be weird to present chrome.exe as a loaded module.
479 if (_wcsicmp(chrome::kBrowserProcessExecutableName, module.szModule) == 0) 549 if (_wcsicmp(chrome::kBrowserProcessExecutableName, module.szModule) == 0)
480 continue; 550 continue;
481 551
482 Module entry; 552 Module entry;
483 entry.type = LOADED_MODULE; 553 entry.type = LOADED_MODULE;
484 entry.location = module.szExePath; 554 entry.location = module.szExePath;
485 PopulateModuleInformation(&entry);
486
487 NormalizeModule(&entry);
488 CollapsePath(&entry);
489 enumerated_modules_->push_back(entry); 555 enumerated_modules_->push_back(entry);
490 } while (::Module32Next(snap.Get(), &module)); 556 } while (::Module32Next(snap.Get(), &module));
491 } 557 }
492 558
493 void ModuleEnumerator::EnumerateShellExtensions() { 559 void ModuleEnumerator::EnumerateShellExtensions() {
494 ReadShellExtensions(HKEY_LOCAL_MACHINE); 560 ReadShellExtensions(HKEY_LOCAL_MACHINE);
495 ReadShellExtensions(HKEY_CURRENT_USER); 561 ReadShellExtensions(HKEY_CURRENT_USER);
496 } 562 }
497 563
498 void ModuleEnumerator::ReadShellExtensions(HKEY parent) { 564 void ModuleEnumerator::ReadShellExtensions(HKEY parent) {
499 base::win::RegistryValueIterator registration(parent, kRegPath); 565 base::win::RegistryValueIterator registration(parent, kRegPath);
500 while (registration.Valid()) { 566 while (registration.Valid()) {
501 std::wstring key(std::wstring(L"CLSID\\") + registration.Name() + 567 std::wstring key(std::wstring(L"CLSID\\") + registration.Name() +
502 L"\\InProcServer32"); 568 L"\\InProcServer32");
503 base::win::RegKey clsid; 569 base::win::RegKey clsid;
504 if (clsid.Open(HKEY_CLASSES_ROOT, key.c_str(), KEY_READ) != ERROR_SUCCESS) { 570 if (clsid.Open(HKEY_CLASSES_ROOT, key.c_str(), KEY_READ) != ERROR_SUCCESS) {
505 ++registration; 571 ++registration;
506 continue; 572 continue;
507 } 573 }
508 base::string16 dll; 574 base::string16 dll;
509 if (clsid.ReadValue(L"", &dll) != ERROR_SUCCESS) { 575 if (clsid.ReadValue(L"", &dll) != ERROR_SUCCESS) {
510 ++registration; 576 ++registration;
511 continue; 577 continue;
512 } 578 }
513 clsid.Close(); 579 clsid.Close();
514 580
515 Module entry; 581 Module entry;
516 entry.type = SHELL_EXTENSION; 582 entry.type = SHELL_EXTENSION;
517 entry.location = dll; 583 entry.location = dll;
518 PopulateModuleInformation(&entry);
519
520 NormalizeModule(&entry);
521 CollapsePath(&entry);
522 AddToListWithoutDuplicating(entry); 584 AddToListWithoutDuplicating(entry);
523 585
524 ++registration; 586 ++registration;
525 } 587 }
526 } 588 }
527 589
528 void ModuleEnumerator::EnumerateWinsockModules() { 590 void ModuleEnumerator::EnumerateWinsockModules() {
529 // Add to this list the Winsock LSP DLLs. 591 // Add to this list the Winsock LSP DLLs.
530 WinsockLayeredServiceProviderList layered_providers; 592 WinsockLayeredServiceProviderList layered_providers;
531 GetWinsockLayeredServiceProviders(&layered_providers); 593 GetWinsockLayeredServiceProviders(&layered_providers);
532 for (size_t i = 0; i < layered_providers.size(); ++i) { 594 for (size_t i = 0; i < layered_providers.size(); ++i) {
533 Module entry; 595 Module entry;
534 entry.type = WINSOCK_MODULE_REGISTRATION; 596 entry.type = WINSOCK_MODULE_REGISTRATION;
535 entry.status = NOT_MATCHED; 597 entry.status = NOT_MATCHED;
536 entry.normalized = false; 598 entry.normalized = false;
537 entry.location = layered_providers[i].path; 599 entry.location = layered_providers[i].path;
538 entry.description = layered_providers[i].name; 600 entry.description = layered_providers[i].name;
539 entry.recommended_action = NONE; 601 entry.recommended_action = NONE;
540 entry.duplicate_count = 0; 602 entry.duplicate_count = 0;
541 603
542 wchar_t expanded[MAX_PATH]; 604 wchar_t expanded[MAX_PATH];
543 DWORD size = ExpandEnvironmentStrings( 605 DWORD size = ExpandEnvironmentStrings(
544 entry.location.c_str(), expanded, MAX_PATH); 606 entry.location.c_str(), expanded, MAX_PATH);
545 if (size != 0 && size <= MAX_PATH) { 607 if (size != 0 && size <= MAX_PATH)
546 GetCertificateInfo(base::FilePath(expanded), &entry.cert_info); 608 entry.location = expanded;
547 }
548 entry.version = base::IntToString16(layered_providers[i].version); 609 entry.version = base::IntToString16(layered_providers[i].version);
549
550 // Paths have already been collapsed.
551 NormalizeModule(&entry);
552 AddToListWithoutDuplicating(entry); 610 AddToListWithoutDuplicating(entry);
553 } 611 }
554 } 612 }
555 613
556 void ModuleEnumerator::PopulateModuleInformation(Module* module) { 614 void ModuleEnumerator::PopulateModuleInformation(Module* module) {
557 module->status = NOT_MATCHED; 615 module->status = NOT_MATCHED;
558 module->duplicate_count = 0; 616 module->duplicate_count = 0;
559 module->normalized = false; 617 module->normalized = false;
560 GetCertificateInfo(base::FilePath(module->location), &module->cert_info); 618 GetCertificateInfo(base::FilePath(module->location), &module->cert_info);
561 module->recommended_action = NONE; 619 module->recommended_action = NONE;
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after
749 return modules_to_notify_about_; 807 return modules_to_notify_about_;
750 } 808 }
751 809
752 void EnumerateModulesModel::MaybePostScanningTask() { 810 void EnumerateModulesModel::MaybePostScanningTask() {
753 DCHECK_CURRENTLY_ON(BrowserThread::UI); 811 DCHECK_CURRENTLY_ON(BrowserThread::UI);
754 static bool done = false; 812 static bool done = false;
755 if (!done) { 813 if (!done) {
756 BrowserThread::PostAfterStartupTask( 814 BrowserThread::PostAfterStartupTask(
757 FROM_HERE, 815 FROM_HERE,
758 BrowserThread::GetTaskRunnerForThread(BrowserThread::UI), 816 BrowserThread::GetTaskRunnerForThread(BrowserThread::UI),
759 base::Bind(&EnumerateModulesModel::ScanNow, base::Unretained(this))); 817 base::Bind(&EnumerateModulesModel::ScanNow,
818 base::Unretained(this),
819 true));
760 done = true; 820 done = true;
761 } 821 }
762 } 822 }
763 823
764 void EnumerateModulesModel::ScanNow() { 824 void EnumerateModulesModel::ScanNow(bool background_mode) {
765 DCHECK_CURRENTLY_ON(BrowserThread::UI); 825 DCHECK_CURRENTLY_ON(BrowserThread::UI);
766 826
767 // |module_enumerator_| is used as a lock to know whether or not there are 827 // |module_enumerator_| is used as a lock to know whether or not there are
768 // active/pending blocking pool tasks. If a module enumerator exists then a 828 // active/pending blocking pool tasks. If a module enumerator exists then a
769 // scan is already underway. Otherwise, either no scan has been completed or 829 // scan is already underway. Otherwise, either no scan has been completed or
770 // a scan has terminated. 830 // a scan has terminated.
771 if (module_enumerator_) 831 if (module_enumerator_) {
832 // If a scan is in progress and this request is for immediate results, then
833 // inform the background scan. This is done without any locks because the
834 // other thread only reads from the value that is being modified, and on
835 // Windows its an atomic write.
Patrick Monette 2016/10/14 22:45:55 Is this really safe?
chrisha 2016/10/17 15:32:21 Yeah, this is a standard trick. The variable is be
Patrick Monette 2016/10/17 19:52:40 Acknowledged.
836 if (!background_mode)
837 module_enumerator_->SetPerModuleDelayToZero();
772 return; 838 return;
839 }
773 840
774 // Only allow a single scan per process lifetime. Immediately notify any 841 // Only allow a single scan per process lifetime. Immediately notify any
775 // observers that the scan is complete. At this point |enumerated_modules_| is 842 // observers that the scan is complete. At this point |enumerated_modules_| is
776 // safe to access as no potentially racing blocking pool task can exist. 843 // safe to access as no potentially racing blocking pool task can exist.
777 if (!enumerated_modules_.empty()) { 844 if (!enumerated_modules_.empty()) {
778 FOR_EACH_OBSERVER(Observer, observers_, OnScanCompleted()); 845 FOR_EACH_OBSERVER(Observer, observers_, OnScanCompleted());
779 return; 846 return;
780 } 847 }
781 848
782 // ScanNow does not block, rather it simply schedules a task. 849 // ScanNow does not block, rather it simply schedules a task.
783 module_enumerator_.reset(new ModuleEnumerator(this)); 850 module_enumerator_.reset(new ModuleEnumerator(this));
851 if (!background_mode)
852 module_enumerator_->SetPerModuleDelayToZero();
784 module_enumerator_->ScanNow(&enumerated_modules_); 853 module_enumerator_->ScanNow(&enumerated_modules_);
785 } 854 }
786 855
787 base::ListValue* EnumerateModulesModel::GetModuleList() { 856 base::ListValue* EnumerateModulesModel::GetModuleList() {
788 DCHECK_CURRENTLY_ON(BrowserThread::UI); 857 DCHECK_CURRENTLY_ON(BrowserThread::UI);
789 858
790 // If a |module_enumerator_| is still around then scanning has not yet 859 // If a |module_enumerator_| is still around then scanning has not yet
791 // completed, and it is unsafe to read from |enumerated_modules_|. 860 // completed, and it is unsafe to read from |enumerated_modules_|.
792 if (module_enumerator_.get()) 861 if (module_enumerator_.get())
793 return nullptr; 862 return nullptr;
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
912 module_enumerator_.reset(); 981 module_enumerator_.reset();
913 982
914 UMA_HISTOGRAM_COUNTS_100("Conflicts.SuspectedBadModules", 983 UMA_HISTOGRAM_COUNTS_100("Conflicts.SuspectedBadModules",
915 suspected_bad_modules_detected_); 984 suspected_bad_modules_detected_);
916 UMA_HISTOGRAM_COUNTS_100("Conflicts.ConfirmedBadModules", 985 UMA_HISTOGRAM_COUNTS_100("Conflicts.ConfirmedBadModules",
917 confirmed_bad_modules_detected_); 986 confirmed_bad_modules_detected_);
918 987
919 // Forward the callback to any registered observers. 988 // Forward the callback to any registered observers.
920 FOR_EACH_OBSERVER(Observer, observers_, OnScanCompleted()); 989 FOR_EACH_OBSERVER(Observer, observers_, OnScanCompleted());
921 } 990 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698