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 4package main 5 6import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/google/syzkaller/pkg/log" 16 "github.com/google/syzkaller/pkg/mgrconfig" 17 "github.com/google/syzkaller/pkg/osutil" 18 "github.com/google/syzkaller/pkg/vcs" 19) 20 21const ( 22 syzkallerRebuildPeriod = 12 * time.Hour 23 buildRetryPeriod = 10 * time.Minute // used for both syzkaller and kernel 24) 25 26// SyzUpdater handles everything related to syzkaller updates. 27// As kernel builder, it maintains 2 builds: 28// - latest: latest known good syzkaller build 29// - current: currently used syzkaller build 30// Additionally it updates and restarts the current executable as necessary. 31// Current executable is always built on the same revision as the rest of syzkaller binaries. 32type SyzUpdater struct { 33 repo vcs.Repo 34 exe string 35 repoAddress string 36 branch string 37 descriptions string 38 gopathDir string 39 syzkallerDir string 40 latestDir string 41 currentDir string 42 syzFiles map[string]bool 43 targets map[string]bool 44} 45 46func NewSyzUpdater(cfg *Config) *SyzUpdater { 47 wd, err := os.Getwd() 48 if err != nil { 49 log.Fatalf("failed to get wd: %v", err) 50 } 51 bin := os.Args[0] 52 if !filepath.IsAbs(bin) { 53 bin = filepath.Join(wd, bin) 54 } 55 bin = filepath.Clean(bin) 56 exe := filepath.Base(bin) 57 if wd != filepath.Dir(bin) { 58 log.Fatalf("%v executable must be in cwd (it will be overwritten on update)", exe) 59 } 60 61 gopath := filepath.Join(wd, "gopath") 62 os.Setenv("GOROOT", cfg.Goroot) 63 os.Unsetenv("GOPATH") 64 os.Setenv("PATH", filepath.Join(cfg.Goroot, "bin")+ 65 string(filepath.ListSeparator)+os.Getenv("PATH")) 66 syzkallerDir := filepath.Join(gopath, "src", "github.com", "google", "syzkaller") 67 osutil.MkdirAll(syzkallerDir) 68 69 // List of required files in syzkaller build (contents of latest/current dirs). 70 files := map[string]bool{ 71 "tag": true, // contains syzkaller repo git hash 72 "bin/syz-ci": true, // these are just copied from syzkaller dir 73 "bin/syz-manager": true, 74 } 75 targets := make(map[string]bool) 76 for _, mgr := range cfg.Managers { 77 mgrcfg, err := mgrconfig.LoadPartialData(mgr.ManagerConfig) 78 if err != nil { 79 log.Fatalf("failed to load manager %v config: %v", mgr.Name, err) 80 } 81 os, vmarch, arch := mgrcfg.TargetOS, mgrcfg.TargetVMArch, mgrcfg.TargetArch 82 targets[os+"/"+vmarch+"/"+arch] = true 83 files[fmt.Sprintf("bin/%v_%v/syz-fuzzer", os, vmarch)] = true 84 files[fmt.Sprintf("bin/%v_%v/syz-execprog", os, vmarch)] = true 85 files[fmt.Sprintf("bin/%v_%v/syz-executor", os, arch)] = true 86 } 87 syzFiles := make(map[string]bool) 88 for f := range files { 89 syzFiles[f] = true 90 } 91 return &SyzUpdater{ 92 repo: vcs.NewSyzkallerRepo(syzkallerDir), 93 exe: exe, 94 repoAddress: cfg.SyzkallerRepo, 95 branch: cfg.SyzkallerBranch, 96 descriptions: cfg.SyzkallerDescriptions, 97 gopathDir: gopath, 98 syzkallerDir: syzkallerDir, 99 latestDir: filepath.Join("syzkaller", "latest"), 100 currentDir: filepath.Join("syzkaller", "current"), 101 syzFiles: syzFiles, 102 targets: targets, 103 } 104} 105 106// UpdateOnStart does 3 things: 107// - ensures that the current executable is fresh 108// - ensures that we have a working syzkaller build in current 109func (upd *SyzUpdater) UpdateOnStart(shutdown chan struct{}) { 110 os.RemoveAll(upd.currentDir) 111 exeTag, exeMod := readTag(upd.exe + ".tag") 112 latestTag := upd.checkLatest() 113 if exeTag == latestTag && time.Since(exeMod) < time.Minute { 114 // Have a freash up-to-date build, probably just restarted. 115 log.Logf(0, "current executable is up-to-date (%v)", exeTag) 116 if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil { 117 log.Fatal(err) 118 } 119 return 120 } 121 if exeTag == "" { 122 log.Logf(0, "current executable is bootstrap") 123 } else { 124 log.Logf(0, "current executable is on %v", exeTag) 125 log.Logf(0, "latest syzkaller build is on %v", latestTag) 126 } 127 128 // No syzkaller build or executable is stale. 129 lastCommit := exeTag 130 for { 131 lastCommit = upd.pollAndBuild(lastCommit) 132 latestTag := upd.checkLatest() 133 if latestTag != "" { 134 // The build was successful or we had the latest build from previous runs. 135 // Either way, use the latest build. 136 log.Logf(0, "using syzkaller built on %v", latestTag) 137 if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil { 138 log.Fatal(err) 139 } 140 if exeTag != latestTag { 141 upd.UpdateAndRestart() 142 } 143 return 144 } 145 146 // No good build at all, try again later. 147 log.Logf(0, "retrying in %v", buildRetryPeriod) 148 select { 149 case <-time.After(buildRetryPeriod): 150 case <-shutdown: 151 os.Exit(0) 152 } 153 } 154} 155 156// WaitForUpdate polls and rebuilds syzkaller. 157// Returns when we have a new good build in latest. 158func (upd *SyzUpdater) WaitForUpdate() { 159 time.Sleep(syzkallerRebuildPeriod) 160 latestTag := upd.checkLatest() 161 lastCommit := latestTag 162 for { 163 lastCommit = upd.pollAndBuild(lastCommit) 164 if latestTag != upd.checkLatest() { 165 break 166 } 167 time.Sleep(buildRetryPeriod) 168 } 169 log.Logf(0, "syzkaller: update available, restarting") 170} 171 172// UpdateAndRestart updates and restarts the current executable. 173// Does not return. 174func (upd *SyzUpdater) UpdateAndRestart() { 175 log.Logf(0, "restarting executable for update") 176 latestBin := filepath.Join(upd.latestDir, "bin", upd.exe) 177 latestTag := filepath.Join(upd.latestDir, "tag") 178 if err := osutil.CopyFile(latestBin, upd.exe); err != nil { 179 log.Fatal(err) 180 } 181 if err := osutil.CopyFile(latestTag, upd.exe+".tag"); err != nil { 182 log.Fatal(err) 183 } 184 if err := syscall.Exec(upd.exe, os.Args, os.Environ()); err != nil { 185 log.Fatal(err) 186 } 187 log.Fatalf("not reachable") 188} 189 190func (upd *SyzUpdater) pollAndBuild(lastCommit string) string { 191 commit, err := upd.repo.Poll(upd.repoAddress, upd.branch) 192 if err != nil { 193 log.Logf(0, "syzkaller: failed to poll: %v", err) 194 return lastCommit 195 } 196 log.Logf(0, "syzkaller: poll: %v (%v)", commit.Hash, commit.Title) 197 if lastCommit != commit.Hash { 198 log.Logf(0, "syzkaller: building ...") 199 lastCommit = commit.Hash 200 if err := upd.build(commit); err != nil { 201 log.Logf(0, "syzkaller: %v", err) 202 } 203 } 204 return lastCommit 205} 206 207func (upd *SyzUpdater) build(commit *vcs.Commit) error { 208 if upd.descriptions != "" { 209 files, err := ioutil.ReadDir(upd.descriptions) 210 if err != nil { 211 return fmt.Errorf("failed to read descriptions dir: %v", err) 212 } 213 for _, f := range files { 214 src := filepath.Join(upd.descriptions, f.Name()) 215 dst := filepath.Join(upd.syzkallerDir, "sys", "linux", f.Name()) 216 if err := osutil.CopyFile(src, dst); err != nil { 217 return err 218 } 219 } 220 } 221 cmd := osutil.Command("make", "generate") 222 cmd.Dir = upd.syzkallerDir 223 cmd.Env = append([]string{"GOPATH=" + upd.gopathDir}, os.Environ()...) 224 if _, err := osutil.Run(time.Hour, cmd); err != nil { 225 return fmt.Errorf("build failed: %v", err) 226 } 227 cmd = osutil.Command("make", "host", "ci") 228 cmd.Dir = upd.syzkallerDir 229 cmd.Env = append([]string{"GOPATH=" + upd.gopathDir}, os.Environ()...) 230 if _, err := osutil.Run(time.Hour, cmd); err != nil { 231 return fmt.Errorf("build failed: %v", err) 232 } 233 for target := range upd.targets { 234 parts := strings.Split(target, "/") 235 cmd = osutil.Command("make", "target") 236 cmd.Dir = upd.syzkallerDir 237 cmd.Env = append([]string{}, os.Environ()...) 238 cmd.Env = append(cmd.Env, 239 "GOPATH="+upd.gopathDir, 240 "TARGETOS="+parts[0], 241 "TARGETVMARCH="+parts[1], 242 "TARGETARCH="+parts[2], 243 ) 244 if _, err := osutil.Run(time.Hour, cmd); err != nil { 245 return fmt.Errorf("build failed: %v", err) 246 } 247 } 248 cmd = osutil.Command("go", "test", "-short", "./...") 249 cmd.Dir = upd.syzkallerDir 250 cmd.Env = append([]string{"GOPATH=" + upd.gopathDir}, os.Environ()...) 251 if _, err := osutil.Run(time.Hour, cmd); err != nil { 252 return fmt.Errorf("tests failed: %v", err) 253 } 254 tagFile := filepath.Join(upd.syzkallerDir, "tag") 255 if err := osutil.WriteFile(tagFile, []byte(commit.Hash)); err != nil { 256 return fmt.Errorf("filed to write tag file: %v", err) 257 } 258 if err := osutil.CopyFiles(upd.syzkallerDir, upd.latestDir, upd.syzFiles); err != nil { 259 return fmt.Errorf("filed to copy syzkaller: %v", err) 260 } 261 return nil 262} 263 264// checkLatest returns tag of the latest build, 265// or an empty string if latest build is missing/broken. 266func (upd *SyzUpdater) checkLatest() string { 267 if !osutil.FilesExist(upd.latestDir, upd.syzFiles) { 268 return "" 269 } 270 tag, _ := readTag(filepath.Join(upd.latestDir, "tag")) 271 return tag 272} 273 274func readTag(file string) (tag string, mod time.Time) { 275 data, _ := ioutil.ReadFile(file) 276 tag = string(data) 277 if st, err := os.Stat(file); err == nil { 278 mod = st.ModTime() 279 } 280 if tag == "" || mod.IsZero() { 281 tag = "" 282 mod = time.Time{} 283 } 284 return 285} 286