1
2
3
4
5
6
7
8 package fuzz
9
10 import (
11 "bytes"
12 "context"
13 "crypto/sha256"
14 "errors"
15 "fmt"
16 "internal/godebug"
17 "io"
18 "math/bits"
19 "os"
20 "path/filepath"
21 "reflect"
22 "runtime"
23 "strings"
24 "time"
25 )
26
27
28
29 type CoordinateFuzzingOpts struct {
30
31
32 Log io.Writer
33
34
35
36 Timeout time.Duration
37
38
39
40 Limit int64
41
42
43
44
45
46 MinimizeTimeout time.Duration
47
48
49
50
51
52
53 MinimizeLimit int64
54
55
56
57 Parallel int
58
59
60
61 Seed []CorpusEntry
62
63
64
65 Types []reflect.Type
66
67
68
69 CorpusDir string
70
71
72
73 CacheDir string
74 }
75
76
77
78
79
80
81
82
83
84
85 func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err error) {
86 if err := ctx.Err(); err != nil {
87 return err
88 }
89 if opts.Log == nil {
90 opts.Log = io.Discard
91 }
92 if opts.Parallel == 0 {
93 opts.Parallel = runtime.GOMAXPROCS(0)
94 }
95 if opts.Limit > 0 && int64(opts.Parallel) > opts.Limit {
96
97 opts.Parallel = int(opts.Limit)
98 }
99
100 c, err := newCoordinator(opts)
101 if err != nil {
102 return err
103 }
104
105 if opts.Timeout > 0 {
106 var cancel func()
107 ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
108 defer cancel()
109 }
110
111
112 fuzzCtx, cancelWorkers := context.WithCancel(ctx)
113 defer cancelWorkers()
114 doneC := ctx.Done()
115
116
117 var fuzzErr error
118 stopping := false
119 stop := func(err error) {
120 if shouldPrintDebugInfo() {
121 _, file, line, ok := runtime.Caller(1)
122 if ok {
123 c.debugLogf("stop called at %s:%d. stopping: %t", file, line, stopping)
124 } else {
125 c.debugLogf("stop called at unknown. stopping: %t", stopping)
126 }
127 }
128
129 if err == ctx.Err() || err == fuzzCtx.Err() || isInterruptError(err) {
130
131
132
133
134
135
136
137 err = nil
138 }
139 if err != nil && (fuzzErr == nil || fuzzErr == ctx.Err()) {
140 fuzzErr = err
141 }
142 if stopping {
143 return
144 }
145 stopping = true
146 cancelWorkers()
147 doneC = nil
148 }
149
150
151
152 crashWritten := false
153 defer func() {
154 if c.crashMinimizing == nil || crashWritten {
155 return
156 }
157 werr := writeToCorpus(&c.crashMinimizing.entry, opts.CorpusDir)
158 if werr != nil {
159 err = fmt.Errorf("%w\n%v", err, werr)
160 return
161 }
162 if err == nil {
163 err = &crashError{
164 path: c.crashMinimizing.entry.Path,
165 err: errors.New(c.crashMinimizing.crasherMsg),
166 }
167 }
168 }()
169
170
171
172 dir := ""
173 binPath := os.Args[0]
174 args := append([]string{"-test.fuzzworker"}, os.Args[1:]...)
175 env := os.Environ()
176
177 errC := make(chan error)
178 workers := make([]*worker, opts.Parallel)
179 for i := range workers {
180 var err error
181 workers[i], err = newWorker(c, dir, binPath, args, env)
182 if err != nil {
183 return err
184 }
185 }
186 for i := range workers {
187 w := workers[i]
188 go func() {
189 err := w.coordinate(fuzzCtx)
190 if fuzzCtx.Err() != nil || isInterruptError(err) {
191 err = nil
192 }
193 cleanErr := w.cleanup()
194 if err == nil {
195 err = cleanErr
196 }
197 errC <- err
198 }()
199 }
200
201
202
203
204 activeWorkers := len(workers)
205 statTicker := time.NewTicker(3 * time.Second)
206 defer statTicker.Stop()
207 defer c.logStats()
208
209 c.logStats()
210 for {
211
212 if c.opts.Limit > 0 && c.count >= c.opts.Limit {
213 stop(nil)
214 }
215
216 var inputC chan fuzzInput
217 input, ok := c.peekInput()
218 if ok && c.crashMinimizing == nil && !stopping {
219 inputC = c.inputC
220 }
221
222 var minimizeC chan fuzzMinimizeInput
223 minimizeInput, ok := c.peekMinimizeInput()
224 if ok && !stopping {
225 minimizeC = c.minimizeC
226 }
227
228 select {
229 case <-doneC:
230
231
232 stop(ctx.Err())
233
234 case err := <-errC:
235
236 stop(err)
237 activeWorkers--
238 if activeWorkers == 0 {
239 return fuzzErr
240 }
241
242 case result := <-c.resultC:
243
244 if stopping {
245 break
246 }
247 c.updateStats(result)
248
249 if result.crasherMsg != "" {
250 if c.warmupRun() && result.entry.IsSeed {
251 target := filepath.Base(c.opts.CorpusDir)
252 fmt.Fprintf(c.opts.Log, "failure while testing seed corpus entry: %s/%s\n", target, testName(result.entry.Parent))
253 stop(errors.New(result.crasherMsg))
254 break
255 }
256 if c.canMinimize() && result.canMinimize {
257 if c.crashMinimizing != nil {
258
259
260 if shouldPrintDebugInfo() {
261 c.debugLogf("found unminimized crasher, skipping in favor of minimizable crasher")
262 }
263 break
264 }
265
266
267
268 c.crashMinimizing = &result
269 fmt.Fprintf(c.opts.Log, "fuzz: minimizing %d-byte failing input file\n", len(result.entry.Data))
270 c.queueForMinimization(result, nil)
271 } else if !crashWritten {
272
273
274 err := writeToCorpus(&result.entry, opts.CorpusDir)
275 if err == nil {
276 crashWritten = true
277 err = &crashError{
278 path: result.entry.Path,
279 err: errors.New(result.crasherMsg),
280 }
281 }
282 if shouldPrintDebugInfo() {
283 c.debugLogf(
284 "found crasher, id: %s, parent: %s, gen: %d, size: %d, exec time: %s",
285 result.entry.Path,
286 result.entry.Parent,
287 result.entry.Generation,
288 len(result.entry.Data),
289 result.entryDuration,
290 )
291 }
292 stop(err)
293 }
294 } else if result.coverageData != nil {
295 if c.warmupRun() {
296 if shouldPrintDebugInfo() {
297 c.debugLogf(
298 "processed an initial input, id: %s, new bits: %d, size: %d, exec time: %s",
299 result.entry.Parent,
300 countBits(diffCoverage(c.coverageMask, result.coverageData)),
301 len(result.entry.Data),
302 result.entryDuration,
303 )
304 }
305 c.updateCoverage(result.coverageData)
306 c.warmupInputLeft--
307 if c.warmupInputLeft == 0 {
308 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
309 if shouldPrintDebugInfo() {
310 c.debugLogf(
311 "finished processing input corpus, entries: %d, initial coverage bits: %d",
312 len(c.corpus.entries),
313 countBits(c.coverageMask),
314 )
315 }
316 }
317 } else if keepCoverage := diffCoverage(c.coverageMask, result.coverageData); keepCoverage != nil {
318
319
320
321
322
323
324
325
326 if c.canMinimize() && result.canMinimize && c.crashMinimizing == nil {
327
328
329 c.queueForMinimization(result, keepCoverage)
330 } else {
331
332 inputSize := len(result.entry.Data)
333 entryNew, err := c.addCorpusEntries(true, result.entry)
334 if err != nil {
335 stop(err)
336 break
337 }
338 if !entryNew {
339 if shouldPrintDebugInfo() {
340 c.debugLogf(
341 "ignoring duplicate input which increased coverage, id: %s",
342 result.entry.Path,
343 )
344 }
345 break
346 }
347 c.updateCoverage(keepCoverage)
348 c.inputQueue.enqueue(result.entry)
349 c.interestingCount++
350 if shouldPrintDebugInfo() {
351 c.debugLogf(
352 "new interesting input, id: %s, parent: %s, gen: %d, new bits: %d, total bits: %d, size: %d, exec time: %s",
353 result.entry.Path,
354 result.entry.Parent,
355 result.entry.Generation,
356 countBits(keepCoverage),
357 countBits(c.coverageMask),
358 inputSize,
359 result.entryDuration,
360 )
361 }
362 }
363 } else {
364 if shouldPrintDebugInfo() {
365 c.debugLogf(
366 "worker reported interesting input that doesn't expand coverage, id: %s, parent: %s, canMinimize: %t",
367 result.entry.Path,
368 result.entry.Parent,
369 result.canMinimize,
370 )
371 }
372 }
373 } else if c.warmupRun() {
374
375
376 c.warmupInputLeft--
377 if c.warmupInputLeft == 0 {
378 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
379 if shouldPrintDebugInfo() {
380 c.debugLogf(
381 "finished testing-only phase, entries: %d",
382 len(c.corpus.entries),
383 )
384 }
385 }
386 }
387
388 case inputC <- input:
389
390 c.sentInput(input)
391
392 case minimizeC <- minimizeInput:
393
394 c.sentMinimizeInput(minimizeInput)
395
396 case <-statTicker.C:
397 c.logStats()
398 }
399 }
400
401
402
403 }
404
405
406
407
408 type crashError struct {
409 path string
410 err error
411 }
412
413 func (e *crashError) Error() string {
414 return e.err.Error()
415 }
416
417 func (e *crashError) Unwrap() error {
418 return e.err
419 }
420
421 func (e *crashError) CrashPath() string {
422 return e.path
423 }
424
425 type corpus struct {
426 entries []CorpusEntry
427 hashes map[[sha256.Size]byte]bool
428 }
429
430
431
432
433
434 func (c *coordinator) addCorpusEntries(addToCache bool, entries ...CorpusEntry) (bool, error) {
435 noDupes := true
436 for _, e := range entries {
437 data, err := corpusEntryData(e)
438 if err != nil {
439 return false, err
440 }
441 h := sha256.Sum256(data)
442 if c.corpus.hashes[h] {
443 noDupes = false
444 continue
445 }
446 if addToCache {
447 if err := writeToCorpus(&e, c.opts.CacheDir); err != nil {
448 return false, err
449 }
450
451
452
453 e.Data = nil
454 }
455 c.corpus.hashes[h] = true
456 c.corpus.entries = append(c.corpus.entries, e)
457 }
458 return noDupes, nil
459 }
460
461
462
463
464
465
466
467 type CorpusEntry = struct {
468 Parent string
469
470
471
472
473 Path string
474
475
476
477
478 Data []byte
479
480
481 Values []any
482
483 Generation int
484
485
486 IsSeed bool
487 }
488
489
490
491 func corpusEntryData(ce CorpusEntry) ([]byte, error) {
492 if ce.Data != nil {
493 return ce.Data, nil
494 }
495
496 return os.ReadFile(ce.Path)
497 }
498
499 type fuzzInput struct {
500
501
502 entry CorpusEntry
503
504
505
506 timeout time.Duration
507
508
509
510
511
512 limit int64
513
514
515
516 warmup bool
517
518
519 coverageData []byte
520 }
521
522 type fuzzResult struct {
523
524 entry CorpusEntry
525
526
527 crasherMsg string
528
529
530
531 canMinimize bool
532
533
534 coverageData []byte
535
536
537
538 limit int64
539
540
541 count int64
542
543
544 totalDuration time.Duration
545
546
547 entryDuration time.Duration
548 }
549
550 type fuzzMinimizeInput struct {
551
552 entry CorpusEntry
553
554
555
556
557 crasherMsg string
558
559
560
561
562 limit int64
563
564
565
566 timeout time.Duration
567
568
569
570
571
572 keepCoverage []byte
573 }
574
575
576
577 type coordinator struct {
578 opts CoordinateFuzzingOpts
579
580
581
582 startTime time.Time
583
584
585
586 inputC chan fuzzInput
587
588
589
590 minimizeC chan fuzzMinimizeInput
591
592
593
594 resultC chan fuzzResult
595
596
597 count int64
598
599
600
601 countLastLog int64
602
603
604 timeLastLog time.Time
605
606
607
608 interestingCount int
609
610
611
612
613
614 warmupInputCount int
615
616
617
618
619 warmupInputLeft int
620
621
622
623 duration time.Duration
624
625
626
627 countWaiting int64
628
629
630
631 corpus corpus
632
633
634
635 minimizationAllowed bool
636
637
638
639
640 inputQueue queue
641
642
643
644
645 minimizeQueue queue
646
647
648 crashMinimizing *fuzzResult
649
650
651
652
653
654
655
656 coverageMask []byte
657 }
658
659 func newCoordinator(opts CoordinateFuzzingOpts) (*coordinator, error) {
660
661 for i := range opts.Seed {
662 if opts.Seed[i].Data == nil && opts.Seed[i].Values != nil {
663 opts.Seed[i].Data = marshalCorpusFile(opts.Seed[i].Values...)
664 }
665 }
666 c := &coordinator{
667 opts: opts,
668 startTime: time.Now(),
669 inputC: make(chan fuzzInput),
670 minimizeC: make(chan fuzzMinimizeInput),
671 resultC: make(chan fuzzResult),
672 timeLastLog: time.Now(),
673 corpus: corpus{hashes: make(map[[sha256.Size]byte]bool)},
674 }
675 if err := c.readCache(); err != nil {
676 return nil, err
677 }
678 if opts.MinimizeLimit > 0 || opts.MinimizeTimeout > 0 {
679 for _, t := range opts.Types {
680 if isMinimizable(t) {
681 c.minimizationAllowed = true
682 break
683 }
684 }
685 }
686
687 covSize := len(coverage())
688 if covSize == 0 {
689 fmt.Fprintf(c.opts.Log, "warning: the test binary was not built with coverage instrumentation, so fuzzing will run without coverage guidance and may be inefficient\n")
690
691
692
693 c.warmupInputCount = len(c.opts.Seed)
694 for _, e := range c.opts.Seed {
695 c.inputQueue.enqueue(e)
696 }
697 } else {
698 c.warmupInputCount = len(c.corpus.entries)
699 for _, e := range c.corpus.entries {
700 c.inputQueue.enqueue(e)
701 }
702
703 c.coverageMask = make([]byte, covSize)
704 }
705 c.warmupInputLeft = c.warmupInputCount
706
707 if len(c.corpus.entries) == 0 {
708 fmt.Fprintf(c.opts.Log, "warning: starting with empty corpus\n")
709 var vals []any
710 for _, t := range opts.Types {
711 vals = append(vals, zeroValue(t))
712 }
713 data := marshalCorpusFile(vals...)
714 h := sha256.Sum256(data)
715 name := fmt.Sprintf("%x", h[:4])
716 c.addCorpusEntries(false, CorpusEntry{Path: name, Data: data})
717 }
718
719 return c, nil
720 }
721
722 func (c *coordinator) updateStats(result fuzzResult) {
723 c.count += result.count
724 c.countWaiting -= result.limit
725 c.duration += result.totalDuration
726 }
727
728 func (c *coordinator) logStats() {
729 now := time.Now()
730 if c.warmupRun() {
731 runSoFar := c.warmupInputCount - c.warmupInputLeft
732 if coverageEnabled {
733 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
734 } else {
735 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
736 }
737 } else if c.crashMinimizing != nil {
738 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, minimizing\n", c.elapsed())
739 } else {
740 rate := float64(c.count-c.countLastLog) / now.Sub(c.timeLastLog).Seconds()
741 if coverageEnabled {
742 total := c.warmupInputCount + c.interestingCount
743 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec), new interesting: %d (total: %d)\n", c.elapsed(), c.count, rate, c.interestingCount, total)
744 } else {
745 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec)\n", c.elapsed(), c.count, rate)
746 }
747 }
748 c.countLastLog = c.count
749 c.timeLastLog = now
750 }
751
752
753
754
755
756
757
758
759
760
761
762 func (c *coordinator) peekInput() (fuzzInput, bool) {
763 if c.opts.Limit > 0 && c.count+c.countWaiting >= c.opts.Limit {
764
765
766 return fuzzInput{}, false
767 }
768 if c.inputQueue.len == 0 {
769 if c.warmupRun() {
770
771
772 return fuzzInput{}, false
773 }
774 c.refillInputQueue()
775 }
776
777 entry, ok := c.inputQueue.peek()
778 if !ok {
779 panic("input queue empty after refill")
780 }
781 input := fuzzInput{
782 entry: entry.(CorpusEntry),
783 timeout: workerFuzzDuration,
784 warmup: c.warmupRun(),
785 }
786 if c.coverageMask != nil {
787 input.coverageData = bytes.Clone(c.coverageMask)
788 }
789 if input.warmup {
790
791
792 input.limit = 1
793 return input, true
794 }
795
796 if c.opts.Limit > 0 {
797 input.limit = c.opts.Limit / int64(c.opts.Parallel)
798 if c.opts.Limit%int64(c.opts.Parallel) > 0 {
799 input.limit++
800 }
801 remaining := c.opts.Limit - c.count - c.countWaiting
802 if input.limit > remaining {
803 input.limit = remaining
804 }
805 }
806 return input, true
807 }
808
809
810 func (c *coordinator) sentInput(input fuzzInput) {
811 c.inputQueue.dequeue()
812 c.countWaiting += input.limit
813 }
814
815
816
817 func (c *coordinator) refillInputQueue() {
818 for _, e := range c.corpus.entries {
819 c.inputQueue.enqueue(e)
820 }
821 }
822
823
824
825 func (c *coordinator) queueForMinimization(result fuzzResult, keepCoverage []byte) {
826 if shouldPrintDebugInfo() {
827 c.debugLogf(
828 "queueing input for minimization, id: %s, parent: %s, keepCoverage: %t, crasher: %t",
829 result.entry.Path,
830 result.entry.Parent,
831 keepCoverage != nil,
832 result.crasherMsg != "",
833 )
834 }
835 if result.crasherMsg != "" {
836 c.minimizeQueue.clear()
837 }
838
839 input := fuzzMinimizeInput{
840 entry: result.entry,
841 crasherMsg: result.crasherMsg,
842 keepCoverage: keepCoverage,
843 }
844 c.minimizeQueue.enqueue(input)
845 }
846
847
848
849 func (c *coordinator) peekMinimizeInput() (fuzzMinimizeInput, bool) {
850 if !c.canMinimize() {
851
852
853 return fuzzMinimizeInput{}, false
854 }
855 v, ok := c.minimizeQueue.peek()
856 if !ok {
857 return fuzzMinimizeInput{}, false
858 }
859 input := v.(fuzzMinimizeInput)
860
861 if c.opts.MinimizeTimeout > 0 {
862 input.timeout = c.opts.MinimizeTimeout
863 }
864 if c.opts.MinimizeLimit > 0 {
865 input.limit = c.opts.MinimizeLimit
866 } else if c.opts.Limit > 0 {
867 if input.crasherMsg != "" {
868 input.limit = c.opts.Limit
869 } else {
870 input.limit = c.opts.Limit / int64(c.opts.Parallel)
871 if c.opts.Limit%int64(c.opts.Parallel) > 0 {
872 input.limit++
873 }
874 }
875 }
876 if c.opts.Limit > 0 {
877 remaining := c.opts.Limit - c.count - c.countWaiting
878 if input.limit > remaining {
879 input.limit = remaining
880 }
881 }
882 return input, true
883 }
884
885
886
887 func (c *coordinator) sentMinimizeInput(input fuzzMinimizeInput) {
888 c.minimizeQueue.dequeue()
889 c.countWaiting += input.limit
890 }
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905 func (c *coordinator) warmupRun() bool {
906 return c.warmupInputLeft > 0
907 }
908
909
910
911
912 func (c *coordinator) updateCoverage(newCoverage []byte) int {
913 if len(newCoverage) != len(c.coverageMask) {
914 panic(fmt.Sprintf("number of coverage counters changed at runtime: %d, expected %d", len(newCoverage), len(c.coverageMask)))
915 }
916 newBitCount := 0
917 for i := range newCoverage {
918 diff := newCoverage[i] &^ c.coverageMask[i]
919 newBitCount += bits.OnesCount8(diff)
920 c.coverageMask[i] |= newCoverage[i]
921 }
922 return newBitCount
923 }
924
925
926
927 func (c *coordinator) canMinimize() bool {
928 return c.minimizationAllowed &&
929 (c.opts.Limit == 0 || c.count+c.countWaiting < c.opts.Limit)
930 }
931
932 func (c *coordinator) elapsed() time.Duration {
933 return time.Since(c.startTime).Round(1 * time.Second)
934 }
935
936
937
938
939
940
941 func (c *coordinator) readCache() error {
942 if _, err := c.addCorpusEntries(false, c.opts.Seed...); err != nil {
943 return err
944 }
945 entries, err := ReadCorpus(c.opts.CacheDir, c.opts.Types)
946 if err != nil {
947 if _, ok := err.(*MalformedCorpusError); !ok {
948
949
950 return err
951 }
952
953
954
955 }
956 if _, err := c.addCorpusEntries(false, entries...); err != nil {
957 return err
958 }
959 return nil
960 }
961
962
963
964
965 type MalformedCorpusError struct {
966 errs []error
967 }
968
969 func (e *MalformedCorpusError) Error() string {
970 var msgs []string
971 for _, s := range e.errs {
972 msgs = append(msgs, s.Error())
973 }
974 return strings.Join(msgs, "\n")
975 }
976
977
978
979
980
981 func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) {
982 files, err := os.ReadDir(dir)
983 if os.IsNotExist(err) {
984 return nil, nil
985 } else if err != nil {
986 return nil, fmt.Errorf("reading seed corpus from testdata: %v", err)
987 }
988 var corpus []CorpusEntry
989 var errs []error
990 for _, file := range files {
991
992
993
994
995
996 if file.IsDir() {
997 continue
998 }
999 filename := filepath.Join(dir, file.Name())
1000 data, err := os.ReadFile(filename)
1001 if err != nil {
1002 return nil, fmt.Errorf("failed to read corpus file: %v", err)
1003 }
1004 var vals []any
1005 vals, err = readCorpusData(data, types)
1006 if err != nil {
1007 errs = append(errs, fmt.Errorf("%q: %v", filename, err))
1008 continue
1009 }
1010 corpus = append(corpus, CorpusEntry{Path: filename, Values: vals})
1011 }
1012 if len(errs) > 0 {
1013 return corpus, &MalformedCorpusError{errs: errs}
1014 }
1015 return corpus, nil
1016 }
1017
1018 func readCorpusData(data []byte, types []reflect.Type) ([]any, error) {
1019 vals, err := unmarshalCorpusFile(data)
1020 if err != nil {
1021 return nil, fmt.Errorf("unmarshal: %v", err)
1022 }
1023 if err = CheckCorpus(vals, types); err != nil {
1024 return nil, err
1025 }
1026 return vals, nil
1027 }
1028
1029
1030
1031 func CheckCorpus(vals []any, types []reflect.Type) error {
1032 if len(vals) != len(types) {
1033 return fmt.Errorf("wrong number of values in corpus entry: %d, want %d", len(vals), len(types))
1034 }
1035 valsT := make([]reflect.Type, len(vals))
1036 for valsI, v := range vals {
1037 valsT[valsI] = reflect.TypeOf(v)
1038 }
1039 for i := range types {
1040 if valsT[i] != types[i] {
1041 return fmt.Errorf("mismatched types in corpus entry: %v, want %v", valsT, types)
1042 }
1043 }
1044 return nil
1045 }
1046
1047
1048
1049
1050
1051 func writeToCorpus(entry *CorpusEntry, dir string) (err error) {
1052 sum := fmt.Sprintf("%x", sha256.Sum256(entry.Data))[:16]
1053 entry.Path = filepath.Join(dir, sum)
1054 if err := os.MkdirAll(dir, 0777); err != nil {
1055 return err
1056 }
1057 if err := os.WriteFile(entry.Path, entry.Data, 0666); err != nil {
1058 os.Remove(entry.Path)
1059 return err
1060 }
1061 return nil
1062 }
1063
1064 func testName(path string) string {
1065 return filepath.Base(path)
1066 }
1067
1068 func zeroValue(t reflect.Type) any {
1069 for _, v := range zeroVals {
1070 if reflect.TypeOf(v) == t {
1071 return v
1072 }
1073 }
1074 panic(fmt.Sprintf("unsupported type: %v", t))
1075 }
1076
1077 var zeroVals []any = []any{
1078 []byte(""),
1079 string(""),
1080 false,
1081 byte(0),
1082 rune(0),
1083 float32(0),
1084 float64(0),
1085 int(0),
1086 int8(0),
1087 int16(0),
1088 int32(0),
1089 int64(0),
1090 uint(0),
1091 uint8(0),
1092 uint16(0),
1093 uint32(0),
1094 uint64(0),
1095 }
1096
1097 var debugInfo = godebug.New("#fuzzdebug").Value() == "1"
1098
1099 func shouldPrintDebugInfo() bool {
1100 return debugInfo
1101 }
1102
1103 func (c *coordinator) debugLogf(format string, args ...any) {
1104 t := time.Now().Format("2006-01-02 15:04:05.999999999")
1105 fmt.Fprintf(c.opts.Log, t+" DEBUG "+format+"\n", args...)
1106 }
1107
View as plain text