github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/kfuzztest/builder.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  package kfuzztest
     4  
     5  import (
     6  	"debug/dwarf"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/google/syzkaller/pkg/ast"
    11  )
    12  
    13  type Builder struct {
    14  	funcs       []SyzFunc
    15  	structs     []SyzStruct
    16  	constraints []SyzConstraint
    17  	annotations []SyzAnnotation
    18  }
    19  
    20  func NewBuilder(
    21  	funcs []SyzFunc,
    22  	structs []SyzStruct,
    23  	constraints []SyzConstraint,
    24  	annotations []SyzAnnotation,
    25  ) *Builder {
    26  	return &Builder{funcs, structs, constraints, annotations}
    27  }
    28  
    29  func (b *Builder) AddStruct(s SyzStruct) {
    30  	b.structs = append(b.structs, s)
    31  }
    32  
    33  func (b *Builder) AddFunc(f SyzFunc) {
    34  	b.funcs = append(b.funcs, f)
    35  }
    36  
    37  func (b *Builder) EmitSyzlangDescription() (string, error) {
    38  	constraintMap := make(map[string]map[string]SyzConstraint)
    39  	for _, constraint := range b.constraints {
    40  		if _, contains := constraintMap[constraint.InputType]; !contains {
    41  			constraintMap[constraint.InputType] = make(map[string]SyzConstraint)
    42  		}
    43  		constraintMap[constraint.InputType][constraint.FieldName] = constraint
    44  	}
    45  	annotationMap := make(map[string]map[string]SyzAnnotation)
    46  	for _, annotation := range b.annotations {
    47  		if _, contains := annotationMap[annotation.InputType]; !contains {
    48  			annotationMap[annotation.InputType] = make(map[string]SyzAnnotation)
    49  		}
    50  		annotationMap[annotation.InputType][annotation.FieldName] = annotation
    51  	}
    52  
    53  	var descBuilder strings.Builder
    54  	descBuilder.WriteString("# This description was automatically generated with tools/kfuzztest-gen\n")
    55  	for _, s := range b.structs {
    56  		structDesc, err := syzStructToSyzlang(s, constraintMap, annotationMap)
    57  		if err != nil {
    58  			return "", err
    59  		}
    60  		descBuilder.WriteString(structDesc)
    61  		descBuilder.WriteString("\n\n")
    62  	}
    63  
    64  	for i, fn := range b.funcs {
    65  		descBuilder.WriteString(syzFuncToSyzlang(fn))
    66  		if i < len(b.funcs)-1 {
    67  			descBuilder.WriteString("\n")
    68  		}
    69  	}
    70  
    71  	// Format the output syzlang descriptions for consistency.
    72  	var astError error
    73  	eh := func(pos ast.Pos, msg string) {
    74  		astError = fmt.Errorf("ast failure: %v: %v", pos, msg)
    75  	}
    76  	descAst := ast.Parse([]byte(descBuilder.String()), "", eh)
    77  	if astError != nil {
    78  		return "", astError
    79  	}
    80  	if descAst == nil {
    81  		return "", fmt.Errorf("failed to format generated syzkaller description - is it well-formed?")
    82  	}
    83  	return string(ast.Format(descAst)), nil
    84  }
    85  
    86  func syzStructToSyzlang(s SyzStruct, constraintMap map[string]map[string]SyzConstraint,
    87  	annotationMap map[string]map[string]SyzAnnotation) (string, error) {
    88  	var builder strings.Builder
    89  
    90  	fmt.Fprintf(&builder, "%s {\n", s.Name)
    91  	structAnnotations := annotationMap["struct "+s.Name]
    92  	structConstraints := constraintMap["struct "+s.Name]
    93  	for _, field := range s.Fields {
    94  		line, err := syzFieldToSyzLang(field, structConstraints, structAnnotations)
    95  		if err != nil {
    96  			return "", err
    97  		}
    98  		fmt.Fprintf(&builder, "\t%s\n", line)
    99  	}
   100  	fmt.Fprint(&builder, "}")
   101  	return builder.String(), nil
   102  }
   103  
   104  func syzFieldToSyzLang(field SyzField, constraintMap map[string]SyzConstraint,
   105  	annotationMap map[string]SyzAnnotation) (string, error) {
   106  	constraint, hasConstraint := constraintMap[field.Name]
   107  	annotation, hasAnnotation := annotationMap[field.Name]
   108  
   109  	var typeDesc string
   110  	var err error
   111  	if hasAnnotation {
   112  		// Annotations override the existing type definitions.
   113  		typeDesc, err = processAnnotation(field, annotation)
   114  	} else {
   115  		typeDesc, err = dwarfToSyzlangType(field.dwarfType)
   116  	}
   117  	if err != nil {
   118  		return "", err
   119  	}
   120  
   121  	// Process constraints only if unannotated.
   122  	// TODO: is there a situation where we would want to process both?
   123  	if hasConstraint && !hasAnnotation {
   124  		constraint, err := processConstraint(constraint)
   125  		if err != nil {
   126  			return "", err
   127  		}
   128  		typeDesc += constraint
   129  	}
   130  	return fmt.Sprintf("%s %s", field.Name, typeDesc), nil
   131  }
   132  
   133  func processConstraint(c SyzConstraint) (string, error) {
   134  	switch c.ConstraintType {
   135  	case ExpectEq:
   136  		return fmt.Sprintf("[%d]", c.Value1), nil
   137  	case ExpectNe:
   138  		// syzkaller does not have a built-in way to support an inequality
   139  		// constraint AFAIK.
   140  		return "", nil
   141  	case ExpectLt:
   142  		return fmt.Sprintf("[0:%d]", c.Value1-1), nil
   143  	case ExpectLe:
   144  		return fmt.Sprintf("[0:%d]", c.Value1), nil
   145  	case ExpectGt:
   146  		return fmt.Sprintf("[%d]", c.Value1+1), nil
   147  	case ExpectGe:
   148  		return fmt.Sprintf("[%d]", c.Value1), nil
   149  	case ExpectInRange:
   150  		return fmt.Sprintf("[%d:%d]", c.Value1, c.Value2), nil
   151  	default:
   152  		fmt.Printf("c = %d\n", c.ConstraintType)
   153  		return "", fmt.Errorf("unsupported constraint type")
   154  	}
   155  }
   156  
   157  func processAnnotation(field SyzField, annotation SyzAnnotation) (string, error) {
   158  	switch annotation.Attribute {
   159  	case AttributeLen:
   160  		underlyingType, err := dwarfToSyzlangType(field.dwarfType)
   161  		if err != nil {
   162  			return "", err
   163  		}
   164  		return fmt.Sprintf("len[%s, %s]", annotation.LinkedFieldName, underlyingType), nil
   165  	case AttributeString:
   166  		return "ptr[in, string]", nil
   167  	case AttributeArray:
   168  		pointeeType, isPtr := resolvesToPtr(field.dwarfType)
   169  		if !isPtr {
   170  			return "", fmt.Errorf("can only annotate pointer fields are arrays")
   171  		}
   172  		// TODO: discards const qualifier.
   173  		typeDesc, err := dwarfToSyzlangType(pointeeType)
   174  		if err != nil {
   175  			return "", err
   176  		}
   177  		return fmt.Sprintf("ptr[in, array[%s]]", typeDesc), nil
   178  	default:
   179  		return "", fmt.Errorf("unsupported attribute type")
   180  	}
   181  }
   182  
   183  // Returns true iff `dwarfType` resolved down to a pointer. For example,
   184  // a `const *void` which isn't directly a pointer.
   185  func resolvesToPtr(dwarfType dwarf.Type) (dwarf.Type, bool) {
   186  	switch t := dwarfType.(type) {
   187  	case *dwarf.QualType:
   188  		return resolvesToPtr(t.Type)
   189  	case *dwarf.PtrType:
   190  		return t.Type, true
   191  	}
   192  	return nil, false
   193  }
   194  
   195  func syzFuncToSyzlang(s SyzFunc) string {
   196  	var builder strings.Builder
   197  	typeName := strings.TrimPrefix(s.InputStructName, "struct ")
   198  
   199  	fmt.Fprintf(&builder, "syz_kfuzztest_run$%s(", s.Name)
   200  	fmt.Fprintf(&builder, "name ptr[in, string[\"%s\"]], ", s.Name)
   201  	fmt.Fprintf(&builder, "data ptr[in, %s], ", typeName)
   202  	builder.WriteString("len bytesize[data], ")
   203  	builder.WriteString("buf ptr[in, array[int8, 65536]]) ")
   204  	// TODO:(ethangraham) The only other way I can think of getting this name
   205  	// would involve using the "reflect" package and matching against the
   206  	// KFuzzTest name, which isn't much better than hardcoding this.
   207  	builder.WriteString("(kfuzz_test)")
   208  	return builder.String()
   209  }
   210  
   211  // Given a dwarf type, returns a syzlang string representation of this type.
   212  func dwarfToSyzlangType(dwarfType dwarf.Type) (string, error) {
   213  	switch t := dwarfType.(type) {
   214  	case *dwarf.PtrType:
   215  		underlyingType, err := dwarfToSyzlangType(t.Type)
   216  		if err != nil {
   217  			return "", err
   218  		}
   219  		return fmt.Sprintf("ptr[in, %s]", underlyingType), nil
   220  	case *dwarf.QualType:
   221  		if t.Qual == "const" {
   222  			return dwarfToSyzlangType(t.Type)
   223  		} else {
   224  			return "", fmt.Errorf("no support for %s qualifier", t.Qual)
   225  		}
   226  	case *dwarf.ArrayType:
   227  		underlyingType, err := dwarfToSyzlangType(t.Type)
   228  		if err != nil {
   229  			return "", err
   230  		}
   231  		// If t.Count == -1 then this is a varlen array as per debug/dwarf
   232  		// documentation.
   233  		if t.Count == -1 {
   234  			return fmt.Sprintf("array[%s]", underlyingType), nil
   235  		} else {
   236  			return fmt.Sprintf("array[%s, %d]", underlyingType, t.Count), nil
   237  		}
   238  	case *dwarf.TypedefType:
   239  		return dwarfToSyzlangType(t.Type)
   240  	case *dwarf.IntType, *dwarf.UintType:
   241  		numBits := t.Size() * 8
   242  		return fmt.Sprintf("int%d", numBits), nil
   243  	case *dwarf.CharType, *dwarf.UcharType:
   244  		return "int8", nil
   245  	// `void` isn't a valid type by itself, so we know that it would have
   246  	// been wrapped in a pointer, e.g., `void *`. For this reason, we can return
   247  	// just interpret it as a byte, i.e., int8.
   248  	case *dwarf.VoidType:
   249  		return "int8", nil
   250  	case *dwarf.StructType:
   251  		return strings.TrimPrefix(t.StructName, "struct "), nil
   252  	default:
   253  		return "", fmt.Errorf("unsupported type %s", dwarfType.String())
   254  	}
   255  }