github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/tools/syz-testbed/instance.go (about)

     1  // Copyright 2021 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  package main
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/google/syzkaller/pkg/config"
    15  	"github.com/google/syzkaller/pkg/osutil"
    16  )
    17  
    18  type Instance interface {
    19  	Run() error
    20  	Stop()
    21  	FetchResult() (RunResult, error)
    22  	Uptime() time.Duration
    23  }
    24  
    25  // The essential information about an active instance.
    26  type InstanceCommon struct {
    27  	Name            string
    28  	LogFile         string
    29  	ExecCommand     string
    30  	ExecCommandArgs []string
    31  	StartedAt       time.Time
    32  	StoppedAt       time.Time
    33  	stopChannel     chan bool
    34  }
    35  
    36  func (inst *InstanceCommon) Run() error {
    37  	const stopDelay = time.Minute
    38  
    39  	log.Printf("[%s] starting", inst.Name)
    40  	cmd := osutil.GraciousCommand(inst.ExecCommand, inst.ExecCommandArgs...)
    41  
    42  	if inst.LogFile != "" {
    43  		logfile, err := os.Create(inst.LogFile)
    44  		if err != nil {
    45  			return fmt.Errorf("[%s] failed to create logfile: %w", inst.Name, err)
    46  		}
    47  		cmd.Stdout = logfile
    48  		cmd.Stderr = logfile
    49  	}
    50  
    51  	complete := make(chan error)
    52  	inst.StartedAt = time.Now()
    53  	cmd.Start()
    54  	go func() {
    55  		complete <- cmd.Wait()
    56  	}()
    57  
    58  	select {
    59  	case err := <-complete:
    60  		return err
    61  	case <-inst.stopChannel:
    62  		// TODO: handle other OSes?
    63  		cmd.Process.Signal(os.Interrupt)
    64  		select {
    65  		case <-complete:
    66  			// The manager has exited.
    67  		case <-time.After(stopDelay):
    68  			// The manager did not exit - kill it.
    69  			log.Printf("[%s] instance did not exit itself, killing it", inst.Name)
    70  			cmd.Process.Kill()
    71  			<-complete
    72  		}
    73  	}
    74  	inst.StoppedAt = time.Now()
    75  	return nil
    76  }
    77  
    78  func (inst *InstanceCommon) Stop() {
    79  	select {
    80  	case inst.stopChannel <- true:
    81  	default:
    82  	}
    83  }
    84  
    85  func (inst *InstanceCommon) Uptime() time.Duration {
    86  	if !inst.StartedAt.IsZero() && inst.StoppedAt.IsZero() {
    87  		return time.Since(inst.StartedAt)
    88  	}
    89  	return inst.StoppedAt.Sub(inst.StartedAt)
    90  }
    91  
    92  type SyzManagerInstance struct {
    93  	InstanceCommon
    94  	SyzkallerInfo
    95  	RunTime time.Duration
    96  }
    97  
    98  func (inst *SyzManagerInstance) FetchResult() (RunResult, error) {
    99  	bugs, err := collectBugs(inst.Workdir)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	records, err := readBenches(inst.BenchFile)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return &SyzManagerResult{
   108  		Bugs:        bugs,
   109  		StatRecords: records,
   110  	}, nil
   111  }
   112  
   113  func (inst *SyzManagerInstance) Run() error {
   114  	ret := make(chan error, 1)
   115  	go func() {
   116  		ret <- inst.InstanceCommon.Run()
   117  	}()
   118  
   119  	select {
   120  	case err := <-ret:
   121  		// Syz-managers are not supposed to stop themselves under normal circumstances.
   122  		// If one of them did stop, there must have been a very good reason to do so.
   123  		return fmt.Errorf("[%s] stopped: %w", inst.Name, err)
   124  	case <-time.After(inst.RunTime):
   125  		inst.Stop()
   126  		<-ret
   127  		return nil
   128  	}
   129  }
   130  
   131  type SyzkallerInfo struct {
   132  	Workdir   string
   133  	CfgFile   string
   134  	BenchFile string
   135  }
   136  
   137  func SetupSyzkallerInstance(mgrName, folder string, checkout *Checkout) (*SyzkallerInfo, error) {
   138  	workdir := filepath.Join(folder, "workdir")
   139  	log.Printf("[%s] Generating workdir", mgrName)
   140  	err := osutil.MkdirAll(workdir)
   141  	if err != nil {
   142  		return nil, fmt.Errorf("failed to create workdir %s", workdir)
   143  	}
   144  	log.Printf("[%s] Generating syz-manager config", mgrName)
   145  	cfgFile := filepath.Join(folder, "manager.cfg")
   146  	managerCfg, err := config.PatchJSON(checkout.ManagerConfig, map[string]interface{}{
   147  		"name":      mgrName,
   148  		"workdir":   workdir,
   149  		"syzkaller": checkout.Path,
   150  	})
   151  	if err != nil {
   152  		return nil, fmt.Errorf("failed to patch mgr config")
   153  	}
   154  	err = osutil.WriteFile(cfgFile, managerCfg)
   155  	if err != nil {
   156  		return nil, fmt.Errorf("failed to save manager config to %s: %w", cfgFile, err)
   157  	}
   158  	return &SyzkallerInfo{
   159  		Workdir:   workdir,
   160  		CfgFile:   cfgFile,
   161  		BenchFile: filepath.Join(folder, "bench.txt"),
   162  	}, nil
   163  }
   164  
   165  func (t *SyzManagerTarget) newSyzManagerInstance(slotName, uniqName string, checkout *Checkout) (Instance, error) {
   166  	folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName))
   167  	common, err := SetupSyzkallerInstance(slotName, folder, checkout)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  	if t.config.Corpus != "" {
   172  		log.Printf("[%s] Copying corpus", uniqName)
   173  		corpusPath := filepath.Join(common.Workdir, "corpus.db")
   174  		err = osutil.CopyFile(t.config.Corpus, corpusPath)
   175  		if err != nil {
   176  			return nil, fmt.Errorf("failed to copy corpus from %s: %w", t.config.Corpus, err)
   177  		}
   178  	}
   179  	return &SyzManagerInstance{
   180  		InstanceCommon: InstanceCommon{
   181  			Name:            uniqName,
   182  			LogFile:         filepath.Join(folder, "log.txt"),
   183  			ExecCommand:     filepath.Join(checkout.Path, "bin", "syz-manager"),
   184  			ExecCommandArgs: []string{"-config", common.CfgFile, "-bench", common.BenchFile},
   185  			stopChannel:     make(chan bool, 1),
   186  		},
   187  		SyzkallerInfo: *common,
   188  		RunTime:       t.config.RunTime.Duration,
   189  	}, nil
   190  }
   191  
   192  type SyzReproInstance struct {
   193  	InstanceCommon
   194  	SyzkallerInfo
   195  	Input      *SyzReproInput
   196  	ReproFile  string
   197  	CReproFile string
   198  	TitleFile  string
   199  }
   200  
   201  func (inst *SyzReproInstance) FetchResult() (RunResult, error) {
   202  	result := &SyzReproResult{
   203  		Input:       inst.Input,
   204  		ReproFound:  osutil.IsExist(inst.ReproFile),
   205  		CReproFound: osutil.IsExist(inst.CReproFile),
   206  		Duration:    inst.Uptime(),
   207  	}
   208  	outTitle, _ := os.ReadFile(inst.TitleFile)
   209  	if outTitle != nil {
   210  		result.ReproTitle = strings.TrimSpace(string(outTitle))
   211  		if result.ReproTitle != inst.Input.origTitle {
   212  			// If we found a different bug, treat the reproduction as unsuccessful.
   213  			result.ReproFound = false
   214  			result.CReproFound = false
   215  		}
   216  	}
   217  	return result, nil
   218  }
   219  
   220  func (t *SyzReproTarget) newSyzReproInstance(slotName, uniqName string, input *SyzReproInput,
   221  	checkout *Checkout) (Instance, error) {
   222  	folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName))
   223  	common, err := SetupSyzkallerInstance(slotName, folder, checkout)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	reproFile := filepath.Join(folder, "repro.txt")
   229  	cReproFile := filepath.Join(folder, "crepro.txt")
   230  	titleFile := filepath.Join(folder, "title.txt")
   231  	newExecLog := filepath.Join(folder, "execution-log.txt")
   232  	err = osutil.CopyFile(input.Path, newExecLog)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	return &SyzReproInstance{
   237  		InstanceCommon: InstanceCommon{
   238  			Name:        uniqName,
   239  			LogFile:     filepath.Join(folder, "log.txt"),
   240  			ExecCommand: filepath.Join(checkout.Path, "bin", "syz-repro"),
   241  			ExecCommandArgs: []string{
   242  				"-config", common.CfgFile,
   243  				"-output", reproFile,
   244  				"-crepro", cReproFile,
   245  				"-title", titleFile,
   246  				newExecLog,
   247  			},
   248  			stopChannel: make(chan bool, 1),
   249  		},
   250  		SyzkallerInfo: *common,
   251  		Input:         input,
   252  		ReproFile:     reproFile,
   253  		CReproFile:    cReproFile,
   254  		TitleFile:     titleFile,
   255  	}, nil
   256  }