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  }