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