diff options
Diffstat (limited to 'tools/fuzzing/messagemanager/MessageManagerFuzzer.cpp')
-rw-r--r-- | tools/fuzzing/messagemanager/MessageManagerFuzzer.cpp | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/tools/fuzzing/messagemanager/MessageManagerFuzzer.cpp b/tools/fuzzing/messagemanager/MessageManagerFuzzer.cpp new file mode 100644 index 0000000000..bc9ac6b6fb --- /dev/null +++ b/tools/fuzzing/messagemanager/MessageManagerFuzzer.cpp @@ -0,0 +1,323 @@ +/* -*- 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 <climits> +#include <cmath> +#include "FuzzingTraits.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/CharacterEncoding.h" +#include "prenv.h" +#include "MessageManagerFuzzer.h" +#include "mozilla/ErrorResult.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsFrameMessageManager.h" +#include "nsJSUtils.h" +#include "nsXULAppAPI.h" +#include "nsNetCID.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "nsIFile.h" +#include "nsIFileStreams.h" +#include "nsILineInputStream.h" +#include "nsLocalFile.h" +#include "nsTArray.h" + +#ifdef IsLoggingEnabled +// This is defined in the Windows SDK urlmon.h +# undef IsLoggingEnabled +#endif + +#define MESSAGEMANAGER_FUZZER_DEFAULT_MUTATION_PROBABILITY 2 +#define MSGMGR_FUZZER_LOG(fmt, args...) \ + if (MessageManagerFuzzer::IsLoggingEnabled()) { \ + printf_stderr("[MessageManagerFuzzer] " fmt "\n", ##args); \ + } + +namespace mozilla { +namespace dom { + +using namespace fuzzing; +using namespace ipc; + +/* static */ +void MessageManagerFuzzer::ReadFile(const char* path, + nsTArray<nsCString>& aArray) { + nsCOMPtr<nsIFile> file; + nsresult rv = + NS_NewLocalFile(NS_ConvertUTF8toUTF16(path), true, getter_AddRefs(file)); + NS_ENSURE_SUCCESS_VOID(rv); + + bool exists = false; + rv = file->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + return; + } + + nsCOMPtr<nsIFileInputStream> fileStream( + do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = fileStream->Init(file, -1, -1, false); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoCString line; + bool more = true; + do { + rv = lineStream->ReadLine(line, &more); + NS_ENSURE_SUCCESS_VOID(rv); + aArray.AppendElement(line); + } while (more); +} + +/* static */ +bool MessageManagerFuzzer::IsMessageNameBlacklisted( + const nsAString& aMessageName) { + static bool sFileLoaded = false; + static nsTArray<nsCString> valuesInFile; + + if (!sFileLoaded) { + ReadFile(PR_GetEnv("MESSAGEMANAGER_FUZZER_BLACKLIST"), valuesInFile); + sFileLoaded = true; + } + + if (valuesInFile.Length() == 0) { + return false; + } + + return valuesInFile.Contains(NS_ConvertUTF16toUTF8(aMessageName).get()); +} + +/* static */ +nsCString MessageManagerFuzzer::GetFuzzValueFromFile() { + static bool sFileLoaded = false; + static nsTArray<nsCString> valuesInFile; + + if (!sFileLoaded) { + ReadFile(PR_GetEnv("MESSAGEMANAGER_FUZZER_STRINGSFILE"), valuesInFile); + sFileLoaded = true; + } + + // If something goes wrong with importing the file we return an empty string. + if (valuesInFile.Length() == 0) { + return nsCString(); + } + + unsigned randIdx = RandomIntegerRange<unsigned>(0, valuesInFile.Length()); + return valuesInFile.ElementAt(randIdx); +} + +/* static */ +void MessageManagerFuzzer::MutateObject(JSContext* aCx, JS::HandleValue aValue, + unsigned short int aRecursionCounter) { + JS::Rooted<JSObject*> object(aCx, &aValue.toObject()); + JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx)); + + if (!JS_Enumerate(aCx, object, &ids)) { + return; + } + + for (size_t i = 0, n = ids.length(); i < n; i++) { + // Retrieve Property name. + nsAutoJSString propName; + if (!propName.init(aCx, ids[i])) { + continue; + } + MSGMGR_FUZZER_LOG("%*s- Property: %s", aRecursionCounter * 4, "", + NS_ConvertUTF16toUTF8(propName).get()); + + // The likelihood when a value gets fuzzed of this object. + if (!FuzzingTraits::Sometimes(DefaultMutationProbability())) { + continue; + } + + // Retrieve Property value. + JS::RootedValue propertyValue(aCx); + JS_GetPropertyById(aCx, object, ids[i], &propertyValue); + + JS::RootedValue newPropValue(aCx); + MutateValue(aCx, propertyValue, &newPropValue, aRecursionCounter); + + JS_SetPropertyById(aCx, object, ids[i], newPropValue); + } +} + +/* static */ +bool MessageManagerFuzzer::MutateValue(JSContext* aCx, JS::HandleValue aValue, + JS::MutableHandleValue aOutMutationValue, + unsigned short int aRecursionCounter) { + if (aValue.isInt32()) { + if (FuzzingTraits::Sometimes(DefaultMutationProbability() * 2)) { + aOutMutationValue.set(JS::Int32Value(RandomNumericLimit<int>())); + } else { + aOutMutationValue.set(JS::Int32Value(RandomInteger<int>())); + } + MSGMGR_FUZZER_LOG("%*s! Mutated value of type |int32|: '%d' to '%d'", + aRecursionCounter * 4, "", aValue.toInt32(), + aOutMutationValue.toInt32()); + return true; + } + + if (aValue.isDouble()) { + aOutMutationValue.set(JS::DoubleValue(RandomFloatingPoint<double>())); + MSGMGR_FUZZER_LOG("%*s! Mutated value of type |double|: '%f' to '%f'", + aRecursionCounter * 4, "", aValue.toDouble(), + aOutMutationValue.toDouble()); + return true; + } + + if (aValue.isBoolean()) { + aOutMutationValue.set(JS::BooleanValue(bool(RandomIntegerRange(0, 2)))); + MSGMGR_FUZZER_LOG("%*s! Mutated value of type |boolean|: '%d' to '%d'", + aRecursionCounter * 4, "", aValue.toBoolean(), + aOutMutationValue.toBoolean()); + return true; + } + + if (aValue.isString()) { + nsCString x = GetFuzzValueFromFile(); + if (x.IsEmpty()) { + return false; + } + JSString* str = JS_NewStringCopyZ(aCx, x.get()); + aOutMutationValue.set(JS::StringValue(str)); + JS::RootedString rootedValue(aCx, aValue.toString()); + JS::UniqueChars valueChars = JS_EncodeStringToUTF8(aCx, rootedValue); + MSGMGR_FUZZER_LOG("%*s! Mutated value of type |string|: '%s' to '%s'", + aRecursionCounter * 4, "", valueChars.get(), x.get()); + return true; + } + + if (aValue.isObject()) { + aRecursionCounter++; + MSGMGR_FUZZER_LOG("%*s<Enumerating found object>", aRecursionCounter * 4, + ""); + MutateObject(aCx, aValue, aRecursionCounter); + aOutMutationValue.set(aValue); + return true; + } + + return false; +} + +/* static */ +bool MessageManagerFuzzer::Mutate(JSContext* aCx, const nsAString& aMessageName, + ipc::StructuredCloneData* aData, + const JS::Value& aTransfer) { + MSGMGR_FUZZER_LOG("Message: %s in process: %d", + NS_ConvertUTF16toUTF8(aMessageName).get(), + XRE_GetProcessType()); + + unsigned short int aRecursionCounter = 0; + ErrorResult rv; + JS::RootedValue t(aCx, aTransfer); + + /* Read original StructuredCloneData. */ + JS::RootedValue scdContent(aCx); + aData->Read(aCx, &scdContent, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + JS_ClearPendingException(aCx); + return false; + } + + JS::RootedValue scdMutationContent(aCx); + bool isMutated = + MutateValue(aCx, scdContent, &scdMutationContent, aRecursionCounter); + + /* Write mutated StructuredCloneData. */ + ipc::StructuredCloneData mutatedStructuredCloneData; + mutatedStructuredCloneData.Write(aCx, scdMutationContent, t, + JS::CloneDataPolicy(), rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + JS_ClearPendingException(aCx); + return false; + } + + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1346040 + aData->Copy(mutatedStructuredCloneData); + + /* Mutated and successfully written to StructuredCloneData object. */ + if (isMutated) { + JS::RootedString str(aCx, JS_ValueToSource(aCx, scdMutationContent)); + JS::UniqueChars strChars = JS_EncodeStringToUTF8(aCx, str); + MSGMGR_FUZZER_LOG("Mutated '%s' Message: %s", + NS_ConvertUTF16toUTF8(aMessageName).get(), + strChars.get()); + } + + return true; +} + +/* static */ +unsigned int MessageManagerFuzzer::DefaultMutationProbability() { + static unsigned long sPropValue = + MESSAGEMANAGER_FUZZER_DEFAULT_MUTATION_PROBABILITY; + static bool sInitialized = false; + + if (sInitialized) { + return sPropValue; + } + sInitialized = true; + + // Defines the likelihood of fuzzing a message. + const char* probability = + PR_GetEnv("MESSAGEMANAGER_FUZZER_MUTATION_PROBABILITY"); + if (probability) { + long n = std::strtol(probability, nullptr, 10); + if (n != 0) { + sPropValue = n; + return sPropValue; + } + } + + return sPropValue; +} + +/* static */ +bool MessageManagerFuzzer::IsLoggingEnabled() { + static bool sInitialized = false; + static bool sIsLoggingEnabled = false; + + if (!sInitialized) { + sIsLoggingEnabled = !!PR_GetEnv("MESSAGEMANAGER_FUZZER_ENABLE_LOGGING"); + sInitialized = true; + } + + return sIsLoggingEnabled; +} + +/* static */ +bool MessageManagerFuzzer::IsEnabled() { + return !!PR_GetEnv("MESSAGEMANAGER_FUZZER_ENABLE") && XRE_IsContentProcess(); +} + +/* static */ +void MessageManagerFuzzer::TryMutate(JSContext* aCx, + const nsAString& aMessageName, + ipc::StructuredCloneData* aData, + const JS::Value& aTransfer) { + if (!IsEnabled()) { + return; + } + + if (IsMessageNameBlacklisted(aMessageName)) { + MSGMGR_FUZZER_LOG("Blacklisted message: %s", + NS_ConvertUTF16toUTF8(aMessageName).get()); + return; + } + + Mutate(aCx, aMessageName, aData, aTransfer); +} + +} // namespace dom +} // namespace mozilla |