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  }