aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/kfuzztest/builder.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/kfuzztest/builder.go')
-rw-r--r--pkg/kfuzztest/builder.go254
1 files changed, 254 insertions, 0 deletions
diff --git a/pkg/kfuzztest/builder.go b/pkg/kfuzztest/builder.go
new file mode 100644
index 000000000..1c62e1093
--- /dev/null
+++ b/pkg/kfuzztest/builder.go
@@ -0,0 +1,254 @@
+// 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
+
+import (
+ "debug/dwarf"
+ "fmt"
+ "strings"
+
+ "github.com/google/syzkaller/pkg/ast"
+)
+
+type Builder struct {
+ funcs []SyzFunc
+ structs []SyzStruct
+ constraints []SyzConstraint
+ annotations []SyzAnnotation
+}
+
+func NewBuilder(
+ funcs []SyzFunc,
+ structs []SyzStruct,
+ constraints []SyzConstraint,
+ annotations []SyzAnnotation,
+) *Builder {
+ return &Builder{funcs, structs, constraints, annotations}
+}
+
+func (b *Builder) AddStruct(s SyzStruct) {
+ b.structs = append(b.structs, s)
+}
+
+func (b *Builder) AddFunc(f SyzFunc) {
+ b.funcs = append(b.funcs, f)
+}
+
+func (b *Builder) EmitSyzlangDescription() (string, error) {
+ constraintMap := make(map[string]map[string]SyzConstraint)
+ for _, constraint := range b.constraints {
+ if _, contains := constraintMap[constraint.InputType]; !contains {
+ constraintMap[constraint.InputType] = make(map[string]SyzConstraint)
+ }
+ constraintMap[constraint.InputType][constraint.FieldName] = constraint
+ }
+ annotationMap := make(map[string]map[string]SyzAnnotation)
+ for _, annotation := range b.annotations {
+ if _, contains := annotationMap[annotation.InputType]; !contains {
+ annotationMap[annotation.InputType] = make(map[string]SyzAnnotation)
+ }
+ annotationMap[annotation.InputType][annotation.FieldName] = annotation
+ }
+
+ var descBuilder strings.Builder
+ descBuilder.WriteString("# This description was automatically generated with tools/kfuzztest-gen\n")
+ for _, s := range b.structs {
+ structDesc, err := syzStructToSyzlang(s, constraintMap, annotationMap)
+ if err != nil {
+ return "", err
+ }
+ descBuilder.WriteString(structDesc)
+ descBuilder.WriteString("\n\n")
+ }
+
+ for i, fn := range b.funcs {
+ descBuilder.WriteString(syzFuncToSyzlang(fn))
+ if i < len(b.funcs)-1 {
+ descBuilder.WriteString("\n")
+ }
+ }
+
+ // Format the output syzlang descriptions for consistency.
+ var astError error
+ eh := func(pos ast.Pos, msg string) {
+ astError = fmt.Errorf("ast failure: %v: %v", pos, msg)
+ }
+ descAst := ast.Parse([]byte(descBuilder.String()), "", eh)
+ if astError != nil {
+ return "", astError
+ }
+ if descAst == nil {
+ return "", fmt.Errorf("failed to format generated syzkaller description - is it well-formed?")
+ }
+ return string(ast.Format(descAst)), nil
+}
+
+func syzStructToSyzlang(s SyzStruct, constraintMap map[string]map[string]SyzConstraint,
+ annotationMap map[string]map[string]SyzAnnotation) (string, error) {
+ var builder strings.Builder
+
+ fmt.Fprintf(&builder, "%s {\n", s.Name)
+ structAnnotations := annotationMap["struct "+s.Name]
+ structConstraints := constraintMap["struct "+s.Name]
+ for _, field := range s.Fields {
+ line, err := syzFieldToSyzLang(field, structConstraints, structAnnotations)
+ if err != nil {
+ return "", err
+ }
+ fmt.Fprintf(&builder, "\t%s\n", line)
+ }
+ fmt.Fprint(&builder, "}")
+ return builder.String(), nil
+}
+
+func syzFieldToSyzLang(field SyzField, constraintMap map[string]SyzConstraint,
+ annotationMap map[string]SyzAnnotation) (string, error) {
+ constraint, hasConstraint := constraintMap[field.Name]
+ annotation, hasAnnotation := annotationMap[field.Name]
+
+ var typeDesc string
+ var err error
+ if hasAnnotation {
+ // Annotations override the existing type definitions.
+ typeDesc, err = processAnnotation(field, annotation)
+ } else {
+ typeDesc, err = dwarfToSyzlangType(field.dwarfType)
+ }
+ if err != nil {
+ return "", err
+ }
+
+ // Process constraints only if unannotated.
+ // TODO: is there a situation where we would want to process both?
+ if hasConstraint && !hasAnnotation {
+ constraint, err := processConstraint(constraint)
+ if err != nil {
+ return "", err
+ }
+ typeDesc += constraint
+ }
+ return fmt.Sprintf("%s %s", field.Name, typeDesc), nil
+}
+
+func processConstraint(c SyzConstraint) (string, error) {
+ switch c.ConstraintType {
+ case ExpectEq:
+ return fmt.Sprintf("[%d]", c.Value1), nil
+ case ExpectNe:
+ // syzkaller does not have a built-in way to support an inequality
+ // constraint AFAIK.
+ return "", nil
+ case ExpectLt:
+ return fmt.Sprintf("[0:%d]", c.Value1-1), nil
+ case ExpectLe:
+ return fmt.Sprintf("[0:%d]", c.Value1), nil
+ case ExpectGt:
+ return fmt.Sprintf("[%d]", c.Value1+1), nil
+ case ExpectGe:
+ return fmt.Sprintf("[%d]", c.Value1), nil
+ case ExpectInRange:
+ return fmt.Sprintf("[%d:%d]", c.Value1, c.Value2), nil
+ default:
+ fmt.Printf("c = %d\n", c.ConstraintType)
+ return "", fmt.Errorf("unsupported constraint type")
+ }
+}
+
+func processAnnotation(field SyzField, annotation SyzAnnotation) (string, error) {
+ switch annotation.Attribute {
+ case AttributeLen:
+ underlyingType, err := dwarfToSyzlangType(field.dwarfType)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("len[%s, %s]", annotation.LinkedFieldName, underlyingType), nil
+ case AttributeString:
+ return "ptr[in, string]", nil
+ case AttributeArray:
+ pointeeType, isPtr := resolvesToPtr(field.dwarfType)
+ if !isPtr {
+ return "", fmt.Errorf("can only annotate pointer fields are arrays")
+ }
+ // TODO: discards const qualifier.
+ typeDesc, err := dwarfToSyzlangType(pointeeType)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("ptr[in, array[%s]]", typeDesc), nil
+ default:
+ return "", fmt.Errorf("unsupported attribute type")
+ }
+}
+
+// Returns true iff `dwarfType` resolved down to a pointer. For example,
+// a `const *void` which isn't directly a pointer.
+func resolvesToPtr(dwarfType dwarf.Type) (dwarf.Type, bool) {
+ switch t := dwarfType.(type) {
+ case *dwarf.QualType:
+ return resolvesToPtr(t.Type)
+ case *dwarf.PtrType:
+ return t.Type, true
+ }
+ return nil, false
+}
+
+func syzFuncToSyzlang(s SyzFunc) string {
+ var builder strings.Builder
+ typeName := strings.TrimPrefix(s.InputStructName, "struct ")
+
+ fmt.Fprintf(&builder, "syz_kfuzztest_run$%s(", s.Name)
+ fmt.Fprintf(&builder, "name ptr[in, string[\"%s\"]], ", s.Name)
+ fmt.Fprintf(&builder, "data ptr[in, %s], ", typeName)
+ builder.WriteString("len bytesize[data])")
+ // TODO:(ethangraham) The only other way I can think of getting this name
+ // would involve using the "reflect" package and matching against the
+ // KFuzzTest name, which isn't much better than hardcoding this.
+ builder.WriteString("(kfuzz_test)")
+ return builder.String()
+}
+
+// Given a dwarf type, returns a syzlang string representation of this type.
+func dwarfToSyzlangType(dwarfType dwarf.Type) (string, error) {
+ switch t := dwarfType.(type) {
+ case *dwarf.PtrType:
+ underlyingType, err := dwarfToSyzlangType(t.Type)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("ptr[in, %s]", underlyingType), nil
+ case *dwarf.QualType:
+ if t.Qual == "const" {
+ return dwarfToSyzlangType(t.Type)
+ } else {
+ return "", fmt.Errorf("no support for %s qualifier", t.Qual)
+ }
+ case *dwarf.ArrayType:
+ underlyingType, err := dwarfToSyzlangType(t.Type)
+ if err != nil {
+ return "", err
+ }
+ // If t.Count == -1 then this is a varlen array as per debug/dwarf
+ // documentation.
+ if t.Count == -1 {
+ return fmt.Sprintf("array[%s]", underlyingType), nil
+ } else {
+ return fmt.Sprintf("array[%s, %d]", underlyingType, t.Count), nil
+ }
+ case *dwarf.TypedefType:
+ return dwarfToSyzlangType(t.Type)
+ case *dwarf.IntType, *dwarf.UintType:
+ numBits := t.Size() * 8
+ return fmt.Sprintf("int%d", numBits), nil
+ case *dwarf.CharType, *dwarf.UcharType:
+ return "int8", nil
+ // `void` isn't a valid type by itself, so we know that it would have
+ // been wrapped in a pointer, e.g., `void *`. For this reason, we can return
+ // just interpret it as a byte, i.e., int8.
+ case *dwarf.VoidType:
+ return "int8", nil
+ case *dwarf.StructType:
+ return strings.TrimPrefix(t.StructName, "struct "), nil
+ default:
+ return "", fmt.Errorf("unsupported type %s", dwarfType.String())
+ }
+}