diff options
Diffstat (limited to 'pkg/kfuzztest/kfuzztest.go')
| -rw-r--r-- | pkg/kfuzztest/kfuzztest.go | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/pkg/kfuzztest/kfuzztest.go b/pkg/kfuzztest/kfuzztest.go new file mode 100644 index 000000000..c4702ac42 --- /dev/null +++ b/pkg/kfuzztest/kfuzztest.go @@ -0,0 +1,207 @@ +// 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 kfuzztest exposes functions discovering KFuzzTest test cases from a +// vmlinux binary and parsing them into syzkaller-compatible formats. +// The general flow includes: +// - Creating an Extractor that extracts these test cases from the binary +// - Creating a Builder that takes the extractor's output and returns some +// compatible encoding of the test cases that were discovered +package kfuzztest + +import ( + "debug/dwarf" + "fmt" + "path" + "strings" + "sync" + + "github.com/google/syzkaller/pkg/ast" + "github.com/google/syzkaller/pkg/compiler" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +type SyzField struct { + Name string + dwarfType dwarf.Type +} + +type SyzStruct struct { + dwarfType *dwarf.StructType + Name string + Fields []SyzField +} + +type SyzFunc struct { + Name string + InputStructName string +} + +type ConstraintType uint8 + +const ( + ExpectEq ConstraintType = iota + ExpectNe + ExpectLt + ExpectLe + ExpectGt + ExpectGe + ExpectInRange +) + +func (c ConstraintType) String() string { + return [...]string{"EXPECT_EQ", "EXPECT_NE", "EXPECT_LT", "EXPECT_LE", "EXPECT_GT", "EXPECT_GE", "EXPECT_IN_RANGE"}[c] +} + +type SyzConstraint struct { + InputType string + FieldName string + Value1 uintptr + Value2 uintptr + ConstraintType +} + +type AnnotationAttribute uint8 + +const ( + AttributeLen AnnotationAttribute = iota + AttributeString + AttributeArray +) + +func (a AnnotationAttribute) String() string { + return [...]string{"ATTRIBUTE_LEN", "ATTRIBUTE_STRING", "ATTRIBUTE_ARRAY"}[a] +} + +type SyzAnnotation struct { + InputType string + FieldName string + LinkedFieldName string + Attribute AnnotationAttribute +} + +// ExtractDescription returns a syzlang description of all discovered KFuzzTest +// targets, or an error on failure. +func ExtractDescription(vmlinuxPath string) (string, error) { + extractor, err := NewExtractor(vmlinuxPath) + if err != nil { + return "", err + } + defer extractor.Close() + eRes, err := extractor.ExtractAll() + if err != nil { + return "", err + } + builder := NewBuilder(eRes.Funcs, eRes.Structs, eRes.Constraints, eRes.Annotations) + return builder.EmitSyzlangDescription() +} + +type KFuzzTestData struct { + Description string + Calls []*prog.Syscall + Resources []*prog.ResourceDesc + Types []prog.Type +} + +func extractData(vmlinuxPath string) (KFuzzTestData, error) { + desc, err := ExtractDescription(vmlinuxPath) + if err != nil { + return KFuzzTestData{}, err + } + + var astError error + eh := func(pos ast.Pos, msg string) { + astError = fmt.Errorf("ast error: %v: %v", pos, msg) + } + descAst := ast.Parse([]byte(desc), "kfuzztest-autogen", eh) + if astError != nil { + return KFuzzTestData{}, astError + } + if descAst == nil { + return KFuzzTestData{}, fmt.Errorf("failed to build AST for program") + } + + // TODO: this assumes x86_64, but KFuzzTest supports (in theory) any + // architecture. + target := targets.Get(targets.Linux, targets.AMD64) + program := compiler.Compile(descAst, make(map[string]uint64), target, eh) + if astError != nil { + return KFuzzTestData{}, fmt.Errorf("failed to compile extracted KFuzzTest target: %w", astError) + } + + kFuzzTestCalls := []*prog.Syscall{} + for _, call := range program.Syscalls { + // The generated descriptions contain some number of built-ins, which + // we want to filter out. + if call.Attrs.KFuzzTest { + kFuzzTestCalls = append(kFuzzTestCalls, call) + } + } + + // We restore links on all generated system calls for completeness, but we + // only return the filtered slice. + prog.RestoreLinks(program.Syscalls, program.Resources, program.Types) + + return KFuzzTestData{ + Description: desc, + Calls: kFuzzTestCalls, + Resources: program.Resources, + Types: program.Types, + }, nil +} + +type extractKFuzzTestDataState struct { + once sync.Once + data KFuzzTestData + err error +} + +var extractState extractKFuzzTestDataState + +// ExtractData extracts KFuzzTest data from a vmlinux binary. The return value +// of this call is cached so that it can be safely called multiple times +// without incurring a new scan of a vmlinux image. +// NOTE: the implementation assumes the existence of only one vmlinux image +// per process, i.e. no attempt is made to distinguish different vmlinux images +// based on their path. +func ExtractData(vmlinuxPath string) (KFuzzTestData, error) { + extractState.once.Do(func() { + extractState.data, extractState.err = extractData(vmlinuxPath) + }) + + return extractState.data, extractState.err +} + +// ActivateKFuzzTargets extracts all KFuzzTest targets from a vmlinux binary +// and extends a target with the discovered pseudo-syscalls. +func ActivateKFuzzTargets(target *prog.Target, vmlinuxPath string) ([]*prog.Syscall, error) { + data, err := ExtractData(vmlinuxPath) + if err != nil { + return nil, err + } + // TODO: comment this properly. It's important to note here that despite + // extending the target, correct encoding relies on syz_kfuzztest_run being + // compiled into the target, and its ID being available. + target.Extend(data.Calls, data.Types, data.Resources) + return data.Calls, nil +} + +const syzKfuzzTestRun string = "syz_kfuzztest_run" + +// Common prefix that all discriminated syz_kfuzztest_run pseudo-syscalls share. +const KfuzzTestTargetPrefix string = syzKfuzzTestRun + "$" + +func GetTestName(syscall *prog.Syscall) (string, bool) { + if syscall.CallName != syzKfuzzTestRun { + return "", false + } + return strings.CutPrefix(syscall.Name, KfuzzTestTargetPrefix) +} + +const kFuzzTestDir string = "/sys/kernel/debug/kfuzztest" +const inputFile string = "input" + +func GetInputFilepath(testName string) string { + return path.Join(kFuzzTestDir, testName, inputFile) +} |
