diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 467d714f4258..67cc44be03ee 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -147,3 +147,5 @@ International Business Machines Corporation Andrew Donnellan Alexander Egorenkov Alexey Kardashevskiy +Daniel Bransky +Matan Kalina diff --git a/Makefile b/Makefile index 5cef2c92baf4..c9bc3e6175e4 100644 --- a/Makefile +++ b/Makefile @@ -229,8 +229,7 @@ kfuzztest: endif verifier: descriptions - # TODO: switch syz-verifier to use syz-executor. - # GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(HOSTGO) build $(GOHOSTFLAGS) -o ./bin/syz-verifier github.com/google/syzkaller/syz-verifier + GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(HOSTGO) build $(GOHOSTFLAGS) -o ./bin/syz-verifier github.com/google/syzkaller/syz-verifier # `extract` extracts const files from various kernel sources, and may only # re-generate parts of files. diff --git a/syz-verifier/execresult.go b/syz-verifier/execresult.go deleted file mode 100644 index c6c3a9bdccf2..000000000000 --- a/syz-verifier/execresult.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "fmt" - "syscall" - - "github.com/google/syzkaller/pkg/ipc" - "github.com/google/syzkaller/prog" -) - -// ExecResult stores the results of executing a program. -type ExecResult struct { - // Pool is the index of the pool. - Pool int - // Hanged is set to true when a program was killed due to hanging. - Hanged bool - // Info contains information about the execution of each system call - // in the generated programs. - Info ipc.ProgInfo - // Crashed is set to true if a crash occurred while executing the program. - // TODO: is not used properly. Crashes are just an errors now. - Crashed bool - // Source task ID is used to route result back to the caller. - ExecTaskID int64 - // To signal the processing errors. - Error error -} - -func (l *ExecResult) IsEqual(r *ExecResult) bool { - if l.Crashed || r.Crashed { - return false - } - - lCalls := l.Info.Calls - rCalls := r.Info.Calls - - if len(lCalls) != len(rCalls) { - return false - } - - for i := 0; i < len(lCalls); i++ { - if lCalls[i].Errno != rCalls[i].Errno || - lCalls[i].Flags != rCalls[i].Flags { - return false - } - } - - return true -} - -type ResultReport struct { - // Prog is the serialized program. - Prog string - // Reports contains information about each system call. - Reports []*CallReport - // Mismatch says whether the Reports differ. - Mismatch bool -} - -type CallReport struct { - // Call is the name of the system call. - Call string - // States is a map between pools and their return state when executing the system call. - States map[int]ReturnState - // Mismatch is set to true if the returned error codes were not the same. - Mismatch bool -} - -// ReturnState stores the results of executing a system call. -type ReturnState struct { - // Errno is returned by executing the system call. - Errno int - // Flags stores the call flags (see pkg/ipc/ipc.go). - Flags ipc.CallFlags - // Crashed is set to true if the kernel crashed while executing the program - // that contains the system call. - Crashed bool -} - -func (s ReturnState) String() string { - state := "" - - if s.Crashed { - return "Crashed" - } - - state += fmt.Sprintf("Flags: %d, ", s.Flags) - errDesc := "success" - if s.Errno != 0 { - errDesc = syscall.Errno(s.Errno).Error() - } - state += fmt.Sprintf("Errno: %d (%s)", s.Errno, errDesc) - return state -} - -// CompareResults checks whether the ExecResult of the same program, -// executed on different kernels, are the same. -// It returns s ResultReport, highlighting the differences. -func CompareResults(res []*ExecResult, prog *prog.Prog) *ResultReport { - rr := &ResultReport{ - Prog: string(prog.Serialize()), - } - - // Build the CallReport for each system call in the program. - for idx, call := range prog.Calls { - cn := call.Meta.Name - - cr := &CallReport{ - Call: cn, - States: map[int]ReturnState{}, - } - - for _, r := range res { - if r.Crashed { - cr.States[r.Pool] = ReturnState{Crashed: true} - continue - } - - ci := r.Info.Calls[idx] - cr.States[r.Pool] = ReturnState{Errno: ci.Errno, Flags: ci.Flags} - } - rr.Reports = append(rr.Reports, cr) - } - - pool0 := res[0].Pool - for _, cr := range rr.Reports { - for _, state := range cr.States { - // For each CallReport, verify whether the ReturnStates from all - // the pools that executed the program are the same - if state0 := cr.States[pool0]; state0 != state { - cr.Mismatch = true - rr.Mismatch = true - } - } - } - - return rr -} diff --git a/syz-verifier/execresult_test.go b/syz-verifier/execresult_test.go deleted file mode 100644 index 0b177e82b022..000000000000 --- a/syz-verifier/execresult_test.go +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/syzkaller/prog" -) - -func TestIsEqual(t *testing.T) { - tests := []struct { - name string - res []*ExecResult - want bool - }{ - { - name: "only crashes", - res: []*ExecResult{ - makeExecResultCrashed(1), - makeExecResultCrashed(4), - }, - want: false, - }, - { - name: "mismatch because result and crash", - res: []*ExecResult{ - makeExecResultCrashed(1), - makeExecResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), - }, - want: false, - }, - { - name: "mismatches because of diffent length", - res: []*ExecResult{ - makeExecResult(2, []int{11, 33}, []int{1, 3}...), - makeExecResult(4, []int{11, 33, 22}, []int{1, 3, 3}...)}, - want: false, - }, - { - name: "mismatches not found", - res: []*ExecResult{ - makeExecResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), - makeExecResult(4, []int{11, 33, 22}, []int{1, 3, 3}...)}, - want: true, - }, - { - name: "mismatches found in results", - res: []*ExecResult{ - makeExecResult(1, []int{1, 3, 2}, []int{4, 7, 7}...), - makeExecResult(4, []int{1, 3, 5}, []int{4, 7, 3}...), - }, - want: false, - }} - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - got := test.res[0].IsEqual(test.res[1]) - if diff := cmp.Diff(test.want, got); diff != "" { - t.Errorf("ExecResult.IsEqual failure (-want +got):\n%s", diff) - } - }) - } -} - -func TestCompareResults(t *testing.T) { - p := "breaks_returns()\n" + - "minimize$0(0x1, 0x1)\n" + - "test$res0()\n" - tests := []struct { - name string - res []*ExecResult - wantReport *ResultReport - wantStats []*CallStats - }{ - { - name: "only crashes", - res: []*ExecResult{ - makeExecResultCrashed(1), - makeExecResultCrashed(4), - }, - wantReport: &ResultReport{ - Prog: p, - Reports: []*CallReport{ - {Call: "breaks_returns", States: map[int]ReturnState{ - 1: crashedReturnState(), - 4: crashedReturnState()}, - }, - {Call: "minimize$0", States: map[int]ReturnState{ - 1: crashedReturnState(), - 4: crashedReturnState()}, - }, - {Call: "test$res0", States: map[int]ReturnState{ - 1: crashedReturnState(), - 4: crashedReturnState()}, - }, - }, - Mismatch: false, - }, - }, - { - name: "mismatches because results and crashes", - res: []*ExecResult{ - makeExecResultCrashed(1), - makeExecResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), - makeExecResult(4, []int{11, 33, 22}, []int{1, 3, 3}...), - }, - wantReport: &ResultReport{ - Prog: p, - Reports: []*CallReport{ - {Call: "breaks_returns", States: map[int]ReturnState{ - 1: crashedReturnState(), - 2: returnState(11, 1), - 4: returnState(11, 1)}, - Mismatch: true}, - {Call: "minimize$0", States: map[int]ReturnState{ - 1: crashedReturnState(), - 2: returnState(33, 3), - 4: returnState(33, 3)}, - Mismatch: true}, - {Call: "test$res0", States: map[int]ReturnState{ - 1: crashedReturnState(), - 2: returnState(22, 3), - 4: returnState(22, 3)}, - Mismatch: true}, - }, - Mismatch: true, - }, - }, - { - name: "mismatches not found in results", - res: []*ExecResult{ - makeExecResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), - makeExecResult(4, []int{11, 33, 22}, []int{1, 3, 3}...)}, - wantReport: &ResultReport{ - Prog: p, - Reports: []*CallReport{ - {Call: "breaks_returns", States: map[int]ReturnState{2: {Errno: 11, Flags: 1}, 4: {Errno: 11, Flags: 1}}}, - {Call: "minimize$0", States: map[int]ReturnState{2: {Errno: 33, Flags: 3}, 4: {Errno: 33, Flags: 3}}}, - {Call: "test$res0", States: map[int]ReturnState{2: {Errno: 22, Flags: 3}, 4: {Errno: 22, Flags: 3}}}, - }, - Mismatch: false, - }, - }, - { - name: "mismatches found in results", - res: []*ExecResult{ - makeExecResult(1, []int{1, 3, 2}, []int{4, 7, 7}...), - makeExecResult(4, []int{1, 3, 5}, []int{4, 7, 3}...), - }, - wantReport: &ResultReport{ - Prog: p, - Reports: []*CallReport{ - {Call: "breaks_returns", States: map[int]ReturnState{1: {Errno: 1, Flags: 4}, 4: {Errno: 1, Flags: 4}}}, - {Call: "minimize$0", States: map[int]ReturnState{1: {Errno: 3, Flags: 7}, 4: {Errno: 3, Flags: 7}}}, - {Call: "test$res0", States: map[int]ReturnState{1: {Errno: 2, Flags: 7}, 4: {Errno: 5, Flags: 3}}, Mismatch: true}, - }, - Mismatch: true, - }, - }} - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - target := prog.InitTargetTest(t, "test", "64") - prog, err := target.Deserialize([]byte(p), prog.Strict) - if err != nil { - t.Fatalf("failed to deserialise test program: %v", err) - } - got := CompareResults(test.res, prog) - if diff := cmp.Diff(test.wantReport, got); diff != "" { - t.Errorf("verify report mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/syz-verifier/exectask.go b/syz-verifier/exectask.go deleted file mode 100644 index 57c14096b365..000000000000 --- a/syz-verifier/exectask.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "container/heap" - "sync" - "sync/atomic" - "time" - - "github.com/google/syzkaller/pkg/rpctype" - "github.com/google/syzkaller/prog" -) - -type EnvDescr int64 - -const ( - AnyEnvironment EnvDescr = iota - NewEnvironment - // TODO: add CleanVMEnvironment support. - - EnvironmentsCount -) - -// ExecTask is the atomic analysis entity. Once executed, it could trigger the -// pipeline propagation for the program. -type ExecTask struct { - CreationTime time.Time - Program *prog.Prog - ID int64 - ExecResultChan ExecResultChan - - priority int // The priority of the item in the queue. - // The index is needed by update and is maintained by the heap.Interface methods. - index int // The index of the item in the heap. -} - -func (t *ExecTask) ToRPC() *rpctype.ExecTask { - return &rpctype.ExecTask{ - Prog: t.Program.Serialize(), - ID: t.ID, - } -} - -type ExecTaskFactory struct { - chanMapMutex sync.Mutex - taskIDToExecResultChan map[int64]ExecResultChan - taskCounter int64 -} - -func MakeExecTaskFactory() *ExecTaskFactory { - return &ExecTaskFactory{ - taskIDToExecResultChan: make(map[int64]ExecResultChan), - taskCounter: -1, - } -} - -type ExecResultChan chan *ExecResult - -func (factory *ExecTaskFactory) MakeExecTask(prog *prog.Prog) *ExecTask { - task := &ExecTask{ - CreationTime: time.Now(), - Program: prog, - ExecResultChan: make(ExecResultChan), - ID: atomic.AddInt64(&factory.taskCounter, 1), - } - - factory.chanMapMutex.Lock() - defer factory.chanMapMutex.Unlock() - factory.taskIDToExecResultChan[task.ID] = task.ExecResultChan - - return task -} - -func (factory *ExecTaskFactory) ExecTasksQueued() int { - factory.chanMapMutex.Lock() - defer factory.chanMapMutex.Unlock() - return len(factory.taskIDToExecResultChan) -} - -func (factory *ExecTaskFactory) DeleteExecTask(task *ExecTask) { - factory.chanMapMutex.Lock() - defer factory.chanMapMutex.Unlock() - delete(factory.taskIDToExecResultChan, task.ID) -} - -func (factory *ExecTaskFactory) GetExecResultChan(taskID int64) ExecResultChan { - factory.chanMapMutex.Lock() - defer factory.chanMapMutex.Unlock() - - return factory.taskIDToExecResultChan[taskID] -} - -func MakeExecTaskQueue() *ExecTaskQueue { - return &ExecTaskQueue{ - pq: make(ExecTaskPriorityQueue, 0), - } -} - -// ExecTaskQueue respects the pq.priority. Internally it is a thread-safe PQ. -type ExecTaskQueue struct { - pq ExecTaskPriorityQueue - mu sync.Mutex -} - -// PopTask return false if no tasks are available. -func (q *ExecTaskQueue) PopTask() (*ExecTask, bool) { - q.mu.Lock() - defer q.mu.Unlock() - if q.pq.Len() == 0 { - return nil, false - } - return heap.Pop(&q.pq).(*ExecTask), true -} - -func (q *ExecTaskQueue) PushTask(task *ExecTask) { - q.mu.Lock() - defer q.mu.Unlock() - heap.Push(&q.pq, task) -} - -func (q *ExecTaskQueue) Len() int { - q.mu.Lock() - defer q.mu.Unlock() - return q.pq.Len() -} - -// ExecTaskPriorityQueue reused example from https://pkg.go.dev/container/heap -type ExecTaskPriorityQueue []*ExecTask - -func (pq ExecTaskPriorityQueue) Len() int { return len(pq) } - -func (pq ExecTaskPriorityQueue) Less(i, j int) bool { - // We want Pop to give us the highest, not lowest, priority so we use greater than here. - return pq[i].priority > pq[j].priority -} - -func (pq ExecTaskPriorityQueue) Swap(i, j int) { - pq[i], pq[j] = pq[j], pq[i] - pq[i].index = i - pq[j].index = j -} - -func (pq *ExecTaskPriorityQueue) Push(x interface{}) { - n := len(*pq) - item := x.(*ExecTask) - item.index = n - *pq = append(*pq, item) -} - -func (pq *ExecTaskPriorityQueue) Pop() interface{} { - old := *pq - n := len(old) - item := old[n-1] - old[n-1] = nil // avoid memory leak - item.index = -1 // for safety - *pq = old[0 : n-1] - return item -} diff --git a/syz-verifier/exectask_test.go b/syz-verifier/exectask_test.go deleted file mode 100644 index 41707626ff30..000000000000 --- a/syz-verifier/exectask_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2022 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "testing" -) - -func TestExecTask_MakeDelete(t *testing.T) { - program := getTestProgram(t) - taskFactory := MakeExecTaskFactory() - if l := taskFactory.ExecTasksQueued(); l != 0 { - t.Errorf("expected to see empty map, current size is %v", l) - } - task := taskFactory.MakeExecTask(program) - if l := taskFactory.ExecTasksQueued(); l != 1 { - t.Errorf("expected map len is 1, current size is %v", l) - } - taskFactory.DeleteExecTask(task) - if l := taskFactory.ExecTasksQueued(); l != 0 { - t.Errorf("expected map len is 0, current size is %v", l) - } -} - -func TestExecTask_ToRPC(t *testing.T) { - program := getTestProgram(t) - taskFactory := MakeExecTaskFactory() - task := taskFactory.MakeExecTask(program) - if task.ToRPC() == nil { - t.Errorf("rpcView generation failed") - } -} - -func TestGetExecResultChan(t *testing.T) { - taskFactory := MakeExecTaskFactory() - if l := taskFactory.ExecTasksQueued(); l != 0 { - t.Errorf("expected to see empty map, current size is %v", l) - } - ch := taskFactory.GetExecResultChan(100) - if l := taskFactory.ExecTasksQueued(); l != 0 { - t.Errorf("expected to see empty map, current size is %v", l) - } - if ch != nil { - t.Errorf("expected to see nil channel") - } -} - -func TestExecTaskQueue_PushTask(t *testing.T) { - q := MakeExecTaskQueue() - if l := q.Len(); l != 0 { - t.Errorf("expected to see zero len, current is %v", l) - } - - taskFactory := MakeExecTaskFactory() - q.PushTask(taskFactory.MakeExecTask(getTestProgram(t))) - if l := q.Len(); l != 1 { - t.Errorf("expected to see single element, current size is %v", l) - } -} - -func TestExecTaskQueue_PopTask(t *testing.T) { - q := MakeExecTaskQueue() - task, gotResult := q.PopTask() - if task != nil || gotResult != false { - t.Errorf("empty queue operation error") - } - program := getTestProgram(t) - taskFactory := MakeExecTaskFactory() - q.PushTask(taskFactory.MakeExecTask(program)) - q.PushTask(taskFactory.MakeExecTask(program)) - q.PushTask(taskFactory.MakeExecTask(program)) - task, gotResult = q.PopTask() - if task == nil || gotResult == false { - t.Errorf("non-empty task or error was expected") - } -} diff --git a/syz-verifier/main.go b/syz-verifier/main.go index c42d24038f96..61accef19d97 100755 --- a/syz-verifier/main.go +++ b/syz-verifier/main.go @@ -1,25 +1,22 @@ // Copyright 2021 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. -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - // package main starts the syz-verifier tool. High-level documentation can be // found in docs/syz_verifier.md. package main import ( "flag" - "io" + "fmt" "os" - "path/filepath" - "strconv" + "github.com/google/syzkaller/pkg/flatrpc" + "github.com/google/syzkaller/pkg/fuzzer/queue" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/report" + "github.com/google/syzkaller/pkg/rpcserver" "github.com/google/syzkaller/pkg/tool" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/vm" @@ -33,146 +30,94 @@ const ( // and reporting crashes. It also keeps track of the Runners executing on // spawned VMs, what programs have been sent to each Runner and what programs // have yet to be sent on any of the Runners. -type poolInfo struct { - cfg *mgrconfig.Config - pool *vm.Pool - Reporter *report.Reporter - // checked is set to true when the set of system calls not supported on the - // kernel is known. - checked bool +func Setup(name string, cfg *mgrconfig.Config, debug bool) (*Kernel, error) { + kernel := &Kernel{ + name: name, + debug: debug, + cfg: cfg, + crashes: make(chan *report.Report, 128), + servStats: rpcserver.NewNamedStats(name), + enabledSyscalls: make(chan map[*prog.Syscall]bool, 1), + features: make(chan flatrpc.Feature, 1), + } + var err error + kernel.reporter, err = report.NewReporter(cfg) + if err != nil { + return nil, fmt.Errorf("failed to create reporter for %q: %w", name, err) + } + + cfg.Experimental.ResetAccState = true // executor process restarts between program executions to clear accumulated kernel/VM stat. + + kernel.serv, err = rpcserver.New(&rpcserver.RemoteConfig{ + Config: cfg, + Manager: kernel, + Stats: kernel.servStats, + Debug: debug, + }) + if err != nil { + return nil, fmt.Errorf("failed to create rpc server for %q: %w", name, err) + } + + vmPool, err := vm.Create(cfg, debug) + if err != nil { + return nil, fmt.Errorf("failed to create vm.Pool for %q: %w", name, err) + } + + kernel.pool = vm.NewDispatcher(vmPool, kernel.FuzzerInstance) + return kernel, nil } func main() { var cfgs tool.CfgsFlag flag.Var(&cfgs, "configs", "[MANDATORY] list of at least two kernel-specific comma-sepatated configuration files") flagDebug := flag.Bool("debug", false, "dump all VM output to console") - flagStats := flag.String("stats", "", "where stats will be written when"+ - "execution of syz-verifier finishes, defaults to stdout") - flagEnv := flag.Bool("new-env", true, "create a new environment for each program") - flagAddress := flag.String("address", "127.0.0.1:8080", "http address for monitoring") - flagReruns := flag.Int("rerun", 3, "number of time program is rerun when a mismatch is found") + flagCorpus := flag.String("corpus", "", "path to corpus.db file (default: /corpus.db)") flag.Parse() - pools := make(map[int]*poolInfo) + kernels := make([]*Kernel, len(cfgs)) for idx, cfg := range cfgs { - var err error - pi := &poolInfo{} - pi.cfg, err = mgrconfig.LoadFile(cfg) + kcfg, err := mgrconfig.LoadFile(cfg) if err != nil { log.Fatalf("%v", err) } - // TODO: call pi.pool.Close() on exit. - pi.pool, err = vm.Create(pi.cfg, *flagDebug) + kernel, err := Setup(kcfg.Name, kcfg, *flagDebug) if err != nil { - log.Fatalf("%v", err) + log.Fatalf("failed to setup kcfg context for %s: %v", kcfg.Name, err) } - pools[idx] = pi + kernel.id = idx + kernels[idx] = kernel + + log.Logf(0, "loaded kernel %s", kcfg.Name) } - if len(pools) < 2 { + log.Logf(0, "loaded %d kernel configurations", len(kernels)) + if len(kernels) < 2 && !*flagDebug { flag.Usage() os.Exit(1) } - - cfg := pools[0].cfg - workdir, target, sysTarget, addr := cfg.Workdir, cfg.Target, cfg.SysTarget, cfg.RPC - for idx := 1; idx < len(pools); idx++ { - cfg := pools[idx].cfg - - // TODO: pass the configurations that should be the same for all - // kernels in a default config file in order to avoid this checks and - // add testing - if workdir != cfg.Workdir { - log.Fatalf("working directory mismatch") - } - if target != cfg.Target { - log.Fatalf("target mismatch") - } - if sysTarget != cfg.SysTarget { - log.Fatalf("system target mismatch") - } - if addr != pools[idx].cfg.RPC { - log.Fatalf("tcp address mismatch") - } - } - - exe := sysTarget.ExeExtension - runnerBin := filepath.Join(cfg.Syzkaller, "bin", target.OS+"_"+target.Arch, "syz-runner"+exe) - if !osutil.IsExist(runnerBin) { - log.Fatalf("bad syzkaller config: can't find %v", runnerBin) - } - execBin := cfg.ExecutorBin - if !osutil.IsExist(execBin) { - log.Fatalf("bad syzkaller config: can't find %v", execBin) - } - - crashdir := filepath.Join(workdir, "crashes") - osutil.MkdirAll(crashdir) - for idx := range pools { - OS, Arch := target.OS, target.Arch - targetPath := OS + "-" + Arch + "-" + strconv.Itoa(idx) - osutil.MkdirAll(filepath.Join(workdir, targetPath)) - osutil.MkdirAll(filepath.Join(crashdir, targetPath)) - } - - resultsdir := filepath.Join(workdir, "results") - osutil.MkdirAll(resultsdir) - - var sw io.Writer - var err error - if *flagStats == "" { - sw = os.Stdout - } else { - statsFile := filepath.Join(workdir, *flagStats) - sw, err = os.Create(statsFile) - if err != nil { - log.Fatalf("failed to create stats output file: %v", err) + workdir := kernels[0].cfg.Workdir + sources := make(map[int]*queue.PlainQueue) + for idx, kernel := range kernels { + if kernel.cfg.Workdir != workdir { + log.Fatalf("all kernel configurations must have the same workdir, got %q and %q", workdir, kernel.cfg.Workdir) } + sources[idx] = queue.Plain() + kernel.source = sources[idx] } + osutil.MkdirAll(workdir) - for idx, pi := range pools { - var err error - pi.Reporter, err = report.NewReporter(pi.cfg) - if err != nil { - log.Fatalf("failed to create reporter for instance-%d: %v", idx, err) - } - } - - calls := make(map[*prog.Syscall]bool) - - for _, id := range cfg.Syscalls { - c := target.Syscalls[id] - calls[c] = true - } + log.EnableLogCaching(1000, 1<<20) + log.Logf(0, "initialized %d sources", len(sources)) vrf := &Verifier{ - workdir: workdir, - crashdir: crashdir, - resultsdir: resultsdir, - pools: pools, - target: target, - calls: calls, - reasons: make(map[*prog.Syscall]string), - runnerBin: runnerBin, - executorBin: execBin, - addr: addr, - reportReasons: len(cfg.EnabledSyscalls) != 0 || len(cfg.DisabledSyscalls) != 0, - stats: MakeStats(), - statsWrite: sw, - newEnv: *flagEnv, - reruns: *flagReruns, + kernels: kernels, + cfg: kernels[0].cfg, // for now take the first kernel's config + target: kernels[0].cfg.Target, + sources: sources, + corpusPath: *flagCorpus, } - vrf.Init() - - vrf.StartProgramsAnalysis() - vrf.startInstances() - - monitor := MakeMonitor() - monitor.SetStatsTracking(vrf.stats) - - log.Logf(0, "run the Monitor at http://%s", *flagAddress) - go monitor.ListenAndServe(*flagAddress) + ctx := vm.ShutdownCtx() + vrf.RunVerifierFuzzer(ctx) - select {} } diff --git a/syz-verifier/monitoring_api.go b/syz-verifier/monitoring_api.go deleted file mode 100644 index eb856b99e70e..000000000000 --- a/syz-verifier/monitoring_api.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "encoding/json" - "net/http" - "time" -) - -// Monitor provides http based data for the syz-verifier monitoring. -// TODO: Add tests to monitoring_api. -type Monitor struct { - externalStats *Stats -} - -// MakeMonitor creates the Monitor instance. -func MakeMonitor() *Monitor { - instance := &Monitor{} - instance.initHTTPHandlers() - return instance -} - -// ListenAndServe starts the server. -func (monitor *Monitor) ListenAndServe(addr string) error { - return http.ListenAndServe(addr, nil) -} - -// SetStatsTracking points Monitor to the Stats object to monitor. -func (monitor *Monitor) SetStatsTracking(s *Stats) { - monitor.externalStats = s -} - -// InitHTTPHandlers initializes the API routing. -func (monitor *Monitor) initHTTPHandlers() { - http.Handle("/api/stats.json", jsonResponse(monitor.renderStats)) - - http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { - writer.Write([]byte("stats_json")) - }) -} - -// statsJSON provides information for the "/api/stats.json" render. -type statsJSON struct { - StartTime time.Time - TotalCallMismatches uint64 - TotalProgs uint64 - ExecErrorProgs uint64 - FlakyProgs uint64 - MismatchingProgs uint64 - AverExecSpeed uint64 -} - -// renderStats renders the statsJSON object. -func (monitor *Monitor) renderStats() interface{} { - stats := monitor.externalStats - - return &statsJSON{ - StartTime: stats.StartTime.Get(), - TotalCallMismatches: stats.TotalCallMismatches.Get(), - TotalProgs: stats.TotalProgs.Get(), - ExecErrorProgs: stats.ExecErrorProgs.Get(), - FlakyProgs: stats.FlakyProgs.Get(), - MismatchingProgs: stats.MismatchingProgs.Get(), - AverExecSpeed: 60 * stats.TotalProgs.Get() / uint64(1+time.Since(stats.StartTime.Get()).Seconds()), - } -} - -// jsonResponse provides general response forming logic. -func jsonResponse(getData func() interface{}) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/json") - - data := getData() - json, err := json.MarshalIndent( - data, - "", - "\t", - ) - if err != nil { - http.Error(w, err.Error(), 500) // Internal Server Error. - return - } - - w.Write(json) - }) -} diff --git a/syz-verifier/rpcserver.go b/syz-verifier/rpcserver.go deleted file mode 100644 index 880657e8d458..000000000000 --- a/syz-verifier/rpcserver.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "errors" - "net" - "os" - "sync" - - "github.com/google/syzkaller/pkg/log" - "github.com/google/syzkaller/pkg/rpctype" -) - -// RPCServer is a wrapper around the rpc.Server. It communicates with Runners, -// generates programs and sends complete Results for verification. -type RPCServer struct { - vrf *Verifier - port int - - // protects next variables - mu sync.Mutex - // used to count the pools w/o UnsupportedCalls result - notChecked int - // vmTasks store the per-VM currently assigned tasks Ids - vmTasksInProgress map[int]map[int64]bool -} - -func startRPCServer(vrf *Verifier) (*RPCServer, error) { - srv := &RPCServer{ - vrf: vrf, - notChecked: len(vrf.pools), - } - - s, err := rpctype.NewRPCServer(vrf.addr, "Verifier", srv) - if err != nil { - return nil, err - } - - log.Logf(0, "serving rpc on tcp://%v", s.Addr()) - srv.port = s.Addr().(*net.TCPAddr).Port - - go s.Serve() - return srv, nil -} - -// Connect notifies the RPCServer that a new Runner was started. -func (srv *RPCServer) Connect(a *rpctype.RunnerConnectArgs, r *rpctype.RunnerConnectRes) error { - r.CheckUnsupportedCalls = !srv.vrf.pools[a.Pool].checked - return nil -} - -// UpdateUnsupported communicates to the server the list of system calls not -// supported by the kernel corresponding to this pool and updates the list of -// enabled system calls. This function is called once for each kernel. -// When all kernels have reported the list of unsupported system calls, the -// choice table will be created using only the system calls supported by all -// kernels. -func (srv *RPCServer) UpdateUnsupported(a *rpctype.UpdateUnsupportedArgs, r *int) error { - srv.mu.Lock() - defer srv.mu.Unlock() - - if srv.vrf.pools[a.Pool].checked { - return nil - } - srv.vrf.pools[a.Pool].checked = true - vrf := srv.vrf - - for _, unsupported := range a.UnsupportedCalls { - if c := vrf.target.Syscalls[unsupported.ID]; vrf.calls[c] { - vrf.reasons[c] = unsupported.Reason - } - } - - srv.notChecked-- - if srv.notChecked == 0 { - vrf.finalizeCallSet(os.Stdout) - - vrf.stats.SetSyscallMask(vrf.calls) - vrf.SetPrintStatAtSIGINT() - - vrf.choiceTable = vrf.target.BuildChoiceTable(nil, vrf.calls) - vrf.progGeneratorInit.Done() - } - return nil -} - -// NextExchange is called when a Runner requests a new program to execute and, -// potentially, wants to send a new Result to the RPCServer. -func (srv *RPCServer) NextExchange(a *rpctype.NextExchangeArgs, r *rpctype.NextExchangeRes) error { - if a.Info.Calls != nil { - srv.stopWaitResult(a.Pool, a.VM, a.ExecTaskID) - srv.vrf.PutExecResult(&ExecResult{ - Pool: a.Pool, - Hanged: a.Hanged, - Info: a.Info, - ExecTaskID: a.ExecTaskID, - }) - } - - // TODO: NewEnvironment is the currently hardcoded logic. Relax it. - task := srv.vrf.GetRunnerTask(a.Pool, NewEnvironment) - srv.startWaitResult(a.Pool, a.VM, task.ID) - r.ExecTask = *task - - return nil -} - -func vmTasksKey(poolID, vmID int) int { - return poolID*1000 + vmID -} - -func (srv *RPCServer) startWaitResult(poolID, vmID int, taskID int64) { - srv.mu.Lock() - defer srv.mu.Unlock() - - if srv.vmTasksInProgress == nil { - srv.vmTasksInProgress = make(map[int]map[int64]bool) - } - - if srv.vmTasksInProgress[vmTasksKey(poolID, vmID)] == nil { - srv.vmTasksInProgress[vmTasksKey(poolID, vmID)] = - make(map[int64]bool) - } - - srv.vmTasksInProgress[vmTasksKey(poolID, vmID)][taskID] = true -} - -func (srv *RPCServer) stopWaitResult(poolID, vmID int, taskID int64) { - srv.mu.Lock() - defer srv.mu.Unlock() - delete(srv.vmTasksInProgress[vmTasksKey(poolID, vmID)], taskID) -} - -// cleanup is called when a vm.Instance crashes. -func (srv *RPCServer) cleanup(poolID, vmID int) { - srv.mu.Lock() - defer srv.mu.Unlock() - - // Signal error for every VM related task and let upper level logic to process it. - for taskID := range srv.vmTasksInProgress[vmTasksKey(poolID, vmID)] { - srv.vrf.PutExecResult(&ExecResult{ - Pool: poolID, - ExecTaskID: taskID, - Crashed: true, - Error: errors.New("VM crashed during the task execution"), - }) - } - delete(srv.vmTasksInProgress, vmTasksKey(poolID, vmID)) -} diff --git a/syz-verifier/rpcserver_test.go b/syz-verifier/rpcserver_test.go deleted file mode 100644 index 5a24a16b92ba..000000000000 --- a/syz-verifier/rpcserver_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/syzkaller/pkg/rpctype" -) - -func TestConnect(t *testing.T) { - vrf := createTestVerifier(t) - vrf.pools = make(map[int]*poolInfo) - vrf.pools[1] = &poolInfo{} - - a := &rpctype.RunnerConnectArgs{ - Pool: 1, - VM: 1, - } - - r := &rpctype.RunnerConnectRes{} - - if err := vrf.srv.Connect(a, r); err != nil { - t.Fatalf("srv.Connect failed: %v", err) - } - - if diff := cmp.Diff(&rpctype.RunnerConnectRes{CheckUnsupportedCalls: true}, r); diff != "" { - t.Errorf("connect result mismatch (-want +got):\n%s", diff) - } -} diff --git a/syz-verifier/stats.go b/syz-verifier/stats.go deleted file mode 100644 index 0ccd1a0ead04..000000000000 --- a/syz-verifier/stats.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "fmt" - "sort" - "strings" - "sync" - "time" - - "github.com/google/syzkaller/prog" -) - -type StatUint64 struct { - uint64 - mu *sync.Mutex -} - -func (s *StatUint64) Inc() { - s.mu.Lock() - defer s.mu.Unlock() - s.uint64++ -} - -func (s *StatUint64) Get() uint64 { - s.mu.Lock() - defer s.mu.Unlock() - return s.uint64 -} - -type StatTime struct { - time.Time - mu *sync.Mutex -} - -func (s *StatTime) Set(t time.Time) { - s.mu.Lock() - defer s.mu.Unlock() - s.Time = t -} - -func (s *StatTime) Get() time.Time { - s.mu.Lock() - defer s.mu.Unlock() - return s.Time -} - -type mapStringToCallStats map[string]*CallStats - -type StatMapStringToCallStats struct { - mapStringToCallStats - mu *sync.Mutex -} - -func (stat *StatMapStringToCallStats) IncCallOccurrenceCount(key string) { - stat.mu.Lock() - stat.mapStringToCallStats[key].Occurrences++ - stat.mu.Unlock() -} - -func (stat *StatMapStringToCallStats) AddState(key string, state ReturnState) { - stat.mu.Lock() - stat.mapStringToCallStats[key].States[state] = true - stat.mu.Unlock() -} - -func (stat *StatMapStringToCallStats) SetCallInfo(key string, info *CallStats) { - stat.mu.Lock() - stat.mapStringToCallStats[key] = info - stat.mu.Unlock() -} - -func (stat *StatMapStringToCallStats) totalExecuted() uint64 { - stat.mu.Lock() - defer stat.mu.Unlock() - - var t uint64 - for _, cs := range stat.mapStringToCallStats { - t += cs.Occurrences - } - return t -} - -func (stat *StatMapStringToCallStats) orderedStats() []*CallStats { - stat.mu.Lock() - defer stat.mu.Unlock() - - css := make([]*CallStats, 0) - for _, cs := range stat.mapStringToCallStats { - if cs.Mismatches > 0 { - css = append(css, cs) - } - } - - sort.Slice(css, func(i, j int) bool { - return getPercentage(css[i].Mismatches, css[i].Occurrences) > getPercentage(css[j].Mismatches, css[j].Occurrences) - }) - - return css -} - -// Stats encapsulates data for creating statistics related to the results -// of the verification process. -type Stats struct { - mu sync.Mutex - TotalCallMismatches StatUint64 - TotalProgs StatUint64 - ExecErrorProgs StatUint64 - FlakyProgs StatUint64 - MismatchingProgs StatUint64 - StartTime StatTime - // Calls stores statistics for all supported system calls. - Calls StatMapStringToCallStats -} - -func (stats *Stats) Init() *Stats { - stats.TotalCallMismatches.mu = &stats.mu - stats.TotalProgs.mu = &stats.mu - stats.ExecErrorProgs.mu = &stats.mu - stats.FlakyProgs.mu = &stats.mu - stats.MismatchingProgs.mu = &stats.mu - stats.StartTime.mu = &stats.mu - stats.Calls.mu = &stats.mu - return stats -} - -func (stats *Stats) MismatchesFound() bool { - return stats.TotalCallMismatches.Get() != 0 -} - -func (stats *Stats) IncCallMismatches(key string) { - stats.mu.Lock() - defer stats.mu.Unlock() - stats.Calls.mapStringToCallStats[key].Mismatches++ - stats.TotalCallMismatches.uint64++ -} - -// CallStats stores information used to generate statistics for the -// system call. -type CallStats struct { - // Name is the system call name. - Name string - // Mismatches stores the number of errno mismatches identified in the - // verified programs for this system call. - Mismatches uint64 - // Occurrences is the number of times the system call appeared in a - // verified program. - Occurrences uint64 - // States stores the kernel return state that caused mismatches. - States map[ReturnState]bool -} - -func (stats *CallStats) orderedStates() []string { - states := stats.States - ss := make([]string, 0, len(states)) - for s := range states { - ss = append(ss, fmt.Sprintf("%q", s)) - } - sort.Strings(ss) - return ss -} - -// MakeStats creates a stats object. -func MakeStats() *Stats { - return (&Stats{ - Calls: StatMapStringToCallStats{ - mapStringToCallStats: make(mapStringToCallStats), - }, - }).Init() -} - -func (stats *Stats) SetCallInfo(key string, info *CallStats) { - stats.Calls.SetCallInfo(key, info) -} - -// SetSyscallMask initializes the allowed syscall list. -func (stats *Stats) SetSyscallMask(calls map[*prog.Syscall]bool) { - stats.StartTime.Set(time.Now()) - - for syscall := range calls { - stats.SetCallInfo(syscall.Name, &CallStats{ - Name: syscall.Name, - States: make(map[ReturnState]bool)}) - } -} - -// ReportGlobalStats creates a report with statistics about all the -// supported system calls for which errno mismatches were identified in -// the verified programs, shown in decreasing order. -func (stats *Stats) GetTextDescription(deltaTime float64) string { - var result strings.Builder - - tc := stats.Calls.totalExecuted() - fmt.Fprintf(&result, "total number of mismatches / total number of calls "+ - "executed: %d / %d (%0.2f %%)\n\n", - stats.TotalCallMismatches.Get(), tc, getPercentage(stats.TotalCallMismatches.Get(), tc)) - fmt.Fprintf(&result, "programs / minute: %0.2f\n\n", float64(stats.TotalProgs.Get())/deltaTime) - fmt.Fprintf(&result, "true mismatching programs: %d / total number of programs: %d (%0.2f %%)\n", - stats.MismatchingProgs.Get(), stats.TotalProgs.Get(), - getPercentage(stats.MismatchingProgs.Get(), stats.TotalProgs.Get())) - fmt.Fprintf(&result, "flaky programs: %d / total number of programs: %d (%0.2f %%)\n\n", - stats.FlakyProgs.Get(), stats.TotalProgs.Get(), getPercentage(stats.FlakyProgs.Get(), stats.TotalProgs.Get())) - cs := stats.Calls.orderedStats() - for _, c := range cs { - fmt.Fprintf(&result, "%s\n", stats.getCallStatsTextDescription(c.Name)) - } - - return result.String() -} - -// getCallStatsTextDescription creates a report with the current statistics for call. -func (stats *Stats) getCallStatsTextDescription(call string) string { - totalCallMismatches := stats.TotalCallMismatches.Get() - stats.mu.Lock() - defer stats.mu.Unlock() - syscallStat, ok := stats.Calls.mapStringToCallStats[call] - if !ok { - return "" - } - syscallName, mismatches, occurrences := syscallStat.Name, syscallStat.Mismatches, syscallStat.Occurrences - return fmt.Sprintf("statistics for %s:\n"+ - "\t↳ mismatches of %s / occurrences of %s: %d / %d (%0.2f %%)\n"+ - "\t↳ mismatches of %s / total number of mismatches: "+ - "%d / %d (%0.2f %%)\n"+ - "\t↳ %d distinct states identified: %v\n", syscallName, syscallName, syscallName, mismatches, occurrences, - getPercentage(mismatches, occurrences), syscallName, mismatches, totalCallMismatches, - getPercentage(mismatches, totalCallMismatches), - len(syscallStat.States), syscallStat.orderedStates()) -} - -func getPercentage(value, total uint64) float64 { - return float64(value) / float64(total) * 100 -} diff --git a/syz-verifier/stats_test.go b/syz-verifier/stats_test.go deleted file mode 100644 index 724206f7efcb..000000000000 --- a/syz-verifier/stats_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -func dummyStats() *Stats { - return (&Stats{ - TotalProgs: StatUint64{uint64: 24}, - TotalCallMismatches: StatUint64{uint64: 10}, - FlakyProgs: StatUint64{uint64: 4}, - MismatchingProgs: StatUint64{uint64: 6}, - Calls: StatMapStringToCallStats{ - mapStringToCallStats: mapStringToCallStats{ - "foo": {"foo", 2, 8, map[ReturnState]bool{ - returnState(1, 7): true, - returnState(3, 7): true}}, - "bar": {"bar", 5, 6, map[ReturnState]bool{ - crashedReturnState(): true, - returnState(10, 7): true, - returnState(22, 7): true}}, - "tar": {"tar", 3, 4, map[ReturnState]bool{ - returnState(31, 7): true, - returnState(17, 7): true, - returnState(5, 7): true}}, - "biz": {"biz", 0, 2, map[ReturnState]bool{}}, - }, - }, - }).Init() -} - -func TestGetCallStatsTextDescription(t *testing.T) { - tests := []struct { - name, call, report string - }{ - { - name: "report for unsupported call", - call: "read", - report: "", - }, - { - name: "report for supported call", - call: "foo", - report: "statistics for foo:\n" + - "\t↳ mismatches of foo / occurrences of foo: 2 / 8 (25.00 %)\n" + - "\t↳ mismatches of foo / total number of mismatches: 2 / 10 (20.00 %)\n" + - "\t↳ 2 distinct states identified: " + - "[\"Flags: 7, Errno: 1 (operation not permitted)\" \"Flags: 7, Errno: 3 (no such process)\"]\n", - }, - } - - for _, test := range tests { - s := dummyStats() - t.Run(test.name, func(t *testing.T) { - got, want := s.getCallStatsTextDescription(test.call), test.report - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("s.getCallStatsTextDescription mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestGetTextDescription(t *testing.T) { - stats := dummyStats() - - got, want := stats.GetTextDescription(float64(10)), - "total number of mismatches / total number of calls "+ - "executed: 10 / 20 (50.00 %)\n\n"+ - "programs / minute: 2.40\n\n"+ - "true mismatching programs: 6 / total number of programs: 24 (25.00 %)\n"+ - "flaky programs: 4 / total number of programs: 24 (16.67 %)\n\n"+ - "statistics for bar:\n"+ - "\t↳ mismatches of bar / occurrences of bar: 5 / 6 (83.33 %)\n"+ - "\t↳ mismatches of bar / total number of mismatches: 5 / 10 (50.00 %)\n"+ - "\t↳ 3 distinct states identified: "+ - "[\"Crashed\" \"Flags: 7, Errno: 10 (no child processes)\" "+ - "\"Flags: 7, Errno: 22 (invalid argument)\"]\n\n"+ - "statistics for tar:\n"+ - "\t↳ mismatches of tar / occurrences of tar: 3 / 4 (75.00 %)\n"+ - "\t↳ mismatches of tar / total number of mismatches: 3 / 10 (30.00 %)\n"+ - "\t↳ 3 distinct states identified: "+ - "[\"Flags: 7, Errno: 17 (file exists)\" "+ - "\"Flags: 7, Errno: 31 (too many links)\" "+ - "\"Flags: 7, Errno: 5 (input/output error)\"]\n\n"+ - "statistics for foo:\n"+ - "\t↳ mismatches of foo / occurrences of foo: 2 / 8 (25.00 %)\n"+ - "\t↳ mismatches of foo / total number of mismatches: 2 / 10 (20.00 %)\n"+ - "\t↳ 2 distinct states identified: "+ - "[\"Flags: 7, Errno: 1 (operation not permitted)\" \"Flags: 7, Errno: 3 (no such process)\"]\n\n" - - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("s.GetTextDescription mismatch (-want +got):\n%s", diff) - } -} diff --git a/syz-verifier/utils_test.go b/syz-verifier/utils_test.go deleted file mode 100644 index 99aa6ef74025..000000000000 --- a/syz-verifier/utils_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2021 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. - -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - -package main - -import ( - "testing" - - "github.com/google/syzkaller/pkg/ipc" - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/prog" -) - -func createTestVerifier(t *testing.T) *Verifier { - target, err := prog.GetTarget("test", "64") - if err != nil { - t.Fatalf("failed to initialise test target: %v", err) - } - vrf := &Verifier{ - target: target, - choiceTable: target.DefaultChoiceTable(), - progIdx: 3, - reruns: 1, - } - vrf.resultsdir = makeTestResultDirectory(t) - vrf.stats = emptyTestStats() - vrf.srv, err = startRPCServer(vrf) - if err != nil { - t.Fatalf("failed to initialise RPC server: %v", err) - } - return vrf -} - -func getTestProgram(t *testing.T) *prog.Prog { - p := "breaks_returns()\n" + - "minimize$0(0x1, 0x1)\n" + - "test$res0()\n" - target := prog.InitTargetTest(t, "test", "64") - prog, err := target.Deserialize([]byte(p), prog.Strict) - if err != nil { - t.Fatalf("failed to deserialise test program: %v", err) - } - return prog -} - -func makeTestResultDirectory(t *testing.T) string { - return osutil.Abs(t.TempDir()) -} - -func makeExecResult(pool int, errnos []int, flags ...int) *ExecResult { - r := &ExecResult{Pool: pool, Info: ipc.ProgInfo{Calls: []ipc.CallInfo{}}} - for _, e := range errnos { - r.Info.Calls = append(r.Info.Calls, ipc.CallInfo{Errno: e}) - } - - for idx, f := range flags { - r.Info.Calls[idx].Flags = ipc.CallFlags(f) - } - return r -} - -func makeExecResultCrashed(pool int) *ExecResult { - return &ExecResult{Pool: pool, Crashed: true} -} - -func emptyTestStats() *Stats { - return (&Stats{ - Calls: StatMapStringToCallStats{ - mapStringToCallStats: mapStringToCallStats{ - "breaks_returns": {Name: "breaks_returns", States: map[ReturnState]bool{}}, - "minimize$0": {Name: "minimize$0", States: map[ReturnState]bool{}}, - "test$res0": {Name: "test$res0", States: map[ReturnState]bool{}}, - }, - }, - }).Init() -} - -func makeCallStats(name string, occurrences, mismatches uint64, states map[ReturnState]bool) *CallStats { - return &CallStats{Name: name, - Occurrences: occurrences, - Mismatches: mismatches, - States: states} -} - -func returnState(errno int, flags ...int) ReturnState { - rs := ReturnState{Errno: errno} - if flags != nil { - rs.Flags = ipc.CallFlags(flags[0]) - } - return rs -} - -func crashedReturnState() ReturnState { - return ReturnState{Crashed: true} -} diff --git a/syz-verifier/verifier.go b/syz-verifier/verifier.go index c800ccb7640a..344b26a8bac1 100644 --- a/syz-verifier/verifier.go +++ b/syz-verifier/verifier.go @@ -1,397 +1,529 @@ -// Copyright 2021 syzkaller project authors. All rights reserved. +// Copyright 2025 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. -// TODO: switch syz-verifier to use syz-fuzzer. - -//go:build ignore - package main import ( - "errors" + "context" "fmt" - "io" - "math/rand" - "os" - "os/signal" - "path/filepath" + "net" "strings" "sync" + "sync/atomic" "time" - "github.com/google/syzkaller/pkg/instance" + "github.com/google/syzkaller/pkg/db" + "github.com/google/syzkaller/pkg/flatrpc" + "github.com/google/syzkaller/pkg/fuzzer" + "github.com/google/syzkaller/pkg/fuzzer/queue" "github.com/google/syzkaller/pkg/log" - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/pkg/rpctype" + "github.com/google/syzkaller/pkg/manager" + "github.com/google/syzkaller/pkg/mgrconfig" + "github.com/google/syzkaller/pkg/report" + "github.com/google/syzkaller/pkg/rpcserver" + "github.com/google/syzkaller/pkg/signal" + "github.com/google/syzkaller/pkg/stat" + "github.com/google/syzkaller/pkg/vminfo" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/vm" + "github.com/google/syzkaller/vm/dispatcher" + "golang.org/x/sync/errgroup" ) -// Verifier TODO. type Verifier struct { - pools map[int]*poolInfo - vmStop chan bool - // Location of a working directory for all VMs for the syz-verifier process. - // Outputs here include: - // - /crashes//*: crash output files grouped by OS/Arch - // - /corpus.db: corpus with interesting programs - // - //instance-x: per VM instance temporary files - // grouped by OS/Arch - workdir string - crashdir string - resultsdir string - target *prog.Target - runnerBin string - executorBin string - progGeneratorInit sync.WaitGroup - choiceTable *prog.ChoiceTable - progIdx int - addr string - srv *RPCServer - calls map[*prog.Syscall]bool - reasons map[*prog.Syscall]string - reportReasons bool - stats *Stats - statsWrite io.Writer - newEnv bool - reruns int - - // We use single queue for every kernel environment. - tasksMutex sync.Mutex - onTaskAdded *sync.Cond - kernelEnvTasks [][]*ExecTaskQueue - taskFactory *ExecTaskFactory -} + // Configuration + cfg *mgrconfig.Config + target *prog.Target + debug bool -func (vrf *Verifier) Init() { - vrf.progGeneratorInit.Add(1) + // Kernel management + kernels []*Kernel + sources map[int]*queue.PlainQueue - vrf.onTaskAdded = sync.NewCond(&vrf.tasksMutex) + // HTTP server + http *manager.HTTPServer - vrf.kernelEnvTasks = make([][]*ExecTaskQueue, len(vrf.pools)) - for i := range vrf.kernelEnvTasks { - vrf.kernelEnvTasks[i] = make([]*ExecTaskQueue, EnvironmentsCount) - for j := range vrf.kernelEnvTasks[i] { - vrf.kernelEnvTasks[i][j] = MakeExecTaskQueue() - } - } + // Statistics and monitoring + servStats rpcserver.Stats + firstConnect atomic.Int64 // unix time, or 0 if not connected - srv, err := startRPCServer(vrf) - if err != nil { - log.Fatalf("failed to initialise RPC server: %v", err) - } - vrf.srv = srv + // Synchronization + mu sync.Mutex + + // Corpus management - load once at startup + corpusDB *db.DB + corpusPath string // Optional custom path to corpus.db + programs []*prog.Prog // Pre-loaded programs from corpus.db - vrf.taskFactory = MakeExecTaskFactory() } -func (vrf *Verifier) StartProgramsAnalysis() { - go func() { - vrf.progGeneratorInit.Wait() +// Kernel represents a single kernel configuration in the verification process. +type Kernel struct { + id int + name string + ctx context.Context + debug bool + cfg *mgrconfig.Config + reporter *report.Reporter + fresh bool + queueConfigured bool + + serv rpcserver.Server + servStats rpcserver.Stats + + pool *vm.Dispatcher + crashes chan *report.Report + + features chan flatrpc.Feature + enabledSyscalls chan map[*prog.Syscall]bool + optsChan chan flatrpc.ExecOpts + + http *manager.HTTPServer + source queue.Source + mu sync.Mutex +} + +// ============================================================================= +// Verifier +// ============================================================================= + +func (vrf *Verifier) RunVerifierFuzzer(ctx context.Context) error { + log.Logf(0, "starting verifier fuzzer") + eg, ctx := errgroup.WithContext(ctx) + Pools := make(map[string]*vm.Dispatcher, len(vrf.kernels)) - type AnalysisResult struct { - Diff []*ExecResult - Prog *prog.Prog + for idx, kernel := range vrf.kernels { + Pools[kernel.name] = kernel.pool + // Also set the first pool as default (empty key) for HTTP access without pool parameter + if idx == 0 { + Pools[""] = kernel.pool } + } - results := make(chan *AnalysisResult) - go func() { - for result := range results { - if result.Diff != nil { - vrf.SaveDiffResults(result.Diff, result.Prog) + for idx, kernel := range vrf.kernels { + if idx == 0 { + if kernel.cfg.HTTP != "" { + // Initialize HTTP server with the first kernel's configuration. + vrf.http = &manager.HTTPServer{ + Cfg: kernel.cfg, + StartTime: time.Now(), + Pools: Pools, } } - }() - - for i := 0; i < 100; i++ { - go func() { - for { - prog := vrf.generate() - results <- &AnalysisResult{ - vrf.TestProgram(prog), - prog, - } - } - }() } - }() + } + eg.Go(func() error { + vrf.preloadCorpus() + return nil + }) + eg.Go(func() error { + log.Logf(0, "starting vrf loop") + return vrf.Loop(ctx) + }) + return eg.Wait() } -func (vrf *Verifier) GetRunnerTask(kernel int, existing EnvDescr) *rpctype.ExecTask { - vrf.tasksMutex.Lock() - defer vrf.tasksMutex.Unlock() +func (vrf *Verifier) preloadCorpus() { + log.Logf(0, "loading corpus.db") - for { - for env := existing; env >= AnyEnvironment; env-- { - if task, ok := vrf.kernelEnvTasks[kernel][env].PopTask(); ok { - return task.ToRPC() + // Use custom corpus path if provided, otherwise use default from workdir + var corpusPath string + if vrf.corpusPath != "" { + corpusPath = vrf.corpusPath + log.Logf(0, "using corpus from: %s", corpusPath) + // Open the custom corpus.db file + corpusDB, err := db.Open(corpusPath, false) + if err != nil { + log.Fatalf("failed to open corpus database %s: %v", corpusPath, err) + } + vrf.corpusDB = corpusDB + + // Load programs from the corpus database + records := corpusDB.Records + vrf.programs = make([]*prog.Prog, 0, len(records)) + for _, rec := range records { + p, err := vrf.target.Deserialize(rec.Val, prog.NonStrict) + if err != nil { + log.Logf(0, "failed to deserialize program: %v", err) + continue } + vrf.programs = append(vrf.programs, p) + } + } else { + // Load using default manager.LoadSeeds from workdir + info, err := manager.LoadSeeds(vrf.cfg, false) + if err != nil { + log.Fatalf("failed to load corpus: %v", err) + } + vrf.corpusDB = info.CorpusDB + // Convert candidates to programs + vrf.programs = make([]*prog.Prog, len(info.Candidates)) + for i, candidate := range info.Candidates { + vrf.programs[i] = candidate.Prog } - - vrf.onTaskAdded.Wait() } + + log.Logf(0, "loaded %d corpus programs for verification", len(vrf.programs)) } -func (vrf *Verifier) PutExecResult(result *ExecResult) { - c := vrf.taskFactory.GetExecResultChan(result.ExecTaskID) - c <- result +// Loop starts the main verifier execution loop. +// It initializes the HTTP server and starts the fuzzing process. +func (vrf *Verifier) Loop(ctx context.Context) error { + log.Logf(0, "starting programs analysis") + g, ctx := errgroup.WithContext(ctx) + + if vrf.http != nil { + g.Go(func() error { + return vrf.http.Serve(ctx) + }) + } + log.Logf(0, "starting kernels listening loops") + for _, kctx := range vrf.kernels { + kctx := kctx + g.Go(func() error { + kctx.loop(ctx) + return nil + }) + } + + // Start the fuzzing synchronization loop + g.Go(func() error { + vrf.verifierLoop(ctx) + return nil + }) + + return g.Wait() } -// TestProgram return the results slice if some exec diff was found. -func (vrf *Verifier) TestProgram(prog *prog.Prog) (result []*ExecResult) { - steps := []EnvDescr{ - NewEnvironment, - NewEnvironment, +func (vrf *Verifier) verifierLoop(ctx context.Context) { + log.Logf(0, "starting fuzzing loop") + + // Wait for kernels to be ready and get enabled syscalls + totalEnabledSyscalls, _, err := vrf.waitForKernelsReady(ctx) + if err != nil { + return } - defer vrf.stats.TotalProgs.Inc() + // Record the time of the first connection and initialize syscall stats + vrf.firstConnect.Store(time.Now().Unix()) + statSyscalls := stat.New("syscalls", "Number of enabled syscalls", stat.Simple, stat.NoGraph, stat.Link("/syscalls")) + statSyscalls.Add(len(totalEnabledSyscalls)) - for i, env := range steps { - stepRes, err := vrf.Run(prog, env) - if err != nil { - vrf.stats.ExecErrorProgs.Inc() - return - } - vrf.AddCallsExecutionStat(stepRes, prog) - if stepRes[0].IsEqual(stepRes[1]) { - if i != 0 { - vrf.stats.FlakyProgs.Inc() - } + // Log enabled syscalls + log.Logf(1, "starting to compare %d programs from given corpus", len(vrf.programs)) + + // The main verifier loop: iterate through corpus programs and compare across kernels + for progIdx, prog := range vrf.programs { + // Check context + select { + case <-ctx.Done(): return + default: } - if i == len(steps)-1 { - vrf.stats.MismatchingProgs.Inc() - return stepRes + + log.Logf(1, "comparing program %d/%d", progIdx+1, len(vrf.programs)) + // Create requests for all kernels + requests, responses, wg := vrf.createRequests(prog) + + // Distribute to all kernels + for kernelID, source := range vrf.sources { + source.Submit(requests[kernelID]) } + + // Wait for all kernels to finish + wg.Wait() + + // Compare execution results + vrf.compareResults(prog, responses) } - return } -// Run sends the program for verification to execution queues and return -// result once it's ready. -// In case of time-out, return (nil, error). -func (vrf *Verifier) Run(prog *prog.Prog, env EnvDescr) (result []*ExecResult, err error) { - totalKernels := len(vrf.kernelEnvTasks) - result = make([]*ExecResult, totalKernels) - - wg := sync.WaitGroup{} - wg.Add(totalKernels) - for i := 0; i < totalKernels; i++ { - q := vrf.kernelEnvTasks[i][env] - - go func() { - defer wg.Done() - task := vrf.taskFactory.MakeExecTask(prog) - defer vrf.taskFactory.DeleteExecTask(task) - - vrf.tasksMutex.Lock() - q.PushTask(task) - vrf.onTaskAdded.Signal() - vrf.tasksMutex.Unlock() - - result[i] = <-task.ExecResultChan - }() - } - wg.Wait() +// waitForKernelsReady waits for all kernels to report their enabled syscalls and features. +// Returns the intersection of enabled syscalls across all kernels, whether comparisons are supported, +// and any error that occurred. +func (vrf *Verifier) waitForKernelsReady(ctx context.Context) (map[*prog.Syscall]bool, bool, error) { + log.Logf(0, "waiting for enabled syscalls and features") + var totalEnabledSyscalls map[*prog.Syscall]bool + comparisonFeature := true + + // Block until all kernels report enabled syscalls and features. + for idx, kernel := range vrf.kernels { + log.Logf(0, "waiting for kernel %s to be ready", kernel.cfg.Name) + var enabledSyscalls map[*prog.Syscall]bool + select { + case <-ctx.Done(): + return nil, false, ctx.Err() + case enabledSyscalls = <-kernel.enabledSyscalls: + } - for _, item := range result { - if item == nil { - err = errors.New("something went wrong and we exit w/o results") - return nil, err + if idx == 0 { + // Initialize with first kernel's syscalls + totalEnabledSyscalls = make(map[*prog.Syscall]bool) + for k, v := range enabledSyscalls { + totalEnabledSyscalls[k] = v + } + } else { + // Intersect: keep only syscalls enabled in ALL kernels + for k := range totalEnabledSyscalls { + if !enabledSyscalls[k] { + delete(totalEnabledSyscalls, k) + } + } } - if item.Error != nil { - err = item.Error - return nil, err + + var kernelFeatures flatrpc.Feature + select { + case <-ctx.Done(): + return nil, false, ctx.Err() + case kernelFeatures = <-kernel.features: } + + // Only enable if ALL kernels support it (intersection) + comparisonFeature = comparisonFeature && (kernelFeatures&flatrpc.FeatureComparisons != 0) + // TODO: Handle other features as needed (like fault injection, etc.) } - return result, nil + return totalEnabledSyscalls, comparisonFeature, nil } -// SetPrintStatAtSIGINT asks Stats object to report verification -// statistics when an os.Interrupt occurs and Exit(). -func (vrf *Verifier) SetPrintStatAtSIGINT() error { - if vrf.stats == nil { - return errors.New("verifier.stats is nil") +// compareResults compares execution results across all kernels and logs any mismatches. +func (vrf *Verifier) compareResults(prog *prog.Prog, responses []*queue.Result) { + // Get kernel 0 result as baseline + res0 := responses[0] + if res0 == nil { + return } - osSignalChannel := make(chan os.Signal, 1) - signal.Notify(osSignalChannel, os.Interrupt) - - go func() { - <-osSignalChannel - defer os.Exit(0) + // Compare errno results across kernels - only report true errno mismatches + for i := 1; i < len(responses); i++ { + res := responses[i] + if res == nil { + continue + } - totalExecutionTime := time.Since(vrf.stats.StartTime.Get()).Minutes() - if !vrf.stats.MismatchesFound() { - fmt.Fprint(vrf.statsWrite, "No mismatches occurred until syz-verifier was stopped.") - } else { - fmt.Fprintf(vrf.statsWrite, "%s", vrf.stats.GetTextDescription(totalExecutionTime)) + // Check if any syscall has different errno + // Only compare calls that were actually executed (not skipped/failed) + hasMismatch := false + mismatchCalls := []int{} + for callIdx := 0; callIdx < len(res0.Info.Calls) && callIdx < len(res.Info.Calls); callIdx++ { + call0 := res0.Info.Calls[callIdx] + call1 := res.Info.Calls[callIdx] + + // Only report if errno differs between successfully executed calls + if call0.Error != call1.Error { + hasMismatch = true + mismatchCalls = append(mismatchCalls, callIdx) + } } - }() - return nil -} + if hasMismatch { + log.Logf(0, "") + log.Logf(0, "========== ERRNO MISMATCH DETECTED ==========") + log.Logf(0, "Between: Kernel 0 (%s) and Kernel %d (%s)", + vrf.kernels[0].cfg.Name, i, vrf.kernels[i].cfg.Name) + log.Logf(0, "") + log.Logf(0, "Complete Program Sequence:") + log.Logf(0, "-------------------------------------------") + + // Serialize the entire program once to get properly formatted calls + progLines := strings.Split(strings.TrimSpace(string(prog.Serialize())), "\n") + + // Print full program with detailed call information + for callIdx, call := range prog.Calls { + isMismatch := false + for _, mc := range mismatchCalls { + if mc == callIdx { + isMismatch = true + break + } + } -func (vrf *Verifier) startInstances() { - for poolID, pi := range vrf.pools { - totalInstances := pi.pool.Count() - for vmID := 0; vmID < totalInstances; vmID++ { - go func(pi *poolInfo, poolID, vmID int) { - for { - vrf.createAndManageInstance(pi, poolID, vmID) + prefix := " " + if isMismatch { + prefix = ">>>" + } + + // Print syscall with arguments from the serialized program + callStr := "" + if callIdx < len(progLines) { + callStr = progLines[callIdx] + } else { + callStr = call.Meta.CallName + "(...)" + } + log.Logf(0, "%s [%d] %s", prefix, callIdx, callStr) + + // Print execution results + if callIdx < len(res0.Info.Calls) && callIdx < len(res.Info.Calls) { + call0 := res0.Info.Calls[callIdx] + call1 := res.Info.Calls[callIdx] + + if isMismatch { + log.Logf(0, "%s ┌─ %s: errno=%d, flags=0x%x", + prefix, vrf.kernels[0].cfg.Name, call0.Error, uint8(call0.Flags)) + log.Logf(0, "%s └─ %s: errno=%d, flags=0x%x", + prefix, vrf.kernels[i].cfg.Name, call1.Error, uint8(call1.Flags)) + } else { + log.Logf(0, "%s Result: errno=%d, flags=0x%x", + prefix, call0.Error, uint8(call0.Flags)) + } } - }(pi, poolID, vmID) + log.Logf(0, "") + } + + log.Logf(0, "-------------------------------------------") + log.Logf(0, "Kernel Outputs:") + log.Logf(0, " %s: %q", vrf.kernels[0].cfg.Name, res0.Output) + log.Logf(0, " %s: %q", vrf.kernels[i].cfg.Name, res.Output) + if res0.Err != nil || res.Err != nil { + log.Logf(0, "Execution Errors:") + log.Logf(0, " %s: %v", vrf.kernels[0].cfg.Name, res0.Err) + log.Logf(0, " %s: %v", vrf.kernels[i].cfg.Name, res.Err) + } + log.Logf(0, "=============================================") + log.Logf(0, "") } } } -func (vrf *Verifier) createAndManageInstance(pi *poolInfo, poolID, vmID int) { - inst, err := pi.pool.Create(vmID) - if err != nil { - log.Fatalf("failed to create instance: %v", err) +// createRequests creates execution requests for all kernels for a given program. +// Returns the map of requests, a slice to collect results, and a WaitGroup to track completion. +func (vrf *Verifier) createRequests(prog *prog.Prog) (map[int]*queue.Request, []*queue.Result, *sync.WaitGroup) { + requests := make(map[int]*queue.Request) + responses := make([]*queue.Result, len(vrf.sources)) + var wg sync.WaitGroup + wg.Add(len(vrf.sources)) + + for kernelID := range vrf.sources { + kid := kernelID + // Create request for each kernel + reqCopy := &queue.Request{ + Type: flatrpc.RequestTypeProgram, + Prog: prog.Clone(), + ReturnError: true, // Ensure error info is returned for comparison + ReturnOutput: true, // Return output for debugging mismatches + } + + reqCopy.OnDone(func(r *queue.Request, res *queue.Result) bool { + log.Logf(3, "kernel %d finished executing program", kid) + responses[kid] = res + wg.Done() + return true + }) + requests[kernelID] = reqCopy } - defer inst.Close() - defer vrf.srv.cleanup(poolID, vmID) - fwdAddr, err := inst.Forward(vrf.srv.port) + return requests, responses, &wg +} + +// ============================================================================= +// Kernel +// ============================================================================= + +func (kernel *Kernel) FuzzerInstance(ctx context.Context, inst *vm.Instance, updInfo dispatcher.UpdateInfo) { + index := inst.Index() + injectExec := make(chan bool, 10) + kernel.serv.CreateInstance(index, injectExec, updInfo) + reps, err := kernel.runInstance(ctx, inst, injectExec) + _, _ = kernel.serv.ShutdownInstance(index, len(reps) > 0) + if len(reps) > 0 { + // Just log crashes - syz-verifier focuses on behavioral differences, not crashes + select { + case <-ctx.Done(): + default: + log.Logf(0, "kernel %s: VM crash detected: %s", kernel.cfg.Name, reps[0].Title) + } + } if err != nil { - log.Fatalf("failed to set up port forwarding: %v", err) + log.Errorf("#%d run failed: %s", inst.Index(), err) } +} - runnerBin, err := inst.Copy(vrf.runnerBin) +func (kernel *Kernel) runInstance(ctx context.Context, inst *vm.Instance, + injectExec <-chan bool) ([]*report.Report, error) { + fwdAddr, err := inst.Forward(kernel.serv.Port()) if err != nil { - log.Fatalf(" failed to copy runner binary: %v", err) + return nil, fmt.Errorf("failed to setup port forwarding: %w", err) } - _, err = inst.Copy(vrf.executorBin) + executorBin, err := inst.Copy(kernel.cfg.ExecutorBin) if err != nil { - log.Fatalf("failed to copy executor binary: %v", err) + return nil, fmt.Errorf("failed to copy binary: %w", err) } - - cmd := instance.RunnerCmd(runnerBin, fwdAddr, vrf.target.OS, vrf.target.Arch, poolID, 0, false, vrf.newEnv) - _, _, err = inst.Run(pi.cfg.Timeouts.VMRunningTime, pi.Reporter, cmd, vm.ExitTimeout, vm.StopChan(vrf.vmStop)) + host, port, err := net.SplitHostPort(fwdAddr) if err != nil { - log.Fatalf("failed to start runner: %v", err) + return nil, fmt.Errorf("failed to parse manager's address") } - log.Logf(0, "reboot the VM in pool %d", poolID) + cmd := fmt.Sprintf("%v runner %v %v %v", executorBin, inst.Index(), host, port) + ctxTimeout, cancel := context.WithTimeout(ctx, kernel.cfg.Timeouts.VMRunningTime) + defer cancel() + _, rep, err := inst.Run(ctxTimeout, kernel.reporter, cmd, + vm.WithExitCondition(vm.ExitTimeout), + vm.WithInjectExecuting(injectExec), + vm.WithEarlyFinishCb(func() { + // Depending on the crash type and kernel config, fuzzing may continue + // running for several seconds even after kernel has printed a crash report. + // This litters the log and we want to prevent it. + kernel.serv.StopFuzzing(inst.Index()) + }), + ) + return rep, err } -// finalizeCallSet removes the system calls that are not supported from the set -// of enabled system calls and reports the reason to the io.Writer (either -// because the call is not supported by one of the kernels or because the call -// is missing some transitive dependencies). The resulting set of system calls -// will be used to build the prog.ChoiceTable. -func (vrf *Verifier) finalizeCallSet(w io.Writer) { - for c := range vrf.reasons { - delete(vrf.calls, c) - } - - // Find and report to the user all the system calls that need to be - // disabled due to missing dependencies. - _, disabled := vrf.target.TransitivelyEnabledCalls(vrf.calls) - for c, reason := range disabled { - vrf.reasons[c] = reason - delete(vrf.calls, c) - } - - if len(vrf.calls) == 0 { - log.Logf(0, "All enabled system calls are missing dependencies or not"+ - " supported by some kernels, exiting syz-verifier.") +func (kernel *Kernel) MachineChecked(features flatrpc.Feature, + enabledSyscalls map[*prog.Syscall]bool) (queue.Source, error) { + if len(enabledSyscalls) == 0 { + log.Logf(0, "no syscalls enabled for kernel %s", kernel.cfg.Name) + return nil, nil } + log.Logf(0, "kernel %s: sending enabled syscalls: %v", kernel.cfg.Name, enabledSyscalls) + kernel.enabledSyscalls <- enabledSyscalls - if !vrf.reportReasons { - return - } - - fmt.Fprintln(w, "The following calls have been disabled:") - for c, reason := range vrf.reasons { - fmt.Fprintf(w, "\t%v: %v\n", c.Name, reason) + log.Logf(0, "kernel %s: sending features: %v", kernel.cfg.Name, features) + kernel.features <- features + if kernel.http != nil { + kernel.http.EnabledSyscalls.Store(enabledSyscalls) } + log.Logf(0, "kernel %s: configuring source", kernel.cfg.Name) + opts := fuzzer.DefaultExecOpts(kernel.cfg, features, kernel.debug) + kernel.source = queue.DefaultOpts(kernel.source, opts) + kernel.mu.Lock() + kernel.queueConfigured = true + kernel.mu.Unlock() + return kernel.source, nil } -// AddCallsExecutionStat ignore all the calls after the first mismatch. -func (vrf *Verifier) AddCallsExecutionStat(results []*ExecResult, program *prog.Prog) { - rr := CompareResults(results, program) - for _, cr := range rr.Reports { - vrf.stats.Calls.IncCallOccurrenceCount(cr.Call) - } - - for _, cr := range rr.Reports { - if !cr.Mismatch { - continue - } - vrf.stats.IncCallMismatches(cr.Call) - for _, state := range cr.States { - if state0 := cr.States[0]; state0 != state { - vrf.stats.Calls.AddState(cr.Call, state) - vrf.stats.Calls.AddState(cr.Call, state0) - } - } - break +func (kernel *Kernel) loop(baseCtx context.Context) { + defer log.Logf(1, "syz-verifier (%s): kernel context loop terminated", kernel.cfg.Name) + if err := kernel.serv.Listen(); err != nil { + log.Fatalf("failed to start rpc server: %v", err) } -} - -// SaveDiffResults extract diff and save result on the persistent storage. -func (vrf *Verifier) SaveDiffResults(results []*ExecResult, program *prog.Prog) bool { - rr := CompareResults(results, program) - - oldest := 0 - var oldestTime time.Time - for i := 0; i < maxResultReports; i++ { - info, err := os.Stat(filepath.Join(vrf.resultsdir, fmt.Sprintf("result-%d", i))) + // Respect both the caller-provided context and global shutdown. + shutdown := vm.ShutdownCtx() + ctx, cancel := context.WithCancel(baseCtx) + go func() { + <-shutdown.Done() + cancel() + }() + go func() { + err := kernel.serv.Serve(ctx) if err != nil { - // There are only i-1 report files so the i-th one - // can be created. - oldest = i - break + log.Fatalf("%s", err) } - - // Otherwise, search for the oldest report file to - // overwrite as newer result reports are more useful. - if oldestTime.IsZero() || info.ModTime().Before(oldestTime) { - oldest = i - oldestTime = info.ModTime() - } - } - - err := osutil.WriteFile(filepath.Join(vrf.resultsdir, - fmt.Sprintf("result-%d", oldest)), createReport(rr, len(vrf.pools))) - if err != nil { - log.Logf(0, "failed to write result-%d file, err %v", oldest, err) - } - - log.Logf(0, "result-%d written successfully", oldest) - return true + }() + log.Logf(0, "serving rpc on tcp://%v", kernel.serv.Port()) + kernel.pool.Loop(ctx) } -// generate returns a newly generated program or error. -func (vrf *Verifier) generate() *prog.Prog { - vrf.progGeneratorInit.Wait() - - rnd := rand.New(rand.NewSource(time.Now().UnixNano() + 1e12)) - return vrf.target.Generate(rnd, prog.RecommendedCalls, vrf.choiceTable) +func (kernel *Kernel) CoverageFilter(modules []*vminfo.KernelModule) ([]uint64, error) { + // No coverage filtering needed in corpus exercise mode + // Return empty PC set since we're not discovering coverage + return []uint64{}, nil } -func createReport(rr *ResultReport, pools int) []byte { - calls := strings.Split(rr.Prog, "\n") - calls = calls[:len(calls)-1] - - data := "ERRNO mismatches found for program:\n\n" - for idx, cr := range rr.Reports { - tick := "[=]" - if cr.Mismatch { - tick = "[!]" - } - data += fmt.Sprintf("%s %s\n", tick, calls[idx]) - - // Ensure results are ordered by pool index. - for i := 0; i < pools; i++ { - state := cr.States[i] - data += fmt.Sprintf("\t↳ Pool: %d, %s\n", i, state) - } - - data += "\n" - } +func (kernel *Kernel) MaxSignal() signal.Signal { + // No signal tracking in corpus exercise mode + return nil +} - return []byte(data) +func (kernel *Kernel) BugFrames() (leaks, races []string) { + return nil, nil }