github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/tools/syz-testbed/instance.go (about) 1 // Copyright 2021 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 main 5 6 import ( 7 "fmt" 8 "log" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/google/syzkaller/pkg/config" 15 "github.com/google/syzkaller/pkg/osutil" 16 ) 17 18 type Instance interface { 19 Run() error 20 Stop() 21 FetchResult() (RunResult, error) 22 Uptime() time.Duration 23 } 24 25 // The essential information about an active instance. 26 type InstanceCommon struct { 27 Name string 28 LogFile string 29 ExecCommand string 30 ExecCommandArgs []string 31 StartedAt time.Time 32 StoppedAt time.Time 33 stopChannel chan bool 34 } 35 36 func (inst *InstanceCommon) Run() error { 37 const stopDelay = time.Minute 38 39 log.Printf("[%s] starting", inst.Name) 40 cmd := osutil.GraciousCommand(inst.ExecCommand, inst.ExecCommandArgs...) 41 42 if inst.LogFile != "" { 43 logfile, err := os.Create(inst.LogFile) 44 if err != nil { 45 return fmt.Errorf("[%s] failed to create logfile: %w", inst.Name, err) 46 } 47 cmd.Stdout = logfile 48 cmd.Stderr = logfile 49 } 50 51 complete := make(chan error) 52 inst.StartedAt = time.Now() 53 cmd.Start() 54 go func() { 55 complete <- cmd.Wait() 56 }() 57 58 select { 59 case err := <-complete: 60 return err 61 case <-inst.stopChannel: 62 // TODO: handle other OSes? 63 cmd.Process.Signal(os.Interrupt) 64 select { 65 case <-complete: 66 // The manager has exited. 67 case <-time.After(stopDelay): 68 // The manager did not exit - kill it. 69 log.Printf("[%s] instance did not exit itself, killing it", inst.Name) 70 cmd.Process.Kill() 71 <-complete 72 } 73 } 74 inst.StoppedAt = time.Now() 75 return nil 76 } 77 78 func (inst *InstanceCommon) Stop() { 79 select { 80 case inst.stopChannel <- true: 81 default: 82 } 83 } 84 85 func (inst *InstanceCommon) Uptime() time.Duration { 86 if !inst.StartedAt.IsZero() && inst.StoppedAt.IsZero() { 87 return time.Since(inst.StartedAt) 88 } 89 return inst.StoppedAt.Sub(inst.StartedAt) 90 } 91 92 type SyzManagerInstance struct { 93 InstanceCommon 94 SyzkallerInfo 95 RunTime time.Duration 96 } 97 98 func (inst *SyzManagerInstance) FetchResult() (RunResult, error) { 99 bugs, err := collectBugs(inst.Workdir) 100 if err != nil { 101 return nil, err 102 } 103 records, err := readBenches(inst.BenchFile) 104 if err != nil { 105 return nil, err 106 } 107 return &SyzManagerResult{ 108 Bugs: bugs, 109 StatRecords: records, 110 }, nil 111 } 112 113 func (inst *SyzManagerInstance) Run() error { 114 ret := make(chan error, 1) 115 go func() { 116 ret <- inst.InstanceCommon.Run() 117 }() 118 119 select { 120 case err := <-ret: 121 // Syz-managers are not supposed to stop themselves under normal circumstances. 122 // If one of them did stop, there must have been a very good reason to do so. 123 return fmt.Errorf("[%s] stopped: %w", inst.Name, err) 124 case <-time.After(inst.RunTime): 125 inst.Stop() 126 <-ret 127 return nil 128 } 129 } 130 131 type SyzkallerInfo struct { 132 Workdir string 133 CfgFile string 134 BenchFile string 135 } 136 137 func SetupSyzkallerInstance(mgrName, folder string, checkout *Checkout) (*SyzkallerInfo, error) { 138 workdir := filepath.Join(folder, "workdir") 139 log.Printf("[%s] Generating workdir", mgrName) 140 err := osutil.MkdirAll(workdir) 141 if err != nil { 142 return nil, fmt.Errorf("failed to create workdir %s", workdir) 143 } 144 log.Printf("[%s] Generating syz-manager config", mgrName) 145 cfgFile := filepath.Join(folder, "manager.cfg") 146 managerCfg, err := config.PatchJSON(checkout.ManagerConfig, map[string]interface{}{ 147 "name": mgrName, 148 "workdir": workdir, 149 "syzkaller": checkout.Path, 150 }) 151 if err != nil { 152 return nil, fmt.Errorf("failed to patch mgr config") 153 } 154 err = osutil.WriteFile(cfgFile, managerCfg) 155 if err != nil { 156 return nil, fmt.Errorf("failed to save manager config to %s: %w", cfgFile, err) 157 } 158 return &SyzkallerInfo{ 159 Workdir: workdir, 160 CfgFile: cfgFile, 161 BenchFile: filepath.Join(folder, "bench.txt"), 162 }, nil 163 } 164 165 func (t *SyzManagerTarget) newSyzManagerInstance(slotName, uniqName string, checkout *Checkout) (Instance, error) { 166 folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName)) 167 common, err := SetupSyzkallerInstance(slotName, folder, checkout) 168 if err != nil { 169 return nil, err 170 } 171 if t.config.Corpus != "" { 172 log.Printf("[%s] Copying corpus", uniqName) 173 corpusPath := filepath.Join(common.Workdir, "corpus.db") 174 err = osutil.CopyFile(t.config.Corpus, corpusPath) 175 if err != nil { 176 return nil, fmt.Errorf("failed to copy corpus from %s: %w", t.config.Corpus, err) 177 } 178 } 179 return &SyzManagerInstance{ 180 InstanceCommon: InstanceCommon{ 181 Name: uniqName, 182 LogFile: filepath.Join(folder, "log.txt"), 183 ExecCommand: filepath.Join(checkout.Path, "bin", "syz-manager"), 184 ExecCommandArgs: []string{"-config", common.CfgFile, "-bench", common.BenchFile}, 185 stopChannel: make(chan bool, 1), 186 }, 187 SyzkallerInfo: *common, 188 RunTime: t.config.RunTime.Duration, 189 }, nil 190 } 191 192 type SyzReproInstance struct { 193 InstanceCommon 194 SyzkallerInfo 195 Input *SyzReproInput 196 ReproFile string 197 CReproFile string 198 TitleFile string 199 } 200 201 func (inst *SyzReproInstance) FetchResult() (RunResult, error) { 202 result := &SyzReproResult{ 203 Input: inst.Input, 204 ReproFound: osutil.IsExist(inst.ReproFile), 205 CReproFound: osutil.IsExist(inst.CReproFile), 206 Duration: inst.Uptime(), 207 } 208 outTitle, _ := os.ReadFile(inst.TitleFile) 209 if outTitle != nil { 210 result.ReproTitle = strings.TrimSpace(string(outTitle)) 211 if result.ReproTitle != inst.Input.origTitle { 212 // If we found a different bug, treat the reproduction as unsuccessful. 213 result.ReproFound = false 214 result.CReproFound = false 215 } 216 } 217 return result, nil 218 } 219 220 func (t *SyzReproTarget) newSyzReproInstance(slotName, uniqName string, input *SyzReproInput, 221 checkout *Checkout) (Instance, error) { 222 folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName)) 223 common, err := SetupSyzkallerInstance(slotName, folder, checkout) 224 if err != nil { 225 return nil, err 226 } 227 228 reproFile := filepath.Join(folder, "repro.txt") 229 cReproFile := filepath.Join(folder, "crepro.txt") 230 titleFile := filepath.Join(folder, "title.txt") 231 newExecLog := filepath.Join(folder, "execution-log.txt") 232 err = osutil.CopyFile(input.Path, newExecLog) 233 if err != nil { 234 return nil, err 235 } 236 return &SyzReproInstance{ 237 InstanceCommon: InstanceCommon{ 238 Name: uniqName, 239 LogFile: filepath.Join(folder, "log.txt"), 240 ExecCommand: filepath.Join(checkout.Path, "bin", "syz-repro"), 241 ExecCommandArgs: []string{ 242 "-config", common.CfgFile, 243 "-output", reproFile, 244 "-crepro", cReproFile, 245 "-title", titleFile, 246 newExecLog, 247 }, 248 stopChannel: make(chan bool, 1), 249 }, 250 SyzkallerInfo: *common, 251 Input: input, 252 ReproFile: reproFile, 253 CReproFile: cReproFile, 254 TitleFile: titleFile, 255 }, nil 256 }