From 288cfa16e79d64f1dbaafe91d4aee223fe0dd494 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 15 Sep 2025 13:13:20 +0000 Subject: syz-kfuzztest: add syz-kfuzztest executable syz-kfuzztest is a new standalone designed for fuzzing KFuzzTest on a live kernel VM (e.g., inside QEMU). It has no dependencies on the executor program, instead directly writing into a KFuzzTest target's debugfs entry. Signed-off-by: Ethan Graham --- pkg/kfuzztest-manager/manager.go | 201 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 pkg/kfuzztest-manager/manager.go (limited to 'pkg/kfuzztest-manager') diff --git a/pkg/kfuzztest-manager/manager.go b/pkg/kfuzztest-manager/manager.go new file mode 100644 index 000000000..f728230cc --- /dev/null +++ b/pkg/kfuzztest-manager/manager.go @@ -0,0 +1,201 @@ +// Copyright 2025 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. +package kfuzztestmanager + +import ( + "context" + "fmt" + "math/rand" + "os" + "slices" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/google/syzkaller/pkg/corpus" + "github.com/google/syzkaller/pkg/fuzzer" + "github.com/google/syzkaller/pkg/fuzzer/queue" + "github.com/google/syzkaller/pkg/kfuzztest" + executor "github.com/google/syzkaller/pkg/kfuzztest-executor" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/mgrconfig" + "github.com/google/syzkaller/pkg/stat" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +type kFuzzTestManager struct { + fuzzer atomic.Pointer[fuzzer.Fuzzer] + source queue.Source + target *prog.Target + config Config +} + +type Config struct { + VmlinuxPath string + Cooldown uint32 + DisplayInterval uint32 + NumThreads int + EnabledTargets []string +} + +func NewKFuzzTestManager(ctx context.Context, cfg Config) (*kFuzzTestManager, error) { + var mgr kFuzzTestManager + + target, err := prog.GetTarget(targets.Linux, targets.AMD64) + if err != nil { + return nil, err + } + + log.Logf(0, "extracting KFuzzTest targets from \"%s\" (this will take a few seconds)", cfg.VmlinuxPath) + calls, err := kfuzztest.ActivateKFuzzTargets(target, cfg.VmlinuxPath) + if err != nil { + return nil, err + } + + enabledCalls := make(map[*prog.Syscall]bool) + for _, call := range calls { + enabledCalls[call] = true + } + + // Disable all calls that weren't explicitly enabled. + if len(cfg.EnabledTargets) > 0 { + enabledMap := make(map[string]bool) + for _, enabled := range cfg.EnabledTargets { + enabledMap[enabled] = true + } + for syscall := range enabledCalls { + testName, isSyzKFuzzTest := kfuzztest.GetTestName(syscall) + _, isEnabled := enabledMap[testName] + if isSyzKFuzzTest && syscall.Attrs.KFuzzTest && isEnabled { + enabledMap[testName] = true + } else { + delete(enabledCalls, syscall) + } + } + } + + dispDiscoveredTargets := func() string { + var builder strings.Builder + totalEnabled := 0 + + builder.WriteString("enabled KFuzzTest targets: [\n") + for targ, enabled := range enabledCalls { + if enabled { + fmt.Fprintf(&builder, "\t%s,\n", targ.Name) + totalEnabled++ + } + } + fmt.Fprintf(&builder, "]\ntotal = %d\n", totalEnabled) + return builder.String() + } + log.Logf(0, "%s", dispDiscoveredTargets()) + + corpus := corpus.NewCorpus(ctx) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + fuzzerObj := fuzzer.NewFuzzer(ctx, &fuzzer.Config{ + Corpus: corpus, + Snapshot: false, + Coverage: true, + FaultInjection: false, + Comparisons: false, + Collide: false, + EnabledCalls: enabledCalls, + NoMutateCalls: make(map[int]bool), + FetchRawCover: false, + Logf: func(level int, msg string, args ...any) { + if level != 0 { + return + } + log.Logf(level, msg, args...) + }, + NewInputFilter: func(call string) bool { + // Don't filter anything. + return true + }, + }, rnd, target) + + // TODO: Sufficient for startup, but not ideal that we are passing a + // manager config here. Would require changes to pkg/fuzzer if we wanted to + // avoid the dependency. + execOpts := fuzzer.DefaultExecOpts(&mgrconfig.Config{Sandbox: "none"}, 0, false) + + mgr.target = target + mgr.fuzzer.Store(fuzzerObj) + mgr.source = queue.DefaultOpts(fuzzerObj, execOpts) + mgr.config = cfg + + return &mgr, nil +} + +func (mgr *kFuzzTestManager) Run(ctx context.Context) { + var wg sync.WaitGroup + + // Launches the executor threads. + executor := executor.NewKFuzzTestExecutor(ctx, mgr.config.NumThreads, mgr.config.Cooldown) + + // Display logs periodically. + display := func() { + defer wg.Done() + mgr.displayLoop(ctx) + } + + wg.Add(1) + go display() + +FuzzLoop: + for { + select { + case <-ctx.Done(): + break FuzzLoop + default: + } + + req := mgr.source.Next() + if req == nil { + continue + } + + executor.Submit(req) + } + + log.Log(0, "fuzzing finished, shutting down executor") + executor.Shutdown() + wg.Wait() + + const filepath string = "pcs.out" + log.Logf(0, "writing PCs out to \"%s\"", filepath) + if err := mgr.writePCs(filepath); err != nil { + log.Logf(0, "failed to write PCs: %v", err) + } + + log.Log(0, "KFuzzTest manager exited") +} + +func (mgr *kFuzzTestManager) writePCs(filepath string) error { + pcs := mgr.fuzzer.Load().Config.Corpus.Cover() + slices.Sort(pcs) + var builder strings.Builder + for _, pc := range pcs { + fmt.Fprintf(&builder, "0x%x\n", pc) + } + return os.WriteFile(filepath, []byte(builder.String()), 0644) +} + +func (mgr *kFuzzTestManager) displayLoop(ctx context.Context) { + ticker := time.NewTicker(time.Duration(mgr.config.DisplayInterval) * time.Second) + defer ticker.Stop() + for { + var buf strings.Builder + select { + case <-ctx.Done(): + return + case <-ticker.C: + for _, stat := range stat.Collect(stat.Console) { + fmt.Fprintf(&buf, "%v=%v ", stat.Name, stat.Value) + } + log.Log(0, buf.String()) + } + } +} -- cgit mrf-deployment