#include <limits.h>
#include <stddef.h>
#include "base/compiler_specific.h"
#include "base/strings/string_util.h"
#include "base/win/scoped_handle.h"
#include "sandbox/win/src/handle_closer_agent.h"
#include "sandbox/win/src/nt_internals.h"
#include "sandbox/win/src/sandbox.h"
#include "sandbox/win/src/sandbox_factory.h"
#include "sandbox/win/src/target_services.h"
#include "sandbox/win/src/win_utils.h"
#include "sandbox/win/tests/common/controller.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const wchar_t kDeviceKsecDD[] = L"\\Device\\KsecDD";
HANDLE finish_event;
const int kWaitCount = 20;
HANDLE OpenKsecDD() {
UNICODE_STRING name;
OBJECT_ATTRIBUTES attrs{};
IO_STATUS_BLOCK iosb;
HANDLE hDevice = INVALID_HANDLE_VALUE;
RtlInitUnicodeString(&name, kDeviceKsecDD);
InitializeObjectAttributes(&attrs, &name, 0, nullptr, nullptr);
NTSTATUS status =
NtOpenFile(&hDevice, 0x100001, &attrs, &iosb,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(status)) {
::SetLastError(sandbox::GetLastErrorFromNtStatus(status));
return INVALID_HANDLE_VALUE;
}
return hDevice;
}
}
namespace sandbox {
SBOX_TESTS_COMMAND int CheckForFileHandles(int argc, wchar_t** argv) {
if (argc < 2) {
return SBOX_TEST_FAILED_TO_RUN_TEST;
}
bool should_find = argv[0][0] == L'Y';
if (UNSAFE_TODO(argv[0][1]) != L'\0' ||
(!should_find && argv[0][0] != L'N')) {
return SBOX_TEST_FAILED_TO_RUN_TEST;
}
static int state = BEFORE_INIT;
switch (state++) {
case BEFORE_INIT: {
HANDLE handle = OpenKsecDD();
CHECK_NE(handle, INVALID_HANDLE_VALUE);
return SBOX_TEST_SUCCEEDED;
}
case AFTER_REVERT: {
DWORD handle_count = UINT_MAX;
const int kInvalidHandleThreshold = 100;
const size_t kHandleOffset = 4;
HANDLE handle = nullptr;
int invalid_count = 0;
if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) {
return SBOX_TEST_FAILED_TO_RUN_TEST;
}
while (handle_count && invalid_count < kInvalidHandleThreshold) {
reinterpret_cast<size_t&>(handle) += kHandleOffset;
auto handle_name = GetPathFromHandle(handle);
if (handle_name) {
for (int i = 1; i < argc; ++i) {
if (handle_name.value() == UNSAFE_TODO(argv[i])) {
return should_find ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED;
}
}
--handle_count;
} else {
++invalid_count;
}
}
return should_find ? SBOX_TEST_FAILED : SBOX_TEST_SUCCEEDED;
}
default:
break;
}
return SBOX_TEST_SUCCEEDED;
}
SBOX_TESTS_COMMAND int CheckForEventHandles(int argc, wchar_t** argv) {
static int state = BEFORE_INIT;
static HANDLE to_check;
switch (state++) {
case BEFORE_INIT: {
to_check = OpenKsecDD();
CHECK_NE(to_check, INVALID_HANDLE_VALUE);
return SBOX_TEST_SUCCEEDED;
}
case AFTER_REVERT: {
auto type_name = GetTypeNameFromHandle(to_check);
CHECK(type_name);
CHECK(base::EqualsCaseInsensitiveASCII(type_name.value(), L"Event"));
CHECK_EQ(WaitForSingleObject(to_check, INFINITE), WAIT_FAILED);
CHECK(::CloseHandle(to_check));
return SBOX_TEST_SUCCEEDED;
}
default:
break;
}
return SBOX_TEST_SUCCEEDED;
}
TEST(HandleCloserTest, CheckForDeviceHandles) {
TestRunner runner;
runner.SetTimeout(2000);
runner.SetTestState(EVERY_STATE);
std::wstring command = std::wstring(L"CheckForFileHandles Y");
command += (L" ");
command += kDeviceKsecDD;
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str()))
<< "Failed: " << command;
}
TEST(HandleCloserTest, CloseSupportedDevices) {
TestRunner runner;
runner.SetTimeout(2000);
runner.SetTestState(EVERY_STATE);
sandbox::TargetPolicy* policy = runner.GetPolicy();
std::wstring command = std::wstring(L"CheckForFileHandles N");
command += (L" ");
command += kDeviceKsecDD;
policy->GetConfig()->AddKernelObjectToClose(HandleToClose::kDeviceApi);
policy->GetConfig()->AddKernelObjectToClose(HandleToClose::kKsecDD);
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str()))
<< "Failed: " << command;
}
TEST(HandleCloserTest, CheckStuffedHandle) {
TestRunner runner;
runner.SetTimeout(2000);
runner.SetTestState(EVERY_STATE);
sandbox::TargetPolicy* policy = runner.GetPolicy();
policy->GetConfig()->AddKernelObjectToClose(HandleToClose::kDeviceApi);
policy->GetConfig()->AddKernelObjectToClose(HandleToClose::kKsecDD);
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckForEventHandles"));
}
void WINAPI ThreadPoolTask(void* event, BOOLEAN timeout) {
static volatile LONG waiters_remaining = kWaitCount;
CHECK(!timeout);
CHECK(::CloseHandle(event));
if (::InterlockedDecrement(&waiters_remaining) == 0)
CHECK(::SetEvent(finish_event));
}
SBOX_TESTS_COMMAND int RunThreadPool(int argc, wchar_t** argv) {
HANDLE wait_list[20];
finish_event = ::CreateEvent(nullptr, true, false, nullptr);
CHECK(finish_event);
HANDLE pool = nullptr;
for (int i = 0; i < kWaitCount; ++i) {
HANDLE event = ::CreateEvent(nullptr, true, false, nullptr);
CHECK(event);
CHECK(::RegisterWaitForSingleObject(&pool, event, ThreadPoolTask, event,
INFINITE, WT_EXECUTEONLYONCE));
UNSAFE_TODO(wait_list[i]) = event;
}
for (int i = 0; i < kWaitCount; ++i)
CHECK(::SetEvent(UNSAFE_TODO(wait_list[i])));
CHECK_EQ(::WaitForSingleObject(finish_event, INFINITE), WAIT_OBJECT_0);
CHECK(::CloseHandle(finish_event));
return SBOX_TEST_SUCCEEDED;
}
TEST(HandleCloserTest, RunThreadPool) {
TestRunner runner;
runner.SetTimeout(2000);
runner.SetTestState(AFTER_REVERT);
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"RunThreadPool"));
}
}