1// Copyright 2015 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 kvm provides VMs based on lkvm (kvmtool) virtualization. 5// It is not well tested. 6package kvm 7 8import ( 9 "fmt" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "runtime" 14 "strconv" 15 "sync" 16 "time" 17 18 "github.com/google/syzkaller/pkg/config" 19 "github.com/google/syzkaller/pkg/osutil" 20 "github.com/google/syzkaller/vm/vmimpl" 21) 22 23const ( 24 hostAddr = "192.168.33.1" 25) 26 27func init() { 28 vmimpl.Register("kvm", ctor) 29} 30 31type Config struct { 32 Count int // number of VMs to use 33 Lkvm string // lkvm binary name 34 Kernel string // e.g. arch/x86/boot/bzImage 35 Cmdline string // kernel command line 36 CPU int // number of VM CPUs 37 Mem int // amount of VM memory in MBs 38} 39 40type Pool struct { 41 env *vmimpl.Env 42 cfg *Config 43} 44 45type instance struct { 46 cfg *Config 47 sandbox string 48 sandboxPath string 49 lkvm *exec.Cmd 50 readerC chan error 51 waiterC chan error 52 debug bool 53 54 mu sync.Mutex 55 outputB []byte 56 outputC chan []byte 57} 58 59func ctor(env *vmimpl.Env) (vmimpl.Pool, error) { 60 cfg := &Config{ 61 Count: 1, 62 Lkvm: "lkvm", 63 } 64 if err := config.LoadData(env.Config, cfg); err != nil { 65 return nil, fmt.Errorf("failed to parse kvm vm config: %v", err) 66 } 67 if cfg.Count < 1 || cfg.Count > 1000 { 68 return nil, fmt.Errorf("invalid config param count: %v, want [1, 1000]", cfg.Count) 69 } 70 if env.Debug { 71 cfg.Count = 1 72 } 73 if env.Image != "" { 74 return nil, fmt.Errorf("lkvm does not support custom images") 75 } 76 if _, err := exec.LookPath(cfg.Lkvm); err != nil { 77 return nil, err 78 } 79 if !osutil.IsExist(cfg.Kernel) { 80 return nil, fmt.Errorf("kernel file '%v' does not exist", cfg.Kernel) 81 } 82 if cfg.CPU < 1 || cfg.CPU > 1024 { 83 return nil, fmt.Errorf("invalid config param cpu: %v, want [1-1024]", cfg.CPU) 84 } 85 if cfg.Mem < 128 || cfg.Mem > 1048576 { 86 return nil, fmt.Errorf("invalid config param mem: %v, want [128-1048576]", cfg.Mem) 87 } 88 cfg.Kernel = osutil.Abs(cfg.Kernel) 89 pool := &Pool{ 90 cfg: cfg, 91 env: env, 92 } 93 return pool, nil 94} 95 96func (pool *Pool) Count() int { 97 return pool.cfg.Count 98} 99 100func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) { 101 sandbox := fmt.Sprintf("syz-%v", index) 102 inst := &instance{ 103 cfg: pool.cfg, 104 sandbox: sandbox, 105 sandboxPath: filepath.Join(os.Getenv("HOME"), ".lkvm", sandbox), 106 debug: pool.env.Debug, 107 } 108 closeInst := inst 109 defer func() { 110 if closeInst != nil { 111 closeInst.Close() 112 } 113 }() 114 115 os.RemoveAll(inst.sandboxPath) 116 os.Remove(inst.sandboxPath + ".sock") 117 out, err := osutil.Command(inst.cfg.Lkvm, "setup", sandbox).CombinedOutput() 118 if err != nil { 119 return nil, fmt.Errorf("failed to lkvm setup: %v\n%s", err, out) 120 } 121 scriptPath := filepath.Join(workdir, "script.sh") 122 if err := osutil.WriteExecFile(scriptPath, []byte(script)); err != nil { 123 return nil, fmt.Errorf("failed to create temp file: %v", err) 124 } 125 126 rpipe, wpipe, err := osutil.LongPipe() 127 if err != nil { 128 return nil, fmt.Errorf("failed to create pipe: %v", err) 129 } 130 131 inst.lkvm = osutil.Command("taskset", "-c", strconv.Itoa(index%runtime.NumCPU()), 132 inst.cfg.Lkvm, "sandbox", 133 "--disk", inst.sandbox, 134 "--kernel", inst.cfg.Kernel, 135 "--params", "slub_debug=UZ "+inst.cfg.Cmdline, 136 "--mem", strconv.Itoa(inst.cfg.Mem), 137 "--cpus", strconv.Itoa(inst.cfg.CPU), 138 "--network", "mode=user", 139 "--sandbox", scriptPath, 140 ) 141 inst.lkvm.Stdout = wpipe 142 inst.lkvm.Stderr = wpipe 143 if err := inst.lkvm.Start(); err != nil { 144 rpipe.Close() 145 wpipe.Close() 146 return nil, fmt.Errorf("failed to start lkvm: %v", err) 147 } 148 149 // Start output reading goroutine. 150 inst.readerC = make(chan error) 151 go func() { 152 var buf [64 << 10]byte 153 for { 154 n, err := rpipe.Read(buf[:]) 155 if n != 0 { 156 if inst.debug { 157 os.Stdout.Write(buf[:n]) 158 os.Stdout.Write([]byte{'\n'}) 159 } 160 inst.mu.Lock() 161 inst.outputB = append(inst.outputB, buf[:n]...) 162 if inst.outputC != nil { 163 select { 164 case inst.outputC <- inst.outputB: 165 inst.outputB = nil 166 default: 167 } 168 } 169 inst.mu.Unlock() 170 time.Sleep(time.Millisecond) 171 } 172 if err != nil { 173 rpipe.Close() 174 inst.readerC <- err 175 return 176 } 177 } 178 }() 179 180 // Wait for the lkvm asynchronously. 181 inst.waiterC = make(chan error, 1) 182 go func() { 183 err := inst.lkvm.Wait() 184 wpipe.Close() 185 inst.waiterC <- err 186 }() 187 188 // Wait for the script to start serving. 189 _, errc, err := inst.Run(10*time.Minute, nil, "mount -t debugfs none /sys/kernel/debug/") 190 if err == nil { 191 err = <-errc 192 } 193 if err != nil { 194 return nil, fmt.Errorf("failed to run script: %v", err) 195 } 196 197 closeInst = nil 198 return inst, nil 199} 200 201func (inst *instance) Close() { 202 if inst.lkvm != nil { 203 inst.lkvm.Process.Kill() 204 err := <-inst.waiterC 205 inst.waiterC <- err // repost it for waiting goroutines 206 <-inst.readerC 207 } 208 os.RemoveAll(inst.sandboxPath) 209 os.Remove(inst.sandboxPath + ".sock") 210} 211 212func (inst *instance) Forward(port int) (string, error) { 213 return fmt.Sprintf("%v:%v", hostAddr, port), nil 214} 215 216func (inst *instance) Copy(hostSrc string) (string, error) { 217 vmDst := filepath.Join("/", filepath.Base(hostSrc)) 218 dst := filepath.Join(inst.sandboxPath, vmDst) 219 if err := osutil.CopyFile(hostSrc, dst); err != nil { 220 return "", err 221 } 222 if err := os.Chmod(dst, 0777); err != nil { 223 return "", err 224 } 225 return vmDst, nil 226} 227 228func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) ( 229 <-chan []byte, <-chan error, error) { 230 outputC := make(chan []byte, 10) 231 errorC := make(chan error, 1) 232 inst.mu.Lock() 233 inst.outputB = nil 234 inst.outputC = outputC 235 inst.mu.Unlock() 236 237 cmdFile := filepath.Join(inst.sandboxPath, "/syz-cmd") 238 tmpFile := cmdFile + "-tmp" 239 if err := osutil.WriteExecFile(tmpFile, []byte(command)); err != nil { 240 return nil, nil, err 241 } 242 if err := os.Rename(tmpFile, cmdFile); err != nil { 243 return nil, nil, err 244 } 245 246 signal := func(err error) { 247 inst.mu.Lock() 248 if inst.outputC == outputC { 249 inst.outputB = nil 250 inst.outputC = nil 251 } 252 inst.mu.Unlock() 253 errorC <- err 254 } 255 256 go func() { 257 timeoutTicker := time.NewTicker(timeout) 258 secondTicker := time.NewTicker(time.Second) 259 var resultErr error 260 loop: 261 for { 262 select { 263 case <-timeoutTicker.C: 264 resultErr = vmimpl.ErrTimeout 265 break loop 266 case <-stop: 267 resultErr = vmimpl.ErrTimeout 268 break loop 269 case <-secondTicker.C: 270 if !osutil.IsExist(cmdFile) { 271 resultErr = nil 272 break loop 273 } 274 case err := <-inst.waiterC: 275 inst.waiterC <- err // repost it for Close 276 resultErr = fmt.Errorf("lkvm exited") 277 break loop 278 } 279 } 280 signal(resultErr) 281 timeoutTicker.Stop() 282 secondTicker.Stop() 283 }() 284 285 return outputC, errorC, nil 286} 287 288func (inst *instance) Diagnose() bool { 289 return false 290} 291 292const script = `#! /bin/bash 293while true; do 294 if [ -e "/syz-cmd" ]; then 295 /syz-cmd 296 rm -f /syz-cmd 297 else 298 sleep 1 299 fi 300done 301` 302