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 }