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 }