// Copyright 2016 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package state import ( "fmt" "io/ioutil" "os" "path/filepath" "sort" "strconv" "time" "github.com/google/syzkaller/pkg/db" "github.com/google/syzkaller/pkg/hash" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" ) // State holds all internal syz-hub state including corpus, // reproducers and information about managers. // It is persisted to and can be restored from a directory. type State struct { corpusSeq uint64 reproSeq uint64 dir string Corpus *db.DB Repros *db.DB Managers map[string]*Manager } // Manager represents one syz-manager instance. type Manager struct { name string corpusSeq uint64 reproSeq uint64 corpusFile string corpusSeqFile string reproSeqFile string ownRepros map[string]bool Connected time.Time Added int Deleted int New int SentRepros int RecvRepros int Calls map[string]struct{} Corpus *db.DB } // Make creates State and initializes it from dir. func Make(dir string) (*State, error) { st := &State{ dir: dir, Managers: make(map[string]*Manager), } osutil.MkdirAll(st.dir) st.Corpus, st.corpusSeq = loadDB(filepath.Join(st.dir, "corpus.db"), "corpus") st.Repros, st.reproSeq = loadDB(filepath.Join(st.dir, "repro.db"), "repro") managersDir := filepath.Join(st.dir, "manager") osutil.MkdirAll(managersDir) managers, err := ioutil.ReadDir(managersDir) if err != nil { return nil, fmt.Errorf("failed to read %v dir: %v", managersDir, err) } for _, manager := range managers { _, err := st.createManager(manager.Name()) if err != nil { return nil, err } } log.Logf(0, "purging corpus...") st.purgeCorpus() log.Logf(0, "done, %v programs", len(st.Corpus.Records)) return st, err } func loadDB(file, name string) (*db.DB, uint64) { log.Logf(0, "reading %v...", name) db, err := db.Open(file) if err != nil { log.Fatalf("failed to open %v database: %v", name, err) } log.Logf(0, "read %v programs", len(db.Records)) var maxSeq uint64 for key, rec := range db.Records { if _, err := prog.CallSet(rec.Val); err != nil { log.Logf(0, "bad file: can't parse call set: %v", err) db.Delete(key) continue } if sig := hash.Hash(rec.Val); sig.String() != key { log.Logf(0, "bad file: hash %v, want hash %v", key, sig.String()) db.Delete(key) continue } if maxSeq < rec.Seq { maxSeq = rec.Seq } } if err := db.Flush(); err != nil { log.Fatalf("failed to flush corpus database: %v", err) } return db, maxSeq } func (st *State) createManager(name string) (*Manager, error) { dir := filepath.Join(st.dir, "manager", name) osutil.MkdirAll(dir) mgr := &Manager{ name: name, corpusFile: filepath.Join(dir, "corpus.db"), corpusSeqFile: filepath.Join(dir, "seq"), reproSeqFile: filepath.Join(dir, "repro.seq"), ownRepros: make(map[string]bool), } mgr.corpusSeq = loadSeqFile(mgr.corpusSeqFile) if st.corpusSeq < mgr.corpusSeq { st.corpusSeq = mgr.corpusSeq } mgr.reproSeq = loadSeqFile(mgr.reproSeqFile) if mgr.reproSeq == 0 { mgr.reproSeq = st.reproSeq } if st.reproSeq < mgr.reproSeq { st.reproSeq = mgr.reproSeq } var err error mgr.Corpus, err = db.Open(mgr.corpusFile) if err != nil { return nil, fmt.Errorf("failed to open manager corpus %v: %v", mgr.corpusFile, err) } log.Logf(0, "created manager %v: corpus=%v, corpusSeq=%v, reproSeq=%v", mgr.name, len(mgr.Corpus.Records), mgr.corpusSeq, mgr.reproSeq) st.Managers[name] = mgr return mgr, nil } func (st *State) Connect(name string, fresh bool, calls []string, corpus [][]byte) error { mgr := st.Managers[name] if mgr == nil { var err error mgr, err = st.createManager(name) if err != nil { return err } } mgr.Connected = time.Now() if fresh { mgr.corpusSeq = 0 mgr.reproSeq = st.reproSeq } saveSeqFile(mgr.corpusSeqFile, mgr.corpusSeq) saveSeqFile(mgr.reproSeqFile, mgr.reproSeq) mgr.Calls = make(map[string]struct{}) for _, c := range calls { mgr.Calls[c] = struct{}{} } os.Remove(mgr.corpusFile) var err error mgr.Corpus, err = db.Open(mgr.corpusFile) if err != nil { log.Logf(0, "failed to open corpus database: %v", err) return err } st.addInputs(mgr, corpus) st.purgeCorpus() return nil } func (st *State) Sync(name string, add [][]byte, del []string) ([][]byte, int, error) { mgr := st.Managers[name] if mgr == nil || mgr.Connected.IsZero() { return nil, 0, fmt.Errorf("unconnected manager %v", name) } if len(del) != 0 { for _, sig := range del { mgr.Corpus.Delete(sig) } if err := mgr.Corpus.Flush(); err != nil { log.Logf(0, "failed to flush corpus database: %v", err) } st.purgeCorpus() } st.addInputs(mgr, add) progs, more, err := st.pendingInputs(mgr) mgr.Added += len(add) mgr.Deleted += len(del) mgr.New += len(progs) return progs, more, err } func (st *State) AddRepro(name string, repro []byte) error { mgr := st.Managers[name] if mgr == nil || mgr.Connected.IsZero() { return fmt.Errorf("unconnected manager %v", name) } if _, err := prog.CallSet(repro); err != nil { log.Logf(0, "manager %v: failed to extract call set: %v, program:\n%v", mgr.name, err, string(repro)) return nil } sig := hash.String(repro) if _, ok := st.Repros.Records[sig]; ok { return nil } mgr.ownRepros[sig] = true mgr.SentRepros++ if mgr.reproSeq == st.reproSeq { mgr.reproSeq++ saveSeqFile(mgr.reproSeqFile, mgr.reproSeq) } st.reproSeq++ st.Repros.Save(sig, repro, st.reproSeq) if err := st.Repros.Flush(); err != nil { log.Logf(0, "failed to flush repro database: %v", err) } return nil } func (st *State) PendingRepro(name string) ([]byte, error) { mgr := st.Managers[name] if mgr == nil || mgr.Connected.IsZero() { return nil, fmt.Errorf("unconnected manager %v", name) } if mgr.reproSeq == st.reproSeq { return nil, nil } var repro []byte minSeq := ^uint64(0) for key, rec := range st.Repros.Records { if mgr.reproSeq >= rec.Seq { continue } if mgr.ownRepros[key] { continue } calls, err := prog.CallSet(rec.Val) if err != nil { return nil, fmt.Errorf("failed to extract call set: %v\nprogram: %s", err, rec.Val) } if !managerSupportsAllCalls(mgr.Calls, calls) { continue } if minSeq > rec.Seq { minSeq = rec.Seq repro = rec.Val } } if repro == nil { mgr.reproSeq = st.reproSeq saveSeqFile(mgr.reproSeqFile, mgr.reproSeq) return nil, nil } mgr.RecvRepros++ mgr.reproSeq = minSeq saveSeqFile(mgr.reproSeqFile, mgr.reproSeq) return repro, nil } func (st *State) pendingInputs(mgr *Manager) ([][]byte, int, error) { if mgr.corpusSeq == st.corpusSeq { return nil, 0, nil } var records []db.Record for key, rec := range st.Corpus.Records { if mgr.corpusSeq >= rec.Seq { continue } if _, ok := mgr.Corpus.Records[key]; ok { continue } calls, err := prog.CallSet(rec.Val) if err != nil { return nil, 0, fmt.Errorf("failed to extract call set: %v\nprogram: %s", err, rec.Val) } if !managerSupportsAllCalls(mgr.Calls, calls) { continue } records = append(records, rec) } maxSeq := st.corpusSeq more := 0 // Send at most that many records (rounded up to next seq number). const maxRecords = 100 if len(records) > maxRecords { sort.Sort(recordSeqSorter(records)) pos := maxRecords maxSeq = records[pos].Seq for pos+1 < len(records) && records[pos+1].Seq == maxSeq { pos++ } pos++ more = len(records) - pos records = records[:pos] } progs := make([][]byte, len(records)) for _, rec := range records { progs = append(progs, rec.Val) } mgr.corpusSeq = maxSeq saveSeqFile(mgr.corpusSeqFile, mgr.corpusSeq) return progs, more, nil } func (st *State) addInputs(mgr *Manager, inputs [][]byte) { if len(inputs) == 0 { return } st.corpusSeq++ for _, input := range inputs { st.addInput(mgr, input) } if err := mgr.Corpus.Flush(); err != nil { log.Logf(0, "failed to flush corpus database: %v", err) } if err := st.Corpus.Flush(); err != nil { log.Logf(0, "failed to flush corpus database: %v", err) } } func (st *State) addInput(mgr *Manager, input []byte) { if _, err := prog.CallSet(input); err != nil { log.Logf(0, "manager %v: failed to extract call set: %v, program:\n%v", mgr.name, err, string(input)) return } sig := hash.String(input) mgr.Corpus.Save(sig, nil, 0) if _, ok := st.Corpus.Records[sig]; !ok { st.Corpus.Save(sig, input, st.corpusSeq) } } func (st *State) purgeCorpus() { used := make(map[string]bool) for _, mgr := range st.Managers { for sig := range mgr.Corpus.Records { used[sig] = true } } for key := range st.Corpus.Records { if used[key] { continue } st.Corpus.Delete(key) } if err := st.Corpus.Flush(); err != nil { log.Logf(0, "failed to flush corpus database: %v", err) } } func managerSupportsAllCalls(mgr, prog map[string]struct{}) bool { for c := range prog { if _, ok := mgr[c]; !ok { return false } } return true } func writeFile(name string, data []byte) { if err := osutil.WriteFile(name, data); err != nil { log.Logf(0, "failed to write file %v: %v", name, err) } } func saveSeqFile(filename string, seq uint64) { writeFile(filename, []byte(fmt.Sprint(seq))) } func loadSeqFile(filename string) uint64 { str, _ := ioutil.ReadFile(filename) seq, _ := strconv.ParseUint(string(str), 10, 64) return seq } type recordSeqSorter []db.Record func (a recordSeqSorter) Len() int { return len(a) } func (a recordSeqSorter) Less(i, j int) bool { return a[i].Seq < a[j].Seq } func (a recordSeqSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }