Tests: Add initial set of unit tests for Detours (#137)

- Import the Catch2 self-contained C++ test framework.
  It's used by many Microsoft OSS projects:
  - https://github.com/microsoft/cppwinrt/tree/master/test
  - https://github.com/microsoft/wil/tree/master/tests
  As well as many OSS projects in general.

  When the CMake PR is merged, we can remove this as
  a checked in development dependency, and can instead
  download it using CMake.

- Start basic set of unit tests to validate failure modes of

- Hook the execution into the existing NMake build system.

- Hook test execution into CI pipeline
This commit is contained in:
Brian Gianforcaro 2020-12-02 00:16:13 +00:00 коммит произвёл GitHub
Родитель d8b8144c54
Коммит 2de2babb25
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 18171 добавлений и 13 удалений

21
.github/workflows/main.yml поставляемый
Просмотреть файл

@ -16,9 +16,11 @@ on:
jobs: jobs:
build: build:
runs-on: windows-latest runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false
matrix: matrix:
os: [windows-2019, windows-2016]
arch: [x86, x64, x64_arm, x64_arm64] arch: [x86, x64, x64_arm, x64_arm64]
steps: steps:
@ -43,24 +45,28 @@ jobs:
with: with:
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL for C++ - name: Initialize CodeQL for C++
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v1
if: ${{ matrix.os == 'windows-2019' }}
with: with:
languages: cpp languages: cpp
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
# Actually run the build - name: Build Detours for ${{ matrix.arch }} on ${{ matrix.os }}
- name: Build Detours for ${{ matrix.arch }}
env: env:
# Tell detours what process to target # Tell detours what process to target
DETOURS_TARGET_PROCESSOR: ${{ env.VSCMD_ARG_TGT_ARCH }} DETOURS_TARGET_PROCESSOR: ${{ env.VSCMD_ARG_TGT_ARCH }}
run: nmake run: nmake
# Upload artifacts for this subsection of the build matrix. - name: Run unit tests for ${{ matrix.arch }} on ${{ matrix.os }}
- name: Upload artifacts for ${{ matrix.arch }} id: run-unit-tests
run: cd tests && nmake test
if: ${{ matrix.arch == 'x86' || matrix.arch == 'x64' }}
- name: Upload artifacts for ${{ matrix.arch }} on ${{ matrix.os }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: artifacts-${{ matrix.os }}
path: | path: |
lib.*/ lib.*/
bin.*/ bin.*/
@ -68,3 +74,4 @@ jobs:
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v1
if: ${{ matrix.os == 'windows-2019' }}

Просмотреть файл

@ -12,32 +12,38 @@ ROOT = .
all: all:
cd "$(MAKEDIR)" cd "$(MAKEDIR)"
@if exist "$(MAKEDIR)\core\makefile" cd "$(MAKEDIR)\core" && $(MAKE) /NOLOGO /$(MAKEFLAGS) @if exist "$(MAKEDIR)\core\makefile" cd "$(MAKEDIR)\core" && $(MAKE) /NOLOGO /$(MAKEFLAGS)
cd "$(MAKEDIR)\src" cd "$(MAKEDIR)\src"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) @$(MAKE) /NOLOGO /$(MAKEFLAGS)
cd "$(MAKEDIR)\samples" cd "$(MAKEDIR)\samples"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) @$(MAKE) /NOLOGO /$(MAKEFLAGS)
@if exist "$(MAKEDIR)\bugs\makefile" cd "$(MAKEDIR)\bugs" && $(MAKE) /NOLOGO /$(MAKEFLAGS) cd "$(MAKEDIR)\tests"
@$(MAKE) /NOLOGO /$(MAKEFLAGS)
@if exist "$(MAKEDIR)\bugs\makefile" cd "$(MAKEDIR)\bugs" && $(MAKE) /NOLOGO /$(MAKEFLAGS)
cd "$(MAKEDIR)" cd "$(MAKEDIR)"
clean: clean:
cd "$(MAKEDIR)" cd "$(MAKEDIR)"
@if exist "$(MAKEDIR)\core\makefile" cd "$(MAKEDIR)\core" && $(MAKE) /NOLOGO /$(MAKEFLAGS) clean @if exist "$(MAKEDIR)\core\makefile" cd "$(MAKEDIR)\core" && $(MAKE) /NOLOGO /$(MAKEFLAGS) clean
cd "$(MAKEDIR)\src" cd "$(MAKEDIR)\src"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) clean @$(MAKE) /NOLOGO /$(MAKEFLAGS) clean
cd "$(MAKEDIR)\samples" cd "$(MAKEDIR)\samples"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) clean @$(MAKE) /NOLOGO /$(MAKEFLAGS) clean
@if exist "$(MAKEDIR)\bugs\makefile" cd "$(MAKEDIR)\bugs" && $(MAKE) /NOLOGO /$(MAKEFLAGS) clean cd "$(MAKEDIR)\tests"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) clean
@if exist "$(MAKEDIR)\bugs\makefile" cd "$(MAKEDIR)\bugs" && $(MAKE) /NOLOGO /$(MAKEFLAGS) clean
cd "$(MAKEDIR)" cd "$(MAKEDIR)"
realclean: clean realclean: clean
cd "$(MAKEDIR)" cd "$(MAKEDIR)"
@if exist "$(MAKEDIR)\core\makefile" cd "$(MAKEDIR)\core" && $(MAKE) /NOLOGO /$(MAKEFLAGS) realclean @if exist "$(MAKEDIR)\core\makefile" cd "$(MAKEDIR)\core" && $(MAKE) /NOLOGO /$(MAKEFLAGS) realclean
cd "$(MAKEDIR)\src" cd "$(MAKEDIR)\src"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) realclean @$(MAKE) /NOLOGO /$(MAKEFLAGS) realclean
cd "$(MAKEDIR)\samples" cd "$(MAKEDIR)\samples"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) realclean @$(MAKE) /NOLOGO /$(MAKEFLAGS) realclean
@if exist "$(MAKEDIR)\bugs\makefile" cd "$(MAKEDIR)\bugs" && $(MAKE) /NOLOGO /$(MAKEFLAGS) realclean cd "$(MAKEDIR)\tests"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) realclean
@if exist "$(MAKEDIR)\bugs\makefile" cd "$(MAKEDIR)\bugs" && $(MAKE) /NOLOGO /$(MAKEFLAGS) realclean
cd "$(MAKEDIR)" cd "$(MAKEDIR)"
-rmdir /q /s $(INCDS) 2> nul -rmdir /q /s $(INCDS) 2> nul
-rmdir /q /s $(LIBDS) 2> nul -rmdir /q /s $(LIBDS) 2> nul
@ -50,6 +56,8 @@ realclean: clean
test: test:
cd "$(MAKEDIR)\samples" cd "$(MAKEDIR)\samples"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) test @$(MAKE) /NOLOGO /$(MAKEFLAGS) test
cd "$(MAKEDIR)\tests"
@$(MAKE) /NOLOGO /$(MAKEFLAGS) test
cd "$(MAKEDIR)" cd "$(MAKEDIR)"
################################################################# End of File. ################################################################# End of File.

66
tests/Makefile Normal file
Просмотреть файл

@ -0,0 +1,66 @@
##############################################################################
##
## Detours Unit Tests.
##
## Microsoft Research Detours Package
##
## Copyright (c) Microsoft Corporation. All rights reserved.
##
ROOT = ..
!include ..\samples\common.mak
DEPS = $(LIBD)\detours.lib
LIBS=$(LIBS) kernel32.lib
CFLAGS=$(CFLAGS) /EHsc /DCATCH_CONFIG_NO_WINDOWS_SEH
##############################################################################
all: dirs \
$(BIND)\unittests.exe \
\
##############################################################################
dirs:
@if not exist $(BIND) mkdir $(BIND) && echo. Created $(BIND)
@if not exist $(OBJD) mkdir $(OBJD) && echo. Created $(OBJD)
$(OBJD)\main.obj : main.cpp
$(OBJD)\test_module_api.obj : test_module_api.cpp
$(OBJD)\test_image_api.obj : test_image_api.cpp
$(OBJD)\corruptor.obj : corruptor.cpp
$(BIND)\unittests.exe : $(OBJD)\main.obj \
$(OBJD)\test_module_api.obj \
$(OBJD)\test_image_api.obj \
$(OBJD)\corruptor.obj $(DEPS)
cl $(CFLAGS) /Fe$@ /Fd$(@R).pdb \
$(OBJD)\main.obj \
$(OBJD)\test_module_api.obj \
$(OBJD)\test_image_api.obj \
$(OBJD)\corruptor.obj \
/link $(LINKFLAGS) $(LIBS) /subsystem:console
##############################################################################
clean:
-del *~ 2>nul
-del $(BIND)\unittests*.* 2>nul
-rmdir /q /s $(OBJD) 2>nul
realclean: clean
-rmdir /q /s $(OBJDS) 2>nul
option:
##############################################################################
test: all
@cls
$(BIND)\unittests.exe --reporter console --success --durations yes
debug: all
windbg -o $(BIND)\unittests.exe
################################################################# End of File.

17799
tests/catch.hpp Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

67
tests/corruptor.cpp Normal file
Просмотреть файл

@ -0,0 +1,67 @@
//////////////////////////////////////////////////////////////////////////////
//
// Unit Test Image Corruptor (corruptor.cpp of unittests.exe)
//
// Microsoft Research Detours Package
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#include "windows.h"
#include "corruptor.h"
ImageCorruptor::ImageCorruptor(PIMAGE_DOS_HEADER Header)
{
m_TargetDosHeader = Header;
m_OriginalDosHeader = *Header;
m_OriginalDosProtection = 0;
m_TargetNtHeaders = (PIMAGE_NT_HEADERS)((PBYTE)Header + Header->e_lfanew);
m_OriginalNtHeaders = *m_TargetNtHeaders;
m_OriginalNtProtection = 0;
VirtualProtect(
m_TargetDosHeader,
sizeof(*m_TargetDosHeader),
PAGE_READWRITE,
&m_OriginalDosProtection);
VirtualProtect(
m_TargetNtHeaders,
sizeof(*m_TargetNtHeaders),
PAGE_READWRITE,
&m_OriginalNtProtection);
}
ImageCorruptor::~ImageCorruptor()
{
// Restore original header contents.
//
*m_TargetDosHeader = m_OriginalDosHeader;
*m_TargetNtHeaders = m_OriginalNtHeaders;
// Restore original protection of DOS header.
//
DWORD OldProtection {};
VirtualProtect(
m_TargetDosHeader,
sizeof(*m_TargetDosHeader),
m_OriginalDosProtection,
&OldProtection);
// Restore original protection of NT headers.
//
VirtualProtect(
m_TargetNtHeaders,
sizeof(*m_TargetNtHeaders),
m_OriginalNtProtection,
&OldProtection);
}
void ImageCorruptor::ModifyDosMagic(WORD Value)
{
m_TargetDosHeader->e_magic = Value;
}
void ImageCorruptor::ModifyNtSignature(ULONG Value)
{
m_TargetNtHeaders->Signature = Value;
}

45
tests/corruptor.h Normal file
Просмотреть файл

@ -0,0 +1,45 @@
//////////////////////////////////////////////////////
//
// Unit Test Image Corruptor (corruptor.h of unittests.exe)
//
// Microsoft Research Detours Package
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
class ImageCorruptor final
{
public:
ImageCorruptor(PIMAGE_DOS_HEADER Header);
~ImageCorruptor();
void ModifyDosMagic(WORD Value);
void ModifyNtSignature(ULONG Value);
private:
// Pointer to the target image header to corrupt.
//
PIMAGE_DOS_HEADER m_TargetDosHeader;
// Cached copy of the DOS header, to restore state with.
//
IMAGE_DOS_HEADER m_OriginalDosHeader;
// The original protection of the DOS header.
//
DWORD m_OriginalDosProtection;
// Pointer to the target NT image header to corrupt.
//
PIMAGE_NT_HEADERS m_TargetNtHeaders;
// Cached copy of the NT headers, to restore state with.
//
IMAGE_NT_HEADERS m_OriginalNtHeaders;
// The original protection of the NT headers.
//
DWORD m_OriginalNtProtection;
};

10
tests/main.cpp Normal file
Просмотреть файл

@ -0,0 +1,10 @@
//////////////////////////////////////////////////////////////////////////////
//
// Unit Test Main (main.cpp of unittests.exe)
//
// Microsoft Research Detours Package
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

21
tests/test_image_api.cpp Normal file
Просмотреть файл

@ -0,0 +1,21 @@
//////////////////////////////////////////////////////////////////////////////
//
// Unit Tests for Detours Image API (test_image_api.cpp of unittests.exe)
//
// Microsoft Research Detours Package
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#include "catch.hpp"
#include "windows.h"
#include "detours.h"
TEST_CASE("DetourBinaryOpen", "[image]")
{
SECTION("Passing INVALID_HANDLE, results in error")
{
auto binary = DetourBinaryOpen(INVALID_HANDLE_VALUE);
REQUIRE( GetLastError() == ERROR_INVALID_HANDLE );
REQUIRE( binary == nullptr );
}
}

135
tests/test_module_api.cpp Normal file
Просмотреть файл

@ -0,0 +1,135 @@
//////////////////////////////////////////////////////////////////////////////
//
// Unit Tests for Detours Module API (test_module_api.cpp of unittests.exe)
//
// Microsoft Research Detours Package
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#include "catch.hpp"
#include "windows.h"
#include "detours.h"
#include "corruptor.h"
// Expose the image base of the current module for test assertions.
//
extern "C" IMAGE_DOS_HEADER __ImageBase;
// Expose default module entry point for test assertions.
//
extern "C" int mainCRTStartup();
// Dummy function pointer used for tests.
//
void NoopFunction() { }
TEST_CASE("DetourGetContainingModule", "[module]")
{
SECTION("Passing nullptr, results in nullptr")
{
auto mod = DetourGetContainingModule(nullptr);
REQUIRE( GetLastError() == ERROR_BAD_EXE_FORMAT );
REQUIRE( mod == nullptr );
}
SECTION("Passing GetCommandLineW, results in kernel32 HMODULE")
{
auto mod = DetourGetContainingModule(GetCommandLineW);
REQUIRE( GetLastError() == NO_ERROR );
REQUIRE( mod == LoadLibrary("kernel32.dll") );
}
SECTION("Passing own function, results in own HMODULE")
{
auto mod = DetourGetContainingModule(NoopFunction);
REQUIRE( GetLastError() == NO_ERROR );
REQUIRE( mod == reinterpret_cast<HMODULE>(&__ImageBase) );
}
}
TEST_CASE("DetourGetEntyPoint", "[module]")
{
SECTION("Passing nullptr, results in CRT entrypoint")
{
auto entry = DetourGetEntryPoint(nullptr);
REQUIRE( GetLastError() == NO_ERROR );
REQUIRE( entry == mainCRTStartup );
}
SECTION("Passing nullptr, equals executing image")
{
REQUIRE( DetourGetEntryPoint(nullptr) ==
DetourGetEntryPoint(reinterpret_cast<HMODULE>(&__ImageBase)) );
}
SECTION("Passing ImageBase, results in CRT main")
{
auto entry = DetourGetEntryPoint(reinterpret_cast<HMODULE>(&__ImageBase));
REQUIRE( GetLastError() == NO_ERROR );
REQUIRE( entry == mainCRTStartup );
}
SECTION("Corrupt image DOS header magic, results in bad exe format error")
{
ImageCorruptor corruptor(&__ImageBase);
corruptor.ModifyDosMagic(0xDEAD);
auto entry = DetourGetEntryPoint(reinterpret_cast<HMODULE>(&__ImageBase));
REQUIRE( GetLastError() == ERROR_BAD_EXE_FORMAT );
REQUIRE( entry == nullptr );
}
SECTION("Corrupt image NT header signature, results in invalid signature error")
{
ImageCorruptor corruptor(&__ImageBase);
corruptor.ModifyNtSignature(0xDEADBEEF);
auto entry = DetourGetEntryPoint(reinterpret_cast<HMODULE>(&__ImageBase));
REQUIRE( GetLastError() == ERROR_INVALID_EXE_SIGNATURE );
REQUIRE( entry == nullptr );
}
}
TEST_CASE("DetourGetModuleSize", "[module]")
{
SECTION("Passing nullptr, results in current module size")
{
auto size = DetourGetModuleSize(nullptr);
REQUIRE( GetLastError() == NO_ERROR );
REQUIRE( size >= 0 );
}
SECTION("Passing stack, results in error")
{
int value;
auto size = DetourGetModuleSize(reinterpret_cast<HMODULE>(&value));
REQUIRE( GetLastError() == ERROR_BAD_EXE_FORMAT);
REQUIRE( size == 0 );
}
SECTION("Passing nullptr, equals executing image")
{
REQUIRE( DetourGetModuleSize(nullptr) ==
DetourGetModuleSize(reinterpret_cast<HMODULE>(&__ImageBase)) );
}
SECTION("Corrupt image DOS header magic, results in bad exe format error")
{
ImageCorruptor corruptor(&__ImageBase);
corruptor.ModifyDosMagic(0xDEAD);
auto size = DetourGetModuleSize(reinterpret_cast<HMODULE>(&__ImageBase));
REQUIRE( GetLastError() == ERROR_BAD_EXE_FORMAT );
REQUIRE( size == 0 );
}
SECTION("Corrupt image NT header signature, results in invalid signature error")
{
ImageCorruptor corruptor(&__ImageBase);
corruptor.ModifyNtSignature(0xDEADBEEF);
auto size = DetourGetModuleSize(reinterpret_cast<HMODULE>(&__ImageBase));
REQUIRE( GetLastError() == ERROR_INVALID_EXE_SIGNATURE );
REQUIRE( size == 0 );
}
}