1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <climits>
8 #include <cmath>
9 #include "FuzzingTraits.h"
10 #include "jsapi.h"
11 #include "jsfriendapi.h"
12 #include "js/CharacterEncoding.h"
13 #include "prenv.h"
14 #include "MessageManagerFuzzer.h"
15 #include "mozilla/ErrorResult.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsDebug.h"
18 #include "nsError.h"
19 #include "nsFrameMessageManager.h"
20 #include "nsJSUtils.h"
21 #include "nsXULAppAPI.h"
22 #include "nsNetCID.h"
23 #include "nsString.h"
24 #include "nsUnicharUtils.h"
25 #include "nsIFile.h"
26 #include "nsIFileStreams.h"
27 #include "nsILineInputStream.h"
28 #include "nsLocalFile.h"
29 #include "nsTArray.h"
30 
31 #ifdef IsLoggingEnabled
32 // This is defined in the Windows SDK urlmon.h
33 #  undef IsLoggingEnabled
34 #endif
35 
36 #define MESSAGEMANAGER_FUZZER_DEFAULT_MUTATION_PROBABILITY 2
37 #define MSGMGR_FUZZER_LOG(fmt, args...)                        \
38   if (MessageManagerFuzzer::IsLoggingEnabled()) {              \
39     printf_stderr("[MessageManagerFuzzer] " fmt "\n", ##args); \
40   }
41 
42 namespace mozilla {
43 namespace dom {
44 
45 using namespace fuzzing;
46 using namespace ipc;
47 
48 /* static */
ReadFile(const char * path,nsTArray<nsCString> & aArray)49 void MessageManagerFuzzer::ReadFile(const char* path,
50                                     nsTArray<nsCString>& aArray) {
51   nsCOMPtr<nsIFile> file;
52   nsresult rv =
53       NS_NewLocalFile(NS_ConvertUTF8toUTF16(path), true, getter_AddRefs(file));
54   NS_ENSURE_SUCCESS_VOID(rv);
55 
56   bool exists = false;
57   rv = file->Exists(&exists);
58   if (NS_FAILED(rv) || !exists) {
59     return;
60   }
61 
62   nsCOMPtr<nsIFileInputStream> fileStream(
63       do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
64   NS_ENSURE_SUCCESS_VOID(rv);
65 
66   rv = fileStream->Init(file, -1, -1, false);
67   NS_ENSURE_SUCCESS_VOID(rv);
68 
69   nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
70   NS_ENSURE_SUCCESS_VOID(rv);
71 
72   nsAutoCString line;
73   bool more = true;
74   do {
75     rv = lineStream->ReadLine(line, &more);
76     NS_ENSURE_SUCCESS_VOID(rv);
77     aArray.AppendElement(line);
78   } while (more);
79 }
80 
81 /* static */
IsMessageNameBlacklisted(const nsAString & aMessageName)82 bool MessageManagerFuzzer::IsMessageNameBlacklisted(
83     const nsAString& aMessageName) {
84   static bool sFileLoaded = false;
85   static nsTArray<nsCString> valuesInFile;
86 
87   if (!sFileLoaded) {
88     ReadFile(PR_GetEnv("MESSAGEMANAGER_FUZZER_BLACKLIST"), valuesInFile);
89     sFileLoaded = true;
90   }
91 
92   if (valuesInFile.Length() == 0) {
93     return false;
94   }
95 
96   return valuesInFile.Contains(NS_ConvertUTF16toUTF8(aMessageName).get());
97 }
98 
99 /* static */
GetFuzzValueFromFile()100 nsCString MessageManagerFuzzer::GetFuzzValueFromFile() {
101   static bool sFileLoaded = false;
102   static nsTArray<nsCString> valuesInFile;
103 
104   if (!sFileLoaded) {
105     ReadFile(PR_GetEnv("MESSAGEMANAGER_FUZZER_STRINGSFILE"), valuesInFile);
106     sFileLoaded = true;
107   }
108 
109   // If something goes wrong with importing the file we return an empty string.
110   if (valuesInFile.Length() == 0) {
111     return nsCString();
112   }
113 
114   unsigned randIdx = RandomIntegerRange<unsigned>(0, valuesInFile.Length());
115   return valuesInFile.ElementAt(randIdx);
116 }
117 
118 /* static */
MutateObject(JSContext * aCx,JS::HandleValue aValue,unsigned short int aRecursionCounter)119 void MessageManagerFuzzer::MutateObject(JSContext* aCx, JS::HandleValue aValue,
120                                         unsigned short int aRecursionCounter) {
121   JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
122   JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
123 
124   if (!JS_Enumerate(aCx, object, &ids)) {
125     return;
126   }
127 
128   for (size_t i = 0, n = ids.length(); i < n; i++) {
129     // Retrieve Property name.
130     nsAutoJSString propName;
131     if (!propName.init(aCx, ids[i])) {
132       continue;
133     }
134     MSGMGR_FUZZER_LOG("%*s- Property: %s", aRecursionCounter * 4, "",
135                       NS_ConvertUTF16toUTF8(propName).get());
136 
137     // The likelihood when a value gets fuzzed of this object.
138     if (!FuzzingTraits::Sometimes(DefaultMutationProbability())) {
139       continue;
140     }
141 
142     // Retrieve Property value.
143     JS::RootedValue propertyValue(aCx);
144     JS_GetPropertyById(aCx, object, ids[i], &propertyValue);
145 
146     JS::RootedValue newPropValue(aCx);
147     MutateValue(aCx, propertyValue, &newPropValue, aRecursionCounter);
148 
149     JS_SetPropertyById(aCx, object, ids[i], newPropValue);
150   }
151 }
152 
153 /* static */
MutateValue(JSContext * aCx,JS::HandleValue aValue,JS::MutableHandleValue aOutMutationValue,unsigned short int aRecursionCounter)154 bool MessageManagerFuzzer::MutateValue(JSContext* aCx, JS::HandleValue aValue,
155                                        JS::MutableHandleValue aOutMutationValue,
156                                        unsigned short int aRecursionCounter) {
157   if (aValue.isInt32()) {
158     if (FuzzingTraits::Sometimes(DefaultMutationProbability() * 2)) {
159       aOutMutationValue.set(JS::Int32Value(RandomNumericLimit<int>()));
160     } else {
161       aOutMutationValue.set(JS::Int32Value(RandomInteger<int>()));
162     }
163     MSGMGR_FUZZER_LOG("%*s! Mutated value of type |int32|: '%d' to '%d'",
164                       aRecursionCounter * 4, "", aValue.toInt32(),
165                       aOutMutationValue.toInt32());
166     return true;
167   }
168 
169   if (aValue.isDouble()) {
170     aOutMutationValue.set(JS::DoubleValue(RandomFloatingPoint<double>()));
171     MSGMGR_FUZZER_LOG("%*s! Mutated value of type |double|: '%f' to '%f'",
172                       aRecursionCounter * 4, "", aValue.toDouble(),
173                       aOutMutationValue.toDouble());
174     return true;
175   }
176 
177   if (aValue.isBoolean()) {
178     aOutMutationValue.set(JS::BooleanValue(bool(RandomIntegerRange(0, 2))));
179     MSGMGR_FUZZER_LOG("%*s! Mutated value of type |boolean|: '%d' to '%d'",
180                       aRecursionCounter * 4, "", aValue.toBoolean(),
181                       aOutMutationValue.toBoolean());
182     return true;
183   }
184 
185   if (aValue.isString()) {
186     nsCString x = GetFuzzValueFromFile();
187     if (x.IsEmpty()) {
188       return false;
189     }
190     JSString* str = JS_NewStringCopyZ(aCx, x.get());
191     aOutMutationValue.set(JS::StringValue(str));
192     JS::RootedString rootedValue(aCx, aValue.toString());
193     JS::UniqueChars valueChars = JS_EncodeStringToUTF8(aCx, rootedValue);
194     MSGMGR_FUZZER_LOG("%*s! Mutated value of type |string|: '%s' to '%s'",
195                       aRecursionCounter * 4, "", valueChars.get(), x.get());
196     return true;
197   }
198 
199   if (aValue.isObject()) {
200     aRecursionCounter++;
201     MSGMGR_FUZZER_LOG("%*s<Enumerating found object>", aRecursionCounter * 4,
202                       "");
203     MutateObject(aCx, aValue, aRecursionCounter);
204     aOutMutationValue.set(aValue);
205     return true;
206   }
207 
208   return false;
209 }
210 
211 /* static */
Mutate(JSContext * aCx,const nsAString & aMessageName,ipc::StructuredCloneData * aData,const JS::Value & aTransfer)212 bool MessageManagerFuzzer::Mutate(JSContext* aCx, const nsAString& aMessageName,
213                                   ipc::StructuredCloneData* aData,
214                                   const JS::Value& aTransfer) {
215   MSGMGR_FUZZER_LOG("Message: %s in process: %d",
216                     NS_ConvertUTF16toUTF8(aMessageName).get(),
217                     XRE_GetProcessType());
218 
219   unsigned short int aRecursionCounter = 0;
220   ErrorResult rv;
221   JS::RootedValue t(aCx, aTransfer);
222 
223   /* Read original StructuredCloneData. */
224   JS::RootedValue scdContent(aCx);
225   aData->Read(aCx, &scdContent, rv);
226   if (NS_WARN_IF(rv.Failed())) {
227     rv.SuppressException();
228     JS_ClearPendingException(aCx);
229     return false;
230   }
231 
232   JS::RootedValue scdMutationContent(aCx);
233   bool isMutated =
234       MutateValue(aCx, scdContent, &scdMutationContent, aRecursionCounter);
235 
236   /* Write mutated StructuredCloneData. */
237   ipc::StructuredCloneData mutatedStructuredCloneData;
238   mutatedStructuredCloneData.Write(aCx, scdMutationContent, t,
239                                    JS::CloneDataPolicy(), rv);
240   if (NS_WARN_IF(rv.Failed())) {
241     rv.SuppressException();
242     JS_ClearPendingException(aCx);
243     return false;
244   }
245 
246   // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1346040
247   aData->Copy(mutatedStructuredCloneData);
248 
249   /* Mutated and successfully written to StructuredCloneData object. */
250   if (isMutated) {
251     JS::RootedString str(aCx, JS_ValueToSource(aCx, scdMutationContent));
252     JS::UniqueChars strChars = JS_EncodeStringToUTF8(aCx, str);
253     MSGMGR_FUZZER_LOG("Mutated '%s' Message: %s",
254                       NS_ConvertUTF16toUTF8(aMessageName).get(),
255                       strChars.get());
256   }
257 
258   return true;
259 }
260 
261 /* static */
DefaultMutationProbability()262 unsigned int MessageManagerFuzzer::DefaultMutationProbability() {
263   static unsigned long sPropValue =
264       MESSAGEMANAGER_FUZZER_DEFAULT_MUTATION_PROBABILITY;
265   static bool sInitialized = false;
266 
267   if (sInitialized) {
268     return sPropValue;
269   }
270   sInitialized = true;
271 
272   // Defines the likelihood of fuzzing a message.
273   const char* probability =
274       PR_GetEnv("MESSAGEMANAGER_FUZZER_MUTATION_PROBABILITY");
275   if (probability) {
276     long n = std::strtol(probability, nullptr, 10);
277     if (n != 0) {
278       sPropValue = n;
279       return sPropValue;
280     }
281   }
282 
283   return sPropValue;
284 }
285 
286 /* static */
IsLoggingEnabled()287 bool MessageManagerFuzzer::IsLoggingEnabled() {
288   static bool sInitialized = false;
289   static bool sIsLoggingEnabled = false;
290 
291   if (!sInitialized) {
292     sIsLoggingEnabled = !!PR_GetEnv("MESSAGEMANAGER_FUZZER_ENABLE_LOGGING");
293     sInitialized = true;
294   }
295 
296   return sIsLoggingEnabled;
297 }
298 
299 /* static */
IsEnabled()300 bool MessageManagerFuzzer::IsEnabled() {
301   return !!PR_GetEnv("MESSAGEMANAGER_FUZZER_ENABLE") && XRE_IsContentProcess();
302 }
303 
304 /* static */
TryMutate(JSContext * aCx,const nsAString & aMessageName,ipc::StructuredCloneData * aData,const JS::Value & aTransfer)305 void MessageManagerFuzzer::TryMutate(JSContext* aCx,
306                                      const nsAString& aMessageName,
307                                      ipc::StructuredCloneData* aData,
308                                      const JS::Value& aTransfer) {
309   if (!IsEnabled()) {
310     return;
311   }
312 
313   if (IsMessageNameBlacklisted(aMessageName)) {
314     MSGMGR_FUZZER_LOG("Blacklisted message: %s",
315                       NS_ConvertUTF16toUTF8(aMessageName).get());
316     return;
317   }
318 
319   Mutate(aCx, aMessageName, aData, aTransfer);
320 }
321 
322 }  // namespace dom
323 }  // namespace mozilla
324