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 }