/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include "gtest/gtest.h" #include "mozilla/IOInterposer.h" #include "mozilla/NativeNt.h" #include "nsWindowsHelpers.h" using namespace mozilla; class TempFile final { wchar_t mFullPath[MAX_PATH + 1]; public: TempFile() : mFullPath{0} { wchar_t tempDir[MAX_PATH + 1]; DWORD len = ::GetTempPathW(ArrayLength(tempDir), tempDir); if (!len) { return; } len = ::GetTempFileNameW(tempDir, L"poi", 0, mFullPath); if (!len) { return; } } operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; } }; class Overlapped final { nsAutoHandle mEvent; OVERLAPPED mOverlapped; public: Overlapped() : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)), mOverlapped{} { mOverlapped.hEvent = mEvent.get(); } operator OVERLAPPED*() { return &mOverlapped; } bool Wait(HANDLE aHandle) { DWORD numBytes; if (!::GetOverlappedResult(aHandle, &mOverlapped, &numBytes, TRUE)) { return false; } return true; } }; const uint32_t kMagic = 0x12345678; void FileOpSync(const wchar_t* aPath) { nsAutoHandle file(::CreateFileW(aPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); ASSERT_NE(file.get(), INVALID_HANDLE_VALUE); DWORD buffer = kMagic, numBytes = 0; OVERLAPPED seek = {}; EXPECT_TRUE(WriteFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek)); EXPECT_TRUE(::FlushFileBuffers(file.get())); seek.Offset = 0; buffer = 0; EXPECT_TRUE( ::ReadFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek)); EXPECT_EQ(buffer, kMagic); WIN32_FILE_ATTRIBUTE_DATA fullAttr = {}; EXPECT_TRUE(::GetFileAttributesExW(aPath, GetFileExInfoStandard, &fullAttr)); } void FileOpAsync(const wchar_t* aPath) { constexpr int kNumPages = 10; constexpr int kPageSize = 4096; Array, kNumPages> pages; Array segments; for (int i = 0; i < kNumPages; ++i) { auto p = reinterpret_cast(::VirtualAlloc( nullptr, kPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); ASSERT_TRUE(p); pages[i].reset(p); segments[i].Buffer = p; p[0] = kMagic; } nsAutoHandle file(::CreateFileW( aPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, nullptr)); ASSERT_NE(file.get(), INVALID_HANDLE_VALUE); Overlapped writeOp; if (!::WriteFileGather(file.get(), segments.begin(), kNumPages * kPageSize, nullptr, writeOp)) { EXPECT_EQ(::GetLastError(), static_cast(ERROR_IO_PENDING)); EXPECT_TRUE(writeOp.Wait(file.get())); } for (int i = 0; i < kNumPages; ++i) { *reinterpret_cast(pages[i].get()) = 0; } Overlapped readOp; if (!::ReadFileScatter(file.get(), segments.begin(), kNumPages * kPageSize, nullptr, readOp)) { EXPECT_EQ(::GetLastError(), static_cast(ERROR_IO_PENDING)); EXPECT_TRUE(readOp.Wait(file.get())); } for (int i = 0; i < kNumPages; ++i) { EXPECT_EQ(*reinterpret_cast(pages[i].get()), kMagic); } } TEST(PoisonIOInterposer, NormalThread) { mozilla::AutoIOInterposer ioInterposerGuard; ioInterposerGuard.Init(); TempFile tempFile; FileOpSync(tempFile); FileOpAsync(tempFile); EXPECT_TRUE(::DeleteFileW(tempFile)); } TEST(PoisonIOInterposer, NullTlsPointer) { void* originalTls = mozilla::nt::RtlGetThreadLocalStoragePointer(); mozilla::AutoIOInterposer ioInterposerGuard; ioInterposerGuard.Init(); // Simulate a loader worker thread (TEB::LoaderWorker = 1) // where ThreadLocalStorage is never allocated. mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(nullptr); TempFile tempFile; FileOpSync(tempFile); FileOpAsync(tempFile); EXPECT_TRUE(::DeleteFileW(tempFile)); mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(originalTls); }