blob: f909ce6fd0807c7872ad9e9c9360331961c3f4c5 [file] [log] [blame]
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "syzygy/genfilter/genfilter_app.h"
#include "base/strings/stringprintf.h"
#include "gtest/gtest.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pe/image_filter.h"
#include "syzygy/pe/unittest_util.h"
namespace genfilter {
namespace {
class TestGenFilterApp : public GenFilterApp {
public:
// Expose for testing.
using GenFilterApp::action_;
using GenFilterApp::input_image_;
using GenFilterApp::input_pdb_;
using GenFilterApp::output_file_;
using GenFilterApp::inputs_;
using GenFilterApp::pretty_print_;
using GenFilterApp::overwrite_;
};
class GenFilterAppTest : public testing::PELibUnitTest {
public:
typedef testing::PELibUnitTest Super;
typedef application::Application<TestGenFilterApp> TestApplication;
GenFilterAppTest()
: cmd_line_(base::FilePath(L"genfilter.exe")),
impl_(app_.implementation()) {
}
virtual void SetUp() override {
Super::SetUp();
// Setup the IO streams.
ASSERT_NO_FATAL_FAILURE(CreateTemporaryDir(&temp_dir_));
stdin_path_ = temp_dir_.Append(L"NUL");
stdout_path_ = temp_dir_.Append(L"stdout.txt");
stderr_path_ = temp_dir_.Append(L"stderr.txt");
ASSERT_NO_FATAL_FAILURE(InitStreams(
stdin_path_, stdout_path_, stderr_path_));
// Point the application at the test's command-line and IO streams.
app_.set_command_line(&cmd_line_);
app_.set_in(in());
app_.set_out(out());
app_.set_err(err());
test_dll_ = testing::GetExeRelativePath(testing::kTestDllName);
test_dll_pdb_ = testing::GetExeRelativePath(testing::kTestDllPdbName);
output_file_ = temp_dir_.Append(L"output.json");
}
// Creates an empty file at the given path.
void MakeFile(const base::FilePath& path) {
base::ScopedFILE file(base::OpenFile(path, "wb"));
ASSERT_TRUE(file.get() != NULL);
}
// Generates a file with the given name in the temp directory, returning the
// path to it.
void MakeFile(const wchar_t* filename, base::FilePath* path) {
DCHECK(filename != NULL);
DCHECK(path != NULL);
*path = temp_dir_.Append(filename);
ASSERT_NO_FATAL_FAILURE(MakeFile(*path));
return;
}
// Builds a series of 2 filters, to test out the various set operation
// actions. Populates filters_ and filter_paths_.
void BuildFilters() {
filters_.resize(2);
filters_[0].Init(test_dll_);
filters_[1].Init(test_dll_);
// Create two filters with overlapping ranges so that we can test all of
// the set operations.
filters_[0].filter.Mark(pe::ImageFilter::RelativeAddressFilter::Range(
core::RelativeAddress(0), 1024));
filters_[1].filter.Mark(pe::ImageFilter::RelativeAddressFilter::Range(
core::RelativeAddress(512), 1024));
filter_paths_.resize(filters_.size());
for (size_t i = 0; i < filters_.size(); ++i) {
filter_paths_[i] = temp_dir_.Append(
base::StringPrintf(L"filter-%d.json", i));
ASSERT_TRUE(filters_[i].SaveToJSON(true, filter_paths_[i]));
}
pe::ImageFilter f(filters_[0]);
f.signature.module_time_date_stamp ^= 0xBAADF00D;
mismatched_filter_path_ = temp_dir_.Append(L"mismatched-filter.json");
ASSERT_TRUE(f.SaveToJSON(false, mismatched_filter_path_));
}
protected:
// The command line to be given to the application under test.
base::CommandLine cmd_line_;
// The application object under test.
TestApplication app_;
// A reference to the underlying application implementation for convenience.
TestGenFilterApp& impl_;
// A temporary folder where all IO will be stored.
base::FilePath temp_dir_;
// @name File paths used for the standard IO streams.
// @{
base::FilePath stdin_path_;
base::FilePath stdout_path_;
base::FilePath stderr_path_;
// @}
// A handful of paths.
base::FilePath test_dll_;
base::FilePath test_dll_pdb_;
base::FilePath output_file_;
// Some generated filters.
std::vector<pe::ImageFilter> filters_;
std::vector<base::FilePath> filter_paths_;
base::FilePath mismatched_filter_path_;
};
} // namespace
TEST_F(GenFilterAppTest, ParseCommandLineFailsWithNoAction) {
cmd_line_.AppendArgPath(base::FilePath(L"foo.json"));
ASSERT_FALSE(impl_.ParseCommandLine(&cmd_line_));
}
TEST_F(GenFilterAppTest, ParseCommandLineFailsWithNoInputFiles) {
cmd_line_.AppendSwitchASCII("action", "invert");
ASSERT_FALSE(impl_.ParseCommandLine(&cmd_line_));
}
TEST_F(GenFilterAppTest, ParseCommandLineExplicitInputFiles) {
std::vector<base::FilePath> temp_files;
cmd_line_.AppendSwitchASCII("action", "union");
for (size_t i = 0; i < 10; ++i) {
base::FilePath temp_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_, &temp_file));
cmd_line_.AppendArgPath(temp_file);
temp_files.push_back(temp_file);
}
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(temp_files, impl_.inputs_);
}
TEST_F(GenFilterAppTest, ParseCommandLineInputFilesGlob) {
std::vector<base::FilePath> temp_files;
cmd_line_.AppendSwitchASCII("action", "union");
for (size_t i = 0; i < 10; ++i) {
base::FilePath path =
temp_dir_.Append(base::StringPrintf(L"filter-%d.json", i));
base::ScopedFILE file(base::OpenFile(path, "wb"));
temp_files.push_back(path);
}
cmd_line_.AppendArgPath(temp_dir_.Append(L"*.json"));
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(temp_files, impl_.inputs_);
}
TEST_F(GenFilterAppTest, ParseCommandLineMinimal) {
base::FilePath foo_json;
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo.json", &foo_json));
cmd_line_.AppendArgPath(foo_json);
cmd_line_.AppendSwitchASCII("action", "invert");
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
EXPECT_EQ(GenFilterApp::kInvert, impl_.action_);
EXPECT_TRUE(impl_.input_image_.empty());
EXPECT_TRUE(impl_.input_pdb_.empty());
EXPECT_TRUE(impl_.output_file_.empty());
EXPECT_EQ(1u, impl_.inputs_.size());
EXPECT_FALSE(impl_.overwrite_);
EXPECT_FALSE(impl_.pretty_print_);
}
TEST_F(GenFilterAppTest, ParseCommandLineFull) {
base::FilePath foo1_txt, foo2_txt;
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo1.txt", &foo1_txt));
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo2.txt", &foo2_txt));
cmd_line_.AppendArgPath(foo1_txt);
cmd_line_.AppendArgPath(foo2_txt);
cmd_line_.AppendSwitchASCII("action", "compile");
cmd_line_.AppendSwitchPath("input-image", test_dll_);
cmd_line_.AppendSwitchPath("input-pdb", test_dll_pdb_);
cmd_line_.AppendSwitchPath("output-file", output_file_);
cmd_line_.AppendSwitch("overwrite");
cmd_line_.AppendSwitch("pretty-print");
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
EXPECT_EQ(GenFilterApp::kCompile, impl_.action_);
EXPECT_EQ(test_dll_, impl_.input_image_);
EXPECT_EQ(test_dll_pdb_, impl_.input_pdb_);
EXPECT_EQ(output_file_, impl_.output_file_);
EXPECT_EQ(2u, impl_.inputs_.size());
EXPECT_TRUE(impl_.overwrite_);
EXPECT_TRUE(impl_.pretty_print_);
}
TEST_F(GenFilterAppTest, ParseCommandLineInvertFailsWithMultipleInputs) {
base::FilePath foo1_json, foo2_json;
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo1.json", &foo1_json));
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo2.json", &foo2_json));
cmd_line_.AppendSwitchASCII("action", "invert");
cmd_line_.AppendArgPath(foo1_json);
cmd_line_.AppendArgPath(foo2_json);
ASSERT_FALSE(impl_.ParseCommandLine(&cmd_line_));
}
TEST_F(GenFilterAppTest, ParseCommandLineIntersectFailsWithSingleInput) {
base::FilePath foo_json;
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo.json", &foo_json));
cmd_line_.AppendSwitchASCII("action", "intersect");
cmd_line_.AppendArgPath(foo_json);
ASSERT_FALSE(impl_.ParseCommandLine(&cmd_line_));
}
TEST_F(GenFilterAppTest, ParseCommandLineUnionFailsWithSingleInput) {
base::FilePath foo_json;
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo.json", &foo_json));
cmd_line_.AppendSwitchASCII("action", "union");
cmd_line_.AppendArgPath(foo_json);
ASSERT_FALSE(impl_.ParseCommandLine(&cmd_line_));
}
TEST_F(GenFilterAppTest, ParseCommandLineSubtractFailsWithSingleInput) {
base::FilePath foo_json;
ASSERT_NO_FATAL_FAILURE(MakeFile(L"foo.json", &foo_json));
cmd_line_.AppendSwitchASCII("action", "subtract");
cmd_line_.AppendArgPath(foo_json);
ASSERT_FALSE(impl_.ParseCommandLine(&cmd_line_));
}
TEST_F(GenFilterAppTest, InvertDoesNotOverwriteExistingOutput) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
ASSERT_NO_FATAL_FAILURE(MakeFile(output_file_));
cmd_line_.AppendSwitchASCII("action", "invert");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendSwitchPath("output-file", output_file_);
base::CopyFile(filter_paths_[0], output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(1, impl_.Run());
}
TEST_F(GenFilterAppTest, InvertOverwriteExistingOutputWorks) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
ASSERT_NO_FATAL_FAILURE(MakeFile(output_file_));
cmd_line_.AppendSwitchASCII("action", "invert");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendSwitchPath("output-file", output_file_);
cmd_line_.AppendSwitch("overwrite");
base::CopyFile(filter_paths_[0], output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(0, impl_.Run());
}
TEST_F(GenFilterAppTest, InvertSucceeds) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
cmd_line_.AppendSwitchASCII("action", "invert");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendSwitchPath("output-file", output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(0, impl_.Run());
ASSERT_TRUE(base::PathExists(output_file_));
pe::ImageFilter f;
ASSERT_TRUE(f.LoadFromJSON(output_file_));
filters_[0].filter.Invert(&filters_[0].filter);
EXPECT_EQ(filters_[0].filter, f.filter);
}
TEST_F(GenFilterAppTest, IntersectSucceeds) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
cmd_line_.AppendSwitchASCII("action", "intersect");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendArgPath(filter_paths_[1]);
cmd_line_.AppendSwitchPath("output-file", output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(0, impl_.Run());
ASSERT_TRUE(base::PathExists(output_file_));
pe::ImageFilter f;
ASSERT_TRUE(f.LoadFromJSON(output_file_));
filters_[0].filter.Intersect(filters_[1].filter, &filters_[0].filter);
EXPECT_EQ(filters_[0].filter, f.filter);
}
TEST_F(GenFilterAppTest, SubtractSucceeds) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
cmd_line_.AppendSwitchASCII("action", "subtract");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendArgPath(filter_paths_[1]);
cmd_line_.AppendSwitchPath("output-file", output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(0, impl_.Run());
ASSERT_TRUE(base::PathExists(output_file_));
pe::ImageFilter f;
ASSERT_TRUE(f.LoadFromJSON(output_file_));
filters_[0].filter.Subtract(filters_[1].filter, &filters_[0].filter);
EXPECT_EQ(filters_[0].filter, f.filter);
}
TEST_F(GenFilterAppTest, UnionSucceeds) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
cmd_line_.AppendSwitchASCII("action", "union");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendArgPath(filter_paths_[1]);
cmd_line_.AppendSwitchPath("output-file", output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(0, impl_.Run());
ASSERT_TRUE(base::PathExists(output_file_));
pe::ImageFilter f;
ASSERT_TRUE(f.LoadFromJSON(output_file_));
filters_[0].filter.Union(filters_[1].filter, &filters_[0].filter);
EXPECT_EQ(filters_[0].filter, f.filter);
}
TEST_F(GenFilterAppTest, IntersectFailsMismatchedFilters) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
cmd_line_.AppendSwitchASCII("action", "intersect");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendArgPath(mismatched_filter_path_);
cmd_line_.AppendSwitchPath("output-file", output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_NE(0, impl_.Run());
}
TEST_F(GenFilterAppTest, SubtractFailsMismatchedFilters) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
cmd_line_.AppendSwitchASCII("action", "subtract");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendArgPath(mismatched_filter_path_);
cmd_line_.AppendSwitchPath("output-file", output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_NE(0, impl_.Run());
}
TEST_F(GenFilterAppTest, UnionFailsMismatchedFilters) {
ASSERT_NO_FATAL_FAILURE(BuildFilters());
cmd_line_.AppendSwitchASCII("action", "union");
cmd_line_.AppendArgPath(filter_paths_[0]);
cmd_line_.AppendArgPath(mismatched_filter_path_);
cmd_line_.AppendSwitchPath("output-file", output_file_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_NE(0, impl_.Run());
}
TEST_F(GenFilterAppTest, CompileFailsInvalidInput) {
base::FilePath filter_txt = temp_dir_.Append(L"badfilter.txt");
{
base::ScopedFILE file(base::OpenFile(filter_txt, "wb"));
::fprintf(file.get(), "This is a badly formatted filter file.");
}
cmd_line_.AppendSwitchASCII("action", "compile");
cmd_line_.AppendArgPath(filter_txt);
cmd_line_.AppendSwitchPath("output-file", output_file_);
cmd_line_.AppendSwitchPath("input-image", test_dll_);
cmd_line_.AppendSwitchPath("input-pdb", test_dll_pdb_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_NE(0, impl_.Run());
}
TEST_F(GenFilterAppTest, CompileSucceeds) {
base::FilePath filter_txt = temp_dir_.Append(L"goodfilter.txt");
{
base::ScopedFILE file(base::OpenFile(filter_txt, "wb"));
::fprintf(file.get(), "# A commend.\n");
::fprintf(file.get(), "+function:DllMain\n");
}
cmd_line_.AppendSwitchASCII("action", "compile");
cmd_line_.AppendArgPath(filter_txt);
cmd_line_.AppendSwitchPath("output-file", output_file_);
cmd_line_.AppendSwitchPath("input-image", test_dll_);
cmd_line_.AppendSwitchPath("input-pdb", test_dll_pdb_);
ASSERT_TRUE(impl_.ParseCommandLine(&cmd_line_));
ASSERT_EQ(0, impl_.Run());
ASSERT_TRUE(base::PathExists(output_file_));
pe::ImageFilter f;
ASSERT_TRUE(f.LoadFromJSON(output_file_));
}
} // namespace genfilter