From a2559a604ca1c751bf41cec5f122fe3c5d2de333 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 15 Apr 2024 14:54:58 +0200 Subject: [PATCH] pkg/ipc: refactor rate limiting 1. Move the flag to Config (logically belongs there). 2. Create rate limter lazily (it's not needed most of the time). This will help to stop passing *prog.Prog to Exec method. --- pkg/ipc/ipc.go | 40 +++++++++++++++++++--------------- pkg/ipc/ipcconfig/ipcconfig.go | 1 + syz-fuzzer/proc.go | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index f0aeb2b41edb..536b494e7287 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -11,6 +11,7 @@ import ( "path/filepath" "reflect" "strings" + "sync" "sync/atomic" "time" "unsafe" @@ -68,6 +69,7 @@ type Config struct { UseShmem bool // use shared memory instead of pipes for communication UseForkServer bool // use extended protocol with handshake + RateLimit bool // rate limit start of new processes for host fuzzer mode // Flags are configuation flags, defined above. Flags EnvFlags @@ -246,8 +248,6 @@ func (env *Env) Close() error { } } -var rateLimit = time.NewTicker(1 * time.Second) - // Exec starts executor binary to execute program p and returns information about the execution: // output: process output // info: per-call info @@ -271,7 +271,7 @@ func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info *ProgInf } atomic.AddUint64(&env.StatExecs, 1) - err0 = env.RestartIfNeeded(p.Target) + err0 = env.RestartIfNeeded() if err0 != nil { return } @@ -306,23 +306,29 @@ func (env *Env) ForceRestart() { } } -// This smethod brings up an executor process if it was stopped. -func (env *Env) RestartIfNeeded(target *prog.Target) error { - if env.cmd == nil { - if target.OS != targets.TestOS && targets.Get(target.OS, target.Arch).HostFuzzer { - // The executor is actually ssh, - // starting them too frequently leads to timeouts. - <-rateLimit.C - } - tmpDirPath := "./" - atomic.AddUint64(&env.StatRestarts, 1) - var err error - env.cmd, err = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile, env.out, tmpDirPath) - return err +// RestartIfNeeded brings up an executor process if it was stopped. +func (env *Env) RestartIfNeeded() error { + if env.cmd != nil { + return nil } - return nil + if env.config.RateLimit { + rateLimiterOnce.Do(func() { + rateLimiter = time.NewTicker(1 * time.Second).C + }) + <-rateLimiter + } + tmpDirPath := "./" + atomic.AddUint64(&env.StatRestarts, 1) + var err error + env.cmd, err = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile, env.out, tmpDirPath) + return err } +var ( + rateLimiterOnce sync.Once + 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. diff --git a/pkg/ipc/ipcconfig/ipcconfig.go b/pkg/ipc/ipcconfig/ipcconfig.go index 15e1dfde019c..4b4aacd8a2f4 100644 --- a/pkg/ipc/ipcconfig/ipcconfig.go +++ b/pkg/ipc/ipcconfig/ipcconfig.go @@ -41,6 +41,7 @@ func Default(target *prog.Target) (*ipc.Config, *ipc.ExecOpts, error) { c.Flags |= sandboxFlags c.UseShmem = sysTarget.ExecutorUsesShmem c.UseForkServer = sysTarget.ExecutorUsesForkServer + c.RateLimit = sysTarget.HostFuzzer && target.OS != targets.TestOS opts := &ipc.ExecOpts{ Flags: ipc.FlagDedupCover, } diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go index e2a308b83199..b47c7ca3e759 100644 --- a/syz-fuzzer/proc.go +++ b/syz-fuzzer/proc.go @@ -93,7 +93,7 @@ func (proc *Proc) executeRaw(opts *ipc.ExecOpts, p *prog.Prog) *ipc.ProgInfo { var hanged bool // On a heavily loaded VM, syz-executor may take significant time to start. // Let's do it outside of the gate ticket. - err := proc.env.RestartIfNeeded(p.Target) + err := proc.env.RestartIfNeeded() if err == nil { // Limit concurrency. ticket := proc.tool.gate.Enter()