/* -*- 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 #include #include #include #include #include #ifdef _WINDOWS # include # define getpid _getpid #else # include #endif #include "base/string_util.h" #include "FuzzingMutate.h" #include "FuzzingTraits.h" #include "chrome/common/ipc_channel.h" #include "chrome/common/ipc_message.h" #include "chrome/common/file_descriptor_set_posix.h" #include "mozilla/ipc/Faulty.h" #include "nsComponentManagerUtils.h" #include "nsNetCID.h" #include "nsIFile.h" #include "nsIFileStreams.h" #include "nsILineInputStream.h" #include "nsIRunnable.h" #include "nsThreadUtils.h" #include "nsLocalFile.h" #include "nsNetCID.h" #include "nsPrintfCString.h" #include "nsTArray.h" #include "nsXULAppAPI.h" #include "prenv.h" #ifdef IsLoggingEnabled // This is defined in the Windows SDK urlmon.h # undef IsLoggingEnabled #endif namespace mozilla { namespace ipc { using namespace mozilla::fuzzing; /** * FuzzIntegralType mutates an incercepted integral type of a pickled message. */ template void FuzzIntegralType(T* v, bool largeValues) { static_assert(std::is_integral_v == true, "T must be an integral type"); switch (FuzzingTraits::Random(6)) { case 0: if (largeValues) { (*v) = RandomInteger(); break; } [[fallthrough]]; case 1: if (largeValues) { (*v) = RandomNumericLimit(); break; } [[fallthrough]]; case 2: if (largeValues) { (*v) = RandomIntegerRange(std::numeric_limits::min(), std::numeric_limits::max()); break; } [[fallthrough]]; default: switch (FuzzingTraits::Random(2)) { case 0: // Prevent underflow if (*v != std::numeric_limits::min()) { (*v)--; break; } [[fallthrough]]; case 1: // Prevent overflow if (*v != std::numeric_limits::max()) { (*v)++; break; } } } } /** * FuzzFloatingPointType mutates an incercepted floating-point type of a * pickled message. */ template void FuzzFloatingPointType(T* v, bool largeValues) { static_assert(std::is_floating_point_v == true, "T must be a floating point type"); switch (FuzzingTraits::Random(6)) { case 0: if (largeValues) { (*v) = RandomNumericLimit(); break; } [[fallthrough]]; case 1: if (largeValues) { (*v) = RandomFloatingPointRange(std::numeric_limits::min(), std::numeric_limits::max()); break; } [[fallthrough]]; default: (*v) = RandomFloatingPoint(); } } /** * FuzzStringType mutates an incercepted string type of a pickled message. */ template void FuzzStringType(T& v, const T& literal1, const T& literal2) { switch (FuzzingTraits::Random(5)) { case 4: v = v + v; [[fallthrough]]; case 3: v = v + v; [[fallthrough]]; case 2: v = v + v; break; case 1: v += literal1; break; case 0: v = literal2; break; } } Faulty::Faulty() // Mutate messages as a blob. : mFuzzMessages(!!PR_GetEnv("FAULTY_MESSAGES")) // Enables the strategy for fuzzing pipes. , mFuzzPipes(!!PR_GetEnv("FAULTY_PIPE")) // Enables the strategy for fuzzing pickled messages. , mFuzzPickle(!!PR_GetEnv("FAULTY_PICKLE")) // Uses very large values while fuzzing pickled messages. // This may cause a high amount of malloc_abort() / NS_ABORT_OOM crashes. , mUseLargeValues(!!PR_GetEnv("FAULTY_LARGE_VALUES")) // Use the provided blacklist as whitelist. , mUseAsWhitelist(!!PR_GetEnv("FAULTY_AS_WHITELIST")) // Sets up our target process. , mIsValidProcessType(IsValidProcessType()) { if (mIsValidProcessType) { FAULTY_LOG("Initializing for new process of type '%s' with pid %u.", XRE_GetProcessTypeString(), getpid()); /* Setup random seed. */ const char* userSeed = PR_GetEnv("FAULTY_SEED"); unsigned long randomSeed = static_cast(PR_IntervalNow()); if (userSeed) { long n = std::strtol(userSeed, nullptr, 10); if (n != 0) { randomSeed = static_cast(n); } } FuzzingTraits::Rng().seed(randomSeed); /* Setup directory for dumping messages. */ mMessagePath = PR_GetEnv("FAULTY_MESSAGE_PATH"); if (mMessagePath && *mMessagePath) { if (CreateOutputDirectory(mMessagePath) != NS_OK) { mMessagePath = nullptr; } } /* Set IPC messages blacklist. */ mBlacklistPath = PR_GetEnv("FAULTY_BLACKLIST"); if (mBlacklistPath && *mBlacklistPath) { FAULTY_LOG("* Using message blacklist = %s", mBlacklistPath); } FAULTY_LOG("* Fuzzing strategy: messages = %s", mFuzzMessages ? "enabled" : "disabled"); FAULTY_LOG("* Fuzzing strategy: pickle = %s", mFuzzPickle ? "enabled" : "disabled"); FAULTY_LOG("* Fuzzing strategy: pipe = %s", mFuzzPipes ? "enabled" : "disabled"); FAULTY_LOG("* Fuzzing probability = %u", DefaultProbability()); FAULTY_LOG("* Fuzzing mutation factor = %u", MutationFactor()); FAULTY_LOG("* RNG seed = %lu", randomSeed); sMsgCounter = 0; } } // static bool Faulty::IsValidProcessType(void) { bool isValidProcessType; const bool targetChildren = !!PR_GetEnv("FAULTY_CHILDREN"); const bool targetParent = !!PR_GetEnv("FAULTY_PARENT"); const bool isParent = XRE_IsParentProcess(); if (targetChildren && !targetParent) { // Fuzz every child process type but not the parent process. isValidProcessType = isParent; } else if (!targetChildren && targetParent && !isParent) { // Fuzz inside any of the above child process only. isValidProcessType = true; } else if (targetChildren && targetParent) { // Fuzz every process type. isValidProcessType = true; } else { // Fuzz no process type at all. isValidProcessType = false; } if (!isValidProcessType) { FAULTY_LOG("Disabled for this process of type '%s' with pid %d.", XRE_GetProcessTypeString(), getpid()); } return isValidProcessType; } // static unsigned int Faulty::DefaultProbability() { static std::once_flag flag; static unsigned probability; std::call_once(flag, [&] { probability = FAULTY_DEFAULT_PROBABILITY; // Defines the likelihood of fuzzing a message. if (const char* p = PR_GetEnv("FAULTY_PROBABILITY")) { long n = std::strtol(p, nullptr, 10); if (n != 0) { probability = n; } } }); return probability; } // static bool Faulty::IsLoggingEnabled(void) { static bool enabled; static std::once_flag flag; std::call_once(flag, [&] { enabled = !!PR_GetEnv("FAULTY_ENABLE_LOGGING"); }); return enabled; } // static uint32_t Faulty::MutationFactor() { static uint64_t sPropValue = FAULTY_DEFAULT_MUTATION_FACTOR; static bool sInitialized = false; if (sInitialized) { return sPropValue; } sInitialized = true; const char* factor = PR_GetEnv("FAULTY_MUTATION_FACTOR"); if (factor) { long n = strtol(factor, nullptr, 10); if (n != 0) { sPropValue = n; return sPropValue; } } return sPropValue; } // static Faulty& Faulty::instance() { static Faulty faulty; return faulty; } // // Strategy: Pipes // void Faulty::MaybeCollectAndClosePipe(int aPipe, unsigned int aProbability) { #ifndef _WINDOWS if (!mFuzzPipes) { return; } if (aPipe > -1) { FAULTY_LOG("Collecting pipe %d to bucket of pipes (count: %zu)", aPipe, mFds.size()); mFds.insert(aPipe); } if (mFds.size() > 0 && FuzzingTraits::Sometimes(aProbability)) { std::set::iterator it(mFds.begin()); std::advance(it, FuzzingTraits::Random(mFds.size())); FAULTY_LOG("Trying to close collected pipe: %d", *it); errno = 0; while ((close(*it) == -1 && (errno == EINTR))) { ; } FAULTY_LOG("Pipe status after attempt to close: %d", errno); mFds.erase(it); } #endif } // // Strategy: Pickle // void Faulty::MutateBool(bool* aValue) { *aValue = !(*aValue); } void Faulty::FuzzBool(bool* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { bool oldValue = *aValue; MutateBool(aValue); FAULTY_LOG("Message field |bool| of value: %d mutated to: %d", (int)oldValue, (int)*aValue); } } } void Faulty::MutateChar(char* aValue) { FuzzIntegralType(aValue, true); } void Faulty::FuzzChar(char* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { char oldValue = *aValue; MutateChar(aValue); FAULTY_LOG("Message field |char| of value: %c mutated to: %c", oldValue, *aValue); } } } void Faulty::MutateUChar(unsigned char* aValue) { FuzzIntegralType(aValue, true); } void Faulty::FuzzUChar(unsigned char* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { unsigned char oldValue = *aValue; MutateUChar(aValue); FAULTY_LOG("Message field |unsigned char| of value: %u mutated to: %u", oldValue, *aValue); } } } void Faulty::MutateInt16(int16_t* aValue) { FuzzIntegralType(aValue, true); } void Faulty::FuzzInt16(int16_t* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { int16_t oldValue = *aValue; MutateInt16(aValue); FAULTY_LOG("Message field |int16| of value: %d mutated to: %d", oldValue, *aValue); } } } void Faulty::MutateUInt16(uint16_t* aValue) { FuzzIntegralType(aValue, true); } void Faulty::FuzzUInt16(uint16_t* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { uint16_t oldValue = *aValue; MutateUInt16(aValue); FAULTY_LOG("Message field |uint16| of value: %d mutated to: %d", oldValue, *aValue); } } } void Faulty::MutateInt(int* aValue) { FuzzIntegralType(aValue, mUseLargeValues); } void Faulty::FuzzInt(int* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { int oldValue = *aValue; MutateInt(aValue); FAULTY_LOG("Message field |int| of value: %d mutated to: %d", oldValue, *aValue); } } } void Faulty::MutateUInt32(uint32_t* aValue) { FuzzIntegralType(aValue, mUseLargeValues); } void Faulty::FuzzUInt32(uint32_t* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { uint32_t oldValue = *aValue; MutateUInt32(aValue); FAULTY_LOG("Message field |uint32| of value: %u mutated to: %u", oldValue, *aValue); } } } void Faulty::MutateLong(long* aValue) { FuzzIntegralType(aValue, mUseLargeValues); } void Faulty::FuzzLong(long* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { long oldValue = *aValue; MutateLong(aValue); FAULTY_LOG("Message field |long| of value: %ld mutated to: %ld", oldValue, *aValue); } } } void Faulty::MutateULong(unsigned long* aValue) { FuzzIntegralType(aValue, mUseLargeValues); } void Faulty::FuzzULong(unsigned long* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { unsigned long oldValue = *aValue; MutateULong(aValue); FAULTY_LOG("Message field |unsigned long| of value: %lu mutated to: %lu", oldValue, *aValue); } } } void Faulty::MutateUInt64(uint64_t* aValue) { FuzzIntegralType(aValue, mUseLargeValues); } void Faulty::FuzzUInt64(uint64_t* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { uint64_t oldValue = *aValue; MutateUInt64(aValue); FAULTY_LOG("Message field |uint64| of value: %" PRIu64 " mutated to: %" PRIu64, oldValue, *aValue); } } } void Faulty::MutateInt64(int64_t* aValue) { FuzzIntegralType(aValue, mUseLargeValues); } void Faulty::FuzzInt64(int64_t* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { int64_t oldValue = *aValue; MutateInt64(aValue); FAULTY_LOG("Message field |int64| of value: %" PRIu64 " mutated to: %" PRIu64, oldValue, *aValue); } } } void Faulty::MutateDouble(double* aValue) { FuzzFloatingPointType(aValue, mUseLargeValues); } void Faulty::FuzzDouble(double* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { double oldValue = *aValue; MutateDouble(aValue); FAULTY_LOG("Message field |double| of value: %f mutated to: %f", oldValue, *aValue); } } } void Faulty::MutateFloat(float* aValue) { FuzzFloatingPointType(aValue, mUseLargeValues); } void Faulty::FuzzFloat(float* aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { float oldValue = *aValue; MutateFloat(aValue); FAULTY_LOG("Message field |float| of value: %f mutated to: %f", oldValue, *aValue); } } } void Faulty::FuzzString(std::string& aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { std::string oldValue = aValue; FuzzStringType(aValue, "xoferiF", std::string()); FAULTY_LOG("Message field |string| of value: %s mutated to: %s", oldValue.c_str(), aValue.c_str()); } } } void Faulty::FuzzWString(std::wstring& aValue, unsigned int aProbability) { if (mIsValidProcessType) { if (mFuzzPickle && FuzzingTraits::Sometimes(aProbability)) { std::wstring oldValue = aValue; FAULTY_LOG("Message field |wstring|"); FuzzStringType(aValue, L"xoferiF", std::wstring()); } } } // static nsresult Faulty::CreateOutputDirectory(const char* aPathname) { nsCOMPtr path; bool exists; nsresult rv; rv = NS_NewNativeLocalFile(nsDependentCString(aPathname), true, getter_AddRefs(path)); rv = path->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } /* static */ nsresult Faulty::ReadFile(const char* aPathname, nsTArray& aArray) { nsresult rv; nsCOMPtr file; rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(aPathname), true, getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists = false; rv = file->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv)) || !exists) { return rv; } nsCOMPtr fileStream( do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = fileStream->Init(file, -1, -1, 0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr lineStream(do_QueryInterface(fileStream, &rv)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoCString line; bool more = true; do { rv = lineStream->ReadLine(line, &more); if (line.IsEmpty()) { continue; } if (line.CharAt(0) == '#') { /* Ignore comments. */ continue; } aArray.AppendElement(line); } while (more); return NS_OK; } bool Faulty::IsMessageNameBlacklisted(const char* aMessageName) { static bool sFileLoaded = false; static nsTArray sMessageBlacklist; if (!sFileLoaded && mBlacklistPath) { /* Run ReadFile() on the main thread to prevent MOZ_ASSERT(NS_IsMainThread()) in nsStandardURL via nsNetStartup(). */ nsCOMPtr r = NS_NewRunnableFunction("Fuzzer::ReadBlacklistOnMainThread", [&]() { if (Faulty::ReadFile(mBlacklistPath, sMessageBlacklist) != NS_OK) { sFileLoaded = false; } else { sFileLoaded = true; } }); NS_DispatchToMainThread(r.forget(), NS_DISPATCH_SYNC); } if (!sFileLoaded) { return false; } if (sMessageBlacklist.Length() == 0) { return false; } return sMessageBlacklist.Contains(aMessageName); } // static std::vector Faulty::GetDataFromIPCMessage(IPC::Message* aMsg) { const Pickle::BufferList& buffers = aMsg->Buffers(); std::vector data; data.reserve(buffers.Size()); Pickle::BufferList::IterImpl i = buffers.Iter(); while (!i.Done()) { size_t s = i.RemainingInSegment(); data.insert(data.end(), i.Data(), i.Data() + s); i.Advance(buffers, s); } return data; } // static void Faulty::CopyFDs(IPC::Message* aDstMsg, IPC::Message* aSrcMsg) { #ifndef _WINDOWS FileDescriptorSet* dstFdSet = aDstMsg->file_descriptor_set(); FileDescriptorSet* srcFdSet = aSrcMsg->file_descriptor_set(); for (size_t i = 0; i < srcFdSet->size(); i++) { int fd = srcFdSet->GetDescriptorAt(i); dstFdSet->Add(fd); } #endif } UniquePtr Faulty::MutateIPCMessage(const char* aChannel, UniquePtr aMsg, unsigned int aProbability) { if (!mIsValidProcessType || !mFuzzMessages) { return aMsg; } sMsgCounter += 1; LogMessage(aChannel, aMsg.get()); /* Skip immediately if we shall not try to fuzz this message. */ if (!FuzzingTraits::Sometimes(aProbability)) { return aMsg; } const bool isMessageListed = IsMessageNameBlacklisted(aMsg->name()); /* Check if this message is blacklisted and shall not get fuzzed. */ if (isMessageListed && !mUseAsWhitelist) { FAULTY_LOG("BLACKLISTED: %s", aMsg->name()); return aMsg; } /* Check if the message is whitelisted. */ if (!isMessageListed && mUseAsWhitelist) { /* Silently skip this message. */ return aMsg; } /* Retrieve BufferLists as data from original message. */ std::vector data(GetDataFromIPCMessage(aMsg.get())); /* Check if there is enough data in the message to fuzz. */ uint32_t headerSize = aMsg->HeaderSizeFromData(nullptr, nullptr); if (headerSize == data.size()) { FAULTY_LOG("IGNORING: %s", aMsg->name()); return aMsg; } /* Mutate the message data. */ size_t maxMutations = FuzzingTraits::Frequency(data.size(), MutationFactor()); FAULTY_LOG("FUZZING (%zu bytes): %s", maxMutations, aMsg->name()); while (maxMutations--) { /* Ignore the header data of the message. */ uint32_t pos = RandomIntegerRange(headerSize, data.size() - 1); switch (FuzzingTraits::Random(6)) { case 0: break; case 1: data.at(pos) = RandomIntegerRange(0, 1); break; case 2: data.at(pos) ^= (1 << FuzzingTraits::Random(9)); break; case 3: data.at(pos) = RandomIntegerRange(254, 255); break; default: data.at(pos) = RandomIntegerRange(0, 255); } } /* Build new message. */ auto mutatedMsg = MakeUnique( reinterpret_cast(data.data()), data.size()); CopyFDs(mutatedMsg.get(), aMsg.get()); /* Dump original message for diff purposes. */ DumpMessage(aChannel, aMsg.get(), nsPrintfCString(".%zu.o", sMsgCounter).get()); /* Dump mutated message for diff purposes. */ DumpMessage(aChannel, mutatedMsg.get(), nsPrintfCString(".%zu.m", sMsgCounter).get()); return mutatedMsg; } void Faulty::LogMessage(const char* aChannel, IPC::Message* aMsg) { if (!mIsValidProcessType) { return; } std::string fileName = nsPrintfCString("message.%u.%zu", getpid(), sMsgCounter).get(); FAULTY_LOG("Process: %u | Size: %10zu | %-20s | %s => %s", XRE_GetProcessType(), aMsg->Buffers().Size(), fileName.c_str(), aChannel, aMsg->name()); } void Faulty::DumpMessage(const char* aChannel, IPC::Message* aMsg, std::string aAppendix) { if (!mIsValidProcessType || !mMessagePath) { return; } std::vector data(GetDataFromIPCMessage(aMsg)); std::string fileName; if (!aAppendix.empty()) { fileName = nsPrintfCString("%s/message.%u%s", mMessagePath, getpid(), aAppendix.c_str()) .get(); } else { fileName = nsPrintfCString("%s/%s", mMessagePath, fileName.c_str()).get(); } std::fstream fp; fp.open(fileName, std::fstream::out | std::fstream::binary); fp.write(reinterpret_cast(data.data()), data.size()); fp.close(); } } // namespace ipc } // namespace mozilla