#include "chrome/elevation_service/caller_validation.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/win/scoped_process_information.h"
#include "base/win/startup_information.h"
#include "chrome/elevation_service/elevator.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace elevation_service {
namespace {
base::Process StartSuspendedFakeProcess(const base::FilePath& path) {
PROCESS_INFORMATION temp_process_info = {};
if (!base::PathExists(path)) {
base::CreateDirectory(path.DirName());
base::CopyFile(base::PathService::CheckedGet(base::FILE_EXE), path);
}
base::win::StartupInformation startup_info;
std::wstring writable_cmd_line = path.value();
if (::CreateProcess(nullptr, writable_cmd_line.data(), nullptr, nullptr,
FALSE, CREATE_SUSPENDED, nullptr,
nullptr, startup_info.startup_info(),
&temp_process_info)) {
base::win::ScopedProcessInformation process_info(temp_process_info);
return base::Process(process_info.TakeProcessHandle());
}
return base::Process();
}
void VerifyValidationResult(const base::FilePath& path1,
const base::FilePath& path2,
bool expected_match) {
auto process1 = StartSuspendedFakeProcess(path1);
ASSERT_TRUE(process1.IsRunning());
auto process2 = StartSuspendedFakeProcess(path2);
ASSERT_TRUE(process2.IsRunning());
const auto data = GenerateValidationData(
ProtectionLevel::PROTECTION_PATH_VALIDATION, process1);
ASSERT_TRUE(data.has_value()) << data.error();
EXPECT_EQ(expected_match, SUCCEEDED(ValidateData(process2, *data)))
<< path1 << " vs. " << path2;
process1.Terminate(0, true);
process2.Terminate(0, true);
}
}
class CallerValidationTest : public ::testing::Test {
protected:
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
base::ScopedTempDir temp_dir_;
};
TEST_F(CallerValidationTest, NoneValidationTest) {
const auto my_process = base::Process::Current();
const auto data =
GenerateValidationData(ProtectionLevel::PROTECTION_NONE, my_process);
ASSERT_TRUE(data.has_value()) << data.error();
ASSERT_HRESULT_SUCCEEDED(ValidateData(my_process, *data));
}
TEST_F(CallerValidationTest, PathValidationTest) {
const auto my_process = base::Process::Current();
const auto data = GenerateValidationData(
ProtectionLevel::PROTECTION_PATH_VALIDATION, my_process);
ASSERT_TRUE(data.has_value()) << data.error();
ASSERT_HRESULT_SUCCEEDED(ValidateData(my_process, *data));
}
TEST_F(CallerValidationTest, PathValidationOldDataTest) {
const std::vector<uint8_t> data = {'P', 'A', 'T', 'H'};
const auto result = ValidateData(base::Process::Current(), data);
ASSERT_HRESULT_FAILED(result);
ASSERT_EQ(result, E_INVALIDARG);
}
TEST_F(CallerValidationTest, DeprecatedPathValidationTest) {
const auto data =
GenerateValidationData(ProtectionLevel::PROTECTION_PATH_VALIDATION_OLD,
base::Process::Current());
ASSERT_FALSE(data.has_value());
EXPECT_EQ(data.error(), Elevator::kErrorUnsupportedProtectionLevel);
}
TEST_F(CallerValidationTest, BackwardsCompatiblePathDataTest) {
auto data = GenerateValidationData(
ProtectionLevel::PROTECTION_PATH_VALIDATION, base::Process::Current());
ASSERT_TRUE(data.has_value());
ASSERT_EQ((*data)[0], ProtectionLevel::PROTECTION_PATH_VALIDATION);
(*data)[0] = ProtectionLevel::PROTECTION_PATH_VALIDATION_OLD;
const auto result = ValidateData(base::Process::Current(), *data);
ASSERT_HRESULT_SUCCEEDED(result);
}
TEST_F(CallerValidationTest, PathValidationTestFail) {
const auto my_process = base::Process::Current();
const auto data = GenerateValidationData(
ProtectionLevel::PROTECTION_PATH_VALIDATION, my_process);
ASSERT_TRUE(data.has_value()) << data.error();
auto notepad_process =
base::LaunchProcess(L"calc.exe", base::LaunchOptions());
ASSERT_TRUE(notepad_process.IsRunning());
const HRESULT res = ValidateData(notepad_process, *data);
ASSERT_HRESULT_FAILED(res);
ASSERT_EQ(res, Elevator::kValidationDidNotPass);
ASSERT_TRUE(notepad_process.Terminate(0, true));
}
TEST_F(CallerValidationTest, PathValidationTestOtherProcess) {
base::expected<std::vector<uint8_t>, HRESULT> data;
{
auto notepad_process =
base::LaunchProcess(L"calc.exe", base::LaunchOptions());
ASSERT_TRUE(notepad_process.IsRunning());
data = GenerateValidationData(ProtectionLevel::PROTECTION_PATH_VALIDATION,
notepad_process);
ASSERT_TRUE(notepad_process.Terminate(0, true));
}
ASSERT_TRUE(data.has_value()) << data.error();
{
auto notepad_process =
base::LaunchProcess(L"calc.exe", base::LaunchOptions());
ASSERT_TRUE(notepad_process.IsRunning());
ASSERT_HRESULT_SUCCEEDED(ValidateData(notepad_process, *data));
ASSERT_TRUE(notepad_process.Terminate(0, true));
}
}
TEST_F(CallerValidationTest, NoneValidationTestOtherProcess) {
const auto my_process = base::Process::Current();
const auto data =
GenerateValidationData(ProtectionLevel::PROTECTION_NONE, my_process);
ASSERT_TRUE(data.has_value()) << data.error();
auto notepad_process =
base::LaunchProcess(L"calc.exe", base::LaunchOptions());
ASSERT_TRUE(notepad_process.IsRunning());
ASSERT_HRESULT_SUCCEEDED(ValidateData(notepad_process, *data));
ASSERT_TRUE(notepad_process.Terminate(0, true));
}
TEST_F(CallerValidationTest, PathValidationFuzzyPathMatch) {
const auto temp_dir = temp_dir_.GetPath().AppendASCII("testdir");
const auto app1_path = temp_dir.AppendASCII("app1.exe");
const auto app2_path =
temp_dir.AppendASCII("Application").AppendASCII("app2.exe");
const auto app3_path =
temp_dir.AppendASCII("Application").AppendASCII("app3.exe");
const auto app4_path = temp_dir.AppendASCII("Temp").AppendASCII("app4.exe");
const auto app5_path = temp_dir.AppendASCII("Blah").AppendASCII("app5.exe");
const auto app6_path = temp_dir.AppendASCII("Blah").AppendASCII("app6.exe");
const auto app7_path = temp_dir.AppendASCII("Application")
.AppendASCII("Temp")
.AppendASCII("app7.exe");
const auto app8_path =
temp_dir.AppendASCII("Program Files").AppendASCII("app8.exe");
const auto app9_path =
temp_dir.AppendASCII("Program Files (x86)").AppendASCII("app9.exe");
const auto app10_path = temp_dir.AppendASCII("Application")
.AppendASCII("1.2.3.4")
.AppendASCII("app10.exe");
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app1_path, app2_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app1_path, app3_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app1_path, app4_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app1_path, app5_path, false));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app2_path, app3_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app2_path, app4_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app2_path, app5_path, false));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app4_path, app2_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app5_path, app6_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app7_path, app3_path, false));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app7_path, app1_path, false));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app8_path, app9_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app1_path, app8_path, false));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app2_path, app10_path, true));
ASSERT_NO_FATAL_FAILURE(
VerifyValidationResult(app5_path, app10_path, false));
}
TEST_F(CallerValidationTest, DISABLED_PathValidationNetwork) {
const auto data = GenerateValidationData(
ProtectionLevel::PROTECTION_PATH_VALIDATION, base::Process::Current());
EXPECT_FALSE(data.has_value());
EXPECT_EQ(data.error(), Elevator::kErrorUnsupportedFilePath);
}
TEST_F(CallerValidationTest, TrimProcessPath) {
struct TestData {
base::FilePath::StringViewType input;
base::FilePath::StringViewType expected;
} cases[] = {
{L"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
L"C:\\Program Files\\Google\\Chrome"},
{L"C:\\Program Files\\Google\\Chrome\\Temp\\chrome.exe",
L"C:\\Program Files\\Google\\Chrome"},
{L"C:\\Program Files (x86)\\Google\\Chrome\\Temp\\chrome.exe",
L"C:\\Program Files\\Google\\Chrome"},
{L"C:\\Program Files (x86)\\Google\\Chrome\\Blah\\chrome.exe",
L"C:\\Program Files\\Google\\Chrome\\Blah"},
{L"C:\\Dir\\app.exe", L"C:\\Dir"},
{L"C:\\Dir\\", L"C:\\Dir"},
{L"C:\\Dir", L"C:\\Dir"},
{L"C:\\Program Files "
L"(x86)\\Google\\Chrome\\Temp\\scoped_dir11452_73964817\\chrome.exe",
L"C:\\Program Files\\Google\\Chrome"},
{L"C:\\Program Files "
L"(x86)\\Google\\Chrome\\scoped_dir11452_73964817\\Temp\\chrome.exe",
L"C:\\Program Files\\Google\\Chrome\\scoped_dir11452_73964817"},
{L"C:\\Program Files (x86)\\Google\\scoped_dir1\\Chrome\\chrome.exe",
L"C:\\Program Files\\Google\\scoped_dir1\\Chrome"},
{L"C:\\Temp\\Program Files "
L"(x86)\\Google\\scoped_dir1\\Chrome\\chrome.exe",
L"C:\\Temp\\Program Files\\Google\\scoped_dir1\\Chrome"},
{L"C:\\scoped_dir1234\\Program Files "
L"(x86)\\Google\\scoped_dir1234\\Chrome\\chrome.exe",
L"C:\\scoped_dir1234\\Program Files\\Google\\scoped_dir1234\\Chrome"},
{L"C:\\Program Files\\Google\\Chrome\\Application\\1.2.3.4\\chrome.exe",
L"C:\\Program Files\\Google\\Chrome"},
};
for (size_t i = 0; i < std::size(cases); ++i) {
base::FilePath input(UNSAFE_TODO(cases[i]).input);
auto output = MaybeTrimProcessPathForTesting(input);
EXPECT_EQ(output.value(), UNSAFE_TODO(cases[i]).expected);
}
}
}