aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/kfuzztest-executor/executor.go
blob: 4637ba5537016a8ed1a3e0ed3a9c159cb5eec417 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 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.

//go:build linux

// Package kfuzztestexecutor implements local execution (i.e., without the
// C++ executor program) for KFuzzTest targets.
package kfuzztestexecutor

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/syzkaller/pkg/flatrpc"
	"github.com/google/syzkaller/pkg/fuzzer/queue"
	"github.com/google/syzkaller/pkg/kcov"
	"github.com/google/syzkaller/pkg/kfuzztest"
	"github.com/google/syzkaller/pkg/log"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/prog"
)

// KFuzzTestExecutor is an executor that upon receiving a request, will invoke
// a KFuzzTest target.
type KFuzzTestExecutor struct {
	ctx     context.Context
	jobChan chan *queue.Request
	// Cooldown between execution requests.
	cooldown time.Duration
	wg       sync.WaitGroup
}

// Implements the queue.Executor interface.
func (kfe *KFuzzTestExecutor) Submit(req *queue.Request) {
	kfe.jobChan <- req
}

func (kfe *KFuzzTestExecutor) Shutdown() {
	close(kfe.jobChan)
	kfe.wg.Wait()
}

func NewKFuzzTestExecutor(ctx context.Context, numWorkers int, cooldown uint32) *KFuzzTestExecutor {
	jobChan := make(chan *queue.Request)

	kfe := &KFuzzTestExecutor{
		ctx:      ctx,
		jobChan:  jobChan,
		cooldown: time.Duration(cooldown) * time.Second,
	}

	kfe.wg.Add(numWorkers)
	for i := range numWorkers {
		go kfe.workerLoop(i)
	}
	return kfe
}

func (kfe *KFuzzTestExecutor) workerLoop(tid int) {
	defer kfe.wg.Done()
	kcovSt, err := kcov.EnableTracingForCurrentGoroutine()
	if err != nil {
		log.Logf(1, "failed to enable kcov for thread_%d", tid)
		return
	}
	defer kcovSt.DisableTracing()

	for req := range kfe.jobChan {
		if req.Prog == nil {
			log.Logf(1, "thread_%d: exec request had nil program", tid)
		}

		info := new(flatrpc.ProgInfo)
		for _, call := range req.Prog.Calls {
			callInfo := new(flatrpc.CallInfo)

			// Trace each individual call, collecting the covered PCs.
			coverage, err := execKFuzzTestCallLocal(kcovSt, call)
			if err != nil {
				// Set this call info as a failure. -1 is a placeholder.
				callInfo.Error = -1
				callInfo.Flags |= flatrpc.CallFlagBlocked
			} else {
				for _, pc := range coverage {
					callInfo.Signal = append(callInfo.Signal, uint64(pc))
					callInfo.Cover = append(callInfo.Cover, uint64(pc))
				}
				callInfo.Flags |= flatrpc.CallFlagExecuted
			}

			info.Calls = append(info.Calls, callInfo)
		}

		req.Done(&queue.Result{Info: info, Executor: queue.ExecutorID{VM: 0, Proc: tid}})

		if kfe.cooldown != 0 {
			time.Sleep(kfe.cooldown)
		}
	}
	log.Logf(0, "thread_%d exiting", tid)
}

func execKFuzzTestCallLocal(st *kcov.KCOVState, call *prog.Call) ([]uintptr, error) {
	if !call.Meta.Attrs.KFuzzTest {
		return []uintptr{}, fmt.Errorf("call is not a KFuzzTest call")
	}
	testName, isKFuzzTest := kfuzztest.GetTestName(call.Meta)
	if !isKFuzzTest {
		return []uintptr{}, fmt.Errorf("tried to execute a syscall that wasn't syz_kfuzztest_run")
	}

	dataArg, ok := call.Args[1].(*prog.PointerArg)
	if !ok {
		return []uintptr{}, fmt.Errorf("second arg for syz_kfuzztest_run should be a pointer")
	}
	finalBlob := prog.MarshallKFuzztestArg(dataArg.Res)
	inputPath := kfuzztest.GetInputFilepath(testName)

	res := st.Trace(func() error { return osutil.WriteFile(inputPath, finalBlob) })
	return res.Coverage, res.Result
}