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>(fd: 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(v: 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(sz: 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>(fd: 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>(fd: 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(), value: 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 | // memcmp cannot take null pointer arguments even if Size is 0. |
583 | if (!Size) |
584 | return true; |
585 | if (Size <= 64) |
586 | return !memcmp(s1: A, s2: B, n: Size); |
587 | // Compare first and last Limit/2 bytes. |
588 | return !memcmp(s1: A, s2: B, n: Limit / 2) && |
589 | !memcmp(s1: A + Size - Limit / 2, s2: B + Size - Limit / 2, n: Limit / 2); |
590 | } |
591 | |
592 | // This method is not inlined because it would cause a test to fail where it |
593 | // is part of the stack unwinding. See D97975 for details. |
594 | ATTRIBUTE_NOINLINE bool Fuzzer::ExecuteCallback(const uint8_t *Data, |
595 | size_t Size) { |
596 | TPC.RecordInitialStack(); |
597 | TotalNumberOfRuns++; |
598 | assert(InFuzzingThread()); |
599 | // We copy the contents of Unit into a separate heap buffer |
600 | // so that we reliably find buffer overflows in it. |
601 | uint8_t *DataCopy = new uint8_t[Size]; |
602 | // memcpy cannot take null pointer arguments even if Size is 0. |
603 | if (Size) |
604 | memcpy(dest: DataCopy, src: Data, n: Size); |
605 | if (EF->__msan_unpoison) |
606 | EF->__msan_unpoison(DataCopy, Size); |
607 | if (EF->__msan_unpoison_param) |
608 | EF->__msan_unpoison_param(2); |
609 | if (CurrentUnitData && CurrentUnitData != Data) |
610 | memcpy(dest: CurrentUnitData, src: Data, n: Size); |
611 | CurrentUnitSize = Size; |
612 | int CBRes = 0; |
613 | { |
614 | ScopedEnableMsanInterceptorChecks S; |
615 | AllocTracer.Start(TraceLevel: Options.TraceMalloc); |
616 | UnitStartTime = system_clock::now(); |
617 | TPC.ResetMaps(); |
618 | RunningUserCallback = true; |
619 | CBRes = CB(DataCopy, Size); |
620 | RunningUserCallback = false; |
621 | UnitStopTime = system_clock::now(); |
622 | assert(CBRes == 0 || CBRes == -1); |
623 | HasMoreMallocsThanFrees = AllocTracer.Stop(); |
624 | } |
625 | if (!LooseMemeq(A: DataCopy, B: Data, Size)) |
626 | CrashOnOverwrittenData(); |
627 | CurrentUnitSize = 0; |
628 | delete[] DataCopy; |
629 | return CBRes == 0; |
630 | } |
631 | |
632 | std::string Fuzzer::WriteToOutputCorpus(const Unit &U) { |
633 | if (Options.OnlyASCII) |
634 | assert(IsASCII(U)); |
635 | if (Options.OutputCorpus.empty()) |
636 | return "" ; |
637 | std::string Path = DirPlusFile(DirPath: Options.OutputCorpus, FileName: Hash(U)); |
638 | WriteToFile(U, Path); |
639 | if (Options.Verbosity >= 2) |
640 | Printf(Fmt: "Written %zd bytes to %s\n" , U.size(), Path.c_str()); |
641 | return Path; |
642 | } |
643 | |
644 | void Fuzzer::WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix) { |
645 | if (!Options.SaveArtifacts) |
646 | return; |
647 | std::string Path = Options.ArtifactPrefix + Prefix + Hash(U); |
648 | if (!Options.ExactArtifactPath.empty()) |
649 | Path = Options.ExactArtifactPath; // Overrides ArtifactPrefix. |
650 | WriteToFile(U, Path); |
651 | Printf(Fmt: "artifact_prefix='%s'; Test unit written to %s\n" , |
652 | Options.ArtifactPrefix.c_str(), Path.c_str()); |
653 | if (U.size() <= kMaxUnitSizeToPrint) |
654 | Printf(Fmt: "Base64: %s\n" , Base64(U).c_str()); |
655 | } |
656 | |
657 | void Fuzzer::PrintStatusForNewUnit(const Unit &U, const char *Text) { |
658 | if (!Options.PrintNEW) |
659 | return; |
660 | PrintStats(Where: Text, End: "" ); |
661 | if (Options.Verbosity) { |
662 | Printf(Fmt: " L: %zd/%zd " , U.size(), Corpus.MaxInputSize()); |
663 | MD.PrintMutationSequence(Verbose: Options.Verbosity >= 2); |
664 | Printf(Fmt: "\n" ); |
665 | } |
666 | } |
667 | |
668 | void Fuzzer::ReportNewCoverage(InputInfo *II, const Unit &U) { |
669 | II->NumSuccessfullMutations++; |
670 | MD.RecordSuccessfulMutationSequence(); |
671 | PrintStatusForNewUnit(U, Text: II->Reduced ? "REDUCE" : "NEW " ); |
672 | WriteToOutputCorpus(U); |
673 | NumberOfNewUnitsAdded++; |
674 | CheckExitOnSrcPosOrItem(); // Check only after the unit is saved to corpus. |
675 | LastCorpusUpdateRun = TotalNumberOfRuns; |
676 | } |
677 | |
678 | // Tries detecting a memory leak on the particular input that we have just |
679 | // executed before calling this function. |
680 | void Fuzzer::TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size, |
681 | bool DuringInitialCorpusExecution) { |
682 | if (!HasMoreMallocsThanFrees) |
683 | return; // mallocs==frees, a leak is unlikely. |
684 | if (!Options.DetectLeaks) |
685 | return; |
686 | if (!DuringInitialCorpusExecution && |
687 | TotalNumberOfRuns >= Options.MaxNumberOfRuns) |
688 | return; |
689 | if (!&(EF->__lsan_enable) || !&(EF->__lsan_disable) || |
690 | !(EF->__lsan_do_recoverable_leak_check)) |
691 | return; // No lsan. |
692 | // Run the target once again, but with lsan disabled so that if there is |
693 | // a real leak we do not report it twice. |
694 | EF->__lsan_disable(); |
695 | ExecuteCallback(Data, Size); |
696 | EF->__lsan_enable(); |
697 | if (!HasMoreMallocsThanFrees) |
698 | return; // a leak is unlikely. |
699 | if (NumberOfLeakDetectionAttempts++ > 1000) { |
700 | Options.DetectLeaks = false; |
701 | Printf(Fmt: "INFO: libFuzzer disabled leak detection after every mutation.\n" |
702 | " Most likely the target function accumulates allocated\n" |
703 | " memory in a global state w/o actually leaking it.\n" |
704 | " You may try running this binary with -trace_malloc=[12]" |
705 | " to get a trace of mallocs and frees.\n" |
706 | " If LeakSanitizer is enabled in this process it will still\n" |
707 | " run on the process shutdown.\n" ); |
708 | return; |
709 | } |
710 | // Now perform the actual lsan pass. This is expensive and we must ensure |
711 | // we don't call it too often. |
712 | if (EF->__lsan_do_recoverable_leak_check()) { // Leak is found, report it. |
713 | if (DuringInitialCorpusExecution) |
714 | Printf(Fmt: "\nINFO: a leak has been found in the initial corpus.\n\n" ); |
715 | Printf(Fmt: "INFO: to ignore leaks on libFuzzer side use -detect_leaks=0.\n\n" ); |
716 | CurrentUnitSize = Size; |
717 | DumpCurrentUnit(Prefix: "leak-" ); |
718 | PrintFinalStats(); |
719 | _Exit(status: Options.ErrorExitCode); // not exit() to disable lsan further on. |
720 | } |
721 | } |
722 | |
723 | void Fuzzer::MutateAndTestOne() { |
724 | MD.StartMutationSequence(); |
725 | |
726 | auto &II = Corpus.ChooseUnitToMutate(Rand&: MD.GetRand()); |
727 | if (Options.DoCrossOver) { |
728 | auto &CrossOverII = Corpus.ChooseUnitToCrossOverWith( |
729 | Rand&: MD.GetRand(), UniformDist: Options.CrossOverUniformDist); |
730 | MD.SetCrossOverWith(&CrossOverII.U); |
731 | } |
732 | const auto &U = II.U; |
733 | memcpy(dest: BaseSha1, src: II.Sha1, n: sizeof(BaseSha1)); |
734 | assert(CurrentUnitData); |
735 | size_t Size = U.size(); |
736 | assert(Size <= MaxInputLen && "Oversized Unit" ); |
737 | memcpy(dest: CurrentUnitData, src: U.data(), n: Size); |
738 | |
739 | assert(MaxMutationLen > 0); |
740 | |
741 | size_t CurrentMaxMutationLen = |
742 | Min(a: MaxMutationLen, b: Max(a: U.size(), b: TmpMaxMutationLen)); |
743 | assert(CurrentMaxMutationLen > 0); |
744 | |
745 | for (int i = 0; i < Options.MutateDepth; i++) { |
746 | if (TotalNumberOfRuns >= Options.MaxNumberOfRuns) |
747 | break; |
748 | MaybeExitGracefully(); |
749 | size_t NewSize = 0; |
750 | if (II.HasFocusFunction && !II.DataFlowTraceForFocusFunction.empty() && |
751 | Size <= CurrentMaxMutationLen) |
752 | NewSize = MD.MutateWithMask(Data: CurrentUnitData, Size, MaxSize: Size, |
753 | Mask: II.DataFlowTraceForFocusFunction); |
754 | |
755 | // If MutateWithMask either failed or wasn't called, call default Mutate. |
756 | if (!NewSize) |
757 | NewSize = MD.Mutate(Data: CurrentUnitData, Size, MaxSize: CurrentMaxMutationLen); |
758 | assert(NewSize > 0 && "Mutator returned empty unit" ); |
759 | assert(NewSize <= CurrentMaxMutationLen && "Mutator return oversized unit" ); |
760 | Size = NewSize; |
761 | II.NumExecutedMutations++; |
762 | Corpus.IncrementNumExecutedMutations(); |
763 | |
764 | bool FoundUniqFeatures = false; |
765 | bool NewCov = RunOne(Data: CurrentUnitData, Size, /*MayDeleteFile=*/true, II: &II, |
766 | /*ForceAddToCorpus*/ false, FoundUniqFeatures: &FoundUniqFeatures); |
767 | TryDetectingAMemoryLeak(Data: CurrentUnitData, Size, |
768 | /*DuringInitialCorpusExecution*/ false); |
769 | if (NewCov) { |
770 | ReportNewCoverage(II: &II, U: {CurrentUnitData, CurrentUnitData + Size}); |
771 | break; // We will mutate this input more in the next rounds. |
772 | } |
773 | if (Options.ReduceDepth && !FoundUniqFeatures) |
774 | break; |
775 | } |
776 | |
777 | II.NeedsEnergyUpdate = true; |
778 | } |
779 | |
780 | void Fuzzer::PurgeAllocator() { |
781 | if (Options.PurgeAllocatorIntervalSec < 0 || !EF->__sanitizer_purge_allocator) |
782 | return; |
783 | if (duration_cast<seconds>(fd: system_clock::now() - |
784 | LastAllocatorPurgeAttemptTime) |
785 | .count() < Options.PurgeAllocatorIntervalSec) |
786 | return; |
787 | |
788 | if (Options.RssLimitMb <= 0 || |
789 | GetPeakRSSMb() > static_cast<size_t>(Options.RssLimitMb) / 2) |
790 | EF->__sanitizer_purge_allocator(); |
791 | |
792 | LastAllocatorPurgeAttemptTime = system_clock::now(); |
793 | } |
794 | |
795 | void Fuzzer::ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles) { |
796 | const size_t kMaxSaneLen = 1 << 20; |
797 | const size_t kMinDefaultLen = 4096; |
798 | size_t MaxSize = 0; |
799 | size_t MinSize = -1; |
800 | size_t TotalSize = 0; |
801 | for (auto &File : CorporaFiles) { |
802 | MaxSize = Max(a: File.Size, b: MaxSize); |
803 | MinSize = Min(a: File.Size, b: MinSize); |
804 | TotalSize += File.Size; |
805 | } |
806 | if (Options.MaxLen == 0) |
807 | SetMaxInputLen(std::clamp(v: MaxSize, lo: kMinDefaultLen, hi: kMaxSaneLen)); |
808 | assert(MaxInputLen > 0); |
809 | |
810 | // Test the callback with empty input and never try it again. |
811 | uint8_t dummy = 0; |
812 | ExecuteCallback(Data: &dummy, Size: 0); |
813 | |
814 | if (CorporaFiles.empty()) { |
815 | Printf(Fmt: "INFO: A corpus is not provided, starting from an empty corpus\n" ); |
816 | Unit U({'\n'}); // Valid ASCII input. |
817 | RunOne(Data: U.data(), Size: U.size()); |
818 | } else { |
819 | Printf(Fmt: "INFO: seed corpus: files: %zd min: %zdb max: %zdb total: %zdb" |
820 | " rss: %zdMb\n" , |
821 | CorporaFiles.size(), MinSize, MaxSize, TotalSize, GetPeakRSSMb()); |
822 | if (Options.ShuffleAtStartUp) |
823 | std::shuffle(first: CorporaFiles.begin(), last: CorporaFiles.end(), g&: MD.GetRand()); |
824 | |
825 | if (Options.PreferSmall) { |
826 | std::stable_sort(first: CorporaFiles.begin(), last: CorporaFiles.end()); |
827 | assert(CorporaFiles.front().Size <= CorporaFiles.back().Size); |
828 | } |
829 | |
830 | // Load and execute inputs one by one. |
831 | for (auto &SF : CorporaFiles) { |
832 | auto U = FileToVector(Path: SF.File, MaxSize: MaxInputLen, /*ExitOnError=*/false); |
833 | assert(U.size() <= MaxInputLen); |
834 | RunOne(Data: U.data(), Size: U.size(), /*MayDeleteFile*/ false, /*II*/ nullptr, |
835 | /*ForceAddToCorpus*/ Options.KeepSeed, |
836 | /*FoundUniqFeatures*/ nullptr); |
837 | CheckExitOnSrcPosOrItem(); |
838 | TryDetectingAMemoryLeak(Data: U.data(), Size: U.size(), |
839 | /*DuringInitialCorpusExecution*/ true); |
840 | } |
841 | } |
842 | |
843 | PrintStats(Where: "INITED" ); |
844 | if (!Options.FocusFunction.empty()) { |
845 | Printf(Fmt: "INFO: %zd/%zd inputs touch the focus function\n" , |
846 | Corpus.NumInputsThatTouchFocusFunction(), Corpus.size()); |
847 | if (!Options.DataFlowTrace.empty()) |
848 | Printf(Fmt: "INFO: %zd/%zd inputs have the Data Flow Trace\n" , |
849 | Corpus.NumInputsWithDataFlowTrace(), |
850 | Corpus.NumInputsThatTouchFocusFunction()); |
851 | } |
852 | |
853 | if (Corpus.empty() && Options.MaxNumberOfRuns) { |
854 | Printf(Fmt: "WARNING: no interesting inputs were found so far. " |
855 | "Is the code instrumented for coverage?\n" |
856 | "This may also happen if the target rejected all inputs we tried so " |
857 | "far\n" ); |
858 | // The remaining logic requires that the corpus is not empty, |
859 | // so we add one fake input to the in-memory corpus. |
860 | Corpus.AddToCorpus(U: {'\n'}, /*NumFeatures=*/1, /*MayDeleteFile=*/true, |
861 | /*HasFocusFunction=*/false, /*NeverReduce=*/false, |
862 | /*TimeOfUnit=*/duration_cast<microseconds>(fd: 0s), FeatureSet: {0}, DFT, |
863 | /*BaseII*/ nullptr); |
864 | } |
865 | } |
866 | |
867 | void Fuzzer::Loop(std::vector<SizedFile> &CorporaFiles) { |
868 | auto FocusFunctionOrAuto = Options.FocusFunction; |
869 | DFT.Init(DirPath: Options.DataFlowTrace, FocusFunction: &FocusFunctionOrAuto, CorporaFiles, |
870 | Rand&: MD.GetRand()); |
871 | TPC.SetFocusFunction(FocusFunctionOrAuto); |
872 | ReadAndExecuteSeedCorpora(CorporaFiles); |
873 | DFT.Clear(); // No need for DFT any more. |
874 | TPC.SetPrintNewPCs(Options.PrintNewCovPcs); |
875 | TPC.SetPrintNewFuncs(Options.PrintNewCovFuncs); |
876 | system_clock::time_point LastCorpusReload = system_clock::now(); |
877 | |
878 | TmpMaxMutationLen = |
879 | Min(a: MaxMutationLen, b: Max(a: size_t(4), b: Corpus.MaxInputSize())); |
880 | |
881 | while (true) { |
882 | auto Now = system_clock::now(); |
883 | if (!Options.StopFile.empty() && |
884 | !FileToVector(Path: Options.StopFile, MaxSize: 1, ExitOnError: false).empty()) |
885 | break; |
886 | if (duration_cast<seconds>(fd: Now - LastCorpusReload).count() >= |
887 | Options.ReloadIntervalSec) { |
888 | RereadOutputCorpus(MaxSize: MaxInputLen); |
889 | LastCorpusReload = system_clock::now(); |
890 | } |
891 | if (TotalNumberOfRuns >= Options.MaxNumberOfRuns) |
892 | break; |
893 | if (TimedOut()) |
894 | break; |
895 | |
896 | // Update TmpMaxMutationLen |
897 | if (Options.LenControl) { |
898 | if (TmpMaxMutationLen < MaxMutationLen && |
899 | TotalNumberOfRuns - LastCorpusUpdateRun > |
900 | Options.LenControl * Log(X: TmpMaxMutationLen)) { |
901 | TmpMaxMutationLen = |
902 | Min(a: MaxMutationLen, b: TmpMaxMutationLen + Log(X: TmpMaxMutationLen)); |
903 | LastCorpusUpdateRun = TotalNumberOfRuns; |
904 | } |
905 | } else { |
906 | TmpMaxMutationLen = MaxMutationLen; |
907 | } |
908 | |
909 | // Perform several mutations and runs. |
910 | MutateAndTestOne(); |
911 | |
912 | PurgeAllocator(); |
913 | } |
914 | |
915 | PrintStats(Where: "DONE " , End: "\n" ); |
916 | MD.PrintRecommendedDictionary(); |
917 | } |
918 | |
919 | void Fuzzer::MinimizeCrashLoop(const Unit &U) { |
920 | if (U.size() <= 1) |
921 | return; |
922 | while (!TimedOut() && TotalNumberOfRuns < Options.MaxNumberOfRuns) { |
923 | MD.StartMutationSequence(); |
924 | memcpy(dest: CurrentUnitData, src: U.data(), n: U.size()); |
925 | for (int i = 0; i < Options.MutateDepth; i++) { |
926 | size_t NewSize = MD.Mutate(Data: CurrentUnitData, Size: U.size(), MaxSize: MaxMutationLen); |
927 | assert(NewSize > 0 && NewSize <= MaxMutationLen); |
928 | ExecuteCallback(Data: CurrentUnitData, Size: NewSize); |
929 | PrintPulseAndReportSlowInput(Data: CurrentUnitData, Size: NewSize); |
930 | TryDetectingAMemoryLeak(Data: CurrentUnitData, Size: NewSize, |
931 | /*DuringInitialCorpusExecution*/ false); |
932 | } |
933 | } |
934 | } |
935 | |
936 | } // namespace fuzzer |
937 | |
938 | extern "C" { |
939 | |
940 | ATTRIBUTE_INTERFACE size_t |
941 | LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) { |
942 | assert(fuzzer::F); |
943 | return fuzzer::F->GetMD().DefaultMutate(Data, Size, MaxSize); |
944 | } |
945 | |
946 | } // extern "C" |
947 | |