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  }