| // Copyright 2018 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/chrome_cleaner/scanner/matcher_util.h" |
| |
| #include <shlobj.h> |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/path_service.h" |
| #include "base/stl_util.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/scoped_path_override.h" |
| #include "base/test/test_reg_util_win.h" |
| #include "base/win/registry.h" |
| #include "chrome/chrome_cleaner/os/file_path_sanitization.h" |
| #include "chrome/chrome_cleaner/scanner/signature_matcher.h" |
| #include "chrome/chrome_cleaner/test/resources/grit/test_resources.h" |
| #include "chrome/chrome_cleaner/test/test_file_util.h" |
| #include "chrome/chrome_cleaner/test/test_pup_data.h" |
| #include "chrome/chrome_cleaner/test/test_signature_matcher.h" |
| #include "chrome/chrome_cleaner/test/test_task_scheduler.h" |
| #include "chrome/chrome_cleaner/test/test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chrome_cleaner { |
| |
| namespace { |
| |
| constexpr base::char16 kFileName1[] = L"File1"; |
| constexpr base::char16 kFileName2[] = L"File2"; |
| constexpr base::char16 kFileName3[] = L"File3"; |
| constexpr base::char16 kFileName4[] = L"File4"; |
| |
| constexpr char kFileContent1[] = "This is the file content."; |
| constexpr char kFileContent2[] = "Hi!"; |
| constexpr char kFileContent3[] = "Hello World!"; |
| constexpr char kFileContent4[] = "Ho!"; |
| |
| constexpr char kFileContent[] = "This is the file content."; |
| |
| const char* const kKnownContentDigests[] = { |
| "00D2BB3E285BA62224888A9AD874AC2787D4CF681F30A2FD8EE2873859ECE1DC", |
| // Hash for content: |kFileContent|. |
| "BD283E41A3672B6BDAA574F8BD7176F8BCA95BD81383CDE32AA6D78B1DB0E371", |
| }; |
| |
| constexpr base::char16 kKnownOriginalFilename[] = L"uws.exe"; |
| constexpr base::char16 kUnknownOriginalFilename[] = L"knowngood.exe"; |
| const base::char16* const kKnownOriginalFilenames[] = { |
| L"dummy entry", L"uws.exe", L"zombie uws.exe", |
| }; |
| |
| constexpr base::char16 kKnownCompanyName[] = L"uws vendor inc"; |
| constexpr base::char16 kUnknownCompanyName[] = L"paradise"; |
| const base::char16* const kKnownCompanyNames[] = { |
| L"dummy entry", L"uws vendor inc", L"ACME", |
| }; |
| |
| constexpr FileDigestInfo kFileContentDigestInfos[] = { |
| {"02544E052F29BBA79C81243EC63B43B6CD85B185461928E65BFF501346C62A75", 33}, |
| {"04614470DDF4939091F5EC4A13C92A9EAAACF07CA5C3F713E792E2D21CD24075", 21}, |
| // Hash for content: |kFileContent2|. |
| {"82E0B92772BC0DA59AAB0B9231AA006FB37B4F99EC3E853C5A62786A1C7215BD", 4}, |
| {"9000000000000000000000000000000000000000000000000000000000000009", 4}, |
| {"94F7BDF53CDFDE7AA5E5C90BCDA6793B7377CE39E2591ABC758EBAE8072A275C", 12}, |
| // Hash for content: |kFileContent1|. |
| {"BD283E41A3672B6BDAA574F8BD7176F8BCA95BD81383CDE32AA6D78B1DB0E371", 26}, |
| }; |
| |
| // Messages are logged to a vector for testing. |
| class LoggingTest : public testing::Test { |
| public: |
| LoggingOverride logger_; |
| }; |
| |
| } // namespace |
| |
| TEST(MatcherUtilTest, IsKnownFileByDigest) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath file_path1(temp_dir.GetPath().Append(kFileName1)); |
| base::FilePath file_path2(temp_dir.GetPath().Append(kFileName2)); |
| base::FilePath file_path3(temp_dir.GetPath().Append(kFileName3)); |
| |
| CreateFileWithContent(file_path1, kFileContent, sizeof(kFileContent)); |
| CreateFileWithRepeatedContent(file_path2, kFileContent, sizeof(kFileContent), |
| 2); |
| |
| std::unique_ptr<SignatureMatcher> signature_matcher = |
| std::make_unique<SignatureMatcher>(); |
| ASSERT_TRUE(signature_matcher); |
| |
| // Hash: BD283E41A3672B6BDAA574F8BD7176F8BCA95BD81383CDE32AA6D78B1DB0E371. |
| EXPECT_TRUE(IsKnownFileByDigest(file_path1, signature_matcher.get(), |
| kKnownContentDigests, |
| base::size(kKnownContentDigests))); |
| // Hash: not present. |
| EXPECT_FALSE(IsKnownFileByDigest(file_path2, signature_matcher.get(), |
| kKnownContentDigests, |
| base::size(kKnownContentDigests))); |
| // The file doesn't exist. |
| EXPECT_FALSE(IsKnownFileByDigest(file_path3, signature_matcher.get(), |
| kKnownContentDigests, |
| base::size(kKnownContentDigests))); |
| } |
| |
| TEST(MatcherUtilTest, IsKnownFileByDigestInfo) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath file_path1(temp_dir.GetPath().Append(kFileName1)); |
| base::FilePath file_path2(temp_dir.GetPath().Append(kFileName2)); |
| base::FilePath file_path3(temp_dir.GetPath().Append(kFileName3)); |
| base::FilePath file_path4(temp_dir.GetPath().Append(kFileName4)); |
| |
| CreateFileWithContent(file_path1, kFileContent1, sizeof(kFileContent1)); |
| CreateFileWithContent(file_path2, kFileContent2, sizeof(kFileContent2)); |
| CreateFileWithContent(file_path3, kFileContent3, sizeof(kFileContent3)); |
| |
| std::unique_ptr<SignatureMatcher> signature_matcher = |
| std::make_unique<SignatureMatcher>(); |
| |
| // Search BD283E41A3672B6BDAA574F8BD7176F8BCA95BD81383CDE32AA6D78B1DB0E371. |
| EXPECT_TRUE(IsKnownFileByDigestInfo(file_path1, signature_matcher.get(), |
| kFileContentDigestInfos, |
| base::size(kFileContentDigestInfos))); |
| // Search 82E0B92772BC0DA59AAB0B9231AA006FB37B4F99EC3E853C5A62786A1C7215BD. |
| EXPECT_TRUE(IsKnownFileByDigestInfo(file_path2, signature_matcher.get(), |
| kFileContentDigestInfos, |
| base::size(kFileContentDigestInfos))); |
| |
| // Replace the content of file_path2 with a content of the same size, it |
| // must no longer match. |
| ASSERT_EQ(sizeof(kFileContent2), sizeof(kFileContent4)); |
| CreateFileWithContent(file_path2, kFileContent4, sizeof(kFileContent4)); |
| EXPECT_FALSE(IsKnownFileByDigestInfo(file_path2, signature_matcher.get(), |
| kFileContentDigestInfos, |
| base::size(kFileContentDigestInfos))); |
| |
| // The digest of |file_path3| is not in the array. |
| EXPECT_FALSE(IsKnownFileByDigestInfo(file_path3, signature_matcher.get(), |
| kFileContentDigestInfos, |
| base::size(kFileContentDigestInfos))); |
| // The |file_path4| doesn't exist. |
| EXPECT_FALSE(IsKnownFileByDigestInfo(file_path4, signature_matcher.get(), |
| kFileContentDigestInfos, |
| base::size(kFileContentDigestInfos))); |
| } |
| |
| TEST(MatcherUtilTest, IsKnownFileByOriginalFilename) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| TestSignatureMatcher signature_matcher; |
| |
| const base::FilePath normalized_temp_dir_path = |
| NormalizePath(temp_dir.GetPath()); |
| |
| // A non-existing file should not be recognized. |
| base::FilePath nonexistent_file_path( |
| normalized_temp_dir_path.Append(kFileName1)); |
| EXPECT_FALSE(IsKnownFileByOriginalFilename( |
| nonexistent_file_path, &signature_matcher, kKnownOriginalFilenames, |
| base::size(kKnownOriginalFilenames))); |
| |
| // An existing file without version information should not be recognized. |
| base::FilePath file_path2(normalized_temp_dir_path.Append(kFileName2)); |
| CreateFileWithContent(file_path2, kFileContent, sizeof(kFileContent)); |
| EXPECT_FALSE(IsKnownFileByOriginalFilename( |
| file_path2, &signature_matcher, kKnownOriginalFilenames, |
| base::size(kKnownOriginalFilenames))); |
| |
| // A file with version information but not in the array should not be |
| // recognized. |
| base::FilePath file_path3(normalized_temp_dir_path.Append(kFileName3)); |
| |
| VersionInformation goodware_information = {}; |
| goodware_information.original_filename = kUnknownOriginalFilename; |
| signature_matcher.MatchVersionInformation(file_path3, goodware_information); |
| |
| CreateFileWithContent(file_path3, kFileContent, sizeof(kFileContent)); |
| EXPECT_FALSE(IsKnownFileByOriginalFilename( |
| file_path3, &signature_matcher, kKnownOriginalFilenames, |
| base::size(kKnownOriginalFilenames))); |
| |
| // A file with version information present in the array should be recognized. |
| base::FilePath file_path4(normalized_temp_dir_path.Append(kFileName4)); |
| |
| VersionInformation badware_information = {}; |
| badware_information.original_filename = kKnownOriginalFilename; |
| signature_matcher.MatchVersionInformation(file_path4, badware_information); |
| |
| CreateFileWithContent(file_path4, kFileContent, sizeof(kFileContent)); |
| EXPECT_TRUE(IsKnownFileByOriginalFilename( |
| file_path4, &signature_matcher, kKnownOriginalFilenames, |
| base::size(kKnownOriginalFilenames))); |
| } |
| |
| TEST(MatcherUtilTest, IsKnownFileByCompanyName) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| const base::FilePath normalized_temp_dir_path = |
| NormalizePath(temp_dir.GetPath()); |
| |
| TestSignatureMatcher signature_matcher; |
| |
| // A non-existing file should not be recognized. |
| base::FilePath nonexistent_file_path( |
| normalized_temp_dir_path.Append(kFileName1)); |
| EXPECT_FALSE(IsKnownFileByCompanyName(nonexistent_file_path, |
| &signature_matcher, kKnownCompanyNames, |
| base::size(kKnownCompanyNames))); |
| |
| // An existing file without version information should not be recognized. |
| base::FilePath file_path2(normalized_temp_dir_path.Append(kFileName2)); |
| CreateFileWithContent(file_path2, kFileContent, sizeof(kFileContent)); |
| EXPECT_FALSE(IsKnownFileByCompanyName(file_path2, &signature_matcher, |
| kKnownCompanyNames, |
| base::size(kKnownCompanyNames))); |
| |
| // A file with version information but not in the array should not be |
| // recognized. |
| base::FilePath file_path3(normalized_temp_dir_path.Append(kFileName3)); |
| |
| VersionInformation goodware_information = {}; |
| goodware_information.company_name = kUnknownCompanyName; |
| signature_matcher.MatchVersionInformation(file_path3, goodware_information); |
| |
| CreateFileWithContent(file_path3, kFileContent, sizeof(kFileContent)); |
| EXPECT_FALSE(IsKnownFileByCompanyName(file_path3, &signature_matcher, |
| kKnownCompanyNames, |
| base::size(kKnownCompanyNames))); |
| |
| // A file with version information present in the array should be recognized. |
| base::FilePath file_path4(normalized_temp_dir_path.Append(kFileName4)); |
| |
| VersionInformation badware_information = {}; |
| badware_information.company_name = kKnownCompanyName; |
| signature_matcher.MatchVersionInformation(file_path4, badware_information); |
| |
| CreateFileWithContent(file_path4, kFileContent, sizeof(kFileContent)); |
| EXPECT_TRUE(IsKnownFileByCompanyName(file_path4, &signature_matcher, |
| kKnownCompanyNames, |
| base::size(kKnownCompanyNames))); |
| } |
| |
| TEST(MatcherUtilTest, MatchSingleFileWithPattern) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath match; |
| |
| // Collect no path. |
| EXPECT_FALSE( |
| MatchSingleFileWithPattern(temp_dir.GetPath(), L"*", false, &match)); |
| |
| // Collect exactly one path matching the pattern. |
| base::FilePath file_path1(temp_dir.GetPath().Append(L"dummy.1.tar.gz")); |
| base::FilePath file_path2(temp_dir.GetPath().Append(L"uws-name.exe")); |
| CreateFileWithContent(file_path1, kFileContent, sizeof(kFileContent)); |
| CreateFileWithContent(file_path2, kFileContent, sizeof(kFileContent)); |
| match.clear(); |
| EXPECT_TRUE(MatchSingleFileWithPattern(temp_dir.GetPath(), L"*.*.*.*", false, |
| &match)); |
| EXPECT_EQ(file_path1, match); |
| match.clear(); |
| EXPECT_TRUE(MatchSingleFileWithPattern(temp_dir.GetPath(), L"uws*.exe", false, |
| &match)); |
| EXPECT_EQ(file_path2, match); |
| match.clear(); |
| EXPECT_TRUE(MatchSingleFileWithPattern(temp_dir.GetPath(), LR"(uws-????.exe)", |
| false, &match)); |
| EXPECT_EQ(file_path2, match); |
| |
| // Collecting multiple paths should fail. |
| base::FilePath file_path3(temp_dir.GetPath().Append(L"dummy.2.tar.gz")); |
| CreateFileWithContent(file_path3, kFileContent, sizeof(kFileContent)); |
| EXPECT_FALSE(MatchSingleFileWithPattern(temp_dir.GetPath(), L"*.*.*.*", false, |
| &match)); |
| } |
| |
| TEST(MatcherUtilTest, MatchSingleFileWithPatternWithFolders) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath match; |
| |
| // Collect no path. |
| EXPECT_FALSE( |
| MatchSingleFileWithPattern(temp_dir.GetPath(), L"*", true, &match)); |
| |
| // Add a folder to the main folder. |
| base::FilePath folder = temp_dir.GetPath().Append(L"folder"); |
| ASSERT_TRUE(base::CreateDirectory(folder)); |
| |
| // Collect exactly one path matching the pattern. |
| base::FilePath file_path(temp_dir.GetPath().Append(L"dummy.1.tar.gz")); |
| CreateFileWithContent(file_path, kFileContent, sizeof(kFileContent)); |
| |
| match.clear(); |
| EXPECT_TRUE(MatchSingleFileWithPattern(temp_dir.GetPath(), L"*.*.*.*", false, |
| &match)); |
| EXPECT_EQ(file_path, match); |
| |
| match.clear(); |
| EXPECT_TRUE( |
| MatchSingleFileWithPattern(temp_dir.GetPath(), L"*.*.*.*", true, &match)); |
| EXPECT_EQ(file_path, match); |
| |
| // Collect with a wild-card matching everything. |
| match.clear(); |
| EXPECT_TRUE( |
| MatchSingleFileWithPattern(temp_dir.GetPath(), L"*", false, &match)); |
| EXPECT_EQ(file_path, match); |
| |
| match.clear(); |
| EXPECT_FALSE( |
| MatchSingleFileWithPattern(temp_dir.GetPath(), L"*", true, &match)); |
| EXPECT_TRUE(match.empty()); |
| |
| // Collecting the folder. |
| EXPECT_FALSE( |
| MatchSingleFileWithPattern(temp_dir.GetPath(), L"fold?r", false, &match)); |
| EXPECT_TRUE(match.empty()); |
| |
| EXPECT_TRUE( |
| MatchSingleFileWithPattern(temp_dir.GetPath(), L"fold?r", true, &match)); |
| EXPECT_EQ(folder, match); |
| } |
| |
| TEST(MatcherUtilTest, CollectPathRecursively) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| // Add a folder to the main folder. |
| base::FilePath folder = temp_dir.GetPath().Append(L"folder"); |
| ASSERT_TRUE(base::CreateDirectory(folder)); |
| base::FilePath subfolder = folder.Append(L"subfolder"); |
| ASSERT_TRUE(base::CreateDirectory(subfolder)); |
| |
| base::FilePath file_path1(folder.Append(kFileName1)); |
| CreateFileWithContent(file_path1, kFileContent, sizeof(kFileContent)); |
| base::FilePath file_path2(subfolder.Append(kFileName2)); |
| CreateFileWithContent(file_path2, kFileContent, sizeof(kFileContent)); |
| |
| base::FilePath nonexistent_path = temp_dir.GetPath().Append(L"nonexistent"); |
| |
| SimpleTestPUP pup; |
| CollectPathRecursively(nonexistent_path, &pup); |
| EXPECT_TRUE(pup.expanded_disk_footprints.empty()); |
| |
| CollectPathRecursively(folder, &pup); |
| EXPECT_FALSE(pup.expanded_disk_footprints.empty()); |
| |
| ExpectDiskFootprint(pup, folder); |
| ExpectDiskFootprint(pup, subfolder); |
| ExpectDiskFootprint(pup, file_path1); |
| ExpectDiskFootprint(pup, file_path2); |
| } |
| |
| TEST(MatcherUtilTest, CollectDiskFootprintRecursively) { |
| base::FilePath local_appdata_path( |
| ExpandSpecialFolderPath(CSIDL_LOCAL_APPDATA, base::FilePath())); |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDirUnderPath(local_appdata_path)); |
| |
| // Add a folder to the main folder. |
| base::FilePath folder = temp_dir.GetPath().Append(L"folder"); |
| ASSERT_TRUE(base::CreateDirectory(folder)); |
| base::FilePath subfolder = folder.Append(L"subfolder"); |
| ASSERT_TRUE(base::CreateDirectory(subfolder)); |
| |
| base::FilePath file_path1(folder.Append(kFileName1)); |
| CreateFileWithContent(file_path1, kFileContent, sizeof(kFileContent)); |
| base::FilePath file_path2(subfolder.Append(kFileName2)); |
| CreateFileWithContent(file_path2, kFileContent, sizeof(kFileContent)); |
| |
| // Create the relative paths. |
| base::FilePath basename = temp_dir.GetPath().BaseName(); |
| base::FilePath folder_path = basename.Append(L"Folder"); |
| base::FilePath not_folder_path = basename.Append(L"not_Folder"); |
| |
| SimpleTestPUP pup; |
| // Collect in the wrong CSIDL. |
| CollectDiskFootprintRecursively(CSIDL_APPDATA, folder_path.value().c_str(), |
| &pup); |
| EXPECT_TRUE(pup.expanded_disk_footprints.empty()); |
| |
| // Collect in the good CSIDL but an inexisting folder. |
| CollectDiskFootprintRecursively(CSIDL_LOCAL_APPDATA, |
| not_folder_path.value().c_str(), &pup); |
| EXPECT_TRUE(pup.expanded_disk_footprints.empty()); |
| |
| // Collect the right files. |
| CollectDiskFootprintRecursively(CSIDL_LOCAL_APPDATA, |
| folder_path.value().c_str(), &pup); |
| EXPECT_FALSE(pup.expanded_disk_footprints.empty()); |
| |
| ExpectDiskFootprint(pup, folder); |
| ExpectDiskFootprint(pup, subfolder); |
| ExpectDiskFootprint(pup, file_path1); |
| ExpectDiskFootprint(pup, file_path2); |
| } |
| |
| TEST_F(LoggingTest, LogFolderContent) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath temp_dir_path = temp_dir.GetPath(); |
| base::FilePath file_path1(temp_dir_path.Append(kFileName1)); |
| base::FilePath file_path2(temp_dir_path.Append(kFileName2)); |
| CreateFileWithContent(file_path1, kFileContent, sizeof(kFileContent)); |
| CreateFileWithContent(file_path2, kFileContent, sizeof(kFileContent)); |
| |
| // Add a folder to the main folder. |
| base::FilePath subfolder = temp_dir.GetPath().Append(L"folder"); |
| ASSERT_TRUE(base::CreateDirectory(subfolder)); |
| base::FilePath file_path3(subfolder.Append(kFileName3)); |
| CreateFileWithContent(file_path3, kFileContent, sizeof(kFileContent)); |
| |
| LOG(INFO) << "Logging content of " << temp_dir.GetPath().value(); |
| |
| LogFolderContent(temp_dir.GetPath()); |
| |
| const std::string filename1 = base::WideToUTF8(kFileName1); |
| const std::string filename2 = base::WideToUTF8(kFileName2); |
| const std::string filename3 = base::WideToUTF8(kFileName3); |
| EXPECT_TRUE(logger_.LoggingMessagesContain(filename1)); |
| EXPECT_TRUE(logger_.LoggingMessagesContain(filename2)); |
| EXPECT_TRUE(logger_.LoggingMessagesContain(filename3)); |
| |
| // Add more files in the folder than maximum to be logged. |
| logger_.FlushMessages(); |
| for (size_t count = 0; count < kMaxFilesInFolderToLog; ++count) { |
| base::FilePath file_path(temp_dir_path.Append( |
| base::StrCat({L"dummy", base::NumberToString16(count)}))); |
| CreateFileWithContent(file_path, kFileContent, sizeof(kFileContent)); |
| } |
| |
| LOG(INFO) << "Logging content of " << temp_dir.GetPath().value(); |
| LogFolderContent(temp_dir.GetPath()); |
| |
| EXPECT_TRUE( |
| logger_.LoggingMessagesContain("The folder contains too many files")); |
| } |
| |
| } // namespace chrome_cleaner |