github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/corpus/corpus.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 corpus
     5  
     6  import (
     7  	"context"
     8  	"sync"
     9  
    10  	"github.com/google/syzkaller/pkg/cover"
    11  	"github.com/google/syzkaller/pkg/hash"
    12  	"github.com/google/syzkaller/pkg/signal"
    13  	"github.com/google/syzkaller/pkg/stats"
    14  	"github.com/google/syzkaller/prog"
    15  )
    16  
    17  // Corpus object represents a set of syzkaller-found programs that
    18  // cover the kernel up to the currently reached frontiers.
    19  type Corpus struct {
    20  	ctx     context.Context
    21  	mu      sync.RWMutex
    22  	progs   map[string]*Item
    23  	signal  signal.Signal // total signal of all items
    24  	cover   cover.Cover   // total coverage of all items
    25  	updates chan<- NewItemEvent
    26  	*ProgramsList
    27  	StatProgs  *stats.Val
    28  	StatSignal *stats.Val
    29  	StatCover  *stats.Val
    30  }
    31  
    32  func NewCorpus(ctx context.Context) *Corpus {
    33  	return NewMonitoredCorpus(ctx, nil)
    34  }
    35  
    36  func NewMonitoredCorpus(ctx context.Context, updates chan<- NewItemEvent) *Corpus {
    37  	corpus := &Corpus{
    38  		ctx:          ctx,
    39  		progs:        make(map[string]*Item),
    40  		updates:      updates,
    41  		ProgramsList: &ProgramsList{},
    42  	}
    43  	corpus.StatProgs = stats.Create("corpus", "Number of test programs in the corpus", stats.Console,
    44  		stats.Link("/corpus"), stats.Graph("corpus"), stats.LenOf(&corpus.progs, &corpus.mu))
    45  	corpus.StatSignal = stats.Create("signal", "Fuzzing signal in the corpus",
    46  		stats.LenOf(&corpus.signal, &corpus.mu))
    47  	corpus.StatCover = stats.Create("coverage", "Source coverage in the corpus", stats.Console,
    48  		stats.Link("/cover"), stats.Prometheus("syz_corpus_cover"), stats.LenOf(&corpus.cover, &corpus.mu))
    49  	return corpus
    50  }
    51  
    52  // It may happen that a single program is relevant because of several
    53  // sysalls. In that case, there will be several ItemUpdate entities.
    54  type ItemUpdate struct {
    55  	Call     int
    56  	RawCover []uint32
    57  }
    58  
    59  // Item objects are to be treated as immutable, otherwise it's just
    60  // too hard to synchonize accesses to them across the whole project.
    61  // When Corpus updates one of its items, it saves a copy of it.
    62  type Item struct {
    63  	Sig      string
    64  	Call     int
    65  	Prog     *prog.Prog
    66  	ProgData []byte // to save some Serialize() calls
    67  	HasAny   bool   // whether the prog contains squashed arguments
    68  	Signal   signal.Signal
    69  	Cover    []uint32
    70  	Updates  []ItemUpdate
    71  }
    72  
    73  func (item Item) StringCall() string {
    74  	return item.Prog.CallName(item.Call)
    75  }
    76  
    77  type NewInput struct {
    78  	Prog     *prog.Prog
    79  	Call     int
    80  	Signal   signal.Signal
    81  	Cover    []uint32
    82  	RawCover []uint32
    83  }
    84  
    85  type NewItemEvent struct {
    86  	Sig      string
    87  	Exists   bool
    88  	ProgData []byte
    89  	NewCover []uint32
    90  }
    91  
    92  func (corpus *Corpus) Save(inp NewInput) {
    93  	progData := inp.Prog.Serialize()
    94  	sig := hash.String(progData)
    95  
    96  	corpus.mu.Lock()
    97  	defer corpus.mu.Unlock()
    98  
    99  	update := ItemUpdate{
   100  		Call:     inp.Call,
   101  		RawCover: inp.RawCover,
   102  	}
   103  	exists := false
   104  	if old, ok := corpus.progs[sig]; ok {
   105  		exists = true
   106  		newSignal := old.Signal.Copy()
   107  		newSignal.Merge(inp.Signal)
   108  		var newCover cover.Cover
   109  		newCover.Merge(old.Cover)
   110  		newCover.Merge(inp.Cover)
   111  		newItem := &Item{
   112  			Sig:      sig,
   113  			Prog:     old.Prog,
   114  			ProgData: progData,
   115  			Call:     old.Call,
   116  			HasAny:   old.HasAny,
   117  			Signal:   newSignal,
   118  			Cover:    newCover.Serialize(),
   119  			Updates:  append([]ItemUpdate{}, old.Updates...),
   120  		}
   121  		const maxUpdates = 32
   122  		if len(newItem.Updates) < maxUpdates {
   123  			newItem.Updates = append(newItem.Updates, update)
   124  		}
   125  		corpus.progs[sig] = newItem
   126  	} else {
   127  		corpus.progs[sig] = &Item{
   128  			Sig:      sig,
   129  			Call:     inp.Call,
   130  			Prog:     inp.Prog,
   131  			ProgData: progData,
   132  			HasAny:   inp.Prog.ContainsAny(),
   133  			Signal:   inp.Signal,
   134  			Cover:    inp.Cover,
   135  			Updates:  []ItemUpdate{update},
   136  		}
   137  		corpus.saveProgram(inp.Prog, inp.Signal)
   138  	}
   139  	corpus.signal.Merge(inp.Signal)
   140  	newCover := corpus.cover.MergeDiff(inp.Cover)
   141  	if corpus.updates != nil {
   142  		select {
   143  		case <-corpus.ctx.Done():
   144  		case corpus.updates <- NewItemEvent{
   145  			Sig:      sig,
   146  			Exists:   exists,
   147  			ProgData: progData,
   148  			NewCover: newCover,
   149  		}:
   150  		}
   151  	}
   152  }
   153  func (corpus *Corpus) Signal() signal.Signal {
   154  	corpus.mu.RLock()
   155  	defer corpus.mu.RUnlock()
   156  	return corpus.signal.Copy()
   157  }
   158  
   159  func (corpus *Corpus) Items() []*Item {
   160  	corpus.mu.RLock()
   161  	defer corpus.mu.RUnlock()
   162  	ret := make([]*Item, 0, len(corpus.progs))
   163  	for _, item := range corpus.progs {
   164  		ret = append(ret, item)
   165  	}
   166  	return ret
   167  }
   168  
   169  func (corpus *Corpus) Item(sig string) *Item {
   170  	corpus.mu.RLock()
   171  	defer corpus.mu.RUnlock()
   172  	return corpus.progs[sig]
   173  }
   174  
   175  type CallCov struct {
   176  	Count int
   177  	Cover cover.Cover
   178  }
   179  
   180  func (corpus *Corpus) CallCover() map[string]*CallCov {
   181  	corpus.mu.RLock()
   182  	defer corpus.mu.RUnlock()
   183  	calls := make(map[string]*CallCov)
   184  	for _, inp := range corpus.progs {
   185  		call := inp.StringCall()
   186  		if calls[call] == nil {
   187  			calls[call] = new(CallCov)
   188  		}
   189  		cc := calls[call]
   190  		cc.Count++
   191  		cc.Cover.Merge(inp.Cover)
   192  	}
   193  	return calls
   194  }