github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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 if err != nil { 122 return fmt.Errorf("[%s] stopped: %w", inst.Name, err) 123 } 124 return nil 125 case <-time.After(inst.RunTime): 126 inst.Stop() 127 <-ret 128 return nil 129 } 130 } 131 132 type SyzkallerInfo struct { 133 Workdir string 134 CfgFile string 135 Mode string 136 BenchFile string 137 } 138 139 func SetupSyzkallerInstance(mgrName, folder string, checkout *Checkout) (*SyzkallerInfo, error) { 140 workdir := filepath.Join(folder, "workdir") 141 log.Printf("[%s] Generating workdir", mgrName) 142 err := osutil.MkdirAll(workdir) 143 if err != nil { 144 return nil, fmt.Errorf("failed to create workdir %s", workdir) 145 } 146 log.Printf("[%s] Generating syz-manager config", mgrName) 147 cfgFile := filepath.Join(folder, "manager.cfg") 148 managerCfg, err := config.PatchJSON(checkout.ManagerConfig, map[string]interface{}{ 149 "name": mgrName, 150 "workdir": workdir, 151 "syzkaller": checkout.Path, 152 }) 153 if err != nil { 154 return nil, fmt.Errorf("failed to patch mgr config") 155 } 156 err = osutil.WriteFile(cfgFile, managerCfg) 157 if err != nil { 158 return nil, fmt.Errorf("failed to save manager config to %s: %w", cfgFile, err) 159 } 160 return &SyzkallerInfo{ 161 Workdir: workdir, 162 CfgFile: cfgFile, 163 BenchFile: filepath.Join(folder, "bench.txt"), 164 }, nil 165 } 166 167 func (t *SyzManagerTarget) newSyzManagerInstance(slotName, uniqName, mode string, checkout *Checkout) ( 168 Instance, error) { 169 folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName)) 170 common, err := SetupSyzkallerInstance(slotName, folder, checkout) 171 if err != nil { 172 return nil, err 173 } 174 if t.config.Corpus != "" { 175 log.Printf("[%s] Copying corpus", uniqName) 176 corpusPath := filepath.Join(common.Workdir, "corpus.db") 177 err = osutil.CopyFile(t.config.Corpus, corpusPath) 178 if err != nil { 179 return nil, fmt.Errorf("failed to copy corpus from %s: %w", t.config.Corpus, err) 180 } 181 } 182 return &SyzManagerInstance{ 183 InstanceCommon: InstanceCommon{ 184 Name: uniqName, 185 LogFile: filepath.Join(folder, "log.txt"), 186 ExecCommand: filepath.Join(checkout.Path, "bin", "syz-manager"), 187 ExecCommandArgs: []string{ 188 "-config", common.CfgFile, 189 "-mode", mode, 190 "-bench", common.BenchFile, 191 }, 192 stopChannel: make(chan bool, 1), 193 }, 194 SyzkallerInfo: *common, 195 RunTime: t.config.RunTime.Duration, 196 }, nil 197 } 198 199 type SyzReproInstance struct { 200 InstanceCommon 201 SyzkallerInfo 202 Input *SyzReproInput 203 ReproFile string 204 CReproFile string 205 TitleFile string 206 } 207 208 func (inst *SyzReproInstance) FetchResult() (RunResult, error) { 209 result := &SyzReproResult{ 210 Input: inst.Input, 211 ReproFound: osutil.IsExist(inst.ReproFile), 212 CReproFound: osutil.IsExist(inst.CReproFile), 213 Duration: inst.Uptime(), 214 } 215 outTitle, _ := os.ReadFile(inst.TitleFile) 216 if outTitle != nil { 217 result.ReproTitle = strings.TrimSpace(string(outTitle)) 218 if result.ReproTitle != inst.Input.origTitle { 219 // If we found a different bug, treat the reproduction as unsuccessful. 220 result.ReproFound = false 221 result.CReproFound = false 222 } 223 } 224 return result, nil 225 } 226 227 func (t *SyzReproTarget) newSyzReproInstance(slotName, uniqName string, input *SyzReproInput, 228 checkout *Checkout) (Instance, error) { 229 folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName)) 230 common, err := SetupSyzkallerInstance(slotName, folder, checkout) 231 if err != nil { 232 return nil, err 233 } 234 235 reproFile := filepath.Join(folder, "repro.txt") 236 cReproFile := filepath.Join(folder, "crepro.txt") 237 titleFile := filepath.Join(folder, "title.txt") 238 newExecLog := filepath.Join(folder, "execution-log.txt") 239 err = osutil.CopyFile(input.Path, newExecLog) 240 if err != nil { 241 return nil, err 242 } 243 return &SyzReproInstance{ 244 InstanceCommon: InstanceCommon{ 245 Name: uniqName, 246 LogFile: filepath.Join(folder, "log.txt"), 247 ExecCommand: filepath.Join(checkout.Path, "bin", "syz-repro"), 248 ExecCommandArgs: []string{ 249 "-config", common.CfgFile, 250 "-output", reproFile, 251 "-crepro", cReproFile, 252 "-title", titleFile, 253 newExecLog, 254 }, 255 stopChannel: make(chan bool, 1), 256 }, 257 SyzkallerInfo: *common, 258 Input: input, 259 ReproFile: reproFile, 260 CReproFile: cReproFile, 261 TitleFile: titleFile, 262 }, nil 263 }