github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/kfuzztest-manager/manager.go (about) 1 // Copyright 2025 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 //go:build linux 5 6 package kfuzztestmanager 7 8 import ( 9 "context" 10 "fmt" 11 "math/rand" 12 "os" 13 "slices" 14 "strings" 15 "sync" 16 "sync/atomic" 17 "time" 18 19 "github.com/google/syzkaller/pkg/corpus" 20 "github.com/google/syzkaller/pkg/fuzzer" 21 "github.com/google/syzkaller/pkg/fuzzer/queue" 22 "github.com/google/syzkaller/pkg/kfuzztest" 23 executor "github.com/google/syzkaller/pkg/kfuzztest-executor" 24 "github.com/google/syzkaller/pkg/log" 25 "github.com/google/syzkaller/pkg/mgrconfig" 26 "github.com/google/syzkaller/pkg/stat" 27 "github.com/google/syzkaller/prog" 28 "github.com/google/syzkaller/sys/targets" 29 ) 30 31 type kFuzzTestManager struct { 32 fuzzer atomic.Pointer[fuzzer.Fuzzer] 33 source queue.Source 34 target *prog.Target 35 config Config 36 } 37 38 type Config struct { 39 VmlinuxPath string 40 Cooldown uint32 41 DisplayInterval uint32 42 NumThreads int 43 EnabledTargets []string 44 } 45 46 func NewKFuzzTestManager(ctx context.Context, cfg Config) (*kFuzzTestManager, error) { 47 var mgr kFuzzTestManager 48 49 target, err := prog.GetTarget(targets.Linux, targets.AMD64) 50 if err != nil { 51 return nil, err 52 } 53 54 log.Logf(0, "extracting KFuzzTest targets from \"%s\" (this will take a few seconds)", cfg.VmlinuxPath) 55 calls, err := kfuzztest.ActivateKFuzzTargets(target, cfg.VmlinuxPath) 56 if err != nil { 57 return nil, err 58 } 59 60 enabledCalls := make(map[*prog.Syscall]bool) 61 for _, call := range calls { 62 enabledCalls[call] = true 63 } 64 65 // Disable all calls that weren't explicitly enabled. 66 if len(cfg.EnabledTargets) > 0 { 67 enabledMap := make(map[string]bool) 68 for _, enabled := range cfg.EnabledTargets { 69 enabledMap[enabled] = true 70 } 71 for syscall := range enabledCalls { 72 testName, isSyzKFuzzTest := kfuzztest.GetTestName(syscall) 73 _, isEnabled := enabledMap[testName] 74 if isSyzKFuzzTest && syscall.Attrs.KFuzzTest && isEnabled { 75 enabledMap[testName] = true 76 } else { 77 delete(enabledCalls, syscall) 78 } 79 } 80 } 81 82 dispDiscoveredTargets := func() string { 83 var builder strings.Builder 84 totalEnabled := 0 85 86 builder.WriteString("enabled KFuzzTest targets: [\n") 87 for targ, enabled := range enabledCalls { 88 if enabled { 89 fmt.Fprintf(&builder, "\t%s,\n", targ.Name) 90 totalEnabled++ 91 } 92 } 93 fmt.Fprintf(&builder, "]\ntotal = %d\n", totalEnabled) 94 return builder.String() 95 } 96 log.Logf(0, "%s", dispDiscoveredTargets()) 97 98 corpus := corpus.NewCorpus(ctx) 99 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 100 fuzzerObj := fuzzer.NewFuzzer(ctx, &fuzzer.Config{ 101 Corpus: corpus, 102 Snapshot: false, 103 Coverage: true, 104 FaultInjection: false, 105 Comparisons: false, 106 Collide: false, 107 EnabledCalls: enabledCalls, 108 NoMutateCalls: make(map[int]bool), 109 FetchRawCover: false, 110 Logf: func(level int, msg string, args ...any) { 111 if level != 0 { 112 return 113 } 114 log.Logf(level, msg, args...) 115 }, 116 NewInputFilter: func(call string) bool { 117 // Don't filter anything. 118 return true 119 }, 120 }, rnd, target) 121 122 // TODO: Sufficient for startup, but not ideal that we are passing a 123 // manager config here. Would require changes to pkg/fuzzer if we wanted to 124 // avoid the dependency. 125 execOpts := fuzzer.DefaultExecOpts(&mgrconfig.Config{Sandbox: "none"}, 0, false) 126 127 mgr.target = target 128 mgr.fuzzer.Store(fuzzerObj) 129 mgr.source = queue.DefaultOpts(fuzzerObj, execOpts) 130 mgr.config = cfg 131 132 return &mgr, nil 133 } 134 135 func (mgr *kFuzzTestManager) Run(ctx context.Context) { 136 var wg sync.WaitGroup 137 138 // Launches the executor threads. 139 executor := executor.NewKFuzzTestExecutor(ctx, mgr.config.NumThreads, mgr.config.Cooldown) 140 141 // Display logs periodically. 142 display := func() { 143 defer wg.Done() 144 mgr.displayLoop(ctx) 145 } 146 147 wg.Add(1) 148 go display() 149 150 FuzzLoop: 151 for { 152 select { 153 case <-ctx.Done(): 154 break FuzzLoop 155 default: 156 } 157 158 req := mgr.source.Next() 159 if req == nil { 160 continue 161 } 162 163 executor.Submit(req) 164 } 165 166 log.Log(0, "fuzzing finished, shutting down executor") 167 executor.Shutdown() 168 wg.Wait() 169 170 const filepath string = "pcs.out" 171 log.Logf(0, "writing PCs out to \"%s\"", filepath) 172 if err := mgr.writePCs(filepath); err != nil { 173 log.Logf(0, "failed to write PCs: %v", err) 174 } 175 176 log.Log(0, "KFuzzTest manager exited") 177 } 178 179 func (mgr *kFuzzTestManager) writePCs(filepath string) error { 180 pcs := mgr.fuzzer.Load().Config.Corpus.Cover() 181 slices.Sort(pcs) 182 var builder strings.Builder 183 for _, pc := range pcs { 184 fmt.Fprintf(&builder, "0x%x\n", pc) 185 } 186 return os.WriteFile(filepath, []byte(builder.String()), 0644) 187 } 188 189 func (mgr *kFuzzTestManager) displayLoop(ctx context.Context) { 190 ticker := time.NewTicker(time.Duration(mgr.config.DisplayInterval) * time.Second) 191 defer ticker.Stop() 192 for { 193 var buf strings.Builder 194 select { 195 case <-ctx.Done(): 196 return 197 case <-ticker.C: 198 for _, stat := range stat.Collect(stat.Console) { 199 fmt.Fprintf(&buf, "%v=%v ", stat.Name, stat.Value) 200 } 201 log.Log(0, buf.String()) 202 } 203 } 204 }