// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "components/zucchini/exception_filter_helper_win.h"
#include <windows.h>
#include "base/test/gtest_util.h"
#include "base/win/windows_types.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace zucchini {
namespace {
static constexpr ULONG_PTR kFlagRead = 0;
static constexpr ULONG_PTR kFlagWrite = 1;
static constexpr ULONG_PTR kTestNtStatus = 47;
} // namespace
TEST(ExceptionFilterHelperTest, NotPageError) {
ExceptionFilterHelper helper;
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION)};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_CONTINUE_SEARCH);
}
TEST(ExceptionFilterHelperTest, NoRanges) {
ExceptionFilterHelper helper;
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 50, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_CONTINUE_SEARCH);
}
TEST(ExceptionFilterHelperTest, OneRangeBelow) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 49, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_CONTINUE_SEARCH);
}
TEST(ExceptionFilterHelperTest, OneRangeAbove) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 51, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_CONTINUE_SEARCH);
}
TEST(ExceptionFilterHelperTest, OneRangeIn) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 50, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_EXECUTE_HANDLER);
ASSERT_FALSE(helper.is_write());
ASSERT_EQ(helper.nt_status(), static_cast<int32_t>(kTestNtStatus));
}
TEST(ExceptionFilterHelperTest, TwoRangesBelow) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 49, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_CONTINUE_SEARCH);
}
TEST(ExceptionFilterHelperTest, TwoRangesAbove) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 102, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_CONTINUE_SEARCH);
}
TEST(ExceptionFilterHelperTest, TwoRangesBetween) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 75, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_CONTINUE_SEARCH);
}
TEST(ExceptionFilterHelperTest, TwoRangesInFirst) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 50, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_EXECUTE_HANDLER);
ASSERT_FALSE(helper.is_write());
ASSERT_EQ(helper.nt_status(), static_cast<int32_t>(kTestNtStatus));
}
TEST(ExceptionFilterHelperTest, TwoRangesInSecond) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, 100, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_EXECUTE_HANDLER);
ASSERT_FALSE(helper.is_write());
ASSERT_EQ(helper.nt_status(), static_cast<int32_t>(kTestNtStatus));
}
TEST(ExceptionFilterHelperTest, IsWrite) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagWrite, 50, kTestNtStatus}};
ASSERT_EQ(helper.FilterPageError(&exception_record),
EXCEPTION_EXECUTE_HANDLER);
ASSERT_TRUE(helper.is_write());
ASSERT_EQ(helper.nt_status(), static_cast<int32_t>(kTestNtStatus));
}
TEST(ExceptionFilterHelperTest, Sweep) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(14), 0U});
helper.AddRange({reinterpret_cast<uint8_t*>(5), 3U});
helper.AddRange({reinterpret_cast<uint8_t*>(3), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(10), 1U});
std::string bitmap(16, ' ');
for (ULONG_PTR address = 0; address < bitmap.size(); ++address) {
EXCEPTION_RECORD exception_record = {
.ExceptionCode = static_cast<DWORD>(EXCEPTION_IN_PAGE_ERROR),
.NumberParameters = 3,
.ExceptionInformation{kFlagRead, address, kTestNtStatus}};
auto result = helper.FilterPageError(&exception_record);
bitmap[address] = (result == EXCEPTION_EXECUTE_HANDLER) ? '*' : '.';
}
ASSERT_STREQ(bitmap.c_str(), "...*****..*.....");
}
TEST(ExceptionFilterHelperDeathTest, DuplicateRange) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
ASSERT_CHECK_DEATH({
helper.AddRange({reinterpret_cast<uint8_t*>(50), 1U});
});
}
TEST(ExceptionFilterHelperDeathTest, OverlappingFirstRange) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
ASSERT_CHECK_DEATH({
helper.AddRange({reinterpret_cast<uint8_t*>(49), 2U});
});
}
TEST(ExceptionFilterHelperDeathTest, CoveringFirstRange) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
ASSERT_CHECK_DEATH({
helper.AddRange({reinterpret_cast<uint8_t*>(49), 4U});
});
}
TEST(ExceptionFilterHelperDeathTest, OverlappingLastRange) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
ASSERT_CHECK_DEATH({
helper.AddRange({reinterpret_cast<uint8_t*>(101), 2U});
});
}
TEST(ExceptionFilterHelperDeathTest, CoveringLastRange) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
ASSERT_CHECK_DEATH({
helper.AddRange({reinterpret_cast<uint8_t*>(99), 4U});
});
}
TEST(ExceptionFilterHelperDeathTest, MidRanges) {
ExceptionFilterHelper helper;
helper.AddRange({reinterpret_cast<uint8_t*>(50), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(100), 2U});
helper.AddRange({reinterpret_cast<uint8_t*>(75), 2U});
ASSERT_CHECK_DEATH({
helper.AddRange({reinterpret_cast<uint8_t*>(76), 4U});
});
}
} // namespace zucchini