github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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  	"context"
    12  	"crypto/rand"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"math/big"
    17  	"net"
    18  	"os"
    19  	"os/exec"
    20  	"strings"
    21  	"syscall"
    22  	"time"
    23  
    24  	"github.com/google/syzkaller/pkg/log"
    25  	"github.com/google/syzkaller/pkg/osutil"
    26  	"github.com/google/syzkaller/pkg/report"
    27  	"github.com/google/syzkaller/sys/targets"
    28  )
    29  
    30  // Pool represents a set of test machines (VMs, physical devices, etc) of particular type.
    31  type Pool interface {
    32  	// Count returns total number of VMs in the pool.
    33  	Count() int
    34  
    35  	// Create creates and boots a new VM instance.
    36  	Create(ctx context.Context, workdir string, index int) (Instance, error)
    37  }
    38  
    39  // Instance represents a single VM.
    40  type Instance interface {
    41  	// Copy copies a hostSrc file into VM and returns file name in VM.
    42  	Copy(hostSrc string) (string, error)
    43  
    44  	// Forward sets up forwarding from within VM to the given tcp
    45  	// port on the host and returns the address to use in VM.
    46  	Forward(port int) (string, error)
    47  
    48  	// Run runs cmd inside of the VM (think of ssh cmd).
    49  	// outc receives combined cmd and kernel console output.
    50  	// errc receives either command Wait return error or vmimpl.ErrTimeout.
    51  	// Command terminates with context. Use context.WithTimeout to terminate it earlier.
    52  	Run(ctx context.Context, command string) (outc <-chan []byte, errc <-chan error, err error)
    53  
    54  	// Diagnose retrieves additional debugging info from the VM
    55  	// (e.g. by sending some sys-rq's or SIGABORT'ing a Go program).
    56  	//
    57  	// Optionally returns (some or all) of the info directly. If wait == true,
    58  	// the caller must wait for the VM to output info directly to its log.
    59  	//
    60  	// rep describes the reason why Diagnose was called.
    61  	Diagnose(rep *report.Report) (diagnosis []byte, wait bool)
    62  
    63  	// Close stops and destroys the VM.
    64  	io.Closer
    65  }
    66  
    67  // Infoer is an optional interface that can be implemented by Instance.
    68  type Infoer interface {
    69  	// MachineInfo returns additional info about the VM, e.g. VMM version/arguments.
    70  	Info() ([]byte, error)
    71  }
    72  
    73  // Env contains global constant parameters for a pool of VMs.
    74  type Env struct {
    75  	// Unique name
    76  	// Can be used for VM name collision resolution if several pools share global name space.
    77  	Name      string
    78  	OS        string // target OS
    79  	Arch      string // target arch
    80  	Workdir   string
    81  	Image     string
    82  	SSHKey    string
    83  	SSHUser   string
    84  	Timeouts  targets.Timeouts
    85  	Snapshot  bool
    86  	Debug     bool
    87  	Config    []byte // json-serialized VM-type-specific config
    88  	KernelSrc string
    89  }
    90  
    91  // BootError is returned by Pool.Create when VM does not boot.
    92  // It should not be used for VMM intfrastructure errors, i.e. for problems not related
    93  // to the tested kernel itself.
    94  type BootError struct {
    95  	Title  string
    96  	Output []byte
    97  }
    98  
    99  func MakeBootError(err error, output []byte) error {
   100  	if len(output) == 0 {
   101  		// In reports, it may be helpful to distinguish the case when the boot output
   102  		// was collected, but turned out to be empty.
   103  		output = []byte("<empty boot output>")
   104  	}
   105  	var verboseError *osutil.VerboseError
   106  	if errors.As(err, &verboseError) {
   107  		return BootError{verboseError.Error(), append(verboseError.Output, output...)}
   108  	}
   109  	return BootError{err.Error(), output}
   110  }
   111  
   112  func (err BootError) Error() string {
   113  	return fmt.Sprintf("%v\n%s", err.Title, err.Output)
   114  }
   115  
   116  func (err BootError) BootError() (string, []byte) {
   117  	return err.Title, err.Output
   118  }
   119  
   120  // By default, all Pool.Create() errors are related to infrastructure problems.
   121  // InfraError is to be used when we want to also attach output to the title.
   122  type InfraError struct {
   123  	Title  string
   124  	Output []byte
   125  }
   126  
   127  func (err InfraError) Error() string {
   128  	return fmt.Sprintf("%v\n%s", err.Title, err.Output)
   129  }
   130  
   131  func (err InfraError) InfraError() (string, []byte) {
   132  	return err.Title, err.Output
   133  }
   134  
   135  // Register registers a new VM type within the package.
   136  func Register(typ string, desc Type) {
   137  	Types[typ] = desc
   138  }
   139  
   140  type Type struct {
   141  	Ctor ctorFunc
   142  	// It's possible to create out-of-thin-air instances of this type.
   143  	// Out-of-thin-air instances are used by syz-ci for image testing, patch testing, bisection, etc.
   144  	Overcommit bool
   145  	// Instances of this type can be preempted and lost connection as the result.
   146  	// For preempted instances executor prints "SYZ-EXECUTOR: PREEMPTED" and then
   147  	// the host understands that the lost connection was expected and is not a bug.
   148  	Preemptible bool
   149  }
   150  
   151  type ctorFunc func(env *Env) (Pool, error)
   152  
   153  var (
   154  	// Close to interrupt all pending operations in all VMs.
   155  	Shutdown   = make(chan struct{})
   156  	ErrTimeout = errors.New("timeout")
   157  
   158  	Types = make(map[string]Type)
   159  )
   160  
   161  type CmdCloser struct {
   162  	*exec.Cmd
   163  }
   164  
   165  func (cc CmdCloser) Close() error {
   166  	cc.Process.Kill()
   167  	return cc.Wait()
   168  }
   169  
   170  var WaitForOutputTimeout = 10 * time.Second
   171  
   172  type MultiplexConfig struct {
   173  	Console     io.Closer
   174  	Close       <-chan bool
   175  	Debug       bool
   176  	Scale       time.Duration
   177  	IgnoreError func(err error) bool
   178  }
   179  
   180  func Multiplex(ctx context.Context, cmd *exec.Cmd, merger *OutputMerger, config MultiplexConfig) (
   181  	<-chan []byte, <-chan error, error) {
   182  	if config.Scale <= 0 {
   183  		panic("slowdown must be set")
   184  	}
   185  	errc := make(chan error, 1)
   186  	signal := func(err error) {
   187  		select {
   188  		case errc <- err:
   189  		default:
   190  		}
   191  	}
   192  	go func() {
   193  		select {
   194  		case <-ctx.Done():
   195  			signal(ErrTimeout)
   196  		case <-config.Close:
   197  			if config.Debug {
   198  				log.Logf(0, "instance closed")
   199  			}
   200  			signal(fmt.Errorf("instance closed"))
   201  		case err := <-merger.Err:
   202  			cmd.Process.Kill()
   203  			if cmdErr := cmd.Wait(); cmdErr == nil {
   204  				// If the command exited successfully, we got EOF error from merger.
   205  				// But in this case no error has happened and the EOF is expected.
   206  				err = nil
   207  			} else if config.IgnoreError != nil && config.IgnoreError(err) {
   208  				err = ErrTimeout
   209  			}
   210  			// Once the command has failed, we might want to let the full console
   211  			// output accumulate before we abort the console connection too.
   212  			if err != nil {
   213  				time.Sleep(WaitForOutputTimeout * config.Scale)
   214  			}
   215  			if config.Console != nil {
   216  				// Only wait for the merger if we're able to control the console stream.
   217  				config.Console.Close()
   218  				merger.Wait()
   219  			}
   220  			signal(err)
   221  			return
   222  		}
   223  		cmd.Process.Kill()
   224  		if config.Console != nil {
   225  			config.Console.Close()
   226  			merger.Wait()
   227  		}
   228  		cmd.Wait()
   229  	}()
   230  	return merger.Output, errc, nil
   231  }
   232  
   233  func RandomPort() int {
   234  	n, err := rand.Int(rand.Reader, big.NewInt(64<<10-1<<10))
   235  	if err != nil {
   236  		panic(err)
   237  	}
   238  	return int(n.Int64()) + 1<<10
   239  }
   240  
   241  func UnusedTCPPort() int {
   242  	for {
   243  		port := RandomPort()
   244  		ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%v", port))
   245  		if err == nil {
   246  			ln.Close()
   247  			return port
   248  		}
   249  
   250  		// Continue searching for a port only if we fail with EADDRINUSE or don't have permissions to use this port.
   251  		// Although we exclude ports <1024 in RandomPort(), it's still possible that we can face a restricted port.
   252  		var opErr *net.OpError
   253  		if errors.As(err, &opErr) && opErr.Op == "listen" {
   254  			var syscallErr *os.SyscallError
   255  			if errors.As(opErr.Err, &syscallErr) {
   256  				if errors.Is(syscallErr.Err, syscall.EADDRINUSE) || errors.Is(syscallErr.Err, syscall.EACCES) {
   257  					continue
   258  				}
   259  			}
   260  		}
   261  		log.Fatalf("error allocating port localhost:%d: %v", port, err)
   262  	}
   263  }
   264  
   265  // Escapes double quotes(and nested double quote escapes). Ignores any other escapes.
   266  // Reference: https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html
   267  func EscapeDoubleQuotes(inp string) string {
   268  	var ret strings.Builder
   269  	for pos := 0; pos < len(inp); pos++ {
   270  		// If inp[pos] is not a double quote or a backslash, just use
   271  		// as is.
   272  		if inp[pos] != '"' && inp[pos] != '\\' {
   273  			ret.WriteByte(inp[pos])
   274  			continue
   275  		}
   276  		// If it is a double quote, escape.
   277  		if inp[pos] == '"' {
   278  			ret.WriteString("\\\"")
   279  			continue
   280  		}
   281  		// If we detect a backslash, reescape only if what it's already escaping
   282  		// is a double-quotes.
   283  		temp := ""
   284  		j := pos
   285  		for ; j < len(inp); j++ {
   286  			if inp[j] == '\\' {
   287  				temp += string(inp[j])
   288  				continue
   289  			}
   290  			// If the escape corresponds to a double quotes, re-escape.
   291  			// Else, just use as is.
   292  			if inp[j] == '"' {
   293  				temp = temp + temp + "\\\""
   294  			} else {
   295  				temp += string(inp[j])
   296  			}
   297  			break
   298  		}
   299  		ret.WriteString(temp)
   300  		pos = j
   301  	}
   302  	return ret.String()
   303  }