github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/kfuzztest/kfuzztest.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 // Package kfuzztest exposes functions discovering KFuzzTest test cases from a 5 // vmlinux binary and parsing them into syzkaller-compatible formats. 6 // The general flow includes: 7 // - Creating an Extractor that extracts these test cases from the binary 8 // - Creating a Builder that takes the extractor's output and returns some 9 // compatible encoding of the test cases that were discovered 10 package kfuzztest 11 12 import ( 13 "debug/dwarf" 14 "fmt" 15 "path" 16 "strings" 17 "sync" 18 19 "github.com/google/syzkaller/pkg/ast" 20 "github.com/google/syzkaller/pkg/compiler" 21 "github.com/google/syzkaller/prog" 22 "github.com/google/syzkaller/sys/targets" 23 ) 24 25 type SyzField struct { 26 Name string 27 dwarfType dwarf.Type 28 } 29 30 type SyzStruct struct { 31 dwarfType *dwarf.StructType 32 Name string 33 Fields []SyzField 34 } 35 36 type SyzFunc struct { 37 Name string 38 InputStructName string 39 } 40 41 type ConstraintType uint8 42 43 const ( 44 ExpectEq ConstraintType = iota 45 ExpectNe 46 ExpectLt 47 ExpectLe 48 ExpectGt 49 ExpectGe 50 ExpectInRange 51 ) 52 53 func (c ConstraintType) String() string { 54 return [...]string{"EXPECT_EQ", "EXPECT_NE", "EXPECT_LT", "EXPECT_LE", "EXPECT_GT", "EXPECT_GE", "EXPECT_IN_RANGE"}[c] 55 } 56 57 type SyzConstraint struct { 58 InputType string 59 FieldName string 60 Value1 uintptr 61 Value2 uintptr 62 ConstraintType 63 } 64 65 type AnnotationAttribute uint8 66 67 const ( 68 AttributeLen AnnotationAttribute = iota 69 AttributeString 70 AttributeArray 71 ) 72 73 func (a AnnotationAttribute) String() string { 74 return [...]string{"ATTRIBUTE_LEN", "ATTRIBUTE_STRING", "ATTRIBUTE_ARRAY"}[a] 75 } 76 77 type SyzAnnotation struct { 78 InputType string 79 FieldName string 80 LinkedFieldName string 81 Attribute AnnotationAttribute 82 } 83 84 // ExtractDescription returns a syzlang description of all discovered KFuzzTest 85 // targets, or an error on failure. 86 func ExtractDescription(vmlinuxPath string) (string, error) { 87 extractor, err := NewExtractor(vmlinuxPath) 88 if err != nil { 89 return "", err 90 } 91 defer extractor.Close() 92 eRes, err := extractor.ExtractAll() 93 if err != nil { 94 return "", err 95 } 96 builder := NewBuilder(eRes.Funcs, eRes.Structs, eRes.Constraints, eRes.Annotations) 97 return builder.EmitSyzlangDescription() 98 } 99 100 type KFuzzTestData struct { 101 Description string 102 Calls []*prog.Syscall 103 Resources []*prog.ResourceDesc 104 Types []prog.Type 105 } 106 107 func extractData(vmlinuxPath string) (KFuzzTestData, error) { 108 desc, err := ExtractDescription(vmlinuxPath) 109 if err != nil { 110 return KFuzzTestData{}, err 111 } 112 113 var astError error 114 eh := func(pos ast.Pos, msg string) { 115 astError = fmt.Errorf("ast error: %v: %v", pos, msg) 116 } 117 descAst := ast.Parse([]byte(desc), "kfuzztest-autogen", eh) 118 if astError != nil { 119 return KFuzzTestData{}, astError 120 } 121 if descAst == nil { 122 return KFuzzTestData{}, fmt.Errorf("failed to build AST for program") 123 } 124 125 // TODO: this assumes x86_64, but KFuzzTest supports (in theory) any 126 // architecture. 127 target := targets.Get(targets.Linux, targets.AMD64) 128 program := compiler.Compile(descAst, make(map[string]uint64), target, eh) 129 if astError != nil { 130 return KFuzzTestData{}, fmt.Errorf("failed to compile extracted KFuzzTest target: %w", astError) 131 } 132 133 kFuzzTestCalls := []*prog.Syscall{} 134 for _, call := range program.Syscalls { 135 // The generated descriptions contain some number of built-ins, which 136 // we want to filter out. 137 if call.Attrs.KFuzzTest { 138 kFuzzTestCalls = append(kFuzzTestCalls, call) 139 } 140 } 141 142 // We restore links on all generated system calls for completeness, but we 143 // only return the filtered slice. 144 prog.RestoreLinks(program.Syscalls, program.Resources, program.Types) 145 146 return KFuzzTestData{ 147 Description: desc, 148 Calls: kFuzzTestCalls, 149 Resources: program.Resources, 150 Types: program.Types, 151 }, nil 152 } 153 154 type extractKFuzzTestDataState struct { 155 once sync.Once 156 data KFuzzTestData 157 err error 158 } 159 160 var extractState extractKFuzzTestDataState 161 162 // ExtractData extracts KFuzzTest data from a vmlinux binary. The return value 163 // of this call is cached so that it can be safely called multiple times 164 // without incurring a new scan of a vmlinux image. 165 // NOTE: the implementation assumes the existence of only one vmlinux image 166 // per process, i.e. no attempt is made to distinguish different vmlinux images 167 // based on their path. 168 func ExtractData(vmlinuxPath string) (KFuzzTestData, error) { 169 extractState.once.Do(func() { 170 extractState.data, extractState.err = extractData(vmlinuxPath) 171 }) 172 173 return extractState.data, extractState.err 174 } 175 176 // ActivateKFuzzTargets extracts all KFuzzTest targets from a vmlinux binary 177 // and extends a target with the discovered pseudo-syscalls. 178 func ActivateKFuzzTargets(target *prog.Target, vmlinuxPath string) ([]*prog.Syscall, error) { 179 data, err := ExtractData(vmlinuxPath) 180 if err != nil { 181 return nil, err 182 } 183 // TODO: comment this properly. It's important to note here that despite 184 // extending the target, correct encoding relies on syz_kfuzztest_run being 185 // compiled into the target, and its ID being available. 186 target.Extend(data.Calls, data.Types, data.Resources) 187 return data.Calls, nil 188 } 189 190 const syzKfuzzTestRun string = "syz_kfuzztest_run" 191 192 // Common prefix that all discriminated syz_kfuzztest_run pseudo-syscalls share. 193 const KfuzzTestTargetPrefix string = syzKfuzzTestRun + "$" 194 195 func GetTestName(syscall *prog.Syscall) (string, bool) { 196 if syscall.CallName != syzKfuzzTestRun { 197 return "", false 198 } 199 return strings.CutPrefix(syscall.Name, KfuzzTestTargetPrefix) 200 } 201 202 const kFuzzTestDir string = "/sys/kernel/debug/kfuzztest" 203 const inputFile string = "input" 204 205 func GetInputFilepath(testName string) string { 206 return path.Join(kFuzzTestDir, testName, inputFile) 207 }