diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2024-04-15 14:54:59 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2024-04-16 14:20:36 +0000 |
| commit | 4cd91fc0b5007710bf0f38de6319ce24c31a52e5 (patch) | |
| tree | 025210f316d065a63e680a054ab04c017ebc7315 | |
| parent | 7e9780e93983e03547322aab489429ae4a7d2fa3 (diff) | |
pkg/ipc: pass only exec encoding to Exec
Does not require passing text program to ipc.Env.Exec.
Make it possible to provide just the exec encoding.
This requires moving fallback coverage to the host
since it need the program.
| -rw-r--r-- | pkg/ipc/ipc.go | 51 | ||||
| -rw-r--r-- | pkg/runtest/run.go | 4 | ||||
| -rw-r--r-- | prog/encodingexec.go | 10 | ||||
| -rw-r--r-- | prog/encodingexec_test.go | 4 | ||||
| -rw-r--r-- | syz-fuzzer/proc.go | 21 | ||||
| -rw-r--r-- | syz-fuzzer/testing.go | 3 | ||||
| -rw-r--r-- | syz-manager/rpc.go | 31 | ||||
| -rw-r--r-- | tools/syz-execprog/execprog.go | 15 |
8 files changed, 75 insertions, 64 deletions
diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index ea2e22569..417f27c24 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -247,13 +247,14 @@ func (env *Env) Close() error { } } -// Exec starts executor binary to execute program p and returns information about the execution: +// Exec starts executor binary to execute program stored in progData in exec encoding +// and returns information about the execution: // output: process output // info: per-call info // hanged: program hanged and was killed // err0: failed to start the process or bug in executor itself. -func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info *ProgInfo, hanged bool, err0 error) { - progData, err := p.SerializeForExec() +func (env *Env) ExecProg(opts *ExecOpts, progData []byte) (output []byte, info *ProgInfo, hanged bool, err0 error) { + ncalls, err := prog.ExecCallCount(progData) if err != nil { err0 = err return @@ -284,12 +285,9 @@ func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info *ProgInf return } - info, err0 = env.parseOutput(p, opts) + info, err0 = env.parseOutput(opts, ncalls) if info != nil { info.Elapsed = elapsed - if env.config.Flags&FlagSignal == 0 { - addFallbackSignal(p, info) - } } if !env.config.UseForkServer { env.cmd.close() @@ -298,6 +296,15 @@ func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info *ProgInf return } +func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info *ProgInfo, hanged bool, err0 error) { + progData, err := p.SerializeForExec() + if err != nil { + err0 = err + return + } + return env.ExecProg(opts, progData) +} + func (env *Env) ForceRestart() { if env.cmd != nil { env.cmd.close() @@ -328,36 +335,13 @@ var ( rateLimiter <-chan time.Time ) -// 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 *ProgInfo) { - callInfos := make([]prog.CallInfo, len(info.Calls)) - for i, inf := range info.Calls { - if inf.Flags&CallExecuted != 0 { - callInfos[i].Flags |= prog.CallExecuted - } - if inf.Flags&CallFinished != 0 { - callInfos[i].Flags |= prog.CallFinished - } - if inf.Flags&CallBlocked != 0 { - callInfos[i].Flags |= prog.CallBlocked - } - callInfos[i].Errno = inf.Errno - } - p.FallbackSignal(callInfos) - for i, inf := range callInfos { - info.Calls[i].Signal = inf.Signal - } -} - -func (env *Env) parseOutput(p *prog.Prog, opts *ExecOpts) (*ProgInfo, error) { +func (env *Env) parseOutput(opts *ExecOpts, ncalls int) (*ProgInfo, error) { out := env.out ncmd, ok := readUint32(&out) if !ok { return nil, fmt.Errorf("failed to read number of calls") } - info := &ProgInfo{Calls: make([]CallInfo, len(p.Calls))} + info := &ProgInfo{Calls: make([]CallInfo, ncalls)} extraParts := make([]CallInfo, 0) for i := uint32(0); i < ncmd; i++ { if len(out) < int(unsafe.Sizeof(callReply{})) { @@ -373,9 +357,6 @@ func (env *Env) parseOutput(p *prog.Prog, opts *ExecOpts) (*ProgInfo, error) { 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.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/runtest/run.go b/pkg/runtest/run.go index 0651b3f42..751ecf74f 100644 --- a/pkg/runtest/run.go +++ b/pkg/runtest/run.go @@ -589,8 +589,8 @@ func checkCallResult(req *RunRequest, isC bool, run, call int, info *ipc.ProgInf } calls[callName] = true } else { - if len(inf.Signal) == 0 { - return fmt.Errorf("run %v: call %v: no fallback signal", run, call) + if len(inf.Signal) != 0 { + return fmt.Errorf("run %v: call %v: got %v unwanted signal", run, call, len(inf.Signal)) } } return nil diff --git a/prog/encodingexec.go b/prog/encodingexec.go index 40cfc9592..fb8e5fbaf 100644 --- a/prog/encodingexec.go +++ b/prog/encodingexec.go @@ -23,7 +23,6 @@ package prog import ( "encoding/binary" - "errors" "fmt" "reflect" "sort" @@ -63,8 +62,6 @@ const ( execMaxCommands = 1000 // executor knows about this constant (kMaxCommands) ) -var ErrExecBufferTooSmall = errors.New("encodingexec: provided buffer is too small") - // SerializeForExec serializes program p for execution by process pid into the provided buffer. // Returns number of bytes written to the buffer. // If the provided buffer is too small for the program an error is returned. @@ -81,8 +78,11 @@ func (p *Prog) SerializeForExec() ([]byte, error) { w.serializeCall(c) } w.write(execInstrEOF) - if len(w.buf) > ExecBufferSize || w.copyoutSeq > execMaxCommands { - return nil, ErrExecBufferTooSmall + if len(w.buf) > ExecBufferSize { + return nil, fmt.Errorf("encodingexec: too large program (%v/%v)", len(w.buf), ExecBufferSize) + } + if w.copyoutSeq > execMaxCommands { + return nil, fmt.Errorf("encodingexec: too many resources (%v/%v)", w.copyoutSeq, execMaxCommands) } return w.buf, nil } diff --git a/prog/encodingexec_test.go b/prog/encodingexec_test.go index 357cca18b..5dfcbd0e3 100644 --- a/prog/encodingexec_test.go +++ b/prog/encodingexec_test.go @@ -752,8 +752,8 @@ func TestSerializeForExecOverflow(t *testing.T) { t.Fatal(err) } _, err = p.SerializeForExec() - if test.overflow && err != ErrExecBufferTooSmall { - t.Fatalf("want overflow but got %v", err) + if test.overflow && err == nil { + t.Fatalf("want overflow but got no error") } if !test.overflow && err != nil { t.Fatalf("want no overflow but got %v", err) diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go index 86f798792..072a4b66f 100644 --- a/syz-fuzzer/proc.go +++ b/syz-fuzzer/proc.go @@ -60,7 +60,7 @@ func (proc *Proc) loop() { (req.NeedCover || req.NeedSignal != rpctype.NoSignal || req.NeedHints) { proc.env.ForceRestart() } - info, try := proc.executeRaw(&opts, req.ID, req.prog) + info, try := proc.execute(&opts, req.ID, req.prog) // Let's perform signal filtering in a separate thread to get the most // exec/sec out of a syz-executor instance. proc.tool.results <- executionResult{ @@ -87,7 +87,15 @@ func (proc *Proc) nextRequest() executionRequest { return req } -func (proc *Proc) executeRaw(opts *ipc.ExecOpts, progID int64, p *prog.Prog) (*ipc.ProgInfo, int) { +func (proc *Proc) execute(opts *ipc.ExecOpts, progID int64, p *prog.Prog) (*ipc.ProgInfo, int) { + progData, err := p.SerializeForExec() + if err != nil { + // It's bad if we systematically fail to serialize programs, + // but so far we don't have a better handling than counting this. + // This error is observed a lot on the seeded syz_mount_image calls. + proc.tool.bufferTooSmall.Add(1) + return nil, 0 + } for try := 0; ; try++ { var output []byte var info *ipc.ProgInfo @@ -99,17 +107,10 @@ func (proc *Proc) executeRaw(opts *ipc.ExecOpts, progID int64, p *prog.Prog) (*i // Limit concurrency. ticket := proc.tool.gate.Enter() proc.tool.startExecutingCall(progID, proc.pid, try) - output, info, hanged, err = proc.env.Exec(opts, p) + output, info, hanged, err = proc.env.ExecProg(opts, progData) proc.tool.gate.Leave(ticket) } if err != nil { - if err == prog.ErrExecBufferTooSmall { - // It's bad if we systematically fail to serialize programs, - // but so far we don't have a better handling than counting this. - // This error is observed a lot on the seeded syz_mount_image calls. - proc.tool.bufferTooSmall.Add(1) - return nil, try - } if try > 10 { log.SyzFatalf("executor %v failed %v times: %v\n%s", proc.pid, try, err, output) } diff --git a/syz-fuzzer/testing.go b/syz-fuzzer/testing.go index 4a8fc7e93..467d1b1a9 100644 --- a/syz-fuzzer/testing.go +++ b/syz-fuzzer/testing.go @@ -272,9 +272,6 @@ func checkSimpleProgram(args *checkArgs, features *host.Features) error { 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.Calls[0].Signal) < 1 { - return fmt.Errorf("got no fallback coverage:\n%s", output) - } return nil } diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go index cd151fd78..4a260738e 100644 --- a/syz-manager/rpc.go +++ b/syz-manager/rpc.go @@ -14,6 +14,7 @@ import ( "github.com/google/syzkaller/pkg/cover" "github.com/google/syzkaller/pkg/fuzzer" "github.com/google/syzkaller/pkg/host" + "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/rpctype" @@ -269,7 +270,7 @@ func (serv *RPCServer) ExchangeInfo(a *rpctype.ExchangeInfoRequest, r *rpctype.E } for _, result := range a.Results { - runner.doneRequest(result, fuzzer) + runner.doneRequest(result, fuzzer, serv.cfg.Cover) } stats.Import(a.StatsDelta) @@ -376,7 +377,7 @@ func (serv *RPCServer) distributeSignalDelta(plus, minus signal.Signal) { }) } -func (runner *Runner) doneRequest(resp rpctype.ExecutionResult, fuzzerObj *fuzzer.Fuzzer) { +func (runner *Runner) doneRequest(resp rpctype.ExecutionResult, fuzzerObj *fuzzer.Fuzzer, cover bool) { runner.mu.Lock() req, ok := runner.requests[resp.ID] if ok { @@ -393,6 +394,9 @@ func (runner *Runner) doneRequest(resp rpctype.ExecutionResult, fuzzerObj *fuzze runner.logProgram(resp.ProcID, req.req.Prog) } info := &resp.Info + if !cover { + addFallbackSignal(req.req.Prog, info) + } for i := 0; i < len(info.Calls); i++ { call := &info.Calls[i] call.Cover = runner.instModules.Canonicalize(call.Cover) @@ -438,3 +442,26 @@ func (runner *Runner) logProgram(procID int, p *prog.Prog) { case <-runner.injectStop: } } + +// 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 *ipc.ProgInfo) { + callInfos := make([]prog.CallInfo, len(info.Calls)) + for i, inf := range info.Calls { + if inf.Flags&ipc.CallExecuted != 0 { + callInfos[i].Flags |= prog.CallExecuted + } + if inf.Flags&ipc.CallFinished != 0 { + callInfos[i].Flags |= prog.CallFinished + } + if inf.Flags&ipc.CallBlocked != 0 { + callInfos[i].Flags |= prog.CallBlocked + } + callInfos[i].Errno = inf.Errno + } + p.FallbackSignal(callInfos) + for i, inf := range callInfos { + info.Calls[i].Signal = inf.Signal + } +} diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index d2d194da2..6bcc92a07 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -176,10 +176,18 @@ func (ctx *Context) execute(pid int, env *ipc.Env, p *prog.Prog, progIndex int) if *flagOutput { ctx.logProgram(pid, p, callOpts) } + progData, err := p.SerializeForExec() + if err != nil { + log.Logf(1, "RESULT: failed to serialize: %v", err) + return + } // This mimics the syz-fuzzer logic. This is important for reproduction. for try := 0; ; try++ { - output, info, hanged, err := env.Exec(callOpts, p) - if err != nil && err != prog.ErrExecBufferTooSmall { + output, info, hanged, err := env.ExecProg(callOpts, progData) + if err != nil { + if ctx.config.Flags&ipc.FlagDebug != 0 { + log.Logf(0, "result: hanged=%v err=%v\n\n%s", hanged, err, output) + } if try > 10 { log.SyzFatalf("executor %d failed %d times: %v\n%s", pid, try, err, output) } @@ -190,9 +198,6 @@ func (ctx *Context) execute(pid int, env *ipc.Env, p *prog.Prog, progIndex int) } continue } - if ctx.config.Flags&ipc.FlagDebug != 0 || err != nil { - log.Logf(0, "result: hanged=%v err=%v\n\n%s", hanged, err, output) - } if info != nil { ctx.printCallResults(info) if *flagHints { |
