github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/declextract/interface.go (about)

     1  // Copyright 2024 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 declextract
     5  
     6  import (
     7  	"fmt"
     8  	"slices"
     9  	"strings"
    10  
    11  	"github.com/google/syzkaller/pkg/clangtool"
    12  	"github.com/google/syzkaller/pkg/cover"
    13  )
    14  
    15  type Interface struct {
    16  	Type               string
    17  	Name               string
    18  	IdentifyingConst   string
    19  	Files              []string
    20  	Func               string
    21  	Access             string
    22  	Subsystems         []string
    23  	ManualDescriptions TristateVal
    24  	AutoDescriptions   TristateVal
    25  	ReachableLOC       int
    26  	CoveredBlocks      int
    27  	TotalBlocks        int
    28  
    29  	scopeArg int
    30  	scopeVal string
    31  }
    32  
    33  type TristateVal int
    34  
    35  const (
    36  	TristateUnknown TristateVal = iota
    37  	TristateYes
    38  	TristateNo
    39  )
    40  
    41  const (
    42  	IfaceSyscall   = "SYSCALL"
    43  	IfaceNetlinkOp = "NETLINK"
    44  	IfaceFileop    = "FILEOP"
    45  	IfaceIoctl     = "IOCTL"
    46  	IfaceIouring   = "IOURING"
    47  
    48  	AccessUnknown = "unknown"
    49  	AccessUser    = "user"
    50  	AccessNsAdmin = "ns_admin"
    51  	AccessAdmin   = "admin"
    52  )
    53  
    54  func (ctx *context) noteInterface(iface *Interface) {
    55  	ctx.interfaces = append(ctx.interfaces, iface)
    56  }
    57  
    58  func (ctx *context) finishInterfaces() {
    59  	ctx.interfaces = clangtool.SortAndDedupSlice(ctx.interfaces)
    60  	count := make(map[string]int)
    61  	for _, iface := range ctx.interfaces {
    62  		count[iface.Type+iface.Name]++
    63  	}
    64  	// Lots of file ops have the same name, add file name to them.
    65  	for _, iface := range ctx.interfaces {
    66  		if count[iface.Type+iface.Name] > 1 {
    67  			iface.Name = iface.Name + "_" + fileNameSuffix(iface.Files[0])
    68  		}
    69  	}
    70  	for _, iface := range ctx.interfaces {
    71  		ctx.calculateLOC(iface)
    72  		slices.Sort(iface.Files)
    73  		iface.Files = slices.Compact(iface.Files)
    74  		if iface.Access == "" {
    75  			iface.Access = AccessUnknown
    76  		}
    77  		if iface.Access == "" {
    78  			iface.Access = AccessUnknown
    79  		}
    80  	}
    81  	ctx.interfaces = clangtool.SortAndDedupSlice(ctx.interfaces)
    82  }
    83  
    84  func (ctx *context) processFunctions() {
    85  	for _, fn := range ctx.Functions {
    86  		ctx.funcs[fn.File+fn.Name] = fn
    87  		// Strictly speaking there may be several different static functions in different headers,
    88  		// but we ignore such possibility for now.
    89  		if !fn.IsStatic || strings.HasSuffix(fn.File, ".h") {
    90  			ctx.funcs[fn.Name] = fn
    91  		}
    92  	}
    93  	coverBlocks := make(map[string][]*cover.Block)
    94  	for _, file := range ctx.coverage {
    95  		for _, fn := range file.Functions {
    96  			coverBlocks[file.FilePath+fn.FuncName] = fn.Blocks
    97  		}
    98  	}
    99  	for _, fn := range ctx.Functions {
   100  		for _, block := range coverBlocks[fn.File+fn.Name] {
   101  			var match *FunctionScope
   102  			for _, scope := range fn.Scopes {
   103  				if scope.Arg == -1 {
   104  					match = scope
   105  				}
   106  				if block.FromLine >= scope.StartLine && block.FromLine <= scope.EndLine {
   107  					match = scope
   108  					break
   109  				}
   110  			}
   111  			match.totalBlocks++
   112  			if block.HitCount != 0 {
   113  				match.coveredBlocks++
   114  			}
   115  		}
   116  		for _, scope := range fn.Scopes {
   117  			for _, callee := range scope.Calls {
   118  				called := ctx.findFunc(callee, fn.File)
   119  				if called == nil || called == fn {
   120  					continue
   121  				}
   122  				scope.calls = append(scope.calls, called)
   123  				called.callers++
   124  			}
   125  		}
   126  	}
   127  }
   128  
   129  func (ctx *context) calculateLOC(iface *Interface) {
   130  	fn := ctx.findFunc(iface.Func, iface.Files[0])
   131  	if fn == nil {
   132  		ctx.warn("can't find function %v called in %v", iface.Func, iface.Files[0])
   133  		return
   134  	}
   135  	scopeFnArgs := ctx.inferArgFlow(fnArg{fn, iface.scopeArg})
   136  	visited := make(map[*Function]bool)
   137  	iface.ReachableLOC = ctx.collectLOC(iface, fn, scopeFnArgs, iface.scopeVal, visited)
   138  }
   139  
   140  func (ctx *context) collectLOC(iface *Interface, fn *Function, scopeFnArgs map[fnArg]bool,
   141  	scopeVal string, visited map[*Function]bool) int {
   142  	// Ignore very common functions when computing reachability for complexity analysis.
   143  	// Counting kmalloc/printk against each caller is not useful (they have ~10K calls).
   144  	// There are also subsystem common functions (e.g. functions called in some parts of fs/net).
   145  	// The current threshold is somewhat arbitrary and is based on the number of callers in syzbot kernel:
   146  	// 6 callers - 2272 functions
   147  	// 5 callers - 3468 functions
   148  	// 4 callers - 6295 functions
   149  	// 3 callers - 16527 functions
   150  	const commonFuncThreshold = 5
   151  
   152  	visited[fn] = true
   153  	loc := max(0, fn.EndLine-fn.StartLine-1)
   154  	for _, scope := range fn.Scopes {
   155  		if !relevantScope(scopeFnArgs, scopeVal, scope) {
   156  			loc -= max(0, scope.EndLine-scope.StartLine)
   157  			continue
   158  		}
   159  		iface.TotalBlocks += scope.totalBlocks
   160  		iface.CoveredBlocks += scope.coveredBlocks
   161  		for _, callee := range scope.calls {
   162  			if visited[callee] || callee.callers >= commonFuncThreshold {
   163  				continue
   164  			}
   165  			loc += ctx.collectLOC(iface, callee, scopeFnArgs, scopeVal, visited)
   166  		}
   167  	}
   168  	return loc
   169  }
   170  
   171  func (ctx *context) findFunc(name, file string) *Function {
   172  	if fn := ctx.funcs[file+name]; fn != nil {
   173  		return fn
   174  	}
   175  	return ctx.funcs[name]
   176  }
   177  
   178  func (ctx *context) mustFindFunc(name, file string) *Function {
   179  	if name == "" {
   180  		return nil
   181  	}
   182  	fn := ctx.findFunc(name, file)
   183  	if fn == nil {
   184  		panic(fmt.Sprintf("no func %q in %q", name, file))
   185  	}
   186  	return fn
   187  }
   188  
   189  func fileNameSuffix(file string) string {
   190  	// Remove file extension.
   191  	ext := strings.LastIndexByte(file, '.')
   192  	if ext != -1 {
   193  		file = file[:ext]
   194  	}
   195  	raw := []byte(file)
   196  	for i, v := range raw {
   197  		if v >= 'a' && v <= 'z' || v >= 'A' && v <= 'Z' || v >= '0' && v <= '9' {
   198  			continue
   199  		}
   200  		raw[i] = '_'
   201  	}
   202  	return string(raw)
   203  }
   204  
   205  func Tristate(v bool) TristateVal {
   206  	if v {
   207  		return TristateYes
   208  	}
   209  	return TristateNo
   210  }
   211  
   212  func (tv TristateVal) String() string {
   213  	switch tv {
   214  	case TristateYes:
   215  		return "true"
   216  	case TristateNo:
   217  		return "false"
   218  	default:
   219  		return "unknown"
   220  	}
   221  }