aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2024-04-15 14:55:02 +0200
committerDmitry Vyukov <dvyukov@google.com>2024-04-16 14:20:36 +0000
commit68911a35911bfdeb62bef87d9e844b1c19f86580 (patch)
treea13c0f84b1e1c19cfb2fee8defdfb1cd566ecfba
parent4cd91fc0b5007710bf0f38de6319ce24c31a52e5 (diff)
syz-manager, syz-fuzzer: send exec encoded programs to fuzzer
Don't send text program to the fuzzer, instead send exec encoding directly. It's more compact now and does not need complex deserialization.
-rw-r--r--syz-fuzzer/fuzzer.go50
-rw-r--r--syz-fuzzer/proc.go41
-rw-r--r--syz-manager/rpc.go49
-rw-r--r--syz-manager/stats.go1
4 files changed, 59 insertions, 82 deletions
diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go
index 7b4fa4217..7bcefa1ff 100644
--- a/syz-fuzzer/fuzzer.go
+++ b/syz-fuzzer/fuzzer.go
@@ -39,12 +39,11 @@ type FuzzerTool struct {
triagedCandidates uint32
timeouts targets.Timeouts
- bufferTooSmall atomic.Uint64
noExecRequests atomic.Uint64
noExecDuration atomic.Uint64
resetAccState bool
- inputs chan executionRequest
+ requests chan rpctype.ExecutionRequest
results chan executionResult
signalMu sync.RWMutex
maxSignal signal.Signal
@@ -59,12 +58,6 @@ type executionResult struct {
info *ipc.ProgInfo
}
-// executionRequest offloads prog deseralization to another thread.
-type executionRequest struct {
- rpctype.ExecutionRequest
- prog *prog.Prog
-}
-
func createIPCConfig(features *host.Features, config *ipc.Config) {
if features[host.FeatureExtraCoverage].Enabled {
config.Flags |= ipc.FlagExtraCover
@@ -165,7 +158,7 @@ func main() {
return
}
- machineInfo, modules := collectMachineInfos(target)
+ machineInfo, modules := collectMachineInfos()
log.Logf(0, "dialing manager at %v", *flagManager)
manager, err := rpctype.NewRPCClient(*flagManager, timeouts.Scale, false, *flagNetCompression)
@@ -236,8 +229,8 @@ func main() {
config: config,
resetAccState: *flagResetAccState,
- inputs: make(chan executionRequest, inputsCount),
- results: make(chan executionResult, inputsCount),
+ requests: make(chan rpctype.ExecutionRequest, inputsCount),
+ results: make(chan executionResult, inputsCount),
}
fuzzerTool.gate = ipc.NewGate(gateSize,
fuzzerTool.useBugFrames(r, *flagProcs))
@@ -259,7 +252,7 @@ func main() {
fuzzerTool.exchangeDataWorker()
}
-func collectMachineInfos(target *prog.Target) ([]byte, []host.KernelModule) {
+func collectMachineInfos() ([]byte, []host.KernelModule) {
machineInfo, err := host.CollectMachineInfo()
if err != nil {
log.SyzFatalf("failed to collect machine information: %v", err)
@@ -330,17 +323,15 @@ func (tool *FuzzerTool) startExecutingCall(progID int64, pid, try int) {
})
}
-func (tool *FuzzerTool) exchangeDataCall(needProgs int, results []executionResult,
+func (tool *FuzzerTool) exchangeDataCall(needProgs int, results []rpctype.ExecutionResult,
latency time.Duration) time.Duration {
a := &rpctype.ExchangeInfoRequest{
Name: tool.name,
NeedProgs: needProgs,
+ Results: results,
StatsDelta: tool.grabStats(),
Latency: latency,
}
- for _, result := range results {
- a.Results = append(a.Results, tool.convertExecutionResult(result))
- }
r := &rpctype.ExchangeInfoReply{}
start := osutil.MonotonicNano()
if err := tool.manager.Call("Manager.ExchangeInfo", a, r); err != nil {
@@ -352,14 +343,7 @@ func (tool *FuzzerTool) exchangeDataCall(needProgs int, results []executionResul
}
tool.updateMaxSignal(r.NewMaxSignal, r.DropMaxSignal)
for _, req := range r.Requests {
- p := tool.deserializeInput(req.ProgData)
- if p == nil {
- log.SyzFatalf("failed to deserialize input: %s", req.ProgData)
- }
- tool.inputs <- executionRequest{
- ExecutionRequest: req,
- prog: p,
- }
+ tool.requests <- req
}
return latency
}
@@ -367,15 +351,13 @@ func (tool *FuzzerTool) exchangeDataCall(needProgs int, results []executionResul
func (tool *FuzzerTool) exchangeDataWorker() {
var latency time.Duration
for result := range tool.results {
- results := []executionResult{
- result,
- }
+ results := []rpctype.ExecutionResult{tool.convertExecutionResult(result)}
// Grab other finished calls, just in case there are any.
loop:
for {
select {
case res := <-tool.results:
- results = append(results, res)
+ results = append(results, tool.convertExecutionResult(res))
default:
break loop
}
@@ -405,23 +387,11 @@ func (tool *FuzzerTool) grabStats() map[string]uint64 {
for _, proc := range tool.procs {
stats["executor restarts"] += atomic.SwapUint64(&proc.env.StatRestarts, 0)
}
- stats["buffer too small"] = tool.bufferTooSmall.Swap(0)
stats["no exec requests"] = tool.noExecRequests.Swap(0)
stats["no exec duration"] = tool.noExecDuration.Swap(0)
return stats
}
-func (tool *FuzzerTool) deserializeInput(inp []byte) *prog.Prog {
- p, err := tool.target.Deserialize(inp, prog.NonStrict)
- if err != nil {
- log.SyzFatalf("failed to deserialize prog: %v\n%s", err, inp)
- }
- if len(p.Calls) > prog.MaxCalls {
- return nil
- }
- return p
-}
-
func (tool *FuzzerTool) diffMaxSignal(info *ipc.ProgInfo, mask signal.Signal, maskCall int) {
tool.signalMu.RLock()
defer tool.signalMu.RUnlock()
diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go
index 072a4b66f..be1c82ab5 100644
--- a/syz-fuzzer/proc.go
+++ b/syz-fuzzer/proc.go
@@ -11,7 +11,6 @@ import (
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/rpctype"
- "github.com/google/syzkaller/prog"
)
// Proc represents a single fuzzing process (executor).
@@ -60,11 +59,11 @@ func (proc *Proc) loop() {
(req.NeedCover || req.NeedSignal != rpctype.NoSignal || req.NeedHints) {
proc.env.ForceRestart()
}
- info, try := proc.execute(&opts, req.ID, req.prog)
+ info, try := proc.execute(&opts, req.ID, req.ProgData)
// 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{
- ExecutionRequest: req.ExecutionRequest,
+ ExecutionRequest: req,
procID: proc.pid,
try: try,
info: info,
@@ -72,30 +71,22 @@ func (proc *Proc) loop() {
}
}
-func (proc *Proc) nextRequest() executionRequest {
+func (proc *Proc) nextRequest() rpctype.ExecutionRequest {
select {
- case req := <-proc.tool.inputs:
+ case req := <-proc.tool.requests:
return req
default:
}
// Not having enough inputs to execute is a sign of RPC communication problems.
// Let's count and report such situations.
start := osutil.MonotonicNano()
- req := <-proc.tool.inputs
+ req := <-proc.tool.requests
proc.tool.noExecDuration.Add(uint64(osutil.MonotonicNano() - start))
proc.tool.noExecRequests.Add(1)
return req
}
-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
- }
+func (proc *Proc) execute(opts *ipc.ExecOpts, progID int64, progData []byte) (*ipc.ProgInfo, int) {
for try := 0; ; try++ {
var output []byte
var info *ipc.ProgInfo
@@ -109,18 +100,16 @@ func (proc *Proc) execute(opts *ipc.ExecOpts, progID int64, p *prog.Prog) (*ipc.
proc.tool.startExecutingCall(progID, proc.pid, try)
output, info, hanged, err = proc.env.ExecProg(opts, progData)
proc.tool.gate.Leave(ticket)
- }
- if err != nil {
- if try > 10 {
- log.SyzFatalf("executor %v failed %v times: %v\n%s", proc.pid, try, err, output)
+ if err == nil {
+ log.Logf(2, "result hanged=%v: %s", hanged, output)
+ return info, try
}
- log.Logf(4, "fuzzer detected executor failure='%v', retrying #%d", err, try+1)
- if try > 3 {
- time.Sleep(100 * time.Millisecond)
- }
- continue
}
- log.Logf(2, "result hanged=%v: %s", hanged, output)
- return info, try
+ log.Logf(4, "fuzzer detected executor failure='%v', retrying #%d", err, try+1)
+ if try > 10 {
+ log.SyzFatalf("executor %v failed %v times: %v\n%s", proc.pid, try, err, output)
+ } else if try > 3 {
+ time.Sleep(100 * time.Millisecond)
+ }
}
}
diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go
index 4a260738e..94f902085 100644
--- a/syz-manager/rpc.go
+++ b/syz-manager/rpc.go
@@ -45,6 +45,7 @@ type RPCServer struct {
statExecs *stats.Val
statExecRetries *stats.Val
+ statExecBufferTooSmall *stats.Val
statVMRestarts *stats.Val
statExchangeCalls *stats.Val
statExchangeProgs *stats.Val
@@ -95,6 +96,8 @@ func startRPCServer(mgr *Manager) (*RPCServer, error) {
statExecRetries: stats.Create("exec retries",
"Number of times a test program was restarted because the first run failed",
stats.Rate{}, stats.Graph("executor")),
+ statExecBufferTooSmall: stats.Create("buffer too small",
+ "Program serialization overflowed exec buffer", stats.NoGraph),
statVMRestarts: stats.Create("vm restarts", "Total number of VM starts",
stats.Rate{}, stats.NoGraph),
statExchangeCalls: stats.Create("exchange calls", "Number of RPC Exchange calls",
@@ -243,20 +246,32 @@ func (serv *RPCServer) ExchangeInfo(a *rpctype.ExchangeInfoRequest, r *rpctype.E
return nil
}
- fuzzer := serv.mgr.getFuzzer()
- if fuzzer == nil {
+ fuzzerObj := serv.mgr.getFuzzer()
+ if fuzzerObj == nil {
// ExchangeInfo calls follow MachineCheck, so the fuzzer must have been initialized.
panic("exchange info call with nil fuzzer")
}
+ appendRequest := func(inp *fuzzer.Request) {
+ if req, ok := runner.newRequest(inp); ok {
+ r.Requests = append(r.Requests, req)
+ } else {
+ // 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.
+ serv.statExecBufferTooSmall.Add(1)
+ fuzzerObj.Done(inp, &fuzzer.Result{Stop: true})
+ }
+ }
+
// Try to collect some of the postponed requests.
if serv.mu.TryLock() {
- for i := len(serv.rescuedInputs) - 1; i >= 0 && a.NeedProgs > 0; i-- {
- inp := serv.rescuedInputs[i]
- serv.rescuedInputs[i] = nil
- serv.rescuedInputs = serv.rescuedInputs[:i]
- r.Requests = append(r.Requests, runner.newRequest(inp))
- a.NeedProgs--
+ for len(serv.rescuedInputs) != 0 && len(r.Requests) < a.NeedProgs {
+ last := len(serv.rescuedInputs) - 1
+ inp := serv.rescuedInputs[last]
+ serv.rescuedInputs[last] = nil
+ serv.rescuedInputs = serv.rescuedInputs[:last]
+ appendRequest(inp)
}
serv.mu.Unlock()
}
@@ -264,13 +279,12 @@ func (serv *RPCServer) ExchangeInfo(a *rpctype.ExchangeInfoRequest, r *rpctype.E
// First query new inputs and only then post results.
// It should foster a more even distribution of executions
// across all VMs.
- for i := 0; i < a.NeedProgs; i++ {
- inp := fuzzer.NextInput()
- r.Requests = append(r.Requests, runner.newRequest(inp))
+ for len(r.Requests) < a.NeedProgs {
+ appendRequest(fuzzerObj.NextInput())
}
for _, result := range a.Results {
- runner.doneRequest(result, fuzzer, serv.cfg.Cover)
+ runner.doneRequest(result, fuzzerObj, serv.cfg.Cover)
}
stats.Import(a.StatsDelta)
@@ -407,7 +421,12 @@ func (runner *Runner) doneRequest(resp rpctype.ExecutionResult, fuzzerObj *fuzze
fuzzerObj.Done(req.req, &fuzzer.Result{Info: info})
}
-func (runner *Runner) newRequest(req *fuzzer.Request) rpctype.ExecutionRequest {
+func (runner *Runner) newRequest(req *fuzzer.Request) (rpctype.ExecutionRequest, bool) {
+ progData, err := req.Prog.SerializeForExec()
+ if err != nil {
+ return rpctype.ExecutionRequest{}, false
+ }
+
var signalFilter signal.Signal
if req.SignalFilter != nil {
newRawSignal := runner.instModules.Decanonicalize(req.SignalFilter.ToRaw())
@@ -425,13 +444,13 @@ func (runner *Runner) newRequest(req *fuzzer.Request) rpctype.ExecutionRequest {
runner.mu.Unlock()
return rpctype.ExecutionRequest{
ID: id,
- ProgData: req.Prog.Serialize(),
+ ProgData: progData,
NeedCover: req.NeedCover,
NeedSignal: req.NeedSignal,
SignalFilter: signalFilter,
SignalFilterCall: req.SignalFilterCall,
NeedHints: req.NeedHints,
- }
+ }, true
}
func (runner *Runner) logProgram(procID int, p *prog.Prog) {
diff --git a/syz-manager/stats.go b/syz-manager/stats.go
index 53373cf7e..a0719bc45 100644
--- a/syz-manager/stats.go
+++ b/syz-manager/stats.go
@@ -78,7 +78,6 @@ func (mgr *Manager) initStats() {
// Stats imported from the fuzzer (names must match the the fuzzer names).
stats.Create("executor restarts", "Number of times executor process was restarted",
stats.Rate{}, stats.Graph("executor"))
- stats.Create("buffer too small", "Program serialization overflowed exec buffer", stats.NoGraph)
stats.Create("no exec requests", "Number of times fuzzer was stalled with no exec requests", stats.Rate{})
stats.Create("no exec duration", "Total duration fuzzer was stalled with no exec requests (ns/sec)", stats.Rate{})
}