| // 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. |
| |
| #ifndef SYZYGY_AGENT_ASAN_UNITTEST_UTIL_H_ |
| #define SYZYGY_AGENT_ASAN_UNITTEST_UTIL_H_ |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/strings/string_piece.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "syzygy/agent/asan/block.h" |
| #include "syzygy/agent/asan/error_info.h" |
| #include "syzygy/agent/asan/heap.h" |
| #include "syzygy/agent/asan/logger.h" |
| #include "syzygy/agent/asan/memory_notifier.h" |
| #include "syzygy/agent/asan/page_protection_helpers.h" |
| #include "syzygy/agent/asan/runtime.h" |
| #include "syzygy/agent/asan/shadow.h" |
| #include "syzygy/agent/asan/stack_capture_cache.h" |
| #include "syzygy/agent/asan/memory_notifiers/null_memory_notifier.h" |
| #include "syzygy/agent/common/stack_capture.h" |
| #include "syzygy/core/address.h" |
| #include "syzygy/core/address_space.h" |
| #include "syzygy/core/unittest_util.h" |
| #include "syzygy/trace/agent_logger/agent_logger.h" |
| #include "syzygy/trace/agent_logger/agent_logger_rpc_impl.h" |
| |
| namespace testing { |
| |
| using agent::asan::AsanErrorInfo; |
| using agent::asan::memory_notifiers::NullMemoryNotifier; |
| using agent::asan::Shadow; |
| using agent::asan::StackCaptureCache; |
| |
| // The default name of the runtime library DLL. |
| extern const wchar_t kSyzyAsanRtlDll[]; |
| |
| // A unittest fixture that sets up a OnExceptionCallback for use with |
| // BlockInfoFromMemory and BlockGetHeaderFromBody. This is the base fixture |
| // class for all ASAN-related test fixtures declared in this file. |
| class LenientOnExceptionCallbackTest : public testing::Test { |
| public: |
| MOCK_METHOD1(OnExceptionCallback, void(EXCEPTION_POINTERS*)); |
| |
| void SetUp() override { |
| testing::Test::SetUp(); |
| agent::asan::SetOnExceptionCallback( |
| base::Bind(&LenientOnExceptionCallbackTest::OnExceptionCallback, |
| base::Unretained(this))); |
| } |
| |
| void TearDown() override { |
| agent::asan::ClearOnExceptionCallback(); |
| testing::Test::TearDown(); |
| } |
| }; |
| typedef testing::StrictMock<LenientOnExceptionCallbackTest> |
| OnExceptionCallbackTest; |
| |
| // A unittest fixture that ensures that an Asan logger instance is up and |
| // running for the duration of the test. Output is captured to a file so that |
| // its contents can be read after the test if necessary. |
| class TestWithAsanLogger : public OnExceptionCallbackTest { |
| public: |
| TestWithAsanLogger(); |
| |
| // @name testing::Test overrides. |
| // @{ |
| void SetUp() override; |
| void TearDown() override; |
| // @} |
| |
| // @name Accessors. |
| // @{ |
| const std::wstring& instance_id() const { return instance_id_; } |
| const base::FilePath& log_file_path() const { return log_file_path_; } |
| const base::FilePath& temp_dir() const { return temp_dir_.path(); } |
| // @} |
| |
| bool LogContains(const base::StringPiece& message); |
| |
| // Delete the temporary file used for the logging and its directory. |
| void DeleteTempFileAndDirectory(); |
| |
| // Starts the logger process. |
| void StartLogger(); |
| |
| // Stops the logger process. |
| void StopLogger(); |
| |
| // Reset the log contents. |
| void ResetLog(); |
| |
| // Appends @p instance to the RPC logger instance environment variable. |
| void AppendToLoggerEnv(const std::string &instance); |
| |
| private: |
| // The instance ID used by the running logger instance. |
| std::wstring instance_id_; |
| |
| // The path to the log file where the the logger instance will write. |
| base::FilePath log_file_path_; |
| |
| // Status of the logger process. |
| bool logger_running_; |
| |
| // A temporary directory into which the log file will be written. |
| base::ScopedTempDir temp_dir_; |
| |
| // The contents of the log. These are read by calling LogContains. |
| bool log_contents_read_; |
| std::string log_contents_; |
| |
| // Value of the logger instance environment variable before SetUp. |
| std::string old_logger_env_; |
| |
| // Value of the asan options environment variable before SetUp. |
| std::string old_asan_options_env_; |
| |
| // Redirection files for the logger. |
| base::ScopedFILE logger_stdin_file_; |
| base::ScopedFILE logger_stdout_file_; |
| base::ScopedFILE logger_stderr_file_; |
| }; |
| |
| // Shorthand for discussing all the asan runtime functions. |
| #ifndef _WIN64 |
| #define ASAN_RTL_FUNCTIONS(F) \ |
| F(WINAPI, HANDLE, GetProcessHeap, (), ()) \ |
| F(WINAPI, HANDLE, HeapCreate, \ |
| (DWORD options, SIZE_T initial_size, SIZE_T maximum_size), \ |
| (options, initial_size, maximum_size)) \ |
| F(WINAPI, BOOL, HeapDestroy, \ |
| (HANDLE heap), (heap)) \ |
| F(WINAPI, LPVOID, HeapAlloc, \ |
| (HANDLE heap, DWORD flags, SIZE_T bytes), (heap, flags, bytes)) \ |
| F(WINAPI, LPVOID, HeapReAlloc, \ |
| (HANDLE heap, DWORD flags, LPVOID mem, SIZE_T bytes), \ |
| (heap, flags, mem, bytes)) \ |
| F(WINAPI, BOOL, HeapFree, \ |
| (HANDLE heap, DWORD flags, LPVOID mem), (heap, flags, mem)) \ |
| F(WINAPI, SIZE_T, HeapSize, \ |
| (HANDLE heap, DWORD flags, LPCVOID mem), (heap, flags, mem)) \ |
| F(WINAPI, BOOL, HeapValidate, \ |
| (HANDLE heap, DWORD flags, LPCVOID mem), (heap, flags, mem)) \ |
| F(WINAPI, SIZE_T, HeapCompact, \ |
| (HANDLE heap, DWORD flags), (heap, flags)) \ |
| F(WINAPI, BOOL, HeapLock, (HANDLE heap), (heap)) \ |
| F(WINAPI, BOOL, HeapUnlock, (HANDLE heap), (heap)) \ |
| F(WINAPI, BOOL, HeapWalk, \ |
| (HANDLE heap, LPPROCESS_HEAP_ENTRY entry), (heap, entry)) \ |
| F(WINAPI, BOOL, HeapSetInformation, \ |
| (HANDLE heap, HEAP_INFORMATION_CLASS info_class, \ |
| PVOID info, SIZE_T info_length), \ |
| (heap, info_class, info, info_length)) \ |
| F(WINAPI, BOOL, HeapQueryInformation, \ |
| (HANDLE heap, HEAP_INFORMATION_CLASS info_class, \ |
| PVOID info, SIZE_T info_length, PSIZE_T return_length), \ |
| (heap, info_class, info, info_length, return_length)) \ |
| F(WINAPI, void, SetCallBack, \ |
| (void (*callback)(AsanErrorInfo* error_info)), \ |
| (callback)) \ |
| F(_cdecl, void*, memcpy, \ |
| (void* destination, const void* source, size_t num), \ |
| (destination, source, num)) \ |
| F(_cdecl, void*, memmove, \ |
| (void* destination, const void* source, size_t num), \ |
| (destination, source, num)) \ |
| F(_cdecl, void*, memset, (void* ptr, int value, size_t num), \ |
| (ptr, value, num)) \ |
| F(_cdecl, const void*, memchr, (const void* ptr, int value, size_t num), \ |
| (ptr, value, num)) \ |
| F(_cdecl, size_t, strcspn, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, size_t, strlen, (const char* str), (str)) \ |
| F(_cdecl, size_t, strnlen, (const char* str, size_t max_len), \ |
| (str, max_len)) \ |
| F(_cdecl, const char*, strrchr, (const char* str, int character), \ |
| (str, character)) \ |
| F(_cdecl, const wchar_t*, wcsrchr, (const wchar_t* str, int character), \ |
| (str, character)) \ |
| F(_cdecl, const wchar_t*, wcschr, (const wchar_t* str, int character), \ |
| (str, character)) \ |
| F(_cdecl, int, strcmp, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, const char*, strpbrk, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, const char*, strstr, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, size_t, wcsnlen, (const wchar_t* str, size_t max_len), \ |
| (str, max_len)) \ |
| F(_cdecl, const wchar_t*, wcsstr, (const wchar_t* str1, \ |
| const wchar_t* str2), (str1, str2)) \ |
| F(_cdecl, size_t, strspn, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, char*, strncpy, \ |
| (char* destination, const char* source, size_t num), \ |
| (destination, source, num)) \ |
| F(_cdecl, char*, strncat, \ |
| (char* destination, const char* source, size_t num), \ |
| (destination, source, num)) \ |
| F(WINAPI, BOOL, ReadFile, \ |
| (HANDLE file_handle, LPVOID buffer, DWORD bytes_to_read, \ |
| LPDWORD bytes_read, LPOVERLAPPED overlapped), \ |
| (file_handle, buffer, bytes_to_read, bytes_read, overlapped)) \ |
| F(WINAPI, BOOL, WriteFile, \ |
| (HANDLE file_handle, LPCVOID buffer, DWORD bytes_to_write, \ |
| LPDWORD bytes_written, LPOVERLAPPED overlapped), \ |
| (file_handle, buffer, bytes_to_write, bytes_written, overlapped)) \ |
| F(_cdecl, void, SetInterceptorCallback, (void (*callback)()), (callback)) \ |
| F(WINAPI, agent::asan::AsanRuntime*, GetActiveRuntime, (), ()) \ |
| F(WINAPI, void, SetAllocationFilterFlag, (), ()) \ |
| F(WINAPI, void, ClearAllocationFilterFlag, (), ()) |
| #else |
| // A copy of the previous block minus {Set,Clear}AllocationFilterFlag functions, |
| // as they're not implemented on win64. |
| // TODO: remove this once {Set,Clear}AllocationFilterFlag are implemented. |
| #define ASAN_RTL_FUNCTIONS(F) \ |
| F(WINAPI, HANDLE, GetProcessHeap, (), ()) \ |
| F(WINAPI, HANDLE, HeapCreate, \ |
| (DWORD options, SIZE_T initial_size, SIZE_T maximum_size), \ |
| (options, initial_size, maximum_size)) \ |
| F(WINAPI, BOOL, HeapDestroy, \ |
| (HANDLE heap), (heap)) \ |
| F(WINAPI, LPVOID, HeapAlloc, \ |
| (HANDLE heap, DWORD flags, SIZE_T bytes), (heap, flags, bytes)) \ |
| F(WINAPI, LPVOID, HeapReAlloc, \ |
| (HANDLE heap, DWORD flags, LPVOID mem, SIZE_T bytes), \ |
| (heap, flags, mem, bytes)) \ |
| F(WINAPI, BOOL, HeapFree, \ |
| (HANDLE heap, DWORD flags, LPVOID mem), (heap, flags, mem)) \ |
| F(WINAPI, SIZE_T, HeapSize, \ |
| (HANDLE heap, DWORD flags, LPCVOID mem), (heap, flags, mem)) \ |
| F(WINAPI, BOOL, HeapValidate, \ |
| (HANDLE heap, DWORD flags, LPCVOID mem), (heap, flags, mem)) \ |
| F(WINAPI, SIZE_T, HeapCompact, \ |
| (HANDLE heap, DWORD flags), (heap, flags)) \ |
| F(WINAPI, BOOL, HeapLock, (HANDLE heap), (heap)) \ |
| F(WINAPI, BOOL, HeapUnlock, (HANDLE heap), (heap)) \ |
| F(WINAPI, BOOL, HeapWalk, \ |
| (HANDLE heap, LPPROCESS_HEAP_ENTRY entry), (heap, entry)) \ |
| F(WINAPI, BOOL, HeapSetInformation, \ |
| (HANDLE heap, HEAP_INFORMATION_CLASS info_class, \ |
| PVOID info, SIZE_T info_length), \ |
| (heap, info_class, info, info_length)) \ |
| F(WINAPI, BOOL, HeapQueryInformation, \ |
| (HANDLE heap, HEAP_INFORMATION_CLASS info_class, \ |
| PVOID info, SIZE_T info_length, PSIZE_T return_length), \ |
| (heap, info_class, info, info_length, return_length)) \ |
| F(WINAPI, void, SetCallBack, \ |
| (void (*callback)(AsanErrorInfo* error_info)), \ |
| (callback)) \ |
| F(_cdecl, void*, memcpy, \ |
| (void* destination, const void* source, size_t num), \ |
| (destination, source, num)) \ |
| F(_cdecl, void*, memmove, \ |
| (void* destination, const void* source, size_t num), \ |
| (destination, source, num)) \ |
| F(_cdecl, void*, memset, (void* ptr, int value, size_t num), \ |
| (ptr, value, num)) \ |
| F(_cdecl, const void*, memchr, (const void* ptr, int value, size_t num), \ |
| (ptr, value, num)) \ |
| F(_cdecl, size_t, strcspn, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, size_t, strlen, (const char* str), (str)) \ |
| F(_cdecl, size_t, strnlen, (const char* str, size_t max_len), \ |
| (str, max_len)) \ |
| F(_cdecl, const char*, strrchr, (const char* str, int character), \ |
| (str, character)) \ |
| F(_cdecl, const wchar_t*, wcsrchr, (const wchar_t* str, int character), \ |
| (str, character)) \ |
| F(_cdecl, const wchar_t*, wcschr, (const wchar_t* str, int character), \ |
| (str, character)) \ |
| F(_cdecl, int, strcmp, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, const char*, strpbrk, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, const char*, strstr, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, size_t, wcsnlen, (const wchar_t* str, size_t max_len), \ |
| (str, max_len)) \ |
| F(_cdecl, const wchar_t*, wcsstr, (const wchar_t* str1, \ |
| const wchar_t* str2), (str1, str2)) \ |
| F(_cdecl, size_t, strspn, (const char* str1, const char* str2), \ |
| (str1, str2)) \ |
| F(_cdecl, char*, strncpy, \ |
| (char* destination, const char* source, size_t num), \ |
| (destination, source, num)) \ |
| F(_cdecl, char*, strncat, \ |
| (char* destination, const char* source, size_t num), \ |
| (destination, source, num)) \ |
| F(WINAPI, BOOL, ReadFile, \ |
| (HANDLE file_handle, LPVOID buffer, DWORD bytes_to_read, \ |
| LPDWORD bytes_read, LPOVERLAPPED overlapped), \ |
| (file_handle, buffer, bytes_to_read, bytes_read, overlapped)) \ |
| F(WINAPI, BOOL, WriteFile, \ |
| (HANDLE file_handle, LPCVOID buffer, DWORD bytes_to_write, \ |
| LPDWORD bytes_written, LPOVERLAPPED overlapped), \ |
| (file_handle, buffer, bytes_to_write, bytes_written, overlapped)) \ |
| F(_cdecl, void, SetInterceptorCallback, (void (*callback)()), (callback)) \ |
| F(WINAPI, agent::asan::AsanRuntime*, GetActiveRuntime, (), ()) |
| #endif |
| |
| // Declare pointer types for the intercepted functions. |
| #define DECLARE_ASAN_FUNCTION_PTR(convention, ret, name, args, argnames) \ |
| typedef ret (convention* name##FunctionPtr)args; |
| ASAN_RTL_FUNCTIONS(DECLARE_ASAN_FUNCTION_PTR) |
| #undef DECLARE_ASAN_FUNCTION_PTR |
| |
| class TestAsanRtl : public testing::TestWithAsanLogger { |
| public: |
| TestAsanRtl() : asan_rtl_(NULL), heap_(NULL) { |
| } |
| |
| void SetUp() override { |
| testing::TestWithAsanLogger::SetUp(); |
| |
| // Load the Asan runtime library. |
| base::FilePath asan_rtl_path = |
| testing::GetExeRelativePath(L"syzyasan_rtl.dll"); |
| asan_rtl_ = ::LoadLibrary(asan_rtl_path.value().c_str()); |
| ASSERT_TRUE(asan_rtl_ != NULL); |
| |
| // Load all the functions and assert that we find them. |
| #define LOAD_ASAN_FUNCTION(convention, ret, name, args, argnames) \ |
| name##Function = reinterpret_cast<name##FunctionPtr>( \ |
| ::GetProcAddress(asan_rtl_, "asan_" #name)); \ |
| ASSERT_TRUE(name##Function != NULL); |
| |
| ASAN_RTL_FUNCTIONS(LOAD_ASAN_FUNCTION) |
| |
| #undef LOAD_ASAN_FUNCTION |
| |
| heap_ = HeapCreateFunction(0, 0, 0); |
| ASSERT_TRUE(heap_ != NULL); |
| |
| agent::asan::AsanRuntime* runtime = GetActiveRuntimeFunction(); |
| ASSERT_NE(reinterpret_cast<agent::asan::AsanRuntime*>(NULL), runtime); |
| // Disable the heap checking as this really slows down the unittests. |
| runtime->params().check_heap_on_failure = false; |
| } |
| |
| void TearDown() override { |
| if (heap_ != NULL) { |
| HeapDestroyFunction(heap_); |
| heap_ = NULL; |
| } |
| |
| if (asan_rtl_ != NULL) { |
| ::FreeLibrary(asan_rtl_); |
| asan_rtl_ = NULL; |
| } |
| |
| testing::TestWithAsanLogger::TearDown(); |
| } |
| |
| HANDLE heap() { return heap_; } |
| |
| // Declare pointers to intercepted functions. |
| #define DECLARE_FUNCTION_PTR_VARIABLE(convention, ret, name, args, argnames) \ |
| static name##FunctionPtr name##Function; |
| ASAN_RTL_FUNCTIONS(DECLARE_FUNCTION_PTR_VARIABLE) |
| #undef DECLARE_FUNCTION_PTR_VARIABLE |
| |
| // Define versions of all of the functions that expect an error to be thrown |
| // by the AsanErrorCallback, and in turn raise an exception if the underlying |
| // function didn't fail. |
| #define DECLARE_FAILING_FUNCTION(convention, ret, name, args, argnames) \ |
| static void name##FunctionFailing args; |
| ASAN_RTL_FUNCTIONS(DECLARE_FAILING_FUNCTION) |
| #undef DECLARE_FAILING_FUNCTION |
| |
| protected: |
| // The AsanAsan runtime module to test. |
| HMODULE asan_rtl_; |
| |
| // Scratch heap handle valid from SetUp to TearDown. |
| HANDLE heap_; |
| }; |
| |
| // A helper struct to be passed as a destructor of Asan scoped allocation. |
| struct AsanDeleteHelper { |
| explicit AsanDeleteHelper(TestAsanRtl* asan_rtl) |
| : asan_rtl_(asan_rtl) { |
| } |
| |
| void operator()(void* ptr) { |
| asan_rtl_->HeapFreeFunction(asan_rtl_->heap(), 0, ptr); |
| } |
| TestAsanRtl* asan_rtl_; |
| }; |
| |
| // A std::unique_ptr specialization for the Asan allocations. |
| template <typename T> |
| class ScopedAsanAlloc : public std::unique_ptr<T, AsanDeleteHelper> { |
| public: |
| explicit ScopedAsanAlloc(TestAsanRtl* asan_rtl) |
| : std::unique_ptr<T, AsanDeleteHelper>(NULL, AsanDeleteHelper(asan_rtl)) { |
| } |
| |
| ScopedAsanAlloc(TestAsanRtl* asan_rtl, size_t size) |
| : std::unique_ptr<T, AsanDeleteHelper>(NULL, AsanDeleteHelper(asan_rtl)) { |
| Allocate(asan_rtl, size); |
| } |
| |
| ScopedAsanAlloc(TestAsanRtl* asan_rtl, size_t size, const T* value) |
| : std::unique_ptr<T, AsanDeleteHelper>(NULL, AsanDeleteHelper(asan_rtl)) { |
| Allocate(asan_rtl, size); |
| ::memcpy(get(), value, size * sizeof(T)); |
| } |
| |
| template <typename T2> |
| T2* GetAs() { |
| return reinterpret_cast<T2*>(get()); |
| } |
| |
| void Allocate(TestAsanRtl* asan_rtl, size_t size) { |
| ASSERT_TRUE(asan_rtl != NULL); |
| reset(reinterpret_cast<T*>( |
| asan_rtl->HeapAllocFunction(asan_rtl->heap(), 0, size * sizeof(T)))); |
| ::memset(get(), 0, size * sizeof(T)); |
| } |
| |
| T operator[](size_t i) const { |
| CHECK(get() != NULL); |
| return get()[i]; |
| } |
| |
| T& operator[](size_t i) { |
| CHECK(get() != NULL); |
| return get()[i]; |
| } |
| }; |
| |
| // A unittest fixture that initializes an Asan runtime instance. |
| class TestWithAsanRuntime : public OnExceptionCallbackTest { |
| public: |
| TestWithAsanRuntime() { |
| runtime_ = new agent::asan::AsanRuntime(); |
| owns_runtime_ = true; |
| } |
| |
| explicit TestWithAsanRuntime(agent::asan::AsanRuntime* runtime) |
| : runtime_(runtime), owns_runtime_(false) { |
| CHECK_NE(reinterpret_cast<agent::asan::AsanRuntime*>(NULL), runtime_); |
| } |
| |
| ~TestWithAsanRuntime() { |
| CHECK_NE(reinterpret_cast<agent::asan::AsanRuntime*>(NULL), runtime_); |
| if (owns_runtime_) |
| delete runtime_; |
| runtime_ = NULL; |
| } |
| |
| void SetUp() override { |
| CHECK_NE(reinterpret_cast<agent::asan::AsanRuntime*>(NULL), runtime_); |
| testing::Test::SetUp(); |
| runtime_->SetUp(L""); |
| } |
| |
| void TearDown() override { |
| CHECK_NE(reinterpret_cast<agent::asan::AsanRuntime*>(NULL), runtime_); |
| runtime_->TearDown(); |
| testing::Test::TearDown(); |
| } |
| |
| protected: |
| // The runtime instance used by the tests. |
| agent::asan::AsanRuntime* runtime_; |
| |
| // Indicates if we own the runtime instance. |
| bool owns_runtime_; |
| }; |
| |
| // A unittest fixture to test the bookkeeping functions. |
| struct FakeAsanBlock { |
| static const size_t kMaxAlignmentLog = 13; |
| static const size_t kMaxAlignment = 1 << kMaxAlignmentLog; |
| // If we want to test the alignments up to 4096 we need a buffer of at least |
| // 3 * 4096 bytes: |
| // +--- 0 <= size < 4096 bytes---+---4096 bytes---+--4096 bytes--+ |
| // ^buffer ^aligned_buffer ^user_pointer |
| static const size_t kBufferSize = 3 * kMaxAlignment; |
| static const uint8_t kBufferHeaderValue = 0xAE; |
| static const uint8_t kBufferTrailerValue = 0xEA; |
| |
| FakeAsanBlock(Shadow* shadow, |
| uint32_t alloc_alignment_log, |
| StackCaptureCache* stack_cache); |
| |
| ~FakeAsanBlock(); |
| |
| // Initialize an Asan block in the buffer. |
| // @param alloc_size The user size of the Asan block. |
| // @returns true on success, false otherwise. |
| bool InitializeBlock(uint32_t alloc_size); |
| |
| // Ensures that this block has a valid block header. |
| bool TestBlockMetadata(); |
| |
| // Mark the current Asan block as quarantined. |
| bool MarkBlockAsQuarantinedImpl(bool flood_filled); |
| |
| // Mark the current Asan block as quarantined. |
| bool MarkBlockAsQuarantined(); |
| |
| // Mark the current Asan block as quarantined and flooded. |
| bool MarkBlockAsQuarantinedFlooded(); |
| |
| // The buffer we use internally. |
| uint8_t buffer[kBufferSize]; |
| |
| // The information about the block once it has been initialized. |
| agent::asan::BlockInfo block_info; |
| |
| // The alignment of the current allocation. |
| uint32_t alloc_alignment; |
| uint32_t alloc_alignment_log; |
| |
| // The sizes of the different sub-structures in the buffer. |
| size_t buffer_header_size; |
| size_t buffer_trailer_size; |
| |
| // The pointers to the different sub-structures in the buffer. |
| uint8_t* buffer_align_begin; |
| |
| // Indicate if the buffer has been initialized. |
| bool is_initialized; |
| |
| // The shadow memory that will be modified. |
| Shadow* shadow_; |
| |
| // The cache that will store the stack traces of this block. |
| StackCaptureCache* stack_cache; |
| }; |
| |
| // A mock memory notifier. Useful when testing objects that have a memory |
| // notifier dependency. |
| class MockMemoryNotifier : public agent::asan::MemoryNotifierInterface { |
| public: |
| // Constructor. |
| MockMemoryNotifier() { } |
| |
| // Virtual destructor. |
| virtual ~MockMemoryNotifier() { } |
| |
| // @name MemoryNotifierInterface implementation. |
| // @{ |
| MOCK_METHOD2(NotifyInternalUse, void(const void*, size_t)); |
| MOCK_METHOD2(NotifyFutureHeapUse, void(const void*, size_t)); |
| MOCK_METHOD2(NotifyReturnedToOS, void(const void*, size_t)); |
| // @} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockMemoryNotifier); |
| }; |
| |
| // A mock HeapInterface. |
| class LenientMockHeap : public agent::asan::HeapInterface { |
| public: |
| LenientMockHeap() { } |
| virtual ~LenientMockHeap() { } |
| MOCK_CONST_METHOD0(GetHeapType, agent::asan::HeapType()); |
| MOCK_CONST_METHOD0(GetHeapFeatures, uint32_t()); |
| MOCK_METHOD1(Allocate, void*(uint32_t)); |
| MOCK_METHOD1(Free, bool(void*)); |
| MOCK_METHOD1(IsAllocated, bool(const void*)); |
| MOCK_METHOD1(GetAllocationSize, uint32_t(const void*)); |
| MOCK_METHOD0(Lock, void()); |
| MOCK_METHOD0(Unlock, void()); |
| MOCK_METHOD0(TryLock, bool()); |
| }; |
| typedef testing::StrictMock<LenientMockHeap> MockHeap; |
| |
| typedef std::vector<agent::asan::AsanBlockInfo> AsanBlockInfoVector; |
| typedef std::pair<agent::asan::AsanCorruptBlockRange, AsanBlockInfoVector> |
| CorruptRangeInfo; |
| typedef std::vector<CorruptRangeInfo> CorruptRangeVector; |
| |
| // A helper for testing the memory accessor instrumentation functions. |
| // |
| // This is an abstract class that should be overridden for each types |
| // of probes (with a different calling convention). |
| class MemoryAccessorTester { |
| public: |
| typedef agent::asan::BadAccessKind BadAccessKind; |
| |
| MemoryAccessorTester(); |
| virtual ~MemoryAccessorTester(); |
| |
| // Call |access_fn| to test an access on |ptr| and make sure that an invalid |
| // access of type |bad_access_kind| is detected. |
| virtual void AssertMemoryErrorIsDetected(FARPROC access_fn, |
| void* ptr, |
| BadAccessKind bad_access_type) = 0; |
| |
| // The callback used to report the errors. |
| static void AsanErrorCallback(AsanErrorInfo* error_info); |
| |
| void set_expected_error_type(BadAccessKind expected) { |
| expected_error_type_ = expected; |
| } |
| bool memory_error_detected() const { return memory_error_detected_; } |
| void set_memory_error_detected(bool memory_error_detected) { |
| memory_error_detected_ = memory_error_detected; |
| } |
| |
| const AsanErrorInfo& last_error_info() const { return last_error_info_; } |
| const CorruptRangeVector& last_corrupt_ranges() const { |
| return last_corrupt_ranges_; |
| } |
| |
| protected: |
| virtual void Initialize(); |
| void AsanErrorCallbackImpl(AsanErrorInfo* error_info); |
| |
| // This will be used in the asan callback to ensure that we detect the right |
| // error. |
| BadAccessKind expected_error_type_; |
| // A flag used in asan callback to ensure that a memory error has been |
| // detected. |
| bool memory_error_detected_; |
| |
| // Context captured on error. |
| CONTEXT error_context_; |
| |
| // The information about the last error. |
| AsanErrorInfo last_error_info_; |
| CorruptRangeVector last_corrupt_ranges_; |
| |
| // Prevent from instantiating several instances of this class at the same |
| // time as the instance gets used as a callback by the runtime. |
| static MemoryAccessorTester* instance_; |
| }; |
| |
| #ifndef _WIN64 |
| // Specialization of a |MemoryAccessorTester| for the probes with the SyzyAsan |
| // custom calling convention. |
| class SyzyAsanMemoryAccessorTester : public MemoryAccessorTester { |
| public: |
| enum IgnoreFlags { |
| IGNORE_FLAGS |
| }; |
| |
| SyzyAsanMemoryAccessorTester(); |
| explicit SyzyAsanMemoryAccessorTester(IgnoreFlags ignore_flags); |
| virtual ~SyzyAsanMemoryAccessorTester() {} |
| |
| // Checks that @p access_fn doesn't raise exceptions on access checking |
| // @p ptr, and that @p access_fn doesn't modify any registers or flags |
| // when executed. |
| void CheckAccessAndCompareContexts(FARPROC access_fn, void* ptr); |
| |
| // Checks that @p access_fn generates @p bad_access_type on checking @p ptr. |
| void AssertMemoryErrorIsDetected(FARPROC access_fn, |
| void* ptr, |
| BadAccessKind bad_access_type) override; |
| |
| enum StringOperationDirection { |
| DIRECTION_FORWARD, |
| DIRECTION_BACKWARD |
| }; |
| |
| // Checks that @p access_fn doesn't raise exceptions on access checking |
| // for a given @p direction, @p src, @p dst and @p len. |
| void CheckSpecialAccessAndCompareContexts( |
| FARPROC access_fn, StringOperationDirection direction, |
| void* dst, void* src, int len); |
| |
| // Checks that @p access_fn generates @p bad_access_type on access checking |
| // for a given @p direction, @p src, @p dst and @p len. |
| void ExpectSpecialMemoryErrorIsDetected(FARPROC access_fn, |
| StringOperationDirection direction, |
| bool expect_error, |
| void* dst, |
| void* src, |
| int32_t length, |
| BadAccessKind bad_access_type); |
| |
| protected: |
| void Initialize() override; |
| |
| // Indicates whether to ignore changes to the flags register. |
| bool ignore_flags_; |
| |
| // The pre- and post-invocation contexts. |
| CONTEXT context_before_hook_; |
| CONTEXT context_after_hook_; |
| }; |
| #endif |
| |
| // Specialization of a |MemoryAccessorTester| for the probes with the Clang |
| // calling convention (cdecl). |
| class ClangMemoryAccessorTester : public MemoryAccessorTester { |
| public: |
| ClangMemoryAccessorTester() {} |
| virtual ~ClangMemoryAccessorTester() {} |
| |
| void AssertMemoryErrorIsDetected(FARPROC access_fn, |
| void* ptr, |
| BadAccessKind bad_access_type) override; |
| |
| void CheckAccess(FARPROC access_fn, void* ptr); |
| }; |
| |
| // A fixture class for testing memory interceptors. |
| class TestMemoryInterceptors : public TestWithAsanLogger { |
| public: |
| // Redefine some enums for local use. |
| enum AccessMode { |
| AsanReadAccess = agent::asan::ASAN_READ_ACCESS, |
| AsanWriteAccess = agent::asan::ASAN_WRITE_ACCESS, |
| AsanUnknownAccess = agent::asan::ASAN_UNKNOWN_ACCESS, |
| }; |
| |
| struct InterceptFunction { |
| void(*function)(); |
| size_t size; |
| }; |
| |
| struct ClangInterceptFunction { |
| void (*function)(const void*); |
| size_t size; |
| }; |
| |
| struct StringInterceptFunction { |
| void(*function)(); |
| size_t size; |
| AccessMode dst_access_mode; |
| AccessMode src_access_mode; |
| bool uses_counter; |
| }; |
| |
| static const bool kCounterInit_ecx = true; |
| static const bool kCounterInit_1 = false; |
| |
| TestMemoryInterceptors(); |
| void SetUp() override; |
| void TearDown() override; |
| |
| template <size_t N_1, size_t N_2> |
| void TestValidAccess(const InterceptFunction(&fns)[N_1], |
| const ClangInterceptFunction(&clang_fns)[N_2]) { |
| #ifndef _WIN64 |
| TestValidAccess(fns, N_1); |
| #endif |
| TestClangValidAccess(clang_fns, N_2); |
| } |
| template <size_t N_1, size_t N_2> |
| void TestOverrunAccess(const InterceptFunction(&fns)[N_1], |
| const ClangInterceptFunction(&clang_fns)[N_2]) { |
| #ifndef _WIN64 |
| TestOverrunAccess(fns, N_2); |
| #endif |
| TestClangOverrunAccess(clang_fns, N_2); |
| } |
| template <size_t N_1, size_t N_2> |
| void TestUnderrunAccess(const InterceptFunction(&fns)[N_1], |
| const ClangInterceptFunction(&clang_fns)[N_2]) { |
| #ifndef _WIN64 |
| TestUnderrunAccess(fns, N_1); |
| #endif |
| TestClangUnderrunAccess(clang_fns, N_2); |
| } |
| #ifndef _WIN64 |
| template <size_t N> |
| void TestValidAccessIgnoreFlags(const InterceptFunction (&fns)[N]) { |
| TestValidAccessIgnoreFlags(fns, N); |
| } |
| template <size_t N> |
| void TestOverrunAccessIgnoreFlags(const InterceptFunction (&fns)[N]) { |
| TestOverrunAccessIgnoreFlags(fns, N); |
| } |
| template <size_t N> |
| void TestUnderrunAccessIgnoreFlags(const InterceptFunction (&fns)[N]) { |
| TestUnderrunAccessIgnoreFlags(fns, N); |
| } |
| template <size_t N> |
| void TestStringValidAccess(const StringInterceptFunction (&fns)[N]) { |
| TestStringValidAccess(fns, N); |
| } |
| template <size_t N> |
| void TestStringOverrunAccess(const StringInterceptFunction (&fns)[N]) { |
| TestStringOverrunAccess(fns, N); |
| } |
| #endif |
| |
| protected: |
| #ifndef _WIN64 |
| void TestValidAccess(const InterceptFunction* fns, size_t num_fns); |
| void TestValidAccessIgnoreFlags(const InterceptFunction* fns, |
| size_t num_fns); |
| void TestOverrunAccess(const InterceptFunction* fns, size_t num_fns); |
| void TestOverrunAccessIgnoreFlags(const InterceptFunction* fns, |
| size_t num_fns); |
| void TestUnderrunAccess(const InterceptFunction* fns, size_t num_fns); |
| void TestUnderrunAccessIgnoreFlags(const InterceptFunction* fns, |
| size_t num_fns); |
| void TestStringValidAccess( |
| const StringInterceptFunction* fns, size_t num_fns); |
| void TestStringOverrunAccess( |
| const StringInterceptFunction* fns, size_t num_fns); |
| #endif |
| void TestClangValidAccess(const ClangInterceptFunction* fns, size_t num_fns); |
| void TestClangOverrunAccess(const ClangInterceptFunction* fns, |
| size_t num_fns); |
| void TestClangUnderrunAccess(const ClangInterceptFunction* fns, |
| size_t num_fns); |
| |
| const size_t kAllocSize = 64; |
| |
| agent::asan::AsanRuntime asan_runtime_; |
| HANDLE heap_; |
| |
| // Convenience allocs of kAllocSize. Valid from SetUp to TearDown. |
| byte* src_; |
| byte* dst_; |
| }; |
| |
| // A very lightweight dummy heap to be used in stress testing the |
| // HeapAllocator. Lock and Unlock are noops, so this is not thread |
| // safe. |
| class DummyHeap : public agent::asan::HeapInterface { |
| public: |
| ~DummyHeap() override { } |
| agent::asan::HeapType GetHeapType() const override { |
| return agent::asan::kUnknownHeapType; |
| } |
| uint32_t GetHeapFeatures() const override { return 0; } |
| void* Allocate(uint32_t bytes) override { return ::malloc(bytes); } |
| bool Free(void* alloc) override { ::free(alloc); return true; } |
| bool IsAllocated(const void* alloc) override { return false; } |
| uint32_t GetAllocationSize(const void* alloc) override { return 0; } |
| void Lock() override { return; } |
| void Unlock() override { return; } |
| bool TryLock() override { return true; } |
| }; |
| |
| // Test read and write access. |
| // Use carefully, since it will try to overwrite the value at @p address with 0. |
| // @returns true if the address is readable and writable, false otherwise. |
| bool IsAccessible(void* address); |
| |
| // Test read and write access. |
| // Use carefully, since it will try to overwrite the value at @p address with 0. |
| // @returns true if the address is neither readable nor writable, |
| // false otherwise. |
| bool IsNotAccessible(void* address); |
| |
| // A scoped block access helper. Removes block protections when created via |
| // BlockProtectNone, and restores them via BlockProtectAuto. |
| // TODO(chrisha): Consider recording the fact the block protections on this |
| // block are being blocked in some synchronous manner. This will prevent |
| // the page protections from being added during the lifetime of this |
| // object. |
| class ScopedBlockAccess { |
| public: |
| // Constructor. Unprotects the provided block. |
| // @param block_info The block whose protections are to be modified. |
| // @parma shadow The shadow memory to be updated. |
| explicit ScopedBlockAccess(const agent::asan::BlockInfo& block_info, |
| Shadow* shadow) |
| : block_info_(block_info), shadow_(shadow) { |
| BlockProtectNone(block_info_, shadow_); |
| } |
| |
| // Destructor. Restores protections on the provided block. |
| ~ScopedBlockAccess() { BlockProtectAuto(block_info_, shadow_); } |
| |
| private: |
| const agent::asan::BlockInfo& block_info_; |
| Shadow* shadow_; |
| }; |
| |
| // A debugging shadow class. This keeps extra details in the form of an address |
| // space with stack traces. This makes it much easier to track down |
| // inconsistencies in the shadow memory. |
| class DebugShadow : public Shadow { |
| public: |
| using ShadowMarker = agent::asan::ShadowMarker; |
| |
| DebugShadow() : Shadow() { |
| } |
| |
| explicit DebugShadow(size_t length) |
| : Shadow(length) { |
| } |
| |
| ~DebugShadow() override { |
| // If the shadow has been properly used it will be completely empty by the |
| // time it is torn down. |
| CHECK(shadow_address_space_.empty()); |
| } |
| |
| protected: |
| // @name Shadow implementation. |
| // @{ |
| void SetShadowMemory( |
| const void* address, size_t length, ShadowMarker marker) override; |
| void GetPointerAndSizeImpl(void const** self, size_t* size) const override; |
| // @} |
| |
| private: |
| using StackCapture = agent::common::StackCapture; |
| |
| // Holds details about a given range of shadow memory. Persists the |
| // original size of a region, even if it is subsequently fragmented. |
| struct Metadata { |
| const void* address; |
| size_t size; |
| ShadowMarker marker; |
| StackCapture stack_capture; |
| |
| // Explicitly enable copy and assignment. |
| Metadata(); |
| Metadata(const void* address, size_t size, ShadowMarker marker); |
| Metadata(const Metadata& rhs); |
| Metadata& operator=(const Metadata& rhs); |
| }; |
| using ShadowAddressSpace = |
| core::AddressSpace<uintptr_t, size_t, Metadata>; |
| using Range = ShadowAddressSpace::Range; |
| |
| // Ensure that the given range has been cleared from the address-space, |
| // readying it for a subsequent insertion. |
| void ClearIntersection(const void* addr, size_t size); |
| |
| // An alternative view of shadow memory. Accessible regions are not |
| // displayed. Neighboring regions of the same type are merged. |
| ShadowAddressSpace shadow_address_space_; |
| }; |
| |
| } // namespace testing |
| |
| #endif // SYZYGY_AGENT_ASAN_UNITTEST_UTIL_H_ |