1 | //===- FuzzerLoop.cpp - Fuzzer's main loop --------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // Fuzzer's main loop. |
9 | //===----------------------------------------------------------------------===// |
10 | |
11 | #include "FuzzerCorpus.h" |
12 | #include "FuzzerIO.h" |
13 | #include "FuzzerInternal.h" |
14 | #include "FuzzerMutate.h" |
15 | #include "FuzzerPlatform.h" |
16 | #include "FuzzerRandom.h" |
17 | #include "FuzzerTracePC.h" |
18 | #include <algorithm> |
19 | #include <cstring> |
20 | #include <memory> |
21 | #include <mutex> |
22 | #include <set> |
23 | |
24 | #if defined(__has_include) |
25 | #if __has_include(<sanitizer / lsan_interface.h>) |
26 | #include <sanitizer/lsan_interface.h> |
27 | #endif |
28 | #endif |
29 | |
30 | #define NO_SANITIZE_MEMORY |
31 | #if defined(__has_feature) |
32 | #if __has_feature(memory_sanitizer) |
33 | #undef NO_SANITIZE_MEMORY |
34 | #define NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) |
35 | #endif |
36 | #endif |
37 | |
38 | namespace fuzzer { |
39 | static const size_t kMaxUnitSizeToPrint = 256; |
40 | |
41 | thread_local bool Fuzzer::IsMyThread; |
42 | |
43 | bool RunningUserCallback = false; |
44 | |
45 | // Only one Fuzzer per process. |
46 | static Fuzzer *F; |
47 | |
48 | // Leak detection is expensive, so we first check if there were more mallocs |
49 | // than frees (using the sanitizer malloc hooks) and only then try to call lsan. |
50 | struct MallocFreeTracer { |
51 | void Start(int TraceLevel) { |
52 | this->TraceLevel = TraceLevel; |
53 | if (TraceLevel) |
54 | Printf(Fmt: "MallocFreeTracer: START\n" ); |
55 | Mallocs = 0; |
56 | Frees = 0; |
57 | } |
58 | // Returns true if there were more mallocs than frees. |
59 | bool Stop() { |
60 | if (TraceLevel) |
61 | Printf(Fmt: "MallocFreeTracer: STOP %zd %zd (%s)\n" , Mallocs.load(), |
62 | Frees.load(), Mallocs == Frees ? "same" : "DIFFERENT" ); |
63 | bool Result = Mallocs > Frees; |
64 | Mallocs = 0; |
65 | Frees = 0; |
66 | TraceLevel = 0; |
67 | return Result; |
68 | } |
69 | std::atomic<size_t> Mallocs; |
70 | std::atomic<size_t> Frees; |
71 | int TraceLevel = 0; |
72 | |
73 | std::recursive_mutex TraceMutex; |
74 | bool TraceDisabled = false; |
75 | }; |
76 | |
77 | static MallocFreeTracer AllocTracer; |
78 | |
79 | // Locks printing and avoids nested hooks triggered from mallocs/frees in |
80 | // sanitizer. |
81 | class TraceLock { |
82 | public: |
83 | TraceLock() : Lock(AllocTracer.TraceMutex) { |
84 | AllocTracer.TraceDisabled = !AllocTracer.TraceDisabled; |
85 | } |
86 | ~TraceLock() { AllocTracer.TraceDisabled = !AllocTracer.TraceDisabled; } |
87 | |
88 | bool IsDisabled() const { |
89 | // This is already inverted value. |
90 | return !AllocTracer.TraceDisabled; |
91 | } |
92 | |
93 | private: |
94 | std::lock_guard<std::recursive_mutex> Lock; |
95 | }; |
96 | |
97 | ATTRIBUTE_NO_SANITIZE_MEMORY |
98 | void MallocHook(const volatile void *ptr, size_t size) { |
99 | size_t N = AllocTracer.Mallocs++; |
100 | F->HandleMalloc(Size: size); |
101 | if (int TraceLevel = AllocTracer.TraceLevel) { |
102 | TraceLock Lock; |
103 | if (Lock.IsDisabled()) |
104 | return; |
105 | Printf(Fmt: "MALLOC[%zd] %p %zd\n" , N, ptr, size); |
106 | if (TraceLevel >= 2 && EF) |
107 | PrintStackTrace(); |
108 | } |
109 | } |
110 | |
111 | ATTRIBUTE_NO_SANITIZE_MEMORY |
112 | void FreeHook(const volatile void *ptr) { |
113 | size_t N = AllocTracer.Frees++; |
114 | if (int TraceLevel = AllocTracer.TraceLevel) { |
115 | TraceLock Lock; |
116 | if (Lock.IsDisabled()) |
117 | return; |
118 | Printf(Fmt: "FREE[%zd] %p\n" , N, ptr); |
119 | if (TraceLevel >= 2 && EF) |
120 | PrintStackTrace(); |
121 | } |
122 | } |
123 | |
124 | // Crash on a single malloc that exceeds the rss limit. |
125 | void Fuzzer::HandleMalloc(size_t Size) { |
126 | if (!Options.MallocLimitMb || (Size >> 20) < (size_t)Options.MallocLimitMb) |
127 | return; |
128 | Printf(Fmt: "==%d== ERROR: libFuzzer: out-of-memory (malloc(%zd))\n" , GetPid(), |
129 | Size); |
130 | Printf(Fmt: " To change the out-of-memory limit use -rss_limit_mb=<N>\n\n" ); |
131 | PrintStackTrace(); |
132 | DumpCurrentUnit(Prefix: "oom-" ); |
133 | Printf(Fmt: "SUMMARY: libFuzzer: out-of-memory\n" ); |
134 | PrintFinalStats(); |
135 | _Exit(status: Options.OOMExitCode); // Stop right now. |
136 | } |
137 | |
138 | Fuzzer::Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, |
139 | const FuzzingOptions &Options) |
140 | : CB(CB), Corpus(Corpus), MD(MD), Options(Options) { |
141 | if (EF->__sanitizer_set_death_callback) |
142 | EF->__sanitizer_set_death_callback(StaticDeathCallback); |
143 | assert(!F); |
144 | F = this; |
145 | TPC.ResetMaps(); |
146 | IsMyThread = true; |
147 | if (Options.DetectLeaks && EF->__sanitizer_install_malloc_and_free_hooks) |
148 | EF->__sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook); |
149 | TPC.SetUseCounters(Options.UseCounters); |
150 | TPC.SetUseValueProfileMask(Options.UseValueProfile); |
151 | |
152 | if (Options.Verbosity) |
153 | TPC.PrintModuleInfo(); |
154 | if (!Options.OutputCorpus.empty() && Options.ReloadIntervalSec) |
155 | EpochOfLastReadOfOutputCorpus = GetEpoch(Path: Options.OutputCorpus); |
156 | MaxInputLen = MaxMutationLen = Options.MaxLen; |
157 | TmpMaxMutationLen = 0; // Will be set once we load the corpus. |
158 | AllocateCurrentUnitData(); |
159 | CurrentUnitSize = 0; |
160 | memset(s: BaseSha1, c: 0, n: sizeof(BaseSha1)); |
161 | } |
162 | |
163 | void Fuzzer::AllocateCurrentUnitData() { |
164 | if (CurrentUnitData || MaxInputLen == 0) |
165 | return; |
166 | CurrentUnitData = new uint8_t[MaxInputLen]; |
167 | } |
168 | |
169 | void Fuzzer::StaticDeathCallback() { |
170 | assert(F); |
171 | F->DeathCallback(); |
172 | } |
173 | |
174 | void Fuzzer::DumpCurrentUnit(const char *Prefix) { |
175 | if (!CurrentUnitData) |
176 | return; // Happens when running individual inputs. |
177 | ScopedDisableMsanInterceptorChecks S; |
178 | MD.PrintMutationSequence(); |
179 | Printf(Fmt: "; base unit: %s\n" , Sha1ToString(Sha1: BaseSha1).c_str()); |
180 | size_t UnitSize = CurrentUnitSize; |
181 | if (UnitSize <= kMaxUnitSizeToPrint) { |
182 | PrintHexArray(Data: CurrentUnitData, Size: UnitSize, PrintAfter: "\n" ); |
183 | PrintASCII(Data: CurrentUnitData, Size: UnitSize, PrintAfter: "\n" ); |
184 | } |
185 | WriteUnitToFileWithPrefix(U: {CurrentUnitData, CurrentUnitData + UnitSize}, |
186 | Prefix); |
187 | } |
188 | |
189 | NO_SANITIZE_MEMORY |
190 | void Fuzzer::DeathCallback() { |
191 | DumpCurrentUnit(Prefix: "crash-" ); |
192 | PrintFinalStats(); |
193 | } |
194 | |
195 | void Fuzzer::StaticAlarmCallback() { |
196 | assert(F); |
197 | F->AlarmCallback(); |
198 | } |
199 | |
200 | void Fuzzer::StaticCrashSignalCallback() { |
201 | assert(F); |
202 | F->CrashCallback(); |
203 | } |
204 | |
205 | void Fuzzer::StaticExitCallback() { |
206 | assert(F); |
207 | F->ExitCallback(); |
208 | } |
209 | |
210 | void Fuzzer::StaticInterruptCallback() { |
211 | assert(F); |
212 | F->InterruptCallback(); |
213 | } |
214 | |
215 | void Fuzzer::StaticGracefulExitCallback() { |
216 | assert(F); |
217 | F->GracefulExitRequested = true; |
218 | Printf(Fmt: "INFO: signal received, trying to exit gracefully\n" ); |
219 | } |
220 | |
221 | void Fuzzer::StaticFileSizeExceedCallback() { |
222 | Printf(Fmt: "==%lu== ERROR: libFuzzer: file size exceeded\n" , GetPid()); |
223 | exit(status: 1); |
224 | } |
225 | |
226 | void Fuzzer::CrashCallback() { |
227 | if (EF->__sanitizer_acquire_crash_state && |
228 | !EF->__sanitizer_acquire_crash_state()) |
229 | return; |
230 | Printf(Fmt: "==%lu== ERROR: libFuzzer: deadly signal\n" , GetPid()); |
231 | PrintStackTrace(); |
232 | Printf(Fmt: "NOTE: libFuzzer has rudimentary signal handlers.\n" |
233 | " Combine libFuzzer with AddressSanitizer or similar for better " |
234 | "crash reports.\n" ); |
235 | Printf(Fmt: "SUMMARY: libFuzzer: deadly signal\n" ); |
236 | DumpCurrentUnit(Prefix: "crash-" ); |
237 | PrintFinalStats(); |
238 | _Exit(status: Options.ErrorExitCode); // Stop right now. |
239 | } |
240 | |
241 | void Fuzzer::ExitCallback() { |
242 | if (!RunningUserCallback) |
243 | return; // This exit did not come from the user callback |
244 | if (EF->__sanitizer_acquire_crash_state && |
245 | !EF->__sanitizer_acquire_crash_state()) |
246 | return; |
247 | Printf(Fmt: "==%lu== ERROR: libFuzzer: fuzz target exited\n" , GetPid()); |
248 | PrintStackTrace(); |
249 | Printf(Fmt: "SUMMARY: libFuzzer: fuzz target exited\n" ); |
250 | DumpCurrentUnit(Prefix: "crash-" ); |
251 | PrintFinalStats(); |
252 | _Exit(status: Options.ErrorExitCode); |
253 | } |
254 | |
255 | void Fuzzer::MaybeExitGracefully() { |
256 | if (!F->GracefulExitRequested) return; |
257 | Printf(Fmt: "==%lu== INFO: libFuzzer: exiting as requested\n" , GetPid()); |
258 | RmDirRecursive(Dir: TempPath(Prefix: "FuzzWithFork" , Extension: ".dir" )); |
259 | F->PrintFinalStats(); |
260 | _Exit(status: 0); |
261 | } |
262 | |
263 | int Fuzzer::InterruptExitCode() { |
264 | assert(F); |
265 | return F->Options.InterruptExitCode; |
266 | } |
267 | |
268 | void Fuzzer::InterruptCallback() { |
269 | Printf(Fmt: "==%lu== libFuzzer: run interrupted; exiting\n" , GetPid()); |
270 | PrintFinalStats(); |
271 | ScopedDisableMsanInterceptorChecks S; // RmDirRecursive may call opendir(). |
272 | RmDirRecursive(Dir: TempPath(Prefix: "FuzzWithFork" , Extension: ".dir" )); |
273 | // Stop right now, don't perform any at-exit actions. |
274 | _Exit(status: Options.InterruptExitCode); |
275 | } |
276 | |
277 | NO_SANITIZE_MEMORY |
278 | void Fuzzer::AlarmCallback() { |
279 | assert(Options.UnitTimeoutSec > 0); |
280 | // In Windows and Fuchsia, Alarm callback is executed by a different thread. |
281 | // NetBSD's current behavior needs this change too. |
282 | #if !LIBFUZZER_WINDOWS && !LIBFUZZER_NETBSD && !LIBFUZZER_FUCHSIA |
283 | if (!InFuzzingThread()) |
284 | return; |
285 | #endif |
286 | if (!RunningUserCallback) |
287 | return; // We have not started running units yet. |
288 | size_t Seconds = |
289 | duration_cast<seconds>(d: system_clock::now() - UnitStartTime).count(); |
290 | if (Seconds == 0) |
291 | return; |
292 | if (Options.Verbosity >= 2) |
293 | Printf(Fmt: "AlarmCallback %zd\n" , Seconds); |
294 | if (Seconds >= (size_t)Options.UnitTimeoutSec) { |
295 | if (EF->__sanitizer_acquire_crash_state && |
296 | !EF->__sanitizer_acquire_crash_state()) |
297 | return; |
298 | Printf(Fmt: "ALARM: working on the last Unit for %zd seconds\n" , Seconds); |
299 | Printf(Fmt: " and the timeout value is %d (use -timeout=N to change)\n" , |
300 | Options.UnitTimeoutSec); |
301 | DumpCurrentUnit(Prefix: "timeout-" ); |
302 | Printf(Fmt: "==%lu== ERROR: libFuzzer: timeout after %zu seconds\n" , GetPid(), |
303 | Seconds); |
304 | PrintStackTrace(); |
305 | Printf(Fmt: "SUMMARY: libFuzzer: timeout\n" ); |
306 | PrintFinalStats(); |
307 | _Exit(status: Options.TimeoutExitCode); // Stop right now. |
308 | } |
309 | } |
310 | |
311 | void Fuzzer::() { |
312 | if (EF->__sanitizer_acquire_crash_state && |
313 | !EF->__sanitizer_acquire_crash_state()) |
314 | return; |
315 | Printf(Fmt: "==%lu== ERROR: libFuzzer: out-of-memory (used: %zdMb; limit: %dMb)\n" , |
316 | GetPid(), GetPeakRSSMb(), Options.RssLimitMb); |
317 | Printf(Fmt: " To change the out-of-memory limit use -rss_limit_mb=<N>\n\n" ); |
318 | PrintMemoryProfile(); |
319 | DumpCurrentUnit(Prefix: "oom-" ); |
320 | Printf(Fmt: "SUMMARY: libFuzzer: out-of-memory\n" ); |
321 | PrintFinalStats(); |
322 | _Exit(status: Options.OOMExitCode); // Stop right now. |
323 | } |
324 | |
325 | void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units, |
326 | size_t Features) { |
327 | size_t ExecPerSec = execPerSec(); |
328 | if (!Options.Verbosity) |
329 | return; |
330 | Printf(Fmt: "#%zd\t%s" , TotalNumberOfRuns, Where); |
331 | if (size_t N = TPC.GetTotalPCCoverage()) |
332 | Printf(Fmt: " cov: %zd" , N); |
333 | if (size_t N = Features ? Features : Corpus.NumFeatures()) |
334 | Printf(Fmt: " ft: %zd" , N); |
335 | if (!Corpus.empty()) { |
336 | Printf(Fmt: " corp: %zd" , Corpus.NumActiveUnits()); |
337 | if (size_t N = Corpus.SizeInBytes()) { |
338 | if (N < (1 << 14)) |
339 | Printf(Fmt: "/%zdb" , N); |
340 | else if (N < (1 << 24)) |
341 | Printf(Fmt: "/%zdKb" , N >> 10); |
342 | else |
343 | Printf(Fmt: "/%zdMb" , N >> 20); |
344 | } |
345 | if (size_t FF = Corpus.NumInputsThatTouchFocusFunction()) |
346 | Printf(Fmt: " focus: %zd" , FF); |
347 | } |
348 | if (TmpMaxMutationLen) |
349 | Printf(Fmt: " lim: %zd" , TmpMaxMutationLen); |
350 | if (Units) |
351 | Printf(Fmt: " units: %zd" , Units); |
352 | |
353 | Printf(Fmt: " exec/s: %zd" , ExecPerSec); |
354 | Printf(Fmt: " rss: %zdMb" , GetPeakRSSMb()); |
355 | Printf(Fmt: "%s" , End); |
356 | } |
357 | |
358 | void Fuzzer::PrintFinalStats() { |
359 | if (Options.PrintFullCoverage) |
360 | TPC.PrintCoverage(/*PrintAllCounters=*/true); |
361 | if (Options.PrintCoverage) |
362 | TPC.PrintCoverage(/*PrintAllCounters=*/false); |
363 | if (Options.PrintCorpusStats) |
364 | Corpus.PrintStats(); |
365 | if (!Options.PrintFinalStats) |
366 | return; |
367 | size_t ExecPerSec = execPerSec(); |
368 | Printf(Fmt: "stat::number_of_executed_units: %zd\n" , TotalNumberOfRuns); |
369 | Printf(Fmt: "stat::average_exec_per_sec: %zd\n" , ExecPerSec); |
370 | Printf(Fmt: "stat::new_units_added: %zd\n" , NumberOfNewUnitsAdded); |
371 | Printf(Fmt: "stat::slowest_unit_time_sec: %ld\n" , TimeOfLongestUnitInSeconds); |
372 | Printf(Fmt: "stat::peak_rss_mb: %zd\n" , GetPeakRSSMb()); |
373 | } |
374 | |
375 | void Fuzzer::SetMaxInputLen(size_t MaxInputLen) { |
376 | assert(this->MaxInputLen == 0); // Can only reset MaxInputLen from 0 to non-0. |
377 | assert(MaxInputLen); |
378 | this->MaxInputLen = MaxInputLen; |
379 | this->MaxMutationLen = MaxInputLen; |
380 | AllocateCurrentUnitData(); |
381 | Printf(Fmt: "INFO: -max_len is not provided; " |
382 | "libFuzzer will not generate inputs larger than %zd bytes\n" , |
383 | MaxInputLen); |
384 | } |
385 | |
386 | void Fuzzer::SetMaxMutationLen(size_t MaxMutationLen) { |
387 | assert(MaxMutationLen && MaxMutationLen <= MaxInputLen); |
388 | this->MaxMutationLen = MaxMutationLen; |
389 | } |
390 | |
391 | void Fuzzer::CheckExitOnSrcPosOrItem() { |
392 | if (!Options.ExitOnSrcPos.empty()) { |
393 | static auto *PCsSet = new std::set<uintptr_t>; |
394 | auto HandlePC = [&](const TracePC::PCTableEntry *TE) { |
395 | if (!PCsSet->insert(x: TE->PC).second) |
396 | return; |
397 | std::string Descr = DescribePC(SymbolizedFMT: "%F %L" , PC: TE->PC + 1); |
398 | if (Descr.find(str: Options.ExitOnSrcPos) != std::string::npos) { |
399 | Printf(Fmt: "INFO: found line matching '%s', exiting.\n" , |
400 | Options.ExitOnSrcPos.c_str()); |
401 | _Exit(status: 0); |
402 | } |
403 | }; |
404 | TPC.ForEachObservedPC(CB: HandlePC); |
405 | } |
406 | if (!Options.ExitOnItem.empty()) { |
407 | if (Corpus.HasUnit(H: Options.ExitOnItem)) { |
408 | Printf(Fmt: "INFO: found item with checksum '%s', exiting.\n" , |
409 | Options.ExitOnItem.c_str()); |
410 | _Exit(status: 0); |
411 | } |
412 | } |
413 | } |
414 | |
415 | void Fuzzer::RereadOutputCorpus(size_t MaxSize) { |
416 | if (Options.OutputCorpus.empty() || !Options.ReloadIntervalSec) |
417 | return; |
418 | std::vector<Unit> AdditionalCorpus; |
419 | std::vector<std::string> AdditionalCorpusPaths; |
420 | ReadDirToVectorOfUnits( |
421 | Path: Options.OutputCorpus.c_str(), V: &AdditionalCorpus, |
422 | Epoch: &EpochOfLastReadOfOutputCorpus, MaxSize, |
423 | /*ExitOnError*/ false, |
424 | VPaths: (Options.Verbosity >= 2 ? &AdditionalCorpusPaths : nullptr)); |
425 | if (Options.Verbosity >= 2) |
426 | Printf(Fmt: "Reload: read %zd new units.\n" , AdditionalCorpus.size()); |
427 | bool Reloaded = false; |
428 | for (size_t i = 0; i != AdditionalCorpus.size(); ++i) { |
429 | auto &U = AdditionalCorpus[i]; |
430 | if (U.size() > MaxSize) |
431 | U.resize(new_size: MaxSize); |
432 | if (!Corpus.HasUnit(U)) { |
433 | if (RunOne(Data: U.data(), Size: U.size())) { |
434 | CheckExitOnSrcPosOrItem(); |
435 | Reloaded = true; |
436 | if (Options.Verbosity >= 2) |
437 | Printf(Fmt: "Reloaded %s\n" , AdditionalCorpusPaths[i].c_str()); |
438 | } |
439 | } |
440 | } |
441 | if (Reloaded) |
442 | PrintStats(Where: "RELOAD" ); |
443 | } |
444 | |
445 | void Fuzzer::PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size) { |
446 | auto TimeOfUnit = |
447 | duration_cast<seconds>(d: UnitStopTime - UnitStartTime).count(); |
448 | if (!(TotalNumberOfRuns & (TotalNumberOfRuns - 1)) && |
449 | secondsSinceProcessStartUp() >= 2) |
450 | PrintStats(Where: "pulse " ); |
451 | auto Threshhold = |
452 | static_cast<long>(static_cast<double>(TimeOfLongestUnitInSeconds) * 1.1); |
453 | if (TimeOfUnit > Threshhold && TimeOfUnit >= Options.ReportSlowUnits) { |
454 | TimeOfLongestUnitInSeconds = TimeOfUnit; |
455 | Printf(Fmt: "Slowest unit: %ld s:\n" , TimeOfLongestUnitInSeconds); |
456 | WriteUnitToFileWithPrefix(U: {Data, Data + Size}, Prefix: "slow-unit-" ); |
457 | } |
458 | } |
459 | |
460 | static void WriteFeatureSetToFile(const std::string &FeaturesDir, |
461 | const std::string &FileName, |
462 | const std::vector<uint32_t> &FeatureSet) { |
463 | if (FeaturesDir.empty() || FeatureSet.empty()) return; |
464 | WriteToFile(Data: reinterpret_cast<const uint8_t *>(FeatureSet.data()), |
465 | Size: FeatureSet.size() * sizeof(FeatureSet[0]), |
466 | Path: DirPlusFile(DirPath: FeaturesDir, FileName)); |
467 | } |
468 | |
469 | static void RenameFeatureSetFile(const std::string &FeaturesDir, |
470 | const std::string &OldFile, |
471 | const std::string &NewFile) { |
472 | if (FeaturesDir.empty()) return; |
473 | RenameFile(OldPath: DirPlusFile(DirPath: FeaturesDir, FileName: OldFile), |
474 | NewPath: DirPlusFile(DirPath: FeaturesDir, FileName: NewFile)); |
475 | } |
476 | |
477 | static void WriteEdgeToMutationGraphFile(const std::string &MutationGraphFile, |
478 | const InputInfo *II, |
479 | const InputInfo *BaseII, |
480 | const std::string &MS) { |
481 | if (MutationGraphFile.empty()) |
482 | return; |
483 | |
484 | std::string Sha1 = Sha1ToString(Sha1: II->Sha1); |
485 | |
486 | std::string OutputString; |
487 | |
488 | // Add a new vertex. |
489 | OutputString.append(s: "\"" ); |
490 | OutputString.append(str: Sha1); |
491 | OutputString.append(s: "\"\n" ); |
492 | |
493 | // Add a new edge if there is base input. |
494 | if (BaseII) { |
495 | std::string BaseSha1 = Sha1ToString(Sha1: BaseII->Sha1); |
496 | OutputString.append(s: "\"" ); |
497 | OutputString.append(str: BaseSha1); |
498 | OutputString.append(s: "\" -> \"" ); |
499 | OutputString.append(str: Sha1); |
500 | OutputString.append(s: "\" [label=\"" ); |
501 | OutputString.append(str: MS); |
502 | OutputString.append(s: "\"];\n" ); |
503 | } |
504 | |
505 | AppendToFile(Data: OutputString, Path: MutationGraphFile); |
506 | } |
507 | |
508 | bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile, |
509 | InputInfo *II, bool ForceAddToCorpus, |
510 | bool *FoundUniqFeatures) { |
511 | if (!Size) |
512 | return false; |
513 | // Largest input length should be INT_MAX. |
514 | assert(Size < std::numeric_limits<uint32_t>::max()); |
515 | |
516 | if(!ExecuteCallback(Data, Size)) return false; |
517 | auto TimeOfUnit = duration_cast<microseconds>(d: UnitStopTime - UnitStartTime); |
518 | |
519 | UniqFeatureSetTmp.clear(); |
520 | size_t FoundUniqFeaturesOfII = 0; |
521 | size_t NumUpdatesBefore = Corpus.NumFeatureUpdates(); |
522 | TPC.CollectFeatures(HandleFeature: [&](uint32_t Feature) { |
523 | if (Corpus.AddFeature(Idx: Feature, NewSize: static_cast<uint32_t>(Size), Shrink: Options.Shrink)) |
524 | UniqFeatureSetTmp.push_back(x: Feature); |
525 | if (Options.Entropic) |
526 | Corpus.UpdateFeatureFrequency(II, Idx: Feature); |
527 | if (Options.ReduceInputs && II && !II->NeverReduce) |
528 | if (std::binary_search(first: II->UniqFeatureSet.begin(), |
529 | last: II->UniqFeatureSet.end(), val: Feature)) |
530 | FoundUniqFeaturesOfII++; |
531 | }); |
532 | if (FoundUniqFeatures) |
533 | *FoundUniqFeatures = FoundUniqFeaturesOfII; |
534 | PrintPulseAndReportSlowInput(Data, Size); |
535 | size_t NumNewFeatures = Corpus.NumFeatureUpdates() - NumUpdatesBefore; |
536 | if (NumNewFeatures || ForceAddToCorpus) { |
537 | TPC.UpdateObservedPCs(); |
538 | auto NewII = |
539 | Corpus.AddToCorpus(U: {Data, Data + Size}, NumFeatures: NumNewFeatures, MayDeleteFile, |
540 | HasFocusFunction: TPC.ObservedFocusFunction(), NeverReduce: ForceAddToCorpus, |
541 | TimeOfUnit, FeatureSet: UniqFeatureSetTmp, DFT, BaseII: II); |
542 | WriteFeatureSetToFile(FeaturesDir: Options.FeaturesDir, FileName: Sha1ToString(Sha1: NewII->Sha1), |
543 | FeatureSet: NewII->UniqFeatureSet); |
544 | WriteEdgeToMutationGraphFile(MutationGraphFile: Options.MutationGraphFile, II: NewII, BaseII: II, |
545 | MS: MD.MutationSequence()); |
546 | return true; |
547 | } |
548 | if (II && FoundUniqFeaturesOfII && |
549 | II->DataFlowTraceForFocusFunction.empty() && |
550 | FoundUniqFeaturesOfII == II->UniqFeatureSet.size() && |
551 | II->U.size() > Size) { |
552 | auto OldFeaturesFile = Sha1ToString(Sha1: II->Sha1); |
553 | Corpus.Replace(II, U: {Data, Data + Size}, TimeOfUnit); |
554 | RenameFeatureSetFile(FeaturesDir: Options.FeaturesDir, OldFile: OldFeaturesFile, |
555 | NewFile: Sha1ToString(Sha1: II->Sha1)); |
556 | return true; |
557 | } |
558 | return false; |
559 | } |
560 | |
561 | void Fuzzer::TPCUpdateObservedPCs() { TPC.UpdateObservedPCs(); } |
562 | |
563 | size_t Fuzzer::GetCurrentUnitInFuzzingThead(const uint8_t **Data) const { |
564 | assert(InFuzzingThread()); |
565 | *Data = CurrentUnitData; |
566 | return CurrentUnitSize; |
567 | } |
568 | |
569 | void Fuzzer::CrashOnOverwrittenData() { |
570 | Printf(Fmt: "==%d== ERROR: libFuzzer: fuzz target overwrites its const input\n" , |
571 | GetPid()); |
572 | PrintStackTrace(); |
573 | Printf(Fmt: "SUMMARY: libFuzzer: overwrites-const-input\n" ); |
574 | DumpCurrentUnit(Prefix: "crash-" ); |
575 | PrintFinalStats(); |
576 | _Exit(status: Options.ErrorExitCode); // Stop right now. |
577 | } |
578 | |
579 | // Compare two arrays, but not all bytes if the arrays are large. |
580 | static bool LooseMemeq(const uint8_t *A, const uint8_t *B, size_t Size) { |
581 | const size_t Limit = 64; |
582 | if (Size <= 64) |
583 | return !memcmp(s1: A, s2: B, n: Size); |
584 | // Compare first and last Limit/2 bytes. |
585 | return !memcmp(s1: A, s2: B, n: Limit / 2) && |
586 | !memcmp(s1: A + Size - Limit / 2, s2: B + Size - Limit / 2, n: Limit / 2); |
587 | } |
588 | |
589 | // This method is not inlined because it would cause a test to fail where it |
590 | // is part of the stack unwinding. See D97975 for details. |
591 | ATTRIBUTE_NOINLINE bool Fuzzer::ExecuteCallback(const uint8_t *Data, |
592 | size_t Size) { |
593 | TPC.RecordInitialStack(); |
594 | TotalNumberOfRuns++; |
595 | assert(InFuzzingThread()); |
596 | // We copy the contents of Unit into a separate heap buffer |
597 | // so that we reliably find buffer overflows in it. |
598 | uint8_t *DataCopy = new uint8_t[Size]; |
599 | memcpy(dest: DataCopy, src: Data, n: Size); |
600 | if (EF->__msan_unpoison) |
601 | EF->__msan_unpoison(DataCopy, Size); |
602 | if (EF->__msan_unpoison_param) |
603 | EF->__msan_unpoison_param(2); |
604 | if (CurrentUnitData && CurrentUnitData != Data) |
605 | memcpy(dest: CurrentUnitData, src: Data, n: Size); |
606 | CurrentUnitSize = Size; |
607 | int CBRes = 0; |
608 | { |
609 | ScopedEnableMsanInterceptorChecks S; |
610 | AllocTracer.Start(TraceLevel: Options.TraceMalloc); |
611 | UnitStartTime = system_clock::now(); |
612 | TPC.ResetMaps(); |
613 | RunningUserCallback = true; |
614 | CBRes = CB(DataCopy, Size); |
615 | RunningUserCallback = false; |
616 | UnitStopTime = system_clock::now(); |
617 | assert(CBRes == 0 || CBRes == -1); |
618 | HasMoreMallocsThanFrees = AllocTracer.Stop(); |
619 | } |
620 | if (!LooseMemeq(A: DataCopy, B: Data, Size)) |
621 | CrashOnOverwrittenData(); |
622 | CurrentUnitSize = 0; |
623 | delete[] DataCopy; |
624 | return CBRes == 0; |
625 | } |
626 | |
627 | std::string Fuzzer::WriteToOutputCorpus(const Unit &U) { |
628 | if (Options.OnlyASCII) |
629 | assert(IsASCII(U)); |
630 | if (Options.OutputCorpus.empty()) |
631 | return "" ; |
632 | std::string Path = DirPlusFile(DirPath: Options.OutputCorpus, FileName: Hash(U)); |
633 | WriteToFile(U, Path); |
634 | if (Options.Verbosity >= 2) |
635 | Printf(Fmt: "Written %zd bytes to %s\n" , U.size(), Path.c_str()); |
636 | return Path; |
637 | } |
638 | |
639 | void Fuzzer::WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix) { |
640 | if (!Options.SaveArtifacts) |
641 | return; |
642 | std::string Path = Options.ArtifactPrefix + Prefix + Hash(U); |
643 | if (!Options.ExactArtifactPath.empty()) |
644 | Path = Options.ExactArtifactPath; // Overrides ArtifactPrefix. |
645 | WriteToFile(U, Path); |
646 | Printf(Fmt: "artifact_prefix='%s'; Test unit written to %s\n" , |
647 | Options.ArtifactPrefix.c_str(), Path.c_str()); |
648 | if (U.size() <= kMaxUnitSizeToPrint) |
649 | Printf(Fmt: "Base64: %s\n" , Base64(U).c_str()); |
650 | } |
651 | |
652 | void Fuzzer::PrintStatusForNewUnit(const Unit &U, const char *Text) { |
653 | if (!Options.PrintNEW) |
654 | return; |
655 | PrintStats(Where: Text, End: "" ); |
656 | if (Options.Verbosity) { |
657 | Printf(Fmt: " L: %zd/%zd " , U.size(), Corpus.MaxInputSize()); |
658 | MD.PrintMutationSequence(Verbose: Options.Verbosity >= 2); |
659 | Printf(Fmt: "\n" ); |
660 | } |
661 | } |
662 | |
663 | void Fuzzer::ReportNewCoverage(InputInfo *II, const Unit &U) { |
664 | II->NumSuccessfullMutations++; |
665 | MD.RecordSuccessfulMutationSequence(); |
666 | PrintStatusForNewUnit(U, Text: II->Reduced ? "REDUCE" : "NEW " ); |
667 | WriteToOutputCorpus(U); |
668 | NumberOfNewUnitsAdded++; |
669 | CheckExitOnSrcPosOrItem(); // Check only after the unit is saved to corpus. |
670 | LastCorpusUpdateRun = TotalNumberOfRuns; |
671 | } |
672 | |
673 | // Tries detecting a memory leak on the particular input that we have just |
674 | // executed before calling this function. |
675 | void Fuzzer::TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size, |
676 | bool DuringInitialCorpusExecution) { |
677 | if (!HasMoreMallocsThanFrees) |
678 | return; // mallocs==frees, a leak is unlikely. |
679 | if (!Options.DetectLeaks) |
680 | return; |
681 | if (!DuringInitialCorpusExecution && |
682 | TotalNumberOfRuns >= Options.MaxNumberOfRuns) |
683 | return; |
684 | if (!&(EF->__lsan_enable) || !&(EF->__lsan_disable) || |
685 | !(EF->__lsan_do_recoverable_leak_check)) |
686 | return; // No lsan. |
687 | // Run the target once again, but with lsan disabled so that if there is |
688 | // a real leak we do not report it twice. |
689 | EF->__lsan_disable(); |
690 | ExecuteCallback(Data, Size); |
691 | EF->__lsan_enable(); |
692 | if (!HasMoreMallocsThanFrees) |
693 | return; // a leak is unlikely. |
694 | if (NumberOfLeakDetectionAttempts++ > 1000) { |
695 | Options.DetectLeaks = false; |
696 | Printf(Fmt: "INFO: libFuzzer disabled leak detection after every mutation.\n" |
697 | " Most likely the target function accumulates allocated\n" |
698 | " memory in a global state w/o actually leaking it.\n" |
699 | " You may try running this binary with -trace_malloc=[12]" |
700 | " to get a trace of mallocs and frees.\n" |
701 | " If LeakSanitizer is enabled in this process it will still\n" |
702 | " run on the process shutdown.\n" ); |
703 | return; |
704 | } |
705 | // Now perform the actual lsan pass. This is expensive and we must ensure |
706 | // we don't call it too often. |
707 | if (EF->__lsan_do_recoverable_leak_check()) { // Leak is found, report it. |
708 | if (DuringInitialCorpusExecution) |
709 | Printf(Fmt: "\nINFO: a leak has been found in the initial corpus.\n\n" ); |
710 | Printf(Fmt: "INFO: to ignore leaks on libFuzzer side use -detect_leaks=0.\n\n" ); |
711 | CurrentUnitSize = Size; |
712 | DumpCurrentUnit(Prefix: "leak-" ); |
713 | PrintFinalStats(); |
714 | _Exit(status: Options.ErrorExitCode); // not exit() to disable lsan further on. |
715 | } |
716 | } |
717 | |
718 | void Fuzzer::MutateAndTestOne() { |
719 | MD.StartMutationSequence(); |
720 | |
721 | auto &II = Corpus.ChooseUnitToMutate(Rand&: MD.GetRand()); |
722 | if (Options.DoCrossOver) { |
723 | auto &CrossOverII = Corpus.ChooseUnitToCrossOverWith( |
724 | Rand&: MD.GetRand(), UniformDist: Options.CrossOverUniformDist); |
725 | MD.SetCrossOverWith(&CrossOverII.U); |
726 | } |
727 | const auto &U = II.U; |
728 | memcpy(dest: BaseSha1, src: II.Sha1, n: sizeof(BaseSha1)); |
729 | assert(CurrentUnitData); |
730 | size_t Size = U.size(); |
731 | assert(Size <= MaxInputLen && "Oversized Unit" ); |
732 | memcpy(dest: CurrentUnitData, src: U.data(), n: Size); |
733 | |
734 | assert(MaxMutationLen > 0); |
735 | |
736 | size_t CurrentMaxMutationLen = |
737 | Min(a: MaxMutationLen, b: Max(a: U.size(), b: TmpMaxMutationLen)); |
738 | assert(CurrentMaxMutationLen > 0); |
739 | |
740 | for (int i = 0; i < Options.MutateDepth; i++) { |
741 | if (TotalNumberOfRuns >= Options.MaxNumberOfRuns) |
742 | break; |
743 | MaybeExitGracefully(); |
744 | size_t NewSize = 0; |
745 | if (II.HasFocusFunction && !II.DataFlowTraceForFocusFunction.empty() && |
746 | Size <= CurrentMaxMutationLen) |
747 | NewSize = MD.MutateWithMask(Data: CurrentUnitData, Size, MaxSize: Size, |
748 | Mask: II.DataFlowTraceForFocusFunction); |
749 | |
750 | // If MutateWithMask either failed or wasn't called, call default Mutate. |
751 | if (!NewSize) |
752 | NewSize = MD.Mutate(Data: CurrentUnitData, Size, MaxSize: CurrentMaxMutationLen); |
753 | assert(NewSize > 0 && "Mutator returned empty unit" ); |
754 | assert(NewSize <= CurrentMaxMutationLen && "Mutator return oversized unit" ); |
755 | Size = NewSize; |
756 | II.NumExecutedMutations++; |
757 | Corpus.IncrementNumExecutedMutations(); |
758 | |
759 | bool FoundUniqFeatures = false; |
760 | bool NewCov = RunOne(Data: CurrentUnitData, Size, /*MayDeleteFile=*/true, II: &II, |
761 | /*ForceAddToCorpus*/ false, FoundUniqFeatures: &FoundUniqFeatures); |
762 | TryDetectingAMemoryLeak(Data: CurrentUnitData, Size, |
763 | /*DuringInitialCorpusExecution*/ false); |
764 | if (NewCov) { |
765 | ReportNewCoverage(II: &II, U: {CurrentUnitData, CurrentUnitData + Size}); |
766 | break; // We will mutate this input more in the next rounds. |
767 | } |
768 | if (Options.ReduceDepth && !FoundUniqFeatures) |
769 | break; |
770 | } |
771 | |
772 | II.NeedsEnergyUpdate = true; |
773 | } |
774 | |
775 | void Fuzzer::PurgeAllocator() { |
776 | if (Options.PurgeAllocatorIntervalSec < 0 || !EF->__sanitizer_purge_allocator) |
777 | return; |
778 | if (duration_cast<seconds>(d: system_clock::now() - |
779 | LastAllocatorPurgeAttemptTime) |
780 | .count() < Options.PurgeAllocatorIntervalSec) |
781 | return; |
782 | |
783 | if (Options.RssLimitMb <= 0 || |
784 | GetPeakRSSMb() > static_cast<size_t>(Options.RssLimitMb) / 2) |
785 | EF->__sanitizer_purge_allocator(); |
786 | |
787 | LastAllocatorPurgeAttemptTime = system_clock::now(); |
788 | } |
789 | |
790 | void Fuzzer::ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles) { |
791 | const size_t kMaxSaneLen = 1 << 20; |
792 | const size_t kMinDefaultLen = 4096; |
793 | size_t MaxSize = 0; |
794 | size_t MinSize = -1; |
795 | size_t TotalSize = 0; |
796 | for (auto &File : CorporaFiles) { |
797 | MaxSize = Max(a: File.Size, b: MaxSize); |
798 | MinSize = Min(a: File.Size, b: MinSize); |
799 | TotalSize += File.Size; |
800 | } |
801 | if (Options.MaxLen == 0) |
802 | SetMaxInputLen(std::clamp(val: MaxSize, lo: kMinDefaultLen, hi: kMaxSaneLen)); |
803 | assert(MaxInputLen > 0); |
804 | |
805 | // Test the callback with empty input and never try it again. |
806 | uint8_t dummy = 0; |
807 | ExecuteCallback(Data: &dummy, Size: 0); |
808 | |
809 | if (CorporaFiles.empty()) { |
810 | Printf(Fmt: "INFO: A corpus is not provided, starting from an empty corpus\n" ); |
811 | Unit U({'\n'}); // Valid ASCII input. |
812 | RunOne(Data: U.data(), Size: U.size()); |
813 | } else { |
814 | Printf(Fmt: "INFO: seed corpus: files: %zd min: %zdb max: %zdb total: %zdb" |
815 | " rss: %zdMb\n" , |
816 | CorporaFiles.size(), MinSize, MaxSize, TotalSize, GetPeakRSSMb()); |
817 | if (Options.ShuffleAtStartUp) |
818 | std::shuffle(first: CorporaFiles.begin(), last: CorporaFiles.end(), g&: MD.GetRand()); |
819 | |
820 | if (Options.PreferSmall) { |
821 | std::stable_sort(first: CorporaFiles.begin(), last: CorporaFiles.end()); |
822 | assert(CorporaFiles.front().Size <= CorporaFiles.back().Size); |
823 | } |
824 | |
825 | // Load and execute inputs one by one. |
826 | for (auto &SF : CorporaFiles) { |
827 | auto U = FileToVector(Path: SF.File, MaxSize: MaxInputLen, /*ExitOnError=*/false); |
828 | assert(U.size() <= MaxInputLen); |
829 | RunOne(Data: U.data(), Size: U.size(), /*MayDeleteFile*/ false, /*II*/ nullptr, |
830 | /*ForceAddToCorpus*/ Options.KeepSeed, |
831 | /*FoundUniqFeatures*/ nullptr); |
832 | CheckExitOnSrcPosOrItem(); |
833 | TryDetectingAMemoryLeak(Data: U.data(), Size: U.size(), |
834 | /*DuringInitialCorpusExecution*/ true); |
835 | } |
836 | } |
837 | |
838 | PrintStats(Where: "INITED" ); |
839 | if (!Options.FocusFunction.empty()) { |
840 | Printf(Fmt: "INFO: %zd/%zd inputs touch the focus function\n" , |
841 | Corpus.NumInputsThatTouchFocusFunction(), Corpus.size()); |
842 | if (!Options.DataFlowTrace.empty()) |
843 | Printf(Fmt: "INFO: %zd/%zd inputs have the Data Flow Trace\n" , |
844 | Corpus.NumInputsWithDataFlowTrace(), |
845 | Corpus.NumInputsThatTouchFocusFunction()); |
846 | } |
847 | |
848 | if (Corpus.empty() && Options.MaxNumberOfRuns) { |
849 | Printf(Fmt: "WARNING: no interesting inputs were found so far. " |
850 | "Is the code instrumented for coverage?\n" |
851 | "This may also happen if the target rejected all inputs we tried so " |
852 | "far\n" ); |
853 | // The remaining logic requires that the corpus is not empty, |
854 | // so we add one fake input to the in-memory corpus. |
855 | Corpus.AddToCorpus(U: {'\n'}, /*NumFeatures=*/1, /*MayDeleteFile=*/true, |
856 | /*HasFocusFunction=*/false, /*NeverReduce=*/false, |
857 | /*TimeOfUnit=*/duration_cast<microseconds>(d: 0s), FeatureSet: {0}, DFT, |
858 | /*BaseII*/ nullptr); |
859 | } |
860 | } |
861 | |
862 | void Fuzzer::Loop(std::vector<SizedFile> &CorporaFiles) { |
863 | auto FocusFunctionOrAuto = Options.FocusFunction; |
864 | DFT.Init(DirPath: Options.DataFlowTrace, FocusFunction: &FocusFunctionOrAuto, CorporaFiles, |
865 | Rand&: MD.GetRand()); |
866 | TPC.SetFocusFunction(FocusFunctionOrAuto); |
867 | ReadAndExecuteSeedCorpora(CorporaFiles); |
868 | DFT.Clear(); // No need for DFT any more. |
869 | TPC.SetPrintNewPCs(Options.PrintNewCovPcs); |
870 | TPC.SetPrintNewFuncs(Options.PrintNewCovFuncs); |
871 | system_clock::time_point LastCorpusReload = system_clock::now(); |
872 | |
873 | TmpMaxMutationLen = |
874 | Min(a: MaxMutationLen, b: Max(a: size_t(4), b: Corpus.MaxInputSize())); |
875 | |
876 | while (true) { |
877 | auto Now = system_clock::now(); |
878 | if (!Options.StopFile.empty() && |
879 | !FileToVector(Path: Options.StopFile, MaxSize: 1, ExitOnError: false).empty()) |
880 | break; |
881 | if (duration_cast<seconds>(d: Now - LastCorpusReload).count() >= |
882 | Options.ReloadIntervalSec) { |
883 | RereadOutputCorpus(MaxSize: MaxInputLen); |
884 | LastCorpusReload = system_clock::now(); |
885 | } |
886 | if (TotalNumberOfRuns >= Options.MaxNumberOfRuns) |
887 | break; |
888 | if (TimedOut()) |
889 | break; |
890 | |
891 | // Update TmpMaxMutationLen |
892 | if (Options.LenControl) { |
893 | if (TmpMaxMutationLen < MaxMutationLen && |
894 | TotalNumberOfRuns - LastCorpusUpdateRun > |
895 | Options.LenControl * Log(X: TmpMaxMutationLen)) { |
896 | TmpMaxMutationLen = |
897 | Min(a: MaxMutationLen, b: TmpMaxMutationLen + Log(X: TmpMaxMutationLen)); |
898 | LastCorpusUpdateRun = TotalNumberOfRuns; |
899 | } |
900 | } else { |
901 | TmpMaxMutationLen = MaxMutationLen; |
902 | } |
903 | |
904 | // Perform several mutations and runs. |
905 | MutateAndTestOne(); |
906 | |
907 | PurgeAllocator(); |
908 | } |
909 | |
910 | PrintStats(Where: "DONE " , End: "\n" ); |
911 | MD.PrintRecommendedDictionary(); |
912 | } |
913 | |
914 | void Fuzzer::MinimizeCrashLoop(const Unit &U) { |
915 | if (U.size() <= 1) |
916 | return; |
917 | while (!TimedOut() && TotalNumberOfRuns < Options.MaxNumberOfRuns) { |
918 | MD.StartMutationSequence(); |
919 | memcpy(dest: CurrentUnitData, src: U.data(), n: U.size()); |
920 | for (int i = 0; i < Options.MutateDepth; i++) { |
921 | size_t NewSize = MD.Mutate(Data: CurrentUnitData, Size: U.size(), MaxSize: MaxMutationLen); |
922 | assert(NewSize > 0 && NewSize <= MaxMutationLen); |
923 | ExecuteCallback(Data: CurrentUnitData, Size: NewSize); |
924 | PrintPulseAndReportSlowInput(Data: CurrentUnitData, Size: NewSize); |
925 | TryDetectingAMemoryLeak(Data: CurrentUnitData, Size: NewSize, |
926 | /*DuringInitialCorpusExecution*/ false); |
927 | } |
928 | } |
929 | } |
930 | |
931 | } // namespace fuzzer |
932 | |
933 | extern "C" { |
934 | |
935 | ATTRIBUTE_INTERFACE size_t |
936 | LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) { |
937 | assert(fuzzer::F); |
938 | return fuzzer::F->GetMD().DefaultMutate(Data, Size, MaxSize); |
939 | } |
940 | |
941 | } // extern "C" |
942 | |