github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/vm/vmimpl/vmimpl.go (about)

     1  // Copyright 2017 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 vmimpl provides an abstract test machine (VM, physical machine, etc)
     5  // interface for the rest of the system. For convenience test machines are subsequently
     6  // collectively called VMs.
     7  // The package also provides various utility functions for VM implementations.
     8  package vmimpl
     9  
    10  import (
    11  	"crypto/rand"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"math/big"
    16  	"net"
    17  	"os/exec"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/google/syzkaller/pkg/log"
    22  	"github.com/google/syzkaller/pkg/osutil"
    23  	"github.com/google/syzkaller/pkg/report"
    24  	"github.com/google/syzkaller/sys/targets"
    25  )
    26  
    27  // Pool represents a set of test machines (VMs, physical devices, etc) of particular type.
    28  type Pool interface {
    29  	// Count returns total number of VMs in the pool.
    30  	Count() int
    31  
    32  	// Create creates and boots a new VM instance.
    33  	Create(workdir string, index int) (Instance, error)
    34  }
    35  
    36  // Instance represents a single VM.
    37  type Instance interface {
    38  	// Copy copies a hostSrc file into VM and returns file name in VM.
    39  	Copy(hostSrc string) (string, error)
    40  
    41  	// Forward sets up forwarding from within VM to the given tcp
    42  	// port on the host and returns the address to use in VM.
    43  	Forward(port int) (string, error)
    44  
    45  	// Run runs cmd inside of the VM (think of ssh cmd).
    46  	// outc receives combined cmd and kernel console output.
    47  	// errc receives either command Wait return error or vmimpl.ErrTimeout.
    48  	// Command is terminated after timeout. Send on the stop chan can be used to terminate it earlier.
    49  	Run(timeout time.Duration, stop <-chan bool, command string) (outc <-chan []byte, errc <-chan error, err error)
    50  
    51  	// Diagnose retrieves additional debugging info from the VM
    52  	// (e.g. by sending some sys-rq's or SIGABORT'ing a Go program).
    53  	//
    54  	// Optionally returns (some or all) of the info directly. If wait == true,
    55  	// the caller must wait for the VM to output info directly to its log.
    56  	//
    57  	// rep describes the reason why Diagnose was called.
    58  	Diagnose(rep *report.Report) (diagnosis []byte, wait bool)
    59  
    60  	// Close stops and destroys the VM.
    61  	Close()
    62  }
    63  
    64  // Infoer is an optional interface that can be implemented by Instance.
    65  type Infoer interface {
    66  	// MachineInfo returns additional info about the VM, e.g. VMM version/arguments.
    67  	Info() ([]byte, error)
    68  }
    69  
    70  // PprofPortProvider is used when the instance wants to define a custom pprof port.
    71  type PprofPortProvider interface {
    72  	PprofPort() int
    73  }
    74  
    75  // Env contains global constant parameters for a pool of VMs.
    76  type Env struct {
    77  	// Unique name
    78  	// Can be used for VM name collision resolution if several pools share global name space.
    79  	Name      string
    80  	OS        string // target OS
    81  	Arch      string // target arch
    82  	Workdir   string
    83  	Image     string
    84  	SSHKey    string
    85  	SSHUser   string
    86  	Timeouts  targets.Timeouts
    87  	Debug     bool
    88  	Config    []byte // json-serialized VM-type-specific config
    89  	KernelSrc string
    90  }
    91  
    92  // BootError is returned by Pool.Create when VM does not boot.
    93  // It should not be used for VMM intfrastructure errors, i.e. for problems not related
    94  // to the tested kernel itself.
    95  type BootError struct {
    96  	Title  string
    97  	Output []byte
    98  }
    99  
   100  func MakeBootError(err error, output []byte) error {
   101  	var verboseError *osutil.VerboseError
   102  	if errors.As(err, &verboseError) {
   103  		return BootError{verboseError.Title, append(verboseError.Output, output...)}
   104  	}
   105  	return BootError{err.Error(), output}
   106  }
   107  
   108  func (err BootError) Error() string {
   109  	return fmt.Sprintf("%v\n%s", err.Title, err.Output)
   110  }
   111  
   112  func (err BootError) BootError() (string, []byte) {
   113  	return err.Title, err.Output
   114  }
   115  
   116  // By default, all Pool.Create() errors are related to infrastructure problems.
   117  // InfraError is to be used when we want to also attach output to the title.
   118  type InfraError struct {
   119  	Title  string
   120  	Output []byte
   121  }
   122  
   123  func (err InfraError) Error() string {
   124  	return fmt.Sprintf("%v\n%s", err.Title, err.Output)
   125  }
   126  
   127  func (err InfraError) InfraError() (string, []byte) {
   128  	return err.Title, err.Output
   129  }
   130  
   131  // Register registers a new VM type within the package.
   132  func Register(typ string, ctor ctorFunc, allowsOvercommit bool) {
   133  	Types[typ] = Type{
   134  		Ctor:       ctor,
   135  		Overcommit: allowsOvercommit,
   136  	}
   137  }
   138  
   139  type Type struct {
   140  	Ctor       ctorFunc
   141  	Overcommit bool
   142  }
   143  
   144  type ctorFunc func(env *Env) (Pool, error)
   145  
   146  var (
   147  	// Close to interrupt all pending operations in all VMs.
   148  	Shutdown   = make(chan struct{})
   149  	ErrTimeout = errors.New("timeout")
   150  
   151  	Types = make(map[string]Type)
   152  )
   153  
   154  func Multiplex(cmd *exec.Cmd, merger *OutputMerger, console io.Closer, timeout time.Duration,
   155  	stop, closed <-chan bool, debug bool) (<-chan []byte, <-chan error, error) {
   156  	errc := make(chan error, 1)
   157  	signal := func(err error) {
   158  		select {
   159  		case errc <- err:
   160  		default:
   161  		}
   162  	}
   163  	go func() {
   164  		select {
   165  		case <-time.After(timeout):
   166  			signal(ErrTimeout)
   167  		case <-stop:
   168  			signal(ErrTimeout)
   169  		case <-closed:
   170  			if debug {
   171  				log.Logf(0, "instance closed")
   172  			}
   173  			signal(fmt.Errorf("instance closed"))
   174  		case err := <-merger.Err:
   175  			cmd.Process.Kill()
   176  			console.Close()
   177  			merger.Wait()
   178  			if cmdErr := cmd.Wait(); cmdErr == nil {
   179  				// If the command exited successfully, we got EOF error from merger.
   180  				// But in this case no error has happened and the EOF is expected.
   181  				err = nil
   182  			}
   183  			signal(err)
   184  			return
   185  		}
   186  		cmd.Process.Kill()
   187  		console.Close()
   188  		merger.Wait()
   189  		cmd.Wait()
   190  	}()
   191  	return merger.Output, errc, nil
   192  }
   193  
   194  // On VMs, pprof will be listening to this port.
   195  const PprofPort = 6060
   196  
   197  func RandomPort() int {
   198  	n, err := rand.Int(rand.Reader, big.NewInt(64<<10-1<<10))
   199  	if err != nil {
   200  		panic(err)
   201  	}
   202  	return int(n.Int64()) + 1<<10
   203  }
   204  
   205  func UnusedTCPPort() int {
   206  	for {
   207  		port := RandomPort()
   208  		ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%v", port))
   209  		if err == nil {
   210  			ln.Close()
   211  			return port
   212  		}
   213  	}
   214  }
   215  
   216  // Escapes double quotes(and nested double quote escapes). Ignores any other escapes.
   217  // Reference: https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html
   218  func EscapeDoubleQuotes(inp string) string {
   219  	var ret strings.Builder
   220  	for pos := 0; pos < len(inp); pos++ {
   221  		// If inp[pos] is not a double quote or a backslash, just use
   222  		// as is.
   223  		if inp[pos] != '"' && inp[pos] != '\\' {
   224  			ret.WriteByte(inp[pos])
   225  			continue
   226  		}
   227  		// If it is a double quote, escape.
   228  		if inp[pos] == '"' {
   229  			ret.WriteString("\\\"")
   230  			continue
   231  		}
   232  		// If we detect a backslash, reescape only if what it's already escaping
   233  		// is a double-quotes.
   234  		temp := ""
   235  		j := pos
   236  		for ; j < len(inp); j++ {
   237  			if inp[j] == '\\' {
   238  				temp += string(inp[j])
   239  				continue
   240  			}
   241  			// If the escape corresponds to a double quotes, re-escape.
   242  			// Else, just use as is.
   243  			if inp[j] == '"' {
   244  				temp = temp + temp + "\\\""
   245  			} else {
   246  				temp += string(inp[j])
   247  			}
   248  			break
   249  		}
   250  		ret.WriteString(temp)
   251  		pos = j
   252  	}
   253  	return ret.String()
   254  }