github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/tools/syz-execprog/execprog.go (about)

     1  // Copyright 2015 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  // execprog executes a single program or a set of programs
     5  // and optionally prints information about execution.
     6  package main
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"flag"
    12  	"fmt"
    13  	"math/rand"
    14  	"os"
    15  	"runtime"
    16  	"strings"
    17  	"sync"
    18  	"sync/atomic"
    19  	"time"
    20  
    21  	"github.com/google/syzkaller/pkg/cover/backend"
    22  	"github.com/google/syzkaller/pkg/csource"
    23  	"github.com/google/syzkaller/pkg/db"
    24  	"github.com/google/syzkaller/pkg/flatrpc"
    25  	"github.com/google/syzkaller/pkg/fuzzer/queue"
    26  	"github.com/google/syzkaller/pkg/log"
    27  	"github.com/google/syzkaller/pkg/mgrconfig"
    28  	"github.com/google/syzkaller/pkg/osutil"
    29  	"github.com/google/syzkaller/pkg/rpcserver"
    30  	"github.com/google/syzkaller/pkg/tool"
    31  	"github.com/google/syzkaller/pkg/vminfo"
    32  	"github.com/google/syzkaller/prog"
    33  	_ "github.com/google/syzkaller/sys"
    34  	"github.com/google/syzkaller/sys/targets"
    35  )
    36  
    37  var (
    38  	flagOS         = flag.String("os", runtime.GOOS, "target os")
    39  	flagArch       = flag.String("arch", runtime.GOARCH, "target arch")
    40  	flagType       = flag.String("type", "", "target VM type")
    41  	flagCoverFile  = flag.String("coverfile", "", "write coverage to the file")
    42  	flagRepeat     = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)")
    43  	flagProcs      = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes to execute programs")
    44  	flagOutput     = flag.Bool("output", false, "write programs and results to stdout")
    45  	flagHints      = flag.Bool("hints", false, "do a hints-generation run")
    46  	flagEnable     = flag.String("enable", "none", "enable only listed additional features")
    47  	flagDisable    = flag.String("disable", "none", "enable all additional features except listed")
    48  	flagExecutor   = flag.String("executor", "./syz-executor", "path to executor binary")
    49  	flagThreaded   = flag.Bool("threaded", true, "use threaded mode in executor")
    50  	flagSignal     = flag.Bool("cover", false, "collect feedback signals (coverage)")
    51  	flagSandbox    = flag.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace/android)")
    52  	flagSandboxArg = flag.Int("sandbox_arg", 0, "argument for sandbox runner to adjust it via config")
    53  	flagDebug      = flag.Bool("debug", false, "debug output from executor")
    54  	flagSlowdown   = flag.Int("slowdown", 1, "execution slowdown caused by emulation/instrumentation")
    55  	flagUnsafe     = flag.Bool("unsafe", false, "use unsafe program deserialization mode")
    56  	flagGlob       = flag.String("glob", "", "run glob expansion request")
    57  
    58  	// The in the stress mode resembles simple unguided fuzzer.
    59  	// This mode can be used as an intermediate step when porting syzkaller to a new OS,
    60  	// or when testing on a machine that is not supported by the vm package (as syz-manager cannot be used).
    61  	// To use this mode one needs to start a VM manually, copy syz-execprog and run it.
    62  	// syz-execprog will execute random programs infinitely until it's stopped or it crashes
    63  	// the kernel underneath. If it's given a corpus of programs, it will alternate between
    64  	// executing random programs and mutated programs from the corpus.
    65  	flagStress   = flag.Bool("stress", false, "enable stress mode (local fuzzer)")
    66  	flagSyscalls = flag.String("syscalls", "", "comma-separated list of enabled syscalls for the stress mode")
    67  
    68  	flagGDB = flag.Bool("gdb", false, "start executor under gdb")
    69  
    70  	// The following flag is only kept to let syzkaller remain compatible with older execprog versions.
    71  	// In order to test incoming patches or perform bug bisection, syz-ci must use the exact syzkaller
    72  	// version that detected the bug (as descriptions and syntax could've already been changed), and
    73  	// therefore it must be able to invoke older versions of syz-execprog.
    74  	// Unfortunately there's no clean way to drop that flag from newer versions of syz-execprog. If it
    75  	// were false by default, it would be easy - we could modify `instance.ExecprogCmd` only to pass it
    76  	// when it's true - which would never be the case in the newer versions (this is how we got rid of
    77  	// fault injection args). But the collide flag was true by default, so it must be passed by value
    78  	// (-collide=%v). The least kludgy solution is to silently accept this flag also in the newer versions
    79  	// of syzkaller, but do not process it, as there's no such functionality anymore.
    80  	// Note, however, that we do not have to do the same for `syz-prog2c`, as `collide` was there false
    81  	// by default.
    82  	_ = flag.Bool("collide", false, "(DEPRECATED) collide syscalls to provoke data races")
    83  )
    84  
    85  func main() {
    86  	flag.Usage = func() {
    87  		fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs-or-corpus.db+\n")
    88  		flag.PrintDefaults()
    89  		csource.PrintAvailableFeaturesFlags()
    90  	}
    91  	defer tool.Init()()
    92  	target, err := prog.GetTarget(*flagOS, *flagArch)
    93  	if err != nil {
    94  		tool.Fail(err)
    95  	}
    96  
    97  	featureFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true)
    98  	if err != nil {
    99  		log.Fatalf("%v", err)
   100  	}
   101  	features := flatrpc.AllFeatures
   102  	for feat := range flatrpc.EnumNamesFeature {
   103  		opt := csource.FlatRPCFeaturesToCSource[feat]
   104  		if opt != "" && !featureFlags[opt].Enabled {
   105  			features &= ^feat
   106  		}
   107  	}
   108  
   109  	var requestedSyscalls []int
   110  	if *flagStress {
   111  		syscallList := strings.Split(*flagSyscalls, ",")
   112  		if *flagSyscalls == "" {
   113  			syscallList = nil
   114  		}
   115  		requestedSyscalls, err = mgrconfig.ParseEnabledSyscalls(target, syscallList, nil, mgrconfig.AnyDescriptions)
   116  		if err != nil {
   117  			tool.Failf("failed to parse enabled syscalls: %v", err)
   118  		}
   119  	}
   120  
   121  	sandbox, err := flatrpc.SandboxToFlags(*flagSandbox)
   122  	if err != nil {
   123  		tool.Failf("failed to parse sandbox: %v", err)
   124  	}
   125  	env := sandbox
   126  	if *flagDebug {
   127  		env |= flatrpc.ExecEnvDebug
   128  	}
   129  	cover := *flagSignal || *flagHints || *flagCoverFile != ""
   130  	if cover {
   131  		env |= flatrpc.ExecEnvSignal
   132  	}
   133  	var exec flatrpc.ExecFlag
   134  	if *flagThreaded {
   135  		exec |= flatrpc.ExecFlagThreaded
   136  	}
   137  	if *flagCoverFile == "" {
   138  		exec |= flatrpc.ExecFlagDedupCover
   139  	}
   140  
   141  	progs := loadPrograms(target, flag.Args())
   142  	if *flagGlob == "" && !*flagStress && len(progs) == 0 {
   143  		flag.Usage()
   144  		os.Exit(1)
   145  	}
   146  	rpcCtx, done := context.WithCancel(context.Background())
   147  	ctx := &Context{
   148  		target:    target,
   149  		done:      done,
   150  		progs:     progs,
   151  		globs:     strings.Split(*flagGlob, ":"),
   152  		rs:        rand.NewSource(time.Now().UnixNano()),
   153  		coverFile: *flagCoverFile,
   154  		output:    *flagOutput,
   155  		signal:    *flagSignal,
   156  		hints:     *flagHints,
   157  		stress:    *flagStress,
   158  		repeat:    *flagRepeat,
   159  		defaultOpts: flatrpc.ExecOpts{
   160  			EnvFlags:   env,
   161  			ExecFlags:  exec,
   162  			SandboxArg: int64(*flagSandboxArg),
   163  		},
   164  	}
   165  
   166  	cfg := &rpcserver.LocalConfig{
   167  		Config: rpcserver.Config{
   168  			Config: vminfo.Config{
   169  				Target:     target,
   170  				VMType:     *flagType,
   171  				Features:   features,
   172  				Syscalls:   requestedSyscalls,
   173  				Debug:      *flagDebug,
   174  				Cover:      cover,
   175  				Sandbox:    sandbox,
   176  				SandboxArg: int64(*flagSandboxArg),
   177  			},
   178  			Procs:    *flagProcs,
   179  			Slowdown: *flagSlowdown,
   180  		},
   181  		Executor:         *flagExecutor,
   182  		HandleInterrupts: true,
   183  		GDB:              *flagGDB,
   184  		MachineChecked:   ctx.machineChecked,
   185  		OutputWriter:     os.Stderr,
   186  	}
   187  	if err := rpcserver.RunLocal(rpcCtx, cfg); err != nil {
   188  		tool.Fail(err)
   189  	}
   190  }
   191  
   192  type Context struct {
   193  	target      *prog.Target
   194  	done        func()
   195  	progs       []*prog.Prog
   196  	globs       []string
   197  	defaultOpts flatrpc.ExecOpts
   198  	choiceTable *prog.ChoiceTable
   199  	logMu       sync.Mutex
   200  	posMu       sync.Mutex
   201  	rs          rand.Source
   202  	coverFile   string
   203  	output      bool
   204  	signal      bool
   205  	hints       bool
   206  	stress      bool
   207  	repeat      int
   208  	pos         int
   209  	completed   atomic.Uint64
   210  	resultIndex atomic.Int64
   211  	lastPrint   time.Time
   212  }
   213  
   214  func (ctx *Context) machineChecked(features flatrpc.Feature, syscalls map[*prog.Syscall]bool) queue.Source {
   215  	if ctx.stress {
   216  		ctx.choiceTable = ctx.target.BuildChoiceTable(ctx.progs, syscalls)
   217  	}
   218  	ctx.defaultOpts.EnvFlags |= csource.FeaturesToFlags(features, nil)
   219  	return queue.DefaultOpts(ctx, ctx.defaultOpts)
   220  }
   221  
   222  func (ctx *Context) Next() *queue.Request {
   223  	if *flagGlob != "" {
   224  		idx := int(ctx.resultIndex.Add(1) - 1)
   225  		if idx >= len(ctx.globs) {
   226  			return nil
   227  		}
   228  		req := &queue.Request{
   229  			Type:        flatrpc.RequestTypeGlob,
   230  			GlobPattern: ctx.globs[idx],
   231  		}
   232  		req.OnDone(ctx.doneGlob)
   233  		return req
   234  	}
   235  	var p *prog.Prog
   236  	if ctx.stress {
   237  		p = ctx.createStressProg()
   238  	} else {
   239  		idx := ctx.getProgramIndex()
   240  		if idx < 0 {
   241  			return nil
   242  		}
   243  		p = ctx.progs[idx]
   244  	}
   245  	if ctx.output {
   246  		data := p.Serialize()
   247  		ctx.logMu.Lock()
   248  		log.Logf(0, "executing program:\n%s", data)
   249  		ctx.logMu.Unlock()
   250  	}
   251  
   252  	req := &queue.Request{
   253  		Prog: p,
   254  	}
   255  	if ctx.hints {
   256  		req.ExecOpts.ExecFlags |= flatrpc.ExecFlagCollectComps
   257  	} else if ctx.signal || ctx.coverFile != "" {
   258  		req.ExecOpts.ExecFlags |= flatrpc.ExecFlagCollectSignal | flatrpc.ExecFlagCollectCover
   259  	}
   260  	req.OnDone(ctx.Done)
   261  	return req
   262  }
   263  
   264  func (ctx *Context) doneGlob(req *queue.Request, res *queue.Result) bool {
   265  	if res.Status == queue.Success {
   266  		files := res.GlobFiles()
   267  		ctx.logMu.Lock()
   268  		fmt.Printf("glob %q expanded to %v files\n", req.GlobPattern, len(files))
   269  		for _, file := range files {
   270  			fmt.Printf("\t%q\n", file)
   271  		}
   272  		ctx.logMu.Unlock()
   273  	} else {
   274  		fmt.Printf("request failed: %v (%v)\n%s\n", res.Status, res.Err, res.Output)
   275  	}
   276  	completed := int(ctx.completed.Add(1))
   277  	if completed >= len(ctx.globs) {
   278  		ctx.done()
   279  	}
   280  	return true
   281  }
   282  
   283  func (ctx *Context) Done(req *queue.Request, res *queue.Result) bool {
   284  	if res.Info != nil {
   285  		ctx.printCallResults(res.Info)
   286  		if ctx.hints {
   287  			ctx.printHints(req.Prog, res.Info)
   288  		}
   289  		if ctx.coverFile != "" {
   290  			ctx.dumpCoverage(res.Info)
   291  		}
   292  	}
   293  	completed := int(ctx.completed.Add(1))
   294  	if ctx.repeat > 0 && completed >= len(ctx.progs)*ctx.repeat {
   295  		ctx.done()
   296  	}
   297  	return true
   298  }
   299  
   300  func (ctx *Context) printCallResults(info *flatrpc.ProgInfo) {
   301  	for i, inf := range info.Calls {
   302  		if inf.Flags&flatrpc.CallFlagExecuted == 0 {
   303  			continue
   304  		}
   305  		flags := ""
   306  		if inf.Flags&flatrpc.CallFlagFinished == 0 {
   307  			flags += " unfinished"
   308  		}
   309  		if inf.Flags&flatrpc.CallFlagBlocked != 0 {
   310  			flags += " blocked"
   311  		}
   312  		if inf.Flags&flatrpc.CallFlagFaultInjected != 0 {
   313  			flags += " faulted"
   314  		}
   315  		log.Logf(1, "CALL %v: signal %v, coverage %v errno %v%v",
   316  			i, len(inf.Signal), len(inf.Cover), inf.Error, flags)
   317  	}
   318  }
   319  
   320  func (ctx *Context) printHints(p *prog.Prog, info *flatrpc.ProgInfo) {
   321  	ncomps, ncandidates := 0, 0
   322  	for i := range p.Calls {
   323  		if ctx.output {
   324  			fmt.Printf("call %v:\n", i)
   325  		}
   326  		comps := make(prog.CompMap)
   327  		for _, cmp := range info.Calls[i].Comps {
   328  			comps.Add(cmp.Pc, cmp.Op1, cmp.Op2, cmp.IsConst)
   329  			if ctx.output {
   330  				fmt.Printf("comp 0x%x ? 0x%x\n", cmp.Op1, cmp.Op2)
   331  			}
   332  		}
   333  		ncomps += len(comps)
   334  		p.MutateWithHints(i, comps, func(p *prog.Prog) bool {
   335  			ncandidates++
   336  			if ctx.output {
   337  				log.Logf(1, "PROGRAM:\n%s", p.Serialize())
   338  			}
   339  			return true
   340  		})
   341  	}
   342  	log.Logf(0, "ncomps=%v ncandidates=%v", ncomps, ncandidates)
   343  }
   344  
   345  func (ctx *Context) dumpCallCoverage(coverFile string, info *flatrpc.CallInfo) {
   346  	if info == nil || len(info.Cover) == 0 {
   347  		return
   348  	}
   349  	sysTarget := targets.Get(ctx.target.OS, ctx.target.Arch)
   350  	buf := new(bytes.Buffer)
   351  	for _, pc := range info.Cover {
   352  		prev := backend.PreviousInstructionPC(sysTarget, "", pc)
   353  		fmt.Fprintf(buf, "0x%x\n", prev)
   354  	}
   355  	err := osutil.WriteFile(coverFile, buf.Bytes())
   356  	if err != nil {
   357  		log.Fatalf("failed to write coverage file: %v", err)
   358  	}
   359  }
   360  
   361  func (ctx *Context) dumpCoverage(info *flatrpc.ProgInfo) {
   362  	coverFile := fmt.Sprintf("%s_prog%v", ctx.coverFile, ctx.resultIndex.Add(1))
   363  	for i, inf := range info.Calls {
   364  		log.Logf(0, "call #%v: signal %v, coverage %v", i, len(inf.Signal), len(inf.Cover))
   365  		ctx.dumpCallCoverage(fmt.Sprintf("%v.%v", coverFile, i), inf)
   366  	}
   367  	if info.Extra != nil {
   368  		log.Logf(0, "extra: signal %v, coverage %v", len(info.Extra.Signal), len(info.Extra.Cover))
   369  		ctx.dumpCallCoverage(fmt.Sprintf("%v.extra", coverFile), info.Extra)
   370  	}
   371  }
   372  
   373  func (ctx *Context) getProgramIndex() int {
   374  	ctx.posMu.Lock()
   375  	defer ctx.posMu.Unlock()
   376  	if ctx.repeat > 0 && ctx.pos >= len(ctx.progs)*ctx.repeat {
   377  		return -1
   378  	}
   379  	idx := ctx.pos % len(ctx.progs)
   380  	if idx == 0 && time.Since(ctx.lastPrint) > 5*time.Second {
   381  		log.Logf(0, "executed programs: %v", ctx.pos)
   382  		ctx.lastPrint = time.Now()
   383  	}
   384  	ctx.pos++
   385  	return idx
   386  }
   387  
   388  func (ctx *Context) createStressProg() *prog.Prog {
   389  	ctx.posMu.Lock()
   390  	rnd := rand.New(ctx.rs)
   391  	ctx.posMu.Unlock()
   392  	if len(ctx.progs) == 0 || rnd.Intn(2) == 0 {
   393  		return ctx.target.Generate(rnd, prog.RecommendedCalls, ctx.choiceTable)
   394  	}
   395  	p := ctx.progs[rnd.Intn(len(ctx.progs))].Clone()
   396  	p.Mutate(rnd, prog.RecommendedCalls, ctx.choiceTable, nil, ctx.progs)
   397  	return p
   398  }
   399  
   400  func loadPrograms(target *prog.Target, files []string) []*prog.Prog {
   401  	var progs []*prog.Prog
   402  	mode := prog.NonStrict
   403  	if *flagUnsafe {
   404  		mode = prog.NonStrictUnsafe
   405  	}
   406  	for _, fn := range files {
   407  		if corpus, err := db.Open(fn, false); err == nil {
   408  			for _, rec := range corpus.Records {
   409  				p, err := target.Deserialize(rec.Val, mode)
   410  				if err != nil {
   411  					continue
   412  				}
   413  				progs = append(progs, p)
   414  			}
   415  			continue
   416  		}
   417  		data, err := os.ReadFile(fn)
   418  		if err != nil {
   419  			log.Fatalf("failed to read log file: %v", err)
   420  		}
   421  		for _, entry := range target.ParseLog(data, mode) {
   422  			progs = append(progs, entry.P)
   423  		}
   424  	}
   425  	log.Logf(0, "parsed %v programs", len(progs))
   426  	return progs
   427  }