github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/google/syzkaller/pkg/cover"
    22  	"github.com/google/syzkaller/pkg/cover/backend"
    23  	"github.com/google/syzkaller/pkg/csource"
    24  	"github.com/google/syzkaller/pkg/db"
    25  	"github.com/google/syzkaller/pkg/flatrpc"
    26  	"github.com/google/syzkaller/pkg/fuzzer/queue"
    27  	"github.com/google/syzkaller/pkg/host"
    28  	"github.com/google/syzkaller/pkg/ipc"
    29  	"github.com/google/syzkaller/pkg/ipc/ipcconfig"
    30  	"github.com/google/syzkaller/pkg/log"
    31  	"github.com/google/syzkaller/pkg/mgrconfig"
    32  	"github.com/google/syzkaller/pkg/osutil"
    33  	"github.com/google/syzkaller/pkg/tool"
    34  	"github.com/google/syzkaller/pkg/vminfo"
    35  	"github.com/google/syzkaller/prog"
    36  	_ "github.com/google/syzkaller/sys"
    37  	"github.com/google/syzkaller/sys/targets"
    38  )
    39  
    40  var (
    41  	flagOS        = flag.String("os", runtime.GOOS, "target os")
    42  	flagArch      = flag.String("arch", runtime.GOARCH, "target arch")
    43  	flagCoverFile = flag.String("coverfile", "", "write coverage to the file")
    44  	flagRepeat    = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)")
    45  	flagProcs     = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes to execute programs")
    46  	flagOutput    = flag.Bool("output", false, "write programs and results to stdout")
    47  	flagHints     = flag.Bool("hints", false, "do a hints-generation run")
    48  	flagEnable    = flag.String("enable", "none", "enable only listed additional features")
    49  	flagDisable   = flag.String("disable", "none", "enable all additional features except listed")
    50  
    51  	// The in the stress mode resembles simple unguided fuzzer.
    52  	// This mode can be used as an intermediate step when porting syzkaller to a new OS,
    53  	// or when testing on a machine that is not supported by the vm package (as syz-manager cannot be used).
    54  	// To use this mode one needs to start a VM manually, copy syz-execprog and run it.
    55  	// syz-execprog will execute random programs infinitely until it's stopped or it crashes
    56  	// the kernel underneath. If it's given a corpus of programs, it will alternate between
    57  	// executing random programs and mutated programs from the corpus.
    58  	flagStress   = flag.Bool("stress", false, "enable stress mode (local fuzzer)")
    59  	flagSyscalls = flag.String("syscalls", "", "comma-separated list of enabled syscalls for the stress mode")
    60  
    61  	// The following flag is only kept to let syzkaller remain compatible with older execprog versions.
    62  	// In order to test incoming patches or perform bug bisection, syz-ci must use the exact syzkaller
    63  	// version that detected the bug (as descriptions and syntax could've already been changed), and
    64  	// therefore it must be able to invoke older versions of syz-execprog.
    65  	// Unfortunately there's no clean way to drop that flag from newer versions of syz-execprog. If it
    66  	// were false by default, it would be easy - we could modify `instance.ExecprogCmd` only to pass it
    67  	// when it's true - which would never be the case in the newer versions (this is how we got rid of
    68  	// fault injection args). But the collide flag was true by default, so it must be passed by value
    69  	// (-collide=%v). The least kludgy solution is to silently accept this flag also in the newer versions
    70  	// of syzkaller, but do not process it, as there's no such functionality anymore.
    71  	// Note, however, that we do not have to do the same for `syz-prog2c`, as `collide` was there false
    72  	// by default.
    73  	flagCollide = flag.Bool("collide", false, "(DEPRECATED) collide syscalls to provoke data races")
    74  )
    75  
    76  func main() {
    77  	flag.Usage = func() {
    78  		fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs-or-corpus.db+\n")
    79  		flag.PrintDefaults()
    80  		csource.PrintAvailableFeaturesFlags()
    81  	}
    82  	defer tool.Init()()
    83  	featuresFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true)
    84  	if err != nil {
    85  		log.Fatalf("%v", err)
    86  	}
    87  
    88  	target, err := prog.GetTarget(*flagOS, *flagArch)
    89  	if err != nil {
    90  		log.Fatalf("%v", err)
    91  	}
    92  
    93  	progs := loadPrograms(target, flag.Args())
    94  	if !*flagStress && len(progs) == 0 {
    95  		flag.Usage()
    96  		os.Exit(1)
    97  	}
    98  	if *flagCollide {
    99  		log.Logf(0, "note: setting -collide to true is deprecated now and has no effect")
   100  	}
   101  	var requestedSyscalls []int
   102  	if *flagStress {
   103  		syscallList := strings.Split(*flagSyscalls, ",")
   104  		if *flagSyscalls == "" {
   105  			syscallList = nil
   106  		}
   107  		requestedSyscalls, err = mgrconfig.ParseEnabledSyscalls(target, syscallList, nil)
   108  		if err != nil {
   109  			log.Fatalf("failed to parse enabled syscalls: %v", err)
   110  		}
   111  	}
   112  	config, execOpts, syscalls, features := createConfig(target, featuresFlags, requestedSyscalls)
   113  	var gateCallback func()
   114  	if features&flatrpc.FeatureLeak != 0 {
   115  		gateCallback = func() {
   116  			output, err := osutil.RunCmd(10*time.Minute, "", config.Executor, "leak")
   117  			if err != nil {
   118  				os.Stdout.Write(output)
   119  				os.Exit(1)
   120  			}
   121  		}
   122  	}
   123  	var choiceTable *prog.ChoiceTable
   124  	if *flagStress {
   125  		choiceTable = target.BuildChoiceTable(progs, syscalls)
   126  	}
   127  	sysTarget := targets.Get(*flagOS, *flagArch)
   128  	upperBase := getKernelUpperBase(sysTarget)
   129  	ctx := &Context{
   130  		target:      target,
   131  		progs:       progs,
   132  		choiceTable: choiceTable,
   133  		config:      config,
   134  		execOpts:    execOpts,
   135  		gate:        ipc.NewGate(2**flagProcs, gateCallback),
   136  		shutdown:    make(chan struct{}),
   137  		stress:      *flagStress,
   138  		repeat:      *flagRepeat,
   139  		sysTarget:   sysTarget,
   140  		upperBase:   upperBase,
   141  	}
   142  	var wg sync.WaitGroup
   143  	wg.Add(*flagProcs)
   144  	for p := 0; p < *flagProcs; p++ {
   145  		pid := p
   146  		go func() {
   147  			defer wg.Done()
   148  			ctx.run(pid)
   149  		}()
   150  	}
   151  	osutil.HandleInterrupts(ctx.shutdown)
   152  	wg.Wait()
   153  }
   154  
   155  type Context struct {
   156  	target      *prog.Target
   157  	progs       []*prog.Prog
   158  	choiceTable *prog.ChoiceTable
   159  	config      *ipc.Config
   160  	execOpts    *ipc.ExecOpts
   161  	gate        *ipc.Gate
   162  	shutdown    chan struct{}
   163  	logMu       sync.Mutex
   164  	posMu       sync.Mutex
   165  	stress      bool
   166  	repeat      int
   167  	pos         int
   168  	lastPrint   time.Time
   169  	sysTarget   *targets.Target
   170  	upperBase   uint32
   171  }
   172  
   173  func (ctx *Context) run(pid int) {
   174  	env, err := ipc.MakeEnv(ctx.config, pid)
   175  	if err != nil {
   176  		log.Fatalf("failed to create ipc env: %v", err)
   177  	}
   178  	defer env.Close()
   179  	rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12)
   180  	for {
   181  		select {
   182  		case <-ctx.shutdown:
   183  			return
   184  		default:
   185  		}
   186  		if ctx.stress {
   187  			p := ctx.createStressProg(rs)
   188  			ctx.execute(pid, env, p, 0)
   189  		} else {
   190  			idx := ctx.getProgramIndex()
   191  			if ctx.repeat > 0 && idx >= len(ctx.progs)*ctx.repeat {
   192  				return
   193  			}
   194  			p := ctx.progs[idx%len(ctx.progs)]
   195  			ctx.execute(pid, env, p, idx)
   196  		}
   197  	}
   198  }
   199  
   200  func (ctx *Context) execute(pid int, env *ipc.Env, p *prog.Prog, progIndex int) {
   201  	// Limit concurrency window.
   202  	ticket := ctx.gate.Enter()
   203  	defer ctx.gate.Leave(ticket)
   204  
   205  	callOpts := ctx.execOpts
   206  	if *flagOutput {
   207  		ctx.logProgram(pid, p, callOpts)
   208  	}
   209  	progData, err := p.SerializeForExec()
   210  	if err != nil {
   211  		log.Logf(1, "RESULT: failed to serialize: %v", err)
   212  		return
   213  	}
   214  	// This mimics the syz-fuzzer logic. This is important for reproduction.
   215  	for try := 0; ; try++ {
   216  		output, info, hanged, err := env.ExecProg(callOpts, progData)
   217  		if err != nil {
   218  			if ctx.execOpts.EnvFlags&flatrpc.ExecEnvDebug != 0 {
   219  				log.Logf(0, "result: hanged=%v err=%v\n\n%s", hanged, err, output)
   220  			}
   221  			if try > 10 {
   222  				log.SyzFatalf("executor %d failed %d times: %v\n%s", pid, try, err, output)
   223  			}
   224  			// Don't print err/output in this case as it may contain "SYZFAIL" and we want to fail yet.
   225  			log.Logf(1, "executor failed, retrying")
   226  			if try > 3 {
   227  				time.Sleep(100 * time.Millisecond)
   228  			}
   229  			continue
   230  		}
   231  		if info != nil {
   232  			ctx.printCallResults(info)
   233  			if *flagHints {
   234  				ctx.printHints(p, info)
   235  			}
   236  			if *flagCoverFile != "" {
   237  				covFile := fmt.Sprintf("%s_prog%d", *flagCoverFile, progIndex)
   238  				ctx.dumpCoverage(covFile, info)
   239  			}
   240  		} else {
   241  			log.Logf(1, "RESULT: no calls executed")
   242  		}
   243  		break
   244  	}
   245  }
   246  
   247  func (ctx *Context) logProgram(pid int, p *prog.Prog, callOpts *ipc.ExecOpts) {
   248  	data := p.Serialize()
   249  	ctx.logMu.Lock()
   250  	log.Logf(0, "executing program %v:\n%s", pid, data)
   251  	ctx.logMu.Unlock()
   252  }
   253  
   254  func (ctx *Context) printCallResults(info *ipc.ProgInfo) {
   255  	for i, inf := range info.Calls {
   256  		if inf.Flags&ipc.CallExecuted == 0 {
   257  			continue
   258  		}
   259  		flags := ""
   260  		if inf.Flags&ipc.CallFinished == 0 {
   261  			flags += " unfinished"
   262  		}
   263  		if inf.Flags&ipc.CallBlocked != 0 {
   264  			flags += " blocked"
   265  		}
   266  		if inf.Flags&ipc.CallFaultInjected != 0 {
   267  			flags += " faulted"
   268  		}
   269  		log.Logf(1, "CALL %v: signal %v, coverage %v errno %v%v",
   270  			i, len(inf.Signal), len(inf.Cover), inf.Errno, flags)
   271  	}
   272  }
   273  
   274  func (ctx *Context) printHints(p *prog.Prog, info *ipc.ProgInfo) {
   275  	ncomps, ncandidates := 0, 0
   276  	for i := range p.Calls {
   277  		if *flagOutput {
   278  			fmt.Printf("call %v:\n", i)
   279  		}
   280  		comps := info.Calls[i].Comps
   281  		for v, args := range comps {
   282  			ncomps += len(args)
   283  			if *flagOutput {
   284  				fmt.Printf("comp 0x%x:", v)
   285  				for arg := range args {
   286  					fmt.Printf(" 0x%x", arg)
   287  				}
   288  				fmt.Printf("\n")
   289  			}
   290  		}
   291  		p.MutateWithHints(i, comps, func(p *prog.Prog) bool {
   292  			ncandidates++
   293  			if *flagOutput {
   294  				log.Logf(1, "PROGRAM:\n%s", p.Serialize())
   295  			}
   296  			return true
   297  		})
   298  	}
   299  	log.Logf(0, "ncomps=%v ncandidates=%v", ncomps, ncandidates)
   300  }
   301  
   302  func getKernelUpperBase(target *targets.Target) uint32 {
   303  	defaultRet := uint32(0xffffffff)
   304  	if target.OS == targets.Linux {
   305  		// Read the first 8 bytes from /proc/kallsyms.
   306  		f, err := os.Open("/proc/kallsyms")
   307  		if err != nil {
   308  			log.Logf(1, "could not get kernel fixup address: %v", err)
   309  			return defaultRet
   310  		}
   311  		defer f.Close()
   312  		data := make([]byte, 8)
   313  		_, err = f.ReadAt(data, 0)
   314  		if err != nil {
   315  			log.Logf(1, "could not get kernel fixup address: %v", err)
   316  			return defaultRet
   317  		}
   318  		value, err := strconv.ParseUint(string(data), 16, 32)
   319  		if err != nil {
   320  			log.Logf(1, "could not get kernel fixup address: %v", err)
   321  			return defaultRet
   322  		}
   323  		return uint32(value)
   324  	}
   325  	return defaultRet
   326  }
   327  
   328  func (ctx *Context) dumpCallCoverage(coverFile string, info *ipc.CallInfo) {
   329  	if len(info.Cover) == 0 {
   330  		return
   331  	}
   332  	buf := new(bytes.Buffer)
   333  	for _, pc := range info.Cover {
   334  		prev := backend.PreviousInstructionPC(ctx.sysTarget, cover.RestorePC(pc, ctx.upperBase))
   335  		fmt.Fprintf(buf, "0x%x\n", prev)
   336  	}
   337  	err := osutil.WriteFile(coverFile, buf.Bytes())
   338  	if err != nil {
   339  		log.Fatalf("failed to write coverage file: %v", err)
   340  	}
   341  }
   342  
   343  func (ctx *Context) dumpCoverage(coverFile string, info *ipc.ProgInfo) {
   344  	for i, inf := range info.Calls {
   345  		log.Logf(0, "call #%v: signal %v, coverage %v", i, len(inf.Signal), len(inf.Cover))
   346  		ctx.dumpCallCoverage(fmt.Sprintf("%v.%v", coverFile, i), &inf)
   347  	}
   348  	log.Logf(0, "extra: signal %v, coverage %v", len(info.Extra.Signal), len(info.Extra.Cover))
   349  	ctx.dumpCallCoverage(fmt.Sprintf("%v.extra", coverFile), &info.Extra)
   350  }
   351  
   352  func (ctx *Context) getProgramIndex() int {
   353  	ctx.posMu.Lock()
   354  	idx := ctx.pos
   355  	ctx.pos++
   356  	if idx%len(ctx.progs) == 0 && time.Since(ctx.lastPrint) > 5*time.Second {
   357  		log.Logf(0, "executed programs: %v", idx)
   358  		ctx.lastPrint = time.Now()
   359  	}
   360  	ctx.posMu.Unlock()
   361  	return idx
   362  }
   363  
   364  func (ctx *Context) createStressProg(rs rand.Source) *prog.Prog {
   365  	rnd := rand.New(rs)
   366  	if len(ctx.progs) == 0 || rnd.Intn(2) == 0 {
   367  		return ctx.target.Generate(rs, prog.RecommendedCalls, ctx.choiceTable)
   368  	}
   369  	p := ctx.progs[rnd.Intn(len(ctx.progs))].Clone()
   370  	p.Mutate(rs, prog.RecommendedCalls, ctx.choiceTable, nil, ctx.progs)
   371  	return p
   372  }
   373  
   374  func loadPrograms(target *prog.Target, files []string) []*prog.Prog {
   375  	var progs []*prog.Prog
   376  	for _, fn := range files {
   377  		if corpus, err := db.Open(fn, false); err == nil {
   378  			for _, rec := range corpus.Records {
   379  				p, err := target.Deserialize(rec.Val, prog.NonStrict)
   380  				if err != nil {
   381  					continue
   382  				}
   383  				progs = append(progs, p)
   384  			}
   385  			continue
   386  		}
   387  		data, err := os.ReadFile(fn)
   388  		if err != nil {
   389  			log.Fatalf("failed to read log file: %v", err)
   390  		}
   391  		for _, entry := range target.ParseLog(data) {
   392  			progs = append(progs, entry.P)
   393  		}
   394  	}
   395  	log.Logf(0, "parsed %v programs", len(progs))
   396  	return progs
   397  }
   398  
   399  func createConfig(target *prog.Target, featuresFlags csource.Features, syscalls []int) (
   400  	*ipc.Config, *ipc.ExecOpts, map[*prog.Syscall]bool, flatrpc.Feature) {
   401  	config, execOpts, err := ipcconfig.Default(target)
   402  	if err != nil {
   403  		log.Fatalf("%v", err)
   404  	}
   405  	if execOpts.EnvFlags&flatrpc.ExecEnvSignal != 0 {
   406  		execOpts.ExecFlags |= flatrpc.ExecFlagCollectCover
   407  	}
   408  	if *flagCoverFile != "" {
   409  		execOpts.EnvFlags |= flatrpc.ExecEnvSignal
   410  		execOpts.ExecFlags |= flatrpc.ExecFlagCollectCover
   411  		execOpts.ExecFlags &^= flatrpc.ExecFlagDedupCover
   412  	}
   413  	if *flagHints {
   414  		if execOpts.ExecFlags&flatrpc.ExecFlagCollectCover != 0 {
   415  			execOpts.ExecFlags ^= flatrpc.ExecFlagCollectCover
   416  		}
   417  		execOpts.ExecFlags |= flatrpc.ExecFlagCollectComps
   418  	}
   419  	cfg := &mgrconfig.Config{
   420  		Sandbox:    ipc.FlagsToSandbox(execOpts.EnvFlags),
   421  		SandboxArg: execOpts.SandboxArg,
   422  		Derived: mgrconfig.Derived{
   423  			TargetOS:     target.OS,
   424  			TargetArch:   target.Arch,
   425  			TargetVMArch: target.Arch,
   426  			Target:       target,
   427  			SysTarget:    targets.Get(target.OS, target.Arch),
   428  			Syscalls:     syscalls,
   429  		},
   430  	}
   431  	checker := vminfo.New(cfg)
   432  	fileInfos := host.ReadFiles(checker.RequiredFiles())
   433  	featureInfos, err := host.SetupFeatures(target, config.Executor, flatrpc.AllFeatures, featuresFlags)
   434  	if err != nil {
   435  		log.Fatal(err)
   436  	}
   437  
   438  	ctx, cancel := context.WithCancel(context.Background())
   439  	defer cancel()
   440  	go checkerExecutor(ctx, checker, config)
   441  
   442  	enabledSyscalls, disabledSyscalls, features, err := checker.Run(fileInfos, featureInfos)
   443  	if err != nil {
   444  		log.Fatal(err)
   445  	}
   446  	if *flagOutput {
   447  		for feat, info := range features {
   448  			log.Logf(0, "%-24v: %v", flatrpc.EnumNamesFeature[feat], info.Reason)
   449  		}
   450  		for c, reason := range disabledSyscalls {
   451  			log.Logf(0, "unsupported syscall: %v: %v", c.Name, reason)
   452  		}
   453  		enabledSyscalls, disabledSyscalls = target.TransitivelyEnabledCalls(enabledSyscalls)
   454  		for c, reason := range disabledSyscalls {
   455  			log.Logf(0, "transitively unsupported: %v: %v", c.Name, reason)
   456  		}
   457  	}
   458  	execOpts.EnvFlags |= ipc.FeaturesToFlags(features.Enabled(), featuresFlags)
   459  	return config, execOpts, enabledSyscalls, features.Enabled()
   460  }
   461  
   462  func checkerExecutor(ctx context.Context, source queue.Source, config *ipc.Config) {
   463  	env, err := ipc.MakeEnv(config, 0)
   464  	if err != nil {
   465  		log.Fatalf("failed to create ipc env: %v", err)
   466  	}
   467  	defer env.Close()
   468  	for {
   469  		req := source.Next()
   470  		if req == nil {
   471  			select {
   472  			case <-time.After(time.Second / 100):
   473  			case <-ctx.Done():
   474  				return
   475  			}
   476  			continue
   477  		}
   478  		progData, err := req.Prog.SerializeForExec()
   479  		if err != nil {
   480  			log.Fatalf("failed to serialize %s: %v", req.Prog.Serialize(), err)
   481  		}
   482  		output, info, hanged, err := env.ExecProg(&req.ExecOpts, progData)
   483  		res := &queue.Result{
   484  			Status: queue.Success,
   485  			Info:   info,
   486  			Output: output,
   487  			Err:    err,
   488  		}
   489  		if err != nil {
   490  			res.Status = queue.ExecFailure
   491  		}
   492  		if hanged && err == nil {
   493  			res.Status = queue.ExecFailure
   494  			res.Err = fmt.Errorf("hanged")
   495  		}
   496  		req.Done(res)
   497  	}
   498  }