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 }