Bug 1705579 - Skip WinIOAutoObservation if TLS is not available. r=gerald,aklotz

We hook several file APIs to record I/O performance data.  Since TLS is not
allocated in ntdll's loader worker thread, however, if someone does a file
operation, we hit read AV because `WinIOAutoObservation` uses `nsString` and
a thread local variable.

Currently we can see this crash happens only when a DLL rule of AppLocker is
defined, but theoretically this can happen when any module loaded in a worker
thread does file operation in its entrypoint.

The proposed fix is to skip `WinIOAutoObservation` if TLS is not available.

Differential Revision: https://phabricator.services.mozilla.com/D113032
This commit is contained in:
Toshihito Kikuchi 2021-05-05 17:00:09 +00:00
Родитель faed108cb8
Коммит fee87c9b39
5 изменённых файлов: 221 добавлений и 25 удалений

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

@ -299,6 +299,10 @@ struct LocalFreeDeleter {
void operator()(void* aPtr) { ::LocalFree(aPtr); }
};
struct VirtualFreeDeleter {
void operator()(void* aPtr) { ::VirtualFree(aPtr, 0, MEM_RELEASE); }
};
// for UniquePtr to store a PSID
struct FreeSidDeleter {
void operator()(void* aPtr) { ::FreeSid(aPtr); }

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

@ -513,10 +513,10 @@ void IOInterposer::UnregisterCurrentThread() {
if (!sThreadLocalDataInitialized) {
return;
}
PerThreadData* curThreadData = sThreadLocalData.get();
MOZ_ASSERT(curThreadData);
sThreadLocalData.set(nullptr);
delete curThreadData;
if (PerThreadData* curThreadData = sThreadLocalData.get()) {
sThreadLocalData.set(nullptr);
delete curThreadData;
}
}
void IOInterposer::EnteringNextStage() {

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

@ -19,6 +19,7 @@
#include "mozilla/FileUtilsWin.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/Mutex.h"
#include "mozilla/NativeNt.h"
#include "mozilla/SmallArrayLRUCache.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
@ -256,6 +257,16 @@ static NTSTATUS NTAPI InterposedNtCreateFile(
PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess,
ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer,
ULONG aEaLength) {
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtCreateFile);
if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
return gOriginalNtCreateFile(
aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock,
aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition,
aCreateOptions, aEaBuffer, aEaLength);
}
// Report IO
const wchar_t* buf =
aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L"";
@ -266,9 +277,6 @@ static NTSTATUS NTAPI InterposedNtCreateFile(
WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpCreateOrOpen,
filename);
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtCreateFile);
// Execute original function
NTSTATUS status = gOriginalNtCreateFile(
aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock,
@ -286,13 +294,18 @@ static NTSTATUS NTAPI InterposedNtReadFile(HANDLE aFileHandle, HANDLE aEvent,
PVOID aBuffer, ULONG aLength,
PLARGE_INTEGER aOffset,
PULONG aKey) {
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtReadFile);
if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
aBuffer, aLength, aOffset, aKey);
}
// Report IO
WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle,
aOffset);
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtReadFile);
// Execute original function
return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
aBuffer, aLength, aOffset, aKey);
@ -302,13 +315,19 @@ static NTSTATUS NTAPI InterposedNtReadFileScatter(
HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
PLARGE_INTEGER aOffset, PULONG aKey) {
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtReadFileScatter);
if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx,
aIoStatus, aSegments, aLength, aOffset,
aKey);
}
// Report IO
WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle,
aOffset);
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtReadFileScatter);
// Execute original function
return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx,
aIoStatus, aSegments, aLength, aOffset,
@ -322,13 +341,18 @@ static NTSTATUS NTAPI InterposedNtWriteFile(HANDLE aFileHandle, HANDLE aEvent,
PVOID aBuffer, ULONG aLength,
PLARGE_INTEGER aOffset,
PULONG aKey) {
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtWriteFile);
if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
aBuffer, aLength, aOffset, aKey);
}
// Report IO
WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle,
aOffset);
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtWriteFile);
// Execute original function
return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
aBuffer, aLength, aOffset, aKey);
@ -339,13 +363,19 @@ static NTSTATUS NTAPI InterposedNtWriteFileGather(
HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
PLARGE_INTEGER aOffset, PULONG aKey) {
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtWriteFileGather);
if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx,
aIoStatus, aSegments, aLength, aOffset,
aKey);
}
// Report IO
WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle,
aOffset);
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtWriteFileGather);
// Execute original function
return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx,
aIoStatus, aSegments, aLength, aOffset,
@ -354,13 +384,17 @@ static NTSTATUS NTAPI InterposedNtWriteFileGather(
static NTSTATUS NTAPI InterposedNtFlushBuffersFile(
HANDLE aFileHandle, PIO_STATUS_BLOCK aIoStatusBlock) {
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtFlushBuffersFile);
if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock);
}
// Report IO
WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFileHandle,
nullptr);
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtFlushBuffersFile);
// Execute original function
return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock);
}
@ -368,6 +402,14 @@ static NTSTATUS NTAPI InterposedNtFlushBuffersFile(
static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile(
POBJECT_ATTRIBUTES aObjectAttributes,
PFILE_NETWORK_OPEN_INFORMATION aFileInformation) {
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtQueryFullAttributesFile);
if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
return gOriginalNtQueryFullAttributesFile(aObjectAttributes,
aFileInformation);
}
// Report IO
const wchar_t* buf =
aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L"";
@ -377,9 +419,6 @@ static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile(
nsDependentSubstring filename(buf, len);
WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, filename);
// Something is badly wrong if this function is undefined
MOZ_ASSERT(gOriginalNtQueryFullAttributesFile);
// Execute original function
return gOriginalNtQueryFullAttributesFile(aObjectAttributes,
aFileInformation);

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

@ -0,0 +1,152 @@
/* -*- 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 <windows.h>
#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<UniquePtr<void, VirtualFreeDeleter>, kNumPages> pages;
Array<FILE_SEGMENT_ELEMENT, kNumPages + 1> segments;
for (int i = 0; i < kNumPages; ++i) {
auto p = reinterpret_cast<uint32_t*>(::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(), ERROR_IO_PENDING);
EXPECT_TRUE(writeOp.Wait(file.get()));
}
for (int i = 0; i < kNumPages; ++i) {
*reinterpret_cast<uint32_t*>(pages[i].get()) = 0;
}
Overlapped readOp;
if (!::ReadFileScatter(file.get(), segments.begin(), kNumPages * kPageSize,
nullptr, readOp)) {
EXPECT_EQ(::GetLastError(), ERROR_IO_PENDING);
EXPECT_TRUE(readOp.Wait(file.get()));
}
for (int i = 0; i < kNumPages; ++i) {
EXPECT_EQ(*reinterpret_cast<uint32_t*>(pages[i].get()), kMagic);
}
}
TEST(PoisonIOInterposer, NormalThread)
{
mozilla::IOInterposerInit ioInterposerGuard;
TempFile tempFile;
FileOpSync(tempFile);
FileOpAsync(tempFile);
EXPECT_TRUE(::DeleteFileW(tempFile));
}
TEST(PoisonIOInterposer, NullTlsPointer)
{
void* originalTls = mozilla::nt::RtlGetThreadLocalStoragePointer();
mozilla::IOInterposerInit ioInterposerGuard;
// 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);
}

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

@ -7,6 +7,7 @@
UNIFIED_SOURCES += [
"TestCOM.cpp",
"TestNtPathToDosPath.cpp",
"TestPoisonIOInterposer.cpp",
]
OS_LIBS += [