blob: 4d5e3d09687b3f20eafb42c642b92c98a397ed6d [file] [log] [blame]
// Copyright 2015 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.
// execprog executes a single program or a set of programs
// and optionally prints information about execution.
package main
import (
"bytes"
"flag"
"fmt"
"os"
"runtime"
"strconv"
"sync"
"time"
"github.com/google/syzkaller/pkg/cover"
"github.com/google/syzkaller/pkg/cover/backend"
"github.com/google/syzkaller/pkg/csource"
"github.com/google/syzkaller/pkg/db"
"github.com/google/syzkaller/pkg/host"
"github.com/google/syzkaller/pkg/ipc"
"github.com/google/syzkaller/pkg/ipc/ipcconfig"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/tool"
"github.com/google/syzkaller/prog"
_ "github.com/google/syzkaller/sys"
"github.com/google/syzkaller/sys/targets"
)
var (
flagOS = flag.String("os", runtime.GOOS, "target os")
flagArch = flag.String("arch", runtime.GOARCH, "target arch")
flagCoverFile = flag.String("coverfile", "", "write coverage to the file")
flagRepeat = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)")
flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes to execute programs")
flagOutput = flag.Bool("output", false, "write programs and results to stdout")
flagHints = flag.Bool("hints", false, "do a hints-generation run")
flagEnable = flag.String("enable", "none", "enable only listed additional features")
flagDisable = flag.String("disable", "none", "enable all additional features except listed")
// The following flag is only kept to let syzkaller remain compatible with older execprog versions.
// In order to test incoming patches or perform bug bisection, syz-ci must use the exact syzkaller
// version that detected the bug (as descriptions and syntax could've already been changed), and
// therefore it must be able to invoke older versions of syz-execprog.
// Unfortunately there's no clean way to drop that flag from newer versions of syz-execprog. If it
// were false by default, it would be easy - we could modify `instance.ExecprogCmd` only to pass it
// when it's true - which would never be the case in the newer versions (this is how we got rid of
// fault injection args). But the collide flag was true by default, so it must be passed by value
// (-collide=%v). The least kludgy solution is to silently accept this flag also in the newer versions
// of syzkaller, but do not process it, as there's no such functionality anymore.
// Note, however, that we do not have to do the same for `syz-prog2c`, as `collide` was there false
// by default.
flagCollide = flag.Bool("collide", false, "(DEPRECATED) collide syscalls to provoke data races")
)
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs-or-corpus.db+\n")
flag.PrintDefaults()
csource.PrintAvailableFeaturesFlags()
}
defer tool.Init()()
if len(flag.Args()) == 0 {
flag.Usage()
os.Exit(1)
}
featuresFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true)
if err != nil {
log.Fatalf("%v", err)
}
target, err := prog.GetTarget(*flagOS, *flagArch)
if err != nil {
log.Fatalf("%v", err)
}
progs := loadPrograms(target, flag.Args())
if len(progs) == 0 {
return
}
features, err := host.Check(target)
if err != nil {
log.Fatalf("%v", err)
}
if *flagOutput {
for _, feat := range features.Supported() {
log.Logf(0, "%-24v: %v", feat.Name, feat.Reason)
}
}
if *flagCollide {
log.Logf(0, "note: setting -collide to true is deprecated now and has no effect")
}
config, execOpts := createConfig(target, features, featuresFlags)
if err = host.Setup(target, features, featuresFlags, config.Executor); err != nil {
log.Fatal(err)
}
var gateCallback func()
if features[host.FeatureLeak].Enabled {
gateCallback = func() {
output, err := osutil.RunCmd(10*time.Minute, "", config.Executor, "leak")
if err != nil {
os.Stdout.Write(output)
os.Exit(1)
}
}
}
sysTarget := targets.Get(*flagOS, *flagArch)
upperBase := getKernelUpperBase(sysTarget)
ctx := &Context{
progs: progs,
config: config,
execOpts: execOpts,
gate: ipc.NewGate(2**flagProcs, gateCallback),
shutdown: make(chan struct{}),
repeat: *flagRepeat,
target: sysTarget,
upperBase: upperBase,
}
var wg sync.WaitGroup
wg.Add(*flagProcs)
for p := 0; p < *flagProcs; p++ {
pid := p
go func() {
defer wg.Done()
ctx.run(pid)
}()
}
osutil.HandleInterrupts(ctx.shutdown)
wg.Wait()
}
type Context struct {
progs []*prog.Prog
config *ipc.Config
execOpts *ipc.ExecOpts
gate *ipc.Gate
shutdown chan struct{}
logMu sync.Mutex
posMu sync.Mutex
repeat int
pos int
lastPrint time.Time
target *targets.Target
upperBase uint32
}
func (ctx *Context) run(pid int) {
env, err := ipc.MakeEnv(ctx.config, pid)
if err != nil {
log.Fatalf("failed to create ipc env: %v", err)
}
defer env.Close()
for {
select {
case <-ctx.shutdown:
return
default:
}
idx := ctx.getProgramIndex()
if ctx.repeat > 0 && idx >= len(ctx.progs)*ctx.repeat {
return
}
entry := ctx.progs[idx%len(ctx.progs)]
ctx.execute(pid, env, entry, idx)
}
}
func (ctx *Context) execute(pid int, env *ipc.Env, p *prog.Prog, progIndex int) {
// Limit concurrency window.
ticket := ctx.gate.Enter()
defer ctx.gate.Leave(ticket)
callOpts := ctx.execOpts
if *flagOutput {
ctx.logProgram(pid, p, callOpts)
}
// 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 {
if try > 10 {
log.Fatalf("executor failed %v times: %v\n%s", try, err, output)
}
// Don't print err/output in this case as it may contain "SYZFAIL" and we want to fail yet.
log.Logf(1, "executor failed, retrying")
time.Sleep(time.Second)
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 {
ctx.printHints(p, info)
}
if *flagCoverFile != "" {
covFile := fmt.Sprintf("%s_prog%d", *flagCoverFile, progIndex)
ctx.dumpCoverage(covFile, info)
}
} else {
log.Logf(1, "RESULT: no calls executed")
}
break
}
}
func (ctx *Context) logProgram(pid int, p *prog.Prog, callOpts *ipc.ExecOpts) {
data := p.Serialize()
ctx.logMu.Lock()
log.Logf(0, "executing program %v:\n%s", pid, data)
ctx.logMu.Unlock()
}
func (ctx *Context) printCallResults(info *ipc.ProgInfo) {
for i, inf := range info.Calls {
if inf.Flags&ipc.CallExecuted == 0 {
continue
}
flags := ""
if inf.Flags&ipc.CallFinished == 0 {
flags += " unfinished"
}
if inf.Flags&ipc.CallBlocked != 0 {
flags += " blocked"
}
if inf.Flags&ipc.CallFaultInjected != 0 {
flags += " faulted"
}
log.Logf(1, "CALL %v: signal %v, coverage %v errno %v%v",
i, len(inf.Signal), len(inf.Cover), inf.Errno, flags)
}
}
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.Calls[i].Comps
for v, args := range comps {
ncomps += len(args)
if *flagOutput {
fmt.Printf("comp 0x%x:", v)
for arg := range args {
fmt.Printf(" 0x%x", arg)
}
fmt.Printf("\n")
}
}
p.MutateWithHints(i, comps, func(p *prog.Prog) {
ncandidates++
if *flagOutput {
log.Logf(1, "PROGRAM:\n%s", p.Serialize())
}
})
}
log.Logf(0, "ncomps=%v ncandidates=%v", ncomps, ncandidates)
}
func getKernelUpperBase(target *targets.Target) uint32 {
defaultRet := uint32(0xffffffff)
if target.OS == targets.Linux {
// Read the first 8 bytes from /proc/kallsyms.
f, err := os.Open("/proc/kallsyms")
if err != nil {
log.Logf(1, "could not get kernel fixup address: %v", err)
return defaultRet
}
defer f.Close()
data := make([]byte, 8)
_, err = f.ReadAt(data, 0)
if err != nil {
log.Logf(1, "could not get kernel fixup address: %v", err)
return defaultRet
}
value, err := strconv.ParseUint(string(data), 16, 32)
if err != nil {
log.Logf(1, "could not get kernel fixup address: %v", err)
return defaultRet
}
return uint32(value)
}
return defaultRet
}
func (ctx *Context) dumpCallCoverage(coverFile string, info *ipc.CallInfo) {
if len(info.Cover) == 0 {
return
}
buf := new(bytes.Buffer)
for _, pc := range info.Cover {
fmt.Fprintf(buf, "0x%x\n", backend.PreviousInstructionPC(ctx.target, cover.RestorePC(pc, ctx.upperBase)))
}
err := osutil.WriteFile(coverFile, buf.Bytes())
if err != nil {
log.Fatalf("failed to write coverage file: %v", err)
}
}
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))
ctx.dumpCallCoverage(fmt.Sprintf("%v.%v", coverFile, i), &inf)
}
log.Logf(0, "extra: signal %v, coverage %v", len(info.Extra.Signal), len(info.Extra.Cover))
ctx.dumpCallCoverage(fmt.Sprintf("%v.extra", coverFile), &info.Extra)
}
func (ctx *Context) getProgramIndex() int {
ctx.posMu.Lock()
idx := ctx.pos
ctx.pos++
if idx%len(ctx.progs) == 0 && time.Since(ctx.lastPrint) > 5*time.Second {
log.Logf(0, "executed programs: %v", idx)
ctx.lastPrint = time.Now()
}
ctx.posMu.Unlock()
return idx
}
func loadPrograms(target *prog.Target, files []string) []*prog.Prog {
var progs []*prog.Prog
for _, fn := range files {
if corpus, err := db.Open(fn, false); err == nil {
for _, rec := range corpus.Records {
p, err := target.Deserialize(rec.Val, prog.NonStrict)
if err != nil {
continue
}
progs = append(progs, p)
}
continue
}
data, err := os.ReadFile(fn)
if err != nil {
log.Fatalf("failed to read log file: %v", err)
}
for _, entry := range target.ParseLog(data) {
progs = append(progs, entry.P)
}
}
log.Logf(0, "parsed %v programs", len(progs))
return progs
}
func createConfig(target *prog.Target, features *host.Features, featuresFlags csource.Features) (
*ipc.Config, *ipc.ExecOpts) {
config, execOpts, err := ipcconfig.Default(target)
if err != nil {
log.Fatalf("%v", err)
}
if config.Flags&ipc.FlagSignal != 0 {
execOpts.Flags |= ipc.FlagCollectCover
}
if *flagCoverFile != "" {
config.Flags |= ipc.FlagSignal
execOpts.Flags |= ipc.FlagCollectCover
execOpts.Flags &^= ipc.FlagDedupCover
}
if *flagHints {
if execOpts.Flags&ipc.FlagCollectCover != 0 {
execOpts.Flags ^= ipc.FlagCollectCover
}
execOpts.Flags |= ipc.FlagCollectComps
}
if features[host.FeatureExtraCoverage].Enabled {
config.Flags |= ipc.FlagExtraCover
}
if features[host.FeatureDelayKcovMmap].Enabled {
config.Flags |= ipc.FlagDelayKcovMmap
}
if featuresFlags["tun"].Enabled && features[host.FeatureNetInjection].Enabled {
config.Flags |= ipc.FlagEnableTun
}
if featuresFlags["net_dev"].Enabled && features[host.FeatureNetDevices].Enabled {
config.Flags |= ipc.FlagEnableNetDev
}
if featuresFlags["net_reset"].Enabled {
config.Flags |= ipc.FlagEnableNetReset
}
if featuresFlags["cgroups"].Enabled {
config.Flags |= ipc.FlagEnableCgroups
}
if featuresFlags["close_fds"].Enabled {
config.Flags |= ipc.FlagEnableCloseFds
}
if featuresFlags["devlink_pci"].Enabled && features[host.FeatureDevlinkPCI].Enabled {
config.Flags |= ipc.FlagEnableDevlinkPCI
}
if featuresFlags["nic_vf"].Enabled && features[host.FeatureNicVF].Enabled {
config.Flags |= ipc.FlagEnableNicVF
}
if featuresFlags["vhci"].Enabled && features[host.FeatureVhciInjection].Enabled {
config.Flags |= ipc.FlagEnableVhciInjection
}
if featuresFlags["wifi"].Enabled && features[host.FeatureWifiEmulation].Enabled {
config.Flags |= ipc.FlagEnableWifi
}
return config, execOpts
}