github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/manager/diff_store.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package manager
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"path/filepath"
    10  	"sort"
    11  	"sync"
    12  	"text/tabwriter"
    13  	"time"
    14  
    15  	"github.com/google/syzkaller/pkg/log"
    16  	"github.com/google/syzkaller/pkg/osutil"
    17  )
    18  
    19  type DiffBug struct {
    20  	Title   string
    21  	Base    DiffBugInfo
    22  	Patched DiffBugInfo
    23  }
    24  
    25  func (bug DiffBug) PatchedOnly() bool {
    26  	return bug.Base.NotCrashed && bug.Patched.Crashes > 0
    27  }
    28  
    29  func (bug DiffBug) AffectsBoth() bool {
    30  	return bug.Base.Crashes > 0 && bug.Patched.Crashes > 0
    31  }
    32  
    33  type DiffBugInfo struct {
    34  	Crashes    int  // Count of detected crashes.
    35  	NotCrashed bool // If were proven not to crash by running a repro.
    36  
    37  	// File paths.
    38  	Report   string
    39  	Repro    string
    40  	ReproLog string
    41  	CrashLog string
    42  }
    43  
    44  // DiffFuzzerStore provides the functionality of a database of the patch fuzzing.
    45  type DiffFuzzerStore struct {
    46  	BasePath string
    47  
    48  	mu   sync.Mutex
    49  	bugs map[string]*DiffBug
    50  }
    51  
    52  func (s *DiffFuzzerStore) BaseCrashed(title string, report []byte) {
    53  	s.patch(title, func(obj *DiffBug) {
    54  		obj.Base.Crashes++
    55  		if len(report) > 0 {
    56  			obj.Base.Report = s.saveFile(title, "base_report", report)
    57  		}
    58  	})
    59  }
    60  
    61  func (s *DiffFuzzerStore) EverCrashedBase(title string) bool {
    62  	s.mu.Lock()
    63  	defer s.mu.Unlock()
    64  	obj := s.bugs[title]
    65  	return obj != nil && obj.Base.Crashes > 0
    66  }
    67  
    68  func (s *DiffFuzzerStore) BaseNotCrashed(title string) {
    69  	s.patch(title, func(obj *DiffBug) {
    70  		if obj.Base.Crashes == 0 {
    71  			obj.Base.NotCrashed = true
    72  		}
    73  	})
    74  }
    75  
    76  func (s *DiffFuzzerStore) PatchedCrashed(title string, report, log []byte) {
    77  	s.patch(title, func(obj *DiffBug) {
    78  		obj.Patched.Crashes++
    79  		if len(report) > 0 {
    80  			obj.Patched.Report = s.saveFile(title, "patched_report", report)
    81  		}
    82  		if len(log) > 0 && obj.Patched.CrashLog == "" {
    83  			obj.Patched.CrashLog = s.saveFile(title, "patched_crash_log", log)
    84  		}
    85  	})
    86  }
    87  
    88  func (s *DiffFuzzerStore) SaveRepro(result *ReproResult) {
    89  	title := result.Crash.Report.Title
    90  	if result.Repro != nil {
    91  		// If there's a repro, save under the new title.
    92  		title = result.Repro.Report.Title
    93  	}
    94  
    95  	now := time.Now().Unix()
    96  	crashLog := fmt.Sprintf("%v.crash.log", now)
    97  	s.saveFile(title, crashLog, result.Crash.Output)
    98  	log.Logf(0, "%q: saved crash log into %s", title, crashLog)
    99  
   100  	s.patch(title, func(obj *DiffBug) {
   101  		if result.Repro != nil {
   102  			obj.Patched.Repro = s.saveFile(title, reproFileName, result.Repro.Prog.Serialize())
   103  		}
   104  		if result.Stats != nil {
   105  			reproLog := fmt.Sprintf("%v.repro.log", now)
   106  			obj.Patched.ReproLog = s.saveFile(title, reproLog, result.Stats.FullLog())
   107  			log.Logf(0, "%q: saved repro log into %s", title, reproLog)
   108  		}
   109  	})
   110  }
   111  
   112  func (s *DiffFuzzerStore) List() []DiffBug {
   113  	s.mu.Lock()
   114  	defer s.mu.Unlock()
   115  	var list []DiffBug
   116  	for _, obj := range s.bugs {
   117  		list = append(list, *obj)
   118  	}
   119  	return list
   120  }
   121  
   122  func (s *DiffFuzzerStore) PlainTextDump() []byte {
   123  	list := s.List()
   124  	sort.Slice(list, func(i, j int) bool {
   125  		// Put patched-only on top, otherwise sort by the title.
   126  		first, second := list[i].PatchedOnly(), list[j].PatchedOnly()
   127  		if first != second {
   128  			return first
   129  		}
   130  		return list[i].Title < list[j].Title
   131  	})
   132  	var buf bytes.Buffer
   133  	w := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0)
   134  	fmt.Fprintln(w, "Title\tOn-Base\tOn-Patched")
   135  
   136  	printInfo := func(info *DiffBugInfo) {
   137  		if info.Crashes > 0 {
   138  			fmt.Fprintf(w, "%d crashes", info.Crashes)
   139  		}
   140  		if info.Repro != "" {
   141  			fmt.Fprintf(w, "[reproduced]")
   142  		}
   143  	}
   144  
   145  	for _, item := range list {
   146  		fmt.Fprintf(w, "%s\t", item.Title)
   147  		printInfo(&item.Base)
   148  		fmt.Fprintf(w, "\t")
   149  		printInfo(&item.Patched)
   150  		fmt.Fprintf(w, "\n")
   151  	}
   152  	w.Flush()
   153  	return buf.Bytes()
   154  }
   155  
   156  func (s *DiffFuzzerStore) saveFile(title, name string, data []byte) string {
   157  	hash := crashHash(title)
   158  	path := filepath.Join(s.BasePath, "crashes", hash)
   159  	osutil.MkdirAll(path)
   160  	osutil.WriteFile(filepath.Join(path, name), data)
   161  	return filepath.Join("crashes", hash, name)
   162  }
   163  
   164  func (s *DiffFuzzerStore) patch(title string, cb func(*DiffBug)) {
   165  	s.mu.Lock()
   166  	defer s.mu.Unlock()
   167  	if s.bugs == nil {
   168  		s.bugs = map[string]*DiffBug{}
   169  	}
   170  	obj, ok := s.bugs[title]
   171  	if !ok {
   172  		obj = &DiffBug{Title: title}
   173  		s.bugs[title] = obj
   174  	}
   175  	cb(obj)
   176  }