github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/syz-manager/hub.go (about)

     1  // Copyright 2018 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 main
     5  
     6  import (
     7  	"net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/google/syzkaller/pkg/auth"
    12  	"github.com/google/syzkaller/pkg/flatrpc"
    13  	"github.com/google/syzkaller/pkg/fuzzer"
    14  	"github.com/google/syzkaller/pkg/hash"
    15  	"github.com/google/syzkaller/pkg/log"
    16  	"github.com/google/syzkaller/pkg/mgrconfig"
    17  	"github.com/google/syzkaller/pkg/report"
    18  	"github.com/google/syzkaller/pkg/report/crash"
    19  	"github.com/google/syzkaller/pkg/rpctype"
    20  	"github.com/google/syzkaller/pkg/stats"
    21  	"github.com/google/syzkaller/prog"
    22  )
    23  
    24  type keyGetter func() (string, error)
    25  
    26  func pickGetter(key string) keyGetter {
    27  	if key != "" {
    28  		return func() (string, error) { return key, nil }
    29  	}
    30  	// Attempts oauth when the configured hub_key is empty.
    31  	tokenCache, err := auth.MakeCache(http.NewRequest, http.DefaultClient.Do)
    32  	if err != nil {
    33  		log.Fatalf("failed to make auth cache %v", err)
    34  	}
    35  	return func() (string, error) {
    36  		return tokenCache.Get(time.Now())
    37  	}
    38  }
    39  
    40  func (mgr *Manager) hubSyncLoop(keyGet keyGetter) {
    41  	hc := &HubConnector{
    42  		mgr:           mgr,
    43  		cfg:           mgr.cfg,
    44  		target:        mgr.target,
    45  		domain:        mgr.cfg.TargetOS + "/" + mgr.cfg.HubDomain,
    46  		enabledCalls:  mgr.targetEnabledSyscalls,
    47  		leak:          mgr.enabledFeatures&flatrpc.FeatureLeak != 0,
    48  		fresh:         mgr.fresh,
    49  		hubReproQueue: mgr.externalReproQueue,
    50  		keyGet:        keyGet,
    51  
    52  		statSendProgAdd:   stats.Create("hub send prog add", "", stats.Graph("hub progs")),
    53  		statSendProgDel:   stats.Create("hub send prog del", "", stats.Graph("hub progs")),
    54  		statRecvProg:      stats.Create("hub recv prog", "", stats.Graph("hub progs")),
    55  		statRecvProgDrop:  stats.Create("hub recv prog drop", "", stats.NoGraph),
    56  		statSendRepro:     stats.Create("hub send repro", "", stats.Graph("hub repros")),
    57  		statRecvRepro:     stats.Create("hub recv repro", "", stats.Graph("hub repros")),
    58  		statRecvReproDrop: stats.Create("hub recv repro drop", "", stats.NoGraph),
    59  	}
    60  	if mgr.cfg.Reproduce && mgr.dash != nil {
    61  		hc.needMoreRepros = mgr.needMoreRepros
    62  	}
    63  	hc.loop()
    64  }
    65  
    66  type HubConnector struct {
    67  	mgr            HubManagerView
    68  	cfg            *mgrconfig.Config
    69  	target         *prog.Target
    70  	domain         string
    71  	enabledCalls   map[*prog.Syscall]bool
    72  	leak           bool
    73  	fresh          bool
    74  	hubCorpus      map[hash.Sig]bool
    75  	newRepros      [][]byte
    76  	hubReproQueue  chan *Crash
    77  	needMoreRepros chan chan bool
    78  	keyGet         keyGetter
    79  
    80  	statSendProgAdd   *stats.Val
    81  	statSendProgDel   *stats.Val
    82  	statRecvProg      *stats.Val
    83  	statRecvProgDrop  *stats.Val
    84  	statSendRepro     *stats.Val
    85  	statRecvRepro     *stats.Val
    86  	statRecvReproDrop *stats.Val
    87  }
    88  
    89  // HubManagerView restricts interface between HubConnector and Manager.
    90  type HubManagerView interface {
    91  	getMinimizedCorpus() (corpus, repros [][]byte)
    92  	addNewCandidates(candidates []fuzzer.Candidate)
    93  	hubIsUnreachable()
    94  }
    95  
    96  func (hc *HubConnector) loop() {
    97  	var hub *rpctype.RPCClient
    98  	var doneOnce bool
    99  	for query := 0; ; time.Sleep(10 * time.Minute) {
   100  		corpus, repros := hc.mgr.getMinimizedCorpus()
   101  		hc.newRepros = append(hc.newRepros, repros...)
   102  		if hub == nil {
   103  			var err error
   104  			if hub, err = hc.connect(corpus); err != nil {
   105  				log.Logf(0, "failed to connect to hub at %v: %v", hc.cfg.HubAddr, err)
   106  			} else {
   107  				log.Logf(0, "connected to hub at %v, corpus %v", hc.cfg.HubAddr, len(corpus))
   108  			}
   109  		}
   110  		if hub != nil {
   111  			if err := hc.sync(hub, corpus); err != nil {
   112  				log.Logf(0, "hub sync failed: %v", err)
   113  				hub.Close()
   114  				hub = nil
   115  			} else {
   116  				doneOnce = true
   117  			}
   118  		}
   119  		query++
   120  		const maxAttempts = 3
   121  		if hub == nil && query >= maxAttempts && !doneOnce {
   122  			hc.mgr.hubIsUnreachable()
   123  		}
   124  	}
   125  }
   126  
   127  func (hc *HubConnector) connect(corpus [][]byte) (*rpctype.RPCClient, error) {
   128  	key, err := hc.keyGet()
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	hub, err := rpctype.NewRPCClient(hc.cfg.HubAddr)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	a := &rpctype.HubConnectArgs{
   137  		Client:  hc.cfg.HubClient,
   138  		Key:     key,
   139  		Manager: hc.cfg.Name,
   140  		Domain:  hc.domain,
   141  		Fresh:   hc.fresh,
   142  	}
   143  	for call := range hc.enabledCalls {
   144  		a.Calls = append(a.Calls, call.Name)
   145  	}
   146  	hubCorpus := make(map[hash.Sig]bool)
   147  	for _, inp := range corpus {
   148  		hubCorpus[hash.Hash(inp)] = true
   149  		a.Corpus = append(a.Corpus, inp)
   150  	}
   151  	// Never send more than this, this is never healthy but happens episodically
   152  	// due to various reasons: problems with fallback coverage, bugs in kcov,
   153  	// fuzzer exploiting our infrastructure, etc.
   154  	const max = 100 * 1000
   155  	if len(a.Corpus) > max {
   156  		a.Corpus = a.Corpus[:max]
   157  	}
   158  	err = hub.Call("Hub.Connect", a, nil)
   159  	// Hub.Connect request can be very large, so do it on a transient connection
   160  	// (rpc connection buffers never shrink).
   161  	hub.Close()
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	hub, err = rpctype.NewRPCClient(hc.cfg.HubAddr)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	hc.hubCorpus = hubCorpus
   170  	hc.fresh = false
   171  	return hub, nil
   172  }
   173  
   174  func (hc *HubConnector) sync(hub *rpctype.RPCClient, corpus [][]byte) error {
   175  	key, err := hc.keyGet()
   176  	if err != nil {
   177  		return err
   178  	}
   179  	a := &rpctype.HubSyncArgs{
   180  		Client:  hc.cfg.HubClient,
   181  		Key:     key,
   182  		Manager: hc.cfg.Name,
   183  	}
   184  	sigs := make(map[hash.Sig]bool)
   185  	for _, inp := range corpus {
   186  		sig := hash.Hash(inp)
   187  		sigs[sig] = true
   188  		if hc.hubCorpus[sig] {
   189  			continue
   190  		}
   191  		hc.hubCorpus[sig] = true
   192  		a.Add = append(a.Add, inp)
   193  	}
   194  	for sig := range hc.hubCorpus {
   195  		if sigs[sig] {
   196  			continue
   197  		}
   198  		delete(hc.hubCorpus, sig)
   199  		a.Del = append(a.Del, sig.String())
   200  	}
   201  	if hc.needMoreRepros != nil {
   202  		needReproReply := make(chan bool)
   203  		hc.needMoreRepros <- needReproReply
   204  		a.NeedRepros = <-needReproReply
   205  	}
   206  	a.Repros = hc.newRepros
   207  	for {
   208  		r := new(rpctype.HubSyncRes)
   209  		if err := hub.Call("Hub.Sync", a, r); err != nil {
   210  			return err
   211  		}
   212  		minimized, smashed, progDropped := hc.processProgs(r.Inputs)
   213  		reproDropped := hc.processRepros(r.Repros)
   214  		hc.statSendProgAdd.Add(len(a.Add))
   215  		hc.statSendProgDel.Add(len(a.Del))
   216  		hc.statSendRepro.Add(len(a.Repros))
   217  		hc.statRecvProg.Add(len(r.Inputs) - progDropped)
   218  		hc.statRecvProgDrop.Add(progDropped)
   219  		hc.statRecvRepro.Add(len(r.Repros) - reproDropped)
   220  		hc.statRecvReproDrop.Add(reproDropped)
   221  		log.Logf(0, "hub sync: send: add %v, del %v, repros %v;"+
   222  			" recv: progs %v (min %v, smash %v), repros %v; more %v",
   223  			len(a.Add), len(a.Del), len(a.Repros),
   224  			len(r.Inputs)-progDropped, minimized, smashed,
   225  			len(r.Repros)-reproDropped, r.More)
   226  		a.Add = nil
   227  		a.Del = nil
   228  		a.Repros = nil
   229  		a.NeedRepros = false
   230  		hc.newRepros = nil
   231  		if len(r.Inputs)+r.More == 0 {
   232  			return nil
   233  		}
   234  	}
   235  }
   236  
   237  func (hc *HubConnector) processProgs(inputs []rpctype.HubInput) (minimized, smashed, dropped int) {
   238  	candidates := make([]fuzzer.Candidate, 0, len(inputs))
   239  	for _, inp := range inputs {
   240  		p, disabled, bad := parseProgram(hc.target, hc.enabledCalls, inp.Prog)
   241  		if bad != nil || disabled {
   242  			log.Logf(0, "rejecting program from hub (bad=%v, disabled=%v):\n%s",
   243  				bad, disabled, inp)
   244  			dropped++
   245  			continue
   246  		}
   247  		min, smash := matchDomains(hc.domain, inp.Domain)
   248  		if min {
   249  			minimized++
   250  		}
   251  		if smash {
   252  			smashed++
   253  		}
   254  		candidates = append(candidates, fuzzer.Candidate{
   255  			Prog:      p,
   256  			Minimized: min,
   257  			Smashed:   smash,
   258  		})
   259  	}
   260  	hc.mgr.addNewCandidates(candidates)
   261  	return
   262  }
   263  
   264  func matchDomains(self, input string) (bool, bool) {
   265  	if self == "" || input == "" {
   266  		return true, true
   267  	}
   268  	min0, smash0 := splitDomains(self)
   269  	min1, smash1 := splitDomains(input)
   270  	min := min0 != min1
   271  	smash := min || smash0 != smash1
   272  	return min, smash
   273  }
   274  
   275  func splitDomains(domain string) (string, string) {
   276  	delim0 := strings.IndexByte(domain, '/')
   277  	if delim0 == -1 {
   278  		return domain, ""
   279  	}
   280  	if delim0 == len(domain)-1 {
   281  		return domain[:delim0], ""
   282  	}
   283  	delim1 := strings.IndexByte(domain[delim0+1:], '/')
   284  	if delim1 == -1 {
   285  		return domain, ""
   286  	}
   287  	return domain[:delim0+delim1+1], domain[delim0+delim1+2:]
   288  }
   289  
   290  func (hc *HubConnector) processRepros(repros [][]byte) int {
   291  	dropped := 0
   292  	for _, repro := range repros {
   293  		_, disabled, bad := parseProgram(hc.target, hc.enabledCalls, repro)
   294  		if bad != nil || disabled {
   295  			log.Logf(0, "rejecting repro from hub (bad=%v, disabled=%v):\n%s",
   296  				bad, disabled, repro)
   297  			dropped++
   298  			continue
   299  		}
   300  		// On a leak instance we override repro type to leak,
   301  		// because otherwise repro package won't even enable leak detection
   302  		// and we won't reproduce leaks from other instances.
   303  		typ := crash.UnknownType
   304  		if hc.leak {
   305  			typ = crash.MemoryLeak
   306  		}
   307  		hc.hubReproQueue <- &Crash{
   308  			fromHub: true,
   309  			Report: &report.Report{
   310  				Title:  "external repro",
   311  				Type:   typ,
   312  				Output: repro,
   313  			},
   314  		}
   315  	}
   316  	return dropped
   317  }