Bug 1550815 - Handle SIGBUS error when accessing mmapped file, r=aklotz,haik

This patch adds mmap page fault handling to linux/android.

Differential Revision: https://phabricator.services.mozilla.com/D42010

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Michal Novotny 2019-10-10 14:07:06 +00:00
Родитель 0a069e2799
Коммит ef9ac4c5c0
8 изменённых файлов: 303 добавлений и 39 удалений

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

@ -0,0 +1,141 @@
/* -*- 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 "MmapFaultHandler.h"
#if defined(XP_UNIX) && !defined(XP_DARWIN)
# include "nsZipArchive.h"
# include "mozilla/Atomics.h"
# include "mozilla/StaticMutex.h"
# include "MainThreadUtils.h"
# include "mozilla/ThreadLocal.h"
# include <signal.h>
static MOZ_THREAD_LOCAL(MmapAccessScope*) sMmapAccessScope;
static struct sigaction sPrevSIGBUSHandler;
static void MmapSIGBUSHandler(int signum, siginfo_t* info, void* context) {
MOZ_RELEASE_ASSERT(signum == SIGBUS);
MmapAccessScope* mas = sMmapAccessScope.get();
if (mas && mas->IsInsideBuffer(info->si_addr)) {
// The address is inside the buffer, handle the failure.
siglongjmp(mas->mJmpBuf, signum);
return;
}
// This signal is not caused by accessing region protected by MmapAccessScope.
// Forward the signal to the next handler.
if (sPrevSIGBUSHandler.sa_flags & SA_SIGINFO) {
sPrevSIGBUSHandler.sa_sigaction(signum, info, context);
} else if (sPrevSIGBUSHandler.sa_handler == SIG_DFL ||
sPrevSIGBUSHandler.sa_handler == SIG_IGN) {
// There is no next handler. Uninstalling our handler and returning will
// cause a crash.
sigaction(signum, &sPrevSIGBUSHandler, nullptr);
} else {
sPrevSIGBUSHandler.sa_handler(signum);
}
}
mozilla::Atomic<bool> gSIGBUSHandlerInstalled(false);
mozilla::StaticMutex gSIGBUSHandlerMutex;
void InstallMmapFaultHandler() {
// This function is called from MmapAccessScope's constructor because there is
// no single point where we could install the handler during startup. This
// means that it's called quite often, so to minimize using of the mutex we
// first check the atomic variable outside the lock.
if (gSIGBUSHandlerInstalled) {
return;
}
mozilla::StaticMutexAutoLock lock(gSIGBUSHandlerMutex);
// We must check it again, because the handler could be installed on another
// thread when we were waiting for the lock.
if (gSIGBUSHandlerInstalled) {
return;
}
sMmapAccessScope.infallibleInit();
struct sigaction busHandler;
busHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
busHandler.sa_sigaction = MmapSIGBUSHandler;
sigemptyset(&busHandler.sa_mask);
if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler)) {
MOZ_CRASH("Unable to install SIGBUS handler");
}
gSIGBUSHandlerInstalled = true;
}
MmapAccessScope::MmapAccessScope(void* aBuf, uint32_t aBufLen) {
// Install signal handler if it wasn't installed yet.
InstallMmapFaultHandler();
// We'll handle the signal only if the crashing address is inside this buffer.
mBuf = aBuf;
mBufLen = aBufLen;
SetThreadLocalScope();
}
MmapAccessScope::MmapAccessScope(nsZipHandle* aZipHandle)
: mBuf(nullptr), mBufLen(0) {
// Install signal handler if it wasn't installed yet.
InstallMmapFaultHandler();
// It's OK if aZipHandle is null (e.g. called from nsJARInputStream::Read
// when mFd was already release), because no access to mmapped memory is made
// in this case.
if (aZipHandle && aZipHandle->mMap) {
// Handle SIGBUS only when it's an mmaped zip file.
mZipHandle = aZipHandle;
}
SetThreadLocalScope();
}
MmapAccessScope::~MmapAccessScope() {
MOZ_RELEASE_ASSERT(sMmapAccessScope.get() == this);
sMmapAccessScope.set(mPreviousScope);
}
void MmapAccessScope::SetThreadLocalScope() {
// mJmpBuf is set outside of this classs for reasons mentioned in the header
// file, but we need to initialize the member here too to make Coverity happy.
memset(mJmpBuf, 0, sizeof(sigjmp_buf));
// If MmapAccessScopes are nested, save the previous one and restore it in
// the destructor.
mPreviousScope = sMmapAccessScope.get();
// MmapAccessScope is now set up (except mJmpBuf for reasons mentioned in the
// header file). Store the pointer in a thread-local variable sMmapAccessScope
// so we can use it in the handler if the signal is triggered.
sMmapAccessScope.set(this);
}
bool MmapAccessScope::IsInsideBuffer(void* aPtr) {
bool isIn;
if (mZipHandle) {
isIn =
aPtr >= mZipHandle->mFileStart &&
aPtr < (void*)((char*)mZipHandle->mFileStart + mZipHandle->mTotalLen);
} else {
isIn = aPtr >= mBuf && aPtr < (void*)((char*)mBuf + mBufLen);
}
return isIn;
}
#endif

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

@ -0,0 +1,88 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef MmapFaultHandler_h_
#define MmapFaultHandler_h_
#if defined(XP_WIN)
// Windows
# ifdef HAVE_SEH_EXCEPTIONS
# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) __try {
# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) __try {
# define MMAP_FAULT_HANDLER_CATCH(retval) \
} \
__except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR \
? EXCEPTION_EXECUTE_HANDLER \
: EXCEPTION_CONTINUE_SEARCH) { \
NS_WARNING("unexpected EXCEPTION_IN_PAGE_ERROR"); \
return retval; \
}
# else
# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) {
# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) {
# define MMAP_FAULT_HANDLER_CATCH(retval) }
# endif
#elif defined(XP_DARWIN)
// MacOS
# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) {
# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) {
# define MMAP_FAULT_HANDLER_CATCH(retval) }
#else
// Linux
# include "mozilla/RefPtr.h"
# include "mozilla/GuardObjects.h"
# include <stdint.h>
# include <setjmp.h>
class nsZipHandle;
class MOZ_RAII MmapAccessScope {
public:
MmapAccessScope(void* aBuf, uint32_t aBufLen);
explicit MmapAccessScope(nsZipHandle* aZipHandle);
~MmapAccessScope();
MmapAccessScope(const MmapAccessScope&) = delete;
MmapAccessScope& operator=(const MmapAccessScope&) = delete;
void SetThreadLocalScope();
bool IsInsideBuffer(void* aPtr);
// sigsetjmp cannot be called from a method that returns before calling
// siglongjmp, so the macro must call sigsetjmp directly and mJmpBuf must be
// public.
sigjmp_buf mJmpBuf;
private:
void* mBuf;
uint32_t mBufLen;
RefPtr<nsZipHandle> mZipHandle;
MmapAccessScope* mPreviousScope;
};
# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) \
{ \
MmapAccessScope mmapScope(fd); \
if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) {
# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) \
{ \
MmapAccessScope mmapScope((void*)(buf), (bufLen)); \
if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) {
# define MMAP_FAULT_HANDLER_CATCH(retval) \
} \
else { \
NS_WARNING("SIGBUS received when accessing mmapped file"); \
return retval; \
} \
}
#endif
#endif

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

@ -30,6 +30,7 @@ EXPORTS += [
]
UNIFIED_SOURCES += [
'MmapFaultHandler.cpp',
'nsJAR.cpp',
'nsJARChannel.cpp',
'nsJARInputStream.cpp',

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

@ -11,6 +11,7 @@
# include "brotli/decode.h" // brotli
#endif
#include "nsZipArchive.h"
#include "MmapFaultHandler.h"
#include "nsEscape.h"
#include "nsIFile.h"
@ -193,7 +194,7 @@ nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytesRead) {
*aBytesRead = 0;
nsresult rv = NS_OK;
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
switch (mMode) {
case MODE_NOTINITED:
return NS_OK;
@ -235,7 +236,7 @@ nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytesRead) {
}
break;
}
MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE)
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
return rv;
}

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

@ -16,6 +16,7 @@
# include "brotli/decode.h" // brotli
#endif
#include "nsISupportsUtils.h"
#include "MmapFaultHandler.h"
#include "prio.h"
#include "plstr.h"
#include "mozilla/Attributes.h"
@ -278,7 +279,7 @@ nsresult nsZipHandle::findDataStart() {
// type |uint8_t|, which is guaranteed to be 8 bits.
const uint32_t CRXIntSize = 4;
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_HANDLE(this)
if (mTotalLen > CRXIntSize * 4 && xtolong(mFileStart) == kCRXMagic) {
const uint8_t* headerData = mFileStart;
headerData += CRXIntSize * 2; // Skip magic number and version number
@ -294,7 +295,7 @@ nsresult nsZipHandle::findDataStart() {
}
mLen = mTotalLen;
mFileData = mFileStart;
MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
return NS_OK;
}
@ -466,7 +467,7 @@ nsZipItem* nsZipArchive::GetItem(const char* aEntryName) {
if (BuildSynthetics() != NS_OK) return 0;
}
}
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
nsZipItem* item = mFiles[HashName(aEntryName, len)];
while (item) {
if ((len == item->nameLength) &&
@ -480,7 +481,7 @@ nsZipItem* nsZipArchive::GetItem(const char* aEntryName) {
}
item = item->next;
}
MOZ_WIN_MEM_TRY_CATCH(return nullptr)
MMAP_FAULT_HANDLER_CATCH(nullptr)
}
return nullptr;
}
@ -591,7 +592,7 @@ nsresult nsZipFind::FindNext(const char** aResult, uint16_t* aNameLen) {
*aResult = 0;
*aNameLen = 0;
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mArchive->GetFD())
// we start from last match, look for next
while (mSlot < ZIP_TABSIZE) {
// move to next in current chain, or move to new slot
@ -617,7 +618,7 @@ nsresult nsZipFind::FindNext(const char** aResult, uint16_t* aNameLen) {
return NS_OK;
}
}
MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
}
@ -641,7 +642,7 @@ nsresult nsZipArchive::BuildFileList(PRFileDesc* aFd) {
const uint8_t* buf;
const uint8_t* startp = mFd->mFileData;
const uint8_t* endp = startp + mFd->mLen;
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
uint32_t centralOffset = 4;
// Only perform readahead in the parent process. Children processes
// don't need readahead when the file has already been readahead by
@ -734,7 +735,7 @@ nsresult nsZipArchive::BuildFileList(PRFileDesc* aFd) {
}
}
MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
return NS_OK;
}
@ -745,7 +746,7 @@ nsresult nsZipArchive::BuildSynthetics() {
if (mBuiltSynthetics) return NS_OK;
mBuiltSynthetics = true;
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
// Create synthetic entries for any missing directories.
// Do this when all ziptable has scanned to prevent double entries.
for (auto* item : mFiles) {
@ -799,7 +800,7 @@ nsresult nsZipArchive::BuildSynthetics() {
} /* end processing of dirs in item's name */
}
}
MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
return NS_OK;
}
@ -813,12 +814,14 @@ nsZipHandle* nsZipArchive::GetFD() {
//---------------------------------------------
uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
MOZ_ASSERT(aItem);
MOZ_WIN_MEM_TRY_BEGIN
uint32_t offset;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
//-- read local header to get variable length values and calculate
//-- the real data offset
uint32_t len = mFd->mLen;
const uint8_t* data = mFd->mFileData;
uint32_t offset = aItem->LocalOffset();
offset = aItem->LocalOffset();
if (len < ZIPLOCAL_SIZE || offset > len - ZIPLOCAL_SIZE) return 0;
// -- check signature before using the structure, in case the zip file is
@ -832,8 +835,8 @@ uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
offset += ZIPLOCAL_SIZE + xtoint(Local->filename_len) +
xtoint(Local->extrafield_len);
MMAP_FAULT_HANDLER_CATCH(0)
return offset;
MOZ_WIN_MEM_TRY_CATCH(return 0)
}
//---------------------------------------------
@ -841,25 +844,25 @@ uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
//---------------------------------------------
const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) {
MOZ_ASSERT(aItem);
MOZ_WIN_MEM_TRY_BEGIN
uint32_t offset = GetDataOffset(aItem);
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
// -- check if there is enough source data in the file
if (!offset || mFd->mLen < aItem->Size() ||
offset > mFd->mLen - aItem->Size() ||
(aItem->Compression() == STORED && aItem->Size() != aItem->RealSize())) {
return nullptr;
}
MMAP_FAULT_HANDLER_CATCH(nullptr)
return mFd->mFileData + offset;
MOZ_WIN_MEM_TRY_CATCH(return nullptr)
}
// nsZipArchive::GetComment
bool nsZipArchive::GetComment(nsACString& aComment) {
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_BUFFER(mCommentPtr, mCommentLen)
aComment.Assign(mCommentPtr, mCommentLen);
MOZ_WIN_MEM_TRY_CATCH(return false)
MMAP_FAULT_HANDLER_CATCH(false)
return true;
}
@ -1024,13 +1027,19 @@ uint16_t nsZipItem::Mode() {
const uint8_t* nsZipItem::GetExtraField(uint16_t aTag, uint16_t* aBlockSize) {
if (isSynthetic) return nullptr;
MOZ_WIN_MEM_TRY_BEGIN
const unsigned char* buf =
((const unsigned char*)central) + ZIPCENTRAL_SIZE + nameLength;
uint32_t buflen = (uint32_t)xtoint(central->extrafield_len);
uint32_t buflen;
MMAP_FAULT_HANDLER_BEGIN_BUFFER(central, ZIPCENTRAL_SIZE + nameLength)
buflen = (uint32_t)xtoint(central->extrafield_len);
MMAP_FAULT_HANDLER_CATCH(nullptr)
uint32_t pos = 0;
uint16_t tag, blocksize;
MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, buflen)
while (buf && (pos + 4) <= buflen) {
tag = xtoint(buf + pos);
blocksize = xtoint(buf + pos + 2);
@ -1042,8 +1051,8 @@ const uint8_t* nsZipItem::GetExtraField(uint16_t aTag, uint16_t* aBlockSize) {
pos += blocksize + 4;
}
MMAP_FAULT_HANDLER_CATCH(nullptr)
MOZ_WIN_MEM_TRY_CATCH(return nullptr)
return nullptr;
}
@ -1111,7 +1120,7 @@ uint8_t* nsZipCursor::ReadOrCopy(uint32_t* aBytesRead, bool aCopy) {
bool verifyCRC = true;
if (!mZs.next_in) return nullptr;
MOZ_WIN_MEM_TRY_BEGIN
MMAP_FAULT_HANDLER_BEGIN_BUFFER(mZs.next_in, mZs.avail_in)
switch (mItem->Compression()) {
case STORED:
if (!aCopy) {
@ -1170,7 +1179,7 @@ uint8_t* nsZipCursor::ReadOrCopy(uint32_t* aBytesRead, bool aCopy) {
mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead);
if (verifyCRC && mCRC != mItem->CRC32()) return nullptr;
}
MOZ_WIN_MEM_TRY_CATCH(return nullptr)
MMAP_FAULT_HANDLER_CATCH(nullptr)
return buf;
}

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

@ -22,21 +22,6 @@
#include "mozilla/FileLocation.h"
#include "mozilla/UniquePtr.h"
#ifdef HAVE_SEH_EXCEPTIONS
# define MOZ_WIN_MEM_TRY_BEGIN __try {
# define MOZ_WIN_MEM_TRY_CATCH(cmd) \
} \
__except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR \
? EXCEPTION_EXECUTE_HANDLER \
: EXCEPTION_CONTINUE_SEARCH) { \
NS_WARNING("unexpected EXCEPTION_IN_PAGE_ERROR"); \
cmd; \
}
#else
# define MOZ_WIN_MEM_TRY_BEGIN {
# define MOZ_WIN_MEM_TRY_CATCH(cmd) }
#endif
class nsZipFind;
struct PRFileDesc;
#ifdef MOZ_JAR_BROTLI
@ -381,7 +366,12 @@ class nsZipItemPtr final : public nsZipItemPtr_base {
class nsZipHandle final {
friend class nsZipArchive;
friend class nsZipFind;
friend class mozilla::FileLocation;
friend class nsJARInputStream;
#if defined(XP_UNIX) && !defined(XP_DARWIN)
friend class MmapAccessScope;
#endif
public:
static nsresult Init(nsIFile* file, nsZipHandle** ret,

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

@ -0,0 +1,32 @@
// Test checks SIGBUS handling on Linux. The test cannot be used to check page
// error exception on Windows because the file cannot be truncated while it's
// being used by zipreader.
function run_test() {
var file = do_get_file("data/test_bug333423.zip");
var tmpFile = do_get_tempdir();
file.copyTo(tmpFile, "bug1550815.zip");
tmpFile.append("bug1550815.zip");
var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(
Ci.nsIZipReader
);
zipReader.open(tmpFile);
// Truncate the file
var ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
Ci.nsIFileOutputStream
);
ostream.init(tmpFile, -1, -1, 0);
ostream.close();
try {
zipReader.test("modules/libjar/test/Makefile.in");
Assert.ok(false, "Should not reach here.");
} catch (e) {
Assert.equal(e.result, Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
}
zipReader.close();
tmpFile.remove(false);
}

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

@ -41,3 +41,5 @@ skip-if = os == "mac"
[test_uncompressed.js]
[test_umlaute.js]
[test_bug1328865.js]
[test_bug1550815.js]
skip-if = os != "linux"