github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/kfuzztest-executor/executor.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 kfuzztestexecutor implements local execution (i.e., without the 7 // C++ executor program) for KFuzzTest targets. 8 package kfuzztestexecutor 9 10 import ( 11 "context" 12 "fmt" 13 "sync" 14 "time" 15 16 "github.com/google/syzkaller/pkg/flatrpc" 17 "github.com/google/syzkaller/pkg/fuzzer/queue" 18 "github.com/google/syzkaller/pkg/kcov" 19 "github.com/google/syzkaller/pkg/kfuzztest" 20 "github.com/google/syzkaller/pkg/log" 21 "github.com/google/syzkaller/pkg/osutil" 22 "github.com/google/syzkaller/prog" 23 ) 24 25 // KFuzzTestExecutor is an executor that upon receiving a request, will invoke 26 // a KFuzzTest target. 27 type KFuzzTestExecutor struct { 28 ctx context.Context 29 jobChan chan *queue.Request 30 // Cooldown between execution requests. 31 cooldown time.Duration 32 wg sync.WaitGroup 33 } 34 35 // Implements the queue.Executor interface. 36 func (kfe *KFuzzTestExecutor) Submit(req *queue.Request) { 37 kfe.jobChan <- req 38 } 39 40 func (kfe *KFuzzTestExecutor) Shutdown() { 41 close(kfe.jobChan) 42 kfe.wg.Wait() 43 } 44 45 func NewKFuzzTestExecutor(ctx context.Context, numWorkers int, cooldown uint32) *KFuzzTestExecutor { 46 jobChan := make(chan *queue.Request) 47 48 kfe := &KFuzzTestExecutor{ 49 ctx: ctx, 50 jobChan: jobChan, 51 cooldown: time.Duration(cooldown) * time.Second, 52 } 53 54 kfe.wg.Add(numWorkers) 55 for i := range numWorkers { 56 go kfe.workerLoop(i) 57 } 58 return kfe 59 } 60 61 func (kfe *KFuzzTestExecutor) workerLoop(tid int) { 62 defer kfe.wg.Done() 63 kcovSt, err := kcov.EnableTracingForCurrentGoroutine() 64 if err != nil { 65 log.Logf(1, "failed to enable kcov for thread_%d", tid) 66 return 67 } 68 defer kcovSt.DisableTracing() 69 70 for req := range kfe.jobChan { 71 if req.Prog == nil { 72 log.Logf(1, "thread_%d: exec request had nil program", tid) 73 } 74 75 info := new(flatrpc.ProgInfo) 76 for _, call := range req.Prog.Calls { 77 callInfo := new(flatrpc.CallInfo) 78 79 // Trace each individual call, collecting the covered PCs. 80 coverage, err := execKFuzzTestCallLocal(kcovSt, call) 81 if err != nil { 82 // Set this call info as a failure. -1 is a placeholder. 83 callInfo.Error = -1 84 callInfo.Flags |= flatrpc.CallFlagBlocked 85 } else { 86 for _, pc := range coverage { 87 callInfo.Signal = append(callInfo.Signal, uint64(pc)) 88 callInfo.Cover = append(callInfo.Cover, uint64(pc)) 89 } 90 callInfo.Flags |= flatrpc.CallFlagExecuted 91 } 92 93 info.Calls = append(info.Calls, callInfo) 94 } 95 96 req.Done(&queue.Result{Info: info, Executor: queue.ExecutorID{VM: 0, Proc: tid}}) 97 98 if kfe.cooldown != 0 { 99 time.Sleep(kfe.cooldown) 100 } 101 } 102 log.Logf(0, "thread_%d exiting", tid) 103 } 104 105 func execKFuzzTestCallLocal(st *kcov.KCOVState, call *prog.Call) ([]uintptr, error) { 106 if !call.Meta.Attrs.KFuzzTest { 107 return []uintptr{}, fmt.Errorf("call is not a KFuzzTest call") 108 } 109 testName, isKFuzzTest := kfuzztest.GetTestName(call.Meta) 110 if !isKFuzzTest { 111 return []uintptr{}, fmt.Errorf("tried to execute a syscall that wasn't syz_kfuzztest_run") 112 } 113 114 dataArg, ok := call.Args[1].(*prog.PointerArg) 115 if !ok { 116 return []uintptr{}, fmt.Errorf("second arg for syz_kfuzztest_run should be a pointer") 117 } 118 finalBlob := prog.MarshallKFuzztestArg(dataArg.Res) 119 inputPath := kfuzztest.GetInputFilepath(testName) 120 121 res := st.Trace(func() error { return osutil.WriteFile(inputPath, finalBlob) }) 122 return res.Coverage, res.Result 123 }