From 582e1f0d1d51b9237d2dedfcb4c1540b849da8c2 Mon Sep 17 00:00:00 2001 From: Andrey Konovalov Date: Wed, 21 Nov 2018 16:44:29 +0100 Subject: [PATCH] ipc: add ProgInfo struct This patch add a new struct ProgInfo that for now holds info about each call in a program []CallInfo, but in the future will be expanded with remote coverage info. Update all the callers to use the new interface as well. --- pkg/ipc/ipc.go | 25 ++++++++++-------- pkg/ipc/ipc_test.go | 12 ++++----- pkg/rpctype/rpctype.go | 2 +- pkg/runtest/run.go | 46 +++++++++++++++++----------------- syz-fuzzer/fuzzer.go | 4 +-- syz-fuzzer/proc.go | 20 +++++++-------- syz-fuzzer/testing.go | 10 ++++---- tools/syz-execprog/execprog.go | 14 +++++------ 8 files changed, 69 insertions(+), 64 deletions(-) diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index e4aed56c..0db32dfb 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -93,6 +93,11 @@ type CallInfo struct { Errno int // call errno (0 if the call was successful) } +type ProgInfo struct { + Calls []CallInfo + // TODO: remote coverage would go here. +} + type Env struct { in []byte out []byte @@ -215,7 +220,7 @@ var rateLimit = time.NewTicker(1 * time.Second) // failed: true if executor has detected a kernel bug // hanged: program hanged and was killed // err0: failed to start process, or executor has detected a logical error -func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) { +func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info *ProgInfo, failed, hanged bool, err0 error) { // Copy-in serialized program. progSize, err := p.SerializeForExec(env.in) if err != nil { @@ -267,9 +272,9 @@ func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallIn // addFallbackSignal computes simple fallback signal in cases we don't have real coverage signal. // We use syscall number or-ed with returned errno value as signal. // At least this gives us all combinations of syscall+errno. -func addFallbackSignal(p *prog.Prog, info []CallInfo) { - callInfos := make([]prog.CallInfo, len(info)) - for i, inf := range info { +func addFallbackSignal(p *prog.Prog, info *ProgInfo) { + callInfos := make([]prog.CallInfo, len(info.Calls)) + for i, inf := range info.Calls { if inf.Flags&CallExecuted != 0 { callInfos[i].Flags |= prog.CallExecuted } @@ -283,30 +288,30 @@ func addFallbackSignal(p *prog.Prog, info []CallInfo) { } p.FallbackSignal(callInfos) for i, inf := range callInfos { - info[i].Signal = inf.Signal + info.Calls[i].Signal = inf.Signal } } -func (env *Env) parseOutput(p *prog.Prog) ([]CallInfo, error) { +func (env *Env) parseOutput(p *prog.Prog) (*ProgInfo, error) { out := env.out ncmd, ok := readUint32(&out) if !ok { return nil, fmt.Errorf("failed to read number of calls") } - info := make([]CallInfo, len(p.Calls)) + info := &ProgInfo{Calls: make([]CallInfo, len(p.Calls))} for i := uint32(0); i < ncmd; i++ { if len(out) < int(unsafe.Sizeof(callReply{})) { return nil, fmt.Errorf("failed to read call %v reply", i) } reply := *(*callReply)(unsafe.Pointer(&out[0])) out = out[unsafe.Sizeof(callReply{}):] - if int(reply.index) >= len(info) { - return nil, fmt.Errorf("bad call %v index %v/%v", i, reply.index, len(info)) + if int(reply.index) >= len(info.Calls) { + return nil, fmt.Errorf("bad call %v index %v/%v", i, reply.index, len(info.Calls)) } if num := p.Calls[reply.index].Meta.ID; int(reply.num) != num { return nil, fmt.Errorf("wrong call %v num %v/%v", i, reply.num, num) } - inf := &info[reply.index] + inf := &info.Calls[reply.index] if inf.Flags != 0 || inf.Signal != nil { return nil, fmt.Errorf("duplicate reply for call %v/%v/%v", i, reply.index, reply.num) } diff --git a/pkg/ipc/ipc_test.go b/pkg/ipc/ipc_test.go index 55a8749b..dba14b19 100644 --- a/pkg/ipc/ipc_test.go +++ b/pkg/ipc/ipc_test.go @@ -103,11 +103,11 @@ func TestExecute(t *testing.T) { if failed { t.Fatalf("program failed:\n%s", output) } - if len(info) == 0 { + if len(info.Calls) == 0 { t.Fatalf("no calls executed:\n%s", output) } - if info[0].Errno != 0 { - t.Fatalf("simple call failed: %v\n%s", info[0].Errno, output) + if info.Calls[0].Errno != 0 { + t.Fatalf("simple call failed: %v\n%s", info.Calls[0].Errno, output) } if len(output) != 0 { t.Fatalf("output on empty program") @@ -152,12 +152,12 @@ func TestParallel(t *testing.T) { err = fmt.Errorf("program failed:\n%s", output) return } - if len(info) == 0 { + if len(info.Calls) == 0 { err = fmt.Errorf("no calls executed:\n%s", output) return } - if info[0].Errno != 0 { - err = fmt.Errorf("simple call failed: %v\n%s", info[0].Errno, output) + if info.Calls[0].Errno != 0 { + err = fmt.Errorf("simple call failed: %v\n%s", info.Calls[0].Errno, output) return } if len(output) != 0 { diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index 68f85eed..66b61601 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -124,6 +124,6 @@ type RunTestDoneArgs struct { Name string ID int Output []byte - Info [][]ipc.CallInfo + Info []*ipc.ProgInfo Error string } diff --git a/pkg/runtest/run.go b/pkg/runtest/run.go index 0ec0bb12..ebebbf25 100644 --- a/pkg/runtest/run.go +++ b/pkg/runtest/run.go @@ -41,10 +41,10 @@ type RunRequest struct { Done chan struct{} Output []byte - Info [][]ipc.CallInfo + Info []*ipc.ProgInfo Err error - results []ipc.CallInfo + results *ipc.ProgInfo name string broken string skip string @@ -208,7 +208,7 @@ func (ctx *Context) generatePrograms(progs chan *RunRequest) error { return nil } -func (ctx *Context) parseProg(filename string) (*prog.Prog, map[string]bool, []ipc.CallInfo, error) { +func (ctx *Context) parseProg(filename string) (*prog.Prog, map[string]bool, *ipc.ProgInfo, error) { data, err := ioutil.ReadFile(filepath.Join(ctx.Dir, filename)) if err != nil { return nil, nil, nil, fmt.Errorf("failed to read %v: %v", filename, err) @@ -242,28 +242,28 @@ func (ctx *Context) parseProg(filename string) (*prog.Prog, map[string]bool, []i "EACCES": 13, "EINVAL": 22, } - info := make([]ipc.CallInfo, len(p.Calls)) + info := &ipc.ProgInfo{Calls: make([]ipc.CallInfo, len(p.Calls))} for i, call := range p.Calls { - info[i].Flags |= ipc.CallExecuted | ipc.CallFinished + info.Calls[i].Flags |= ipc.CallExecuted | ipc.CallFinished switch call.Comment { case "blocked": - info[i].Flags |= ipc.CallBlocked + info.Calls[i].Flags |= ipc.CallBlocked case "unfinished": - info[i].Flags &^= ipc.CallFinished + info.Calls[i].Flags &^= ipc.CallFinished default: res, ok := errnos[call.Comment] if !ok { return nil, nil, nil, fmt.Errorf("%v: unknown comment %q", filename, call.Comment) } - info[i].Errno = res + info.Calls[i].Errno = res } } return p, requires, info, nil } func (ctx *Context) produceTest(progs chan *RunRequest, req *RunRequest, name string, - properties, requires map[string]bool, results []ipc.CallInfo) { + properties, requires map[string]bool, results *ipc.ProgInfo) { req.name = name req.results = results if match(properties, requires) { @@ -386,8 +386,8 @@ func checkResult(req *RunRequest) error { } calls := make(map[string]bool) for run, info := range req.Info { - for i, inf := range info { - want := req.results[i] + for i, inf := range info.Calls { + want := req.results.Calls[i] for flag, what := range map[ipc.CallFlags]string{ ipc.CallExecuted: "executed", ipc.CallBlocked: "blocked", @@ -433,13 +433,13 @@ func checkResult(req *RunRequest) error { return nil } -func parseBinOutput(req *RunRequest) ([][]ipc.CallInfo, error) { - var infos [][]ipc.CallInfo +func parseBinOutput(req *RunRequest) ([]*ipc.ProgInfo, error) { + var infos []*ipc.ProgInfo s := bufio.NewScanner(bytes.NewReader(req.Output)) re := regexp.MustCompile("^### call=([0-9]+) errno=([0-9]+)$") for s.Scan() { if s.Text() == "### start" { - infos = append(infos, make([]ipc.CallInfo, len(req.P.Calls))) + infos = append(infos, &ipc.ProgInfo{Calls: make([]ipc.CallInfo, len(req.P.Calls))}) } match := re.FindSubmatch(s.Bytes()) if match == nil { @@ -459,18 +459,18 @@ func parseBinOutput(req *RunRequest) ([][]ipc.CallInfo, error) { string(match[2]), s.Text()) } info := infos[len(infos)-1] - if call >= uint64(len(info)) { + if call >= uint64(len(info.Calls)) { return nil, fmt.Errorf("bad call index %v", call) } - if info[call].Flags != 0 { + if info.Calls[call].Flags != 0 { return nil, fmt.Errorf("double result for call %v", call) } - info[call].Flags |= ipc.CallExecuted | ipc.CallFinished - info[call].Errno = int(errno) + info.Calls[call].Flags |= ipc.CallExecuted | ipc.CallFinished + info.Calls[call].Errno = int(errno) } for _, info := range infos { - for i := range info { - info[i].Flags |= ipc.CallExecuted + for i := range info.Calls { + info.Calls[i].Flags |= ipc.CallExecuted } } return infos, nil @@ -509,10 +509,10 @@ func RunTest(req *RunRequest, executor string) { req.Err = fmt.Errorf("run %v: hanged", run) return } - for i := range info { + for i := range info.Calls { // Detach them because they point into the output shmem region. - info[i].Signal = append([]uint32{}, info[i].Signal...) - info[i].Cover = append([]uint32{}, info[i].Cover...) + info.Calls[i].Signal = append([]uint32{}, info.Calls[i].Signal...) + info.Calls[i].Cover = append([]uint32{}, info.Calls[i].Cover...) } req.Info = append(req.Info, info) } diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index fce4b48b..177755b1 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -384,10 +384,10 @@ func (fuzzer *Fuzzer) corpusSignalDiff(sign signal.Signal) signal.Signal { return fuzzer.corpusSignal.Diff(sign) } -func (fuzzer *Fuzzer) checkNewSignal(p *prog.Prog, info []ipc.CallInfo) (calls []int) { +func (fuzzer *Fuzzer) checkNewSignal(p *prog.Prog, info *ipc.ProgInfo) (calls []int) { fuzzer.signalMu.RLock() defer fuzzer.signalMu.RUnlock() - for i, inf := range info { + for i, inf := range info.Calls { diff := fuzzer.maxSignal.DiffRaw(inf.Signal, signalPrio(p.Target, p.Calls[i], &inf)) if diff.Empty() { continue diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go index 8a0615be..2ad6a172 100644 --- a/syz-fuzzer/proc.go +++ b/syz-fuzzer/proc.go @@ -122,8 +122,8 @@ func (proc *Proc) triageInput(item *WorkTriage) { notexecuted := 0 for i := 0; i < signalRuns; i++ { info := proc.executeRaw(proc.execOptsCover, item.p, StatTriage) - if len(info) == 0 || len(info[item.call].Signal) == 0 || - item.info.Errno == 0 && info[item.call].Errno != 0 { + if len(info.Calls) == 0 || len(info.Calls[item.call].Signal) == 0 || + item.info.Errno == 0 && info.Calls[item.call].Errno != 0 { // The call was not executed or failed. notexecuted++ if notexecuted > signalRuns/2+1 { @@ -131,7 +131,7 @@ func (proc *Proc) triageInput(item *WorkTriage) { } continue } - inf := info[item.call] + inf := info.Calls[item.call] thisSignal := signal.FromRaw(inf.Signal, signalPrio(item.p.Target, call, &inf)) newSignal = newSignal.Intersection(thisSignal) // Without !minimized check manager starts losing some considerable amount @@ -146,10 +146,10 @@ func (proc *Proc) triageInput(item *WorkTriage) { func(p1 *prog.Prog, call1 int) bool { for i := 0; i < minimizeAttempts; i++ { info := proc.execute(proc.execOptsNoCollide, p1, ProgNormal, StatMinimize) - if len(info) == 0 || len(info[call1].Signal) == 0 { + if len(info.Calls) == 0 || len(info.Calls[call1].Signal) == 0 { continue // The call was not executed. } - inf := info[call1] + inf := info.Calls[call1] if item.info.Errno == 0 && inf.Errno != 0 { // Don't minimize calls from successful to unsuccessful. // Successful calls are much more valuable. @@ -207,7 +207,7 @@ func (proc *Proc) failCall(p *prog.Prog, call int) { opts.FaultCall = call opts.FaultNth = nth info := proc.executeRaw(&opts, p, StatSmash) - if info != nil && len(info) > call && info[call].Flags&ipc.CallFaultInjected == 0 { + if info != nil && len(info.Calls) > call && info.Calls[call].Flags&ipc.CallFaultInjected == 0 { break } } @@ -224,16 +224,16 @@ func (proc *Proc) executeHintSeed(p *prog.Prog, call int) { // Then mutate the initial program for every match between // a syscall argument and a comparison operand. // Execute each of such mutants to check if it gives new coverage. - p.MutateWithHints(call, info[call].Comps, func(p *prog.Prog) { + p.MutateWithHints(call, info.Calls[call].Comps, func(p *prog.Prog) { log.Logf(1, "#%v: executing comparison hint", proc.pid) proc.execute(proc.execOpts, p, ProgNormal, StatHint) }) } -func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, stat Stat) []ipc.CallInfo { +func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, stat Stat) *ipc.ProgInfo { info := proc.executeRaw(execOpts, p, stat) for _, callIndex := range proc.fuzzer.checkNewSignal(p, info) { - info := info[callIndex] + info := info.Calls[callIndex] // info.Signal points to the output shmem region, detach it before queueing. info.Signal = append([]uint32{}, info.Signal...) // None of the caller use Cover, so just nil it instead of detaching. @@ -249,7 +249,7 @@ func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, return info } -func (proc *Proc) executeRaw(opts *ipc.ExecOpts, p *prog.Prog, stat Stat) []ipc.CallInfo { +func (proc *Proc) executeRaw(opts *ipc.ExecOpts, p *prog.Prog, stat Stat) *ipc.ProgInfo { if opts.Flags&ipc.FlagDedupCover == 0 { log.Fatalf("dedup cover is not enabled") } diff --git a/syz-fuzzer/testing.go b/syz-fuzzer/testing.go index afc7a48f..b71e2813 100644 --- a/syz-fuzzer/testing.go +++ b/syz-fuzzer/testing.go @@ -237,16 +237,16 @@ func checkSimpleProgram(args *checkArgs) error { if failed { return fmt.Errorf("program failed:\n%s", output) } - if len(info) == 0 { + if len(info.Calls) == 0 { return fmt.Errorf("no calls executed:\n%s", output) } - if info[0].Errno != 0 { - return fmt.Errorf("simple call failed: %+v\n%s", info[0], output) + if info.Calls[0].Errno != 0 { + return fmt.Errorf("simple call failed: %+v\n%s", info.Calls[0], output) } - if args.ipcConfig.Flags&ipc.FlagSignal != 0 && len(info[0].Signal) < 2 { + if args.ipcConfig.Flags&ipc.FlagSignal != 0 && len(info.Calls[0].Signal) < 2 { return fmt.Errorf("got no coverage:\n%s", output) } - if len(info[0].Signal) < 1 { + if len(info.Calls[0].Signal) < 1 { return fmt.Errorf("got no fallback coverage:\n%s", output) } return nil diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index 1a42a827..1ece85c1 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -148,7 +148,7 @@ func (ctx *Context) execute(pid int, env *ipc.Env, entry *prog.LogEntry) { log.Logf(0, "result: failed=%v hanged=%v err=%v\n\n%s", failed, hanged, err, output) } - if len(info) != 0 { + if len(info.Calls) != 0 { ctx.printCallResults(info) if *flagHints { ctx.printHints(entry.P, info) @@ -173,8 +173,8 @@ func (ctx *Context) logProgram(pid int, p *prog.Prog, callOpts *ipc.ExecOpts) { ctx.logMu.Unlock() } -func (ctx *Context) printCallResults(info []ipc.CallInfo) { - for i, inf := range info { +func (ctx *Context) printCallResults(info *ipc.ProgInfo) { + for i, inf := range info.Calls { if inf.Flags&ipc.CallExecuted == 0 { continue } @@ -193,13 +193,13 @@ func (ctx *Context) printCallResults(info []ipc.CallInfo) { } } -func (ctx *Context) printHints(p *prog.Prog, info []ipc.CallInfo) { +func (ctx *Context) printHints(p *prog.Prog, info *ipc.ProgInfo) { ncomps, ncandidates := 0, 0 for i := range p.Calls { if *flagOutput { fmt.Printf("call %v:\n", i) } - comps := info[i].Comps + comps := info.Calls[i].Comps for v, args := range comps { ncomps += len(args) if *flagOutput { @@ -220,8 +220,8 @@ func (ctx *Context) printHints(p *prog.Prog, info []ipc.CallInfo) { log.Logf(0, "ncomps=%v ncandidates=%v", ncomps, ncandidates) } -func (ctx *Context) dumpCoverage(coverFile string, info []ipc.CallInfo) { - for i, inf := range info { +func (ctx *Context) dumpCoverage(coverFile string, info *ipc.ProgInfo) { + for i, inf := range info.Calls { log.Logf(0, "call #%v: signal %v, coverage %v", i, len(inf.Signal), len(inf.Cover)) if len(inf.Cover) == 0 { continue