github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/tools/syz-check/check.go (about) 1 // Copyright 2019 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 // syz-check does best-effort static correctness checking of the syscall descriptions in sys/os/*.txt. 5 // Use: 6 // 7 // $ go install ./tools/syz-check 8 // $ syz-check -obj-amd64 /linux_amd64/vmlinux -obj-arm64 /linux_arm64/vmlinux \ 9 // -obj-386 /linux_386/vmlinux -obj-arm /linux_arm/vmlinux 10 // 11 // The vmlinux files should include debug info, enable all relevant configs (since we parse dwarf), 12 // and be compiled with -gdwarf-3 -fno-eliminate-unused-debug-types -fno-eliminate-unused-debug-symbols flags. 13 // -gdwarf-3 is required because version 4 changes the way bitfields are encoded and Go before 1.18 14 // does not support then new encoding and at least earlier versions mis-handle it, see: 15 // https://go-review.googlesource.com/c/go/+/328709/comments/edf0619d_daec236f 16 // 17 // Use the following configs for kernels (x86_64 config for i386 as well): 18 // upstream-apparmor-kasan.config, upstream-arm-full.config, upstream-arm64-full.config 19 // 20 // You may check only one arch as well (but then don't commit changes to warn files): 21 // 22 // $ syz-check -obj-amd64 /linux_amd64/vmlinux 23 // 24 // You may also disable dwarf or netlink checks with the corresponding flags. 25 // E.g. -dwarf=0 greatly speeds up checking if you are only interested in netlink warnings 26 // (but then again don't commit changes). 27 // 28 // The results are produced in sys/os/*.warn files. 29 // On implementation level syz-check parses vmlinux dwarf, extracts struct descriptions 30 // and compares them with what we have (size, fields, alignment, etc). Netlink checking extracts policy symbols 31 // from the object files and parses them. 32 package main 33 34 import ( 35 "bytes" 36 "debug/dwarf" 37 "debug/elf" 38 "flag" 39 "fmt" 40 "os" 41 "path/filepath" 42 "runtime" 43 "sort" 44 "strings" 45 "unsafe" 46 47 "github.com/google/syzkaller/pkg/ast" 48 "github.com/google/syzkaller/pkg/compiler" 49 "github.com/google/syzkaller/pkg/osutil" 50 "github.com/google/syzkaller/pkg/symbolizer" 51 "github.com/google/syzkaller/pkg/tool" 52 "github.com/google/syzkaller/prog" 53 "github.com/google/syzkaller/sys/targets" 54 ) 55 56 func main() { 57 var ( 58 flagOS = flag.String("os", runtime.GOOS, "OS") 59 flagDWARF = flag.Bool("dwarf", true, "do checking based on DWARF") 60 flagNetlink = flag.Bool("netlink", true, "do checking of netlink policies") 61 ) 62 arches := make(map[string]*string) 63 for arch := range targets.List[targets.Linux] { 64 arches[arch] = flag.String("obj-"+arch, "", arch+" kernel object file") 65 } 66 defer tool.Init()() 67 var warnings []Warn 68 for arch, obj := range arches { 69 if *obj == "" { 70 delete(arches, arch) 71 continue 72 } 73 warnings1, err := check(*flagOS, arch, *obj, *flagDWARF, *flagNetlink) 74 if err != nil { 75 tool.Fail(err) 76 } 77 warnings = append(warnings, warnings1...) 78 runtime.GC() 79 } 80 if len(arches) == 0 { 81 fmt.Fprintf(os.Stderr, "specify at least one -obj-arch flag\n") 82 flag.PrintDefaults() 83 os.Exit(1) 84 } 85 if err := writeWarnings(*flagOS, len(arches), warnings); err != nil { 86 fmt.Fprintln(os.Stderr, err) 87 os.Exit(1) 88 } 89 } 90 91 func check(OS, arch, obj string, dwarf, netlink bool) ([]Warn, error) { 92 var warnings []Warn 93 if obj == "" { 94 return nil, fmt.Errorf("no object file in -obj-%v flag", arch) 95 } 96 structTypes, locs, warnings1, err := parseDescriptions(OS, arch) 97 if err != nil { 98 return nil, err 99 } 100 warnings = append(warnings, warnings1...) 101 if dwarf { 102 structs, err := parseKernelObject(obj) 103 if err != nil { 104 return nil, err 105 } 106 warnings2, err := checkImpl(structs, structTypes, locs) 107 if err != nil { 108 return nil, err 109 } 110 warnings = append(warnings, warnings2...) 111 } 112 if netlink { 113 warnings3, err := checkNetlink(arch, obj, structTypes, locs) 114 if err != nil { 115 return nil, err 116 } 117 warnings = append(warnings, warnings3...) 118 } 119 for i := range warnings { 120 warnings[i].arch = arch 121 } 122 return warnings, nil 123 } 124 125 const ( 126 WarnCompiler = "compiler" 127 WarnNoSuchStruct = "no-such-struct" 128 WarnBadStructSize = "bad-struct-size" 129 WarnBadFieldNumber = "bad-field-number" 130 WarnBadFieldSize = "bad-field-size" 131 WarnBadFieldOffset = "bad-field-offset" 132 WarnBadBitfield = "bad-bitfield" 133 WarnNoNetlinkPolicy = "no-such-netlink-policy" 134 WarnNetlinkBadSize = "bad-kernel-netlink-policy-size" 135 WarnNetlinkBadAttrType = "bad-netlink-attr-type" 136 WarnNetlinkBadAttr = "bad-netlink-attr" 137 ) 138 139 type Warn struct { 140 pos ast.Pos 141 arch string 142 typ string 143 msg string 144 } 145 146 func writeWarnings(OS string, narches int, warnings []Warn) error { 147 allFiles, err := filepath.Glob(filepath.Join("sys", OS, "*.warn")) 148 if err != nil { 149 return err 150 } 151 toRemove := make(map[string]bool) 152 for _, file := range allFiles { 153 toRemove[file] = true 154 } 155 byFile := make(map[string][]Warn) 156 for _, warn := range warnings { 157 byFile[warn.pos.File] = append(byFile[warn.pos.File], warn) 158 } 159 for file, warns := range byFile { 160 sort.Slice(warns, func(i, j int) bool { 161 w1, w2 := warns[i], warns[j] 162 if w1.pos.Line != w2.pos.Line { 163 return w1.pos.Line < w2.pos.Line 164 } 165 if w1.typ != w2.typ { 166 return w1.typ < w2.typ 167 } 168 if w1.msg != w2.msg { 169 return w1.msg < w2.msg 170 } 171 return w1.arch < w2.arch 172 }) 173 buf := new(bytes.Buffer) 174 for i := 0; i < len(warns); i++ { 175 warn := warns[i] 176 arch := warn.arch 177 arches := []string{warn.arch} 178 for i < len(warns)-1 && warn.msg == warns[i+1].msg { 179 if arch != warns[i+1].arch { 180 arch = warns[i+1].arch 181 arches = append(arches, arch) 182 } 183 i++ 184 } 185 archStr := "" 186 // We do netlink checking only on amd64, so don't add arch. 187 if len(arches) < narches && !strings.Contains(warn.typ, "netlink") { 188 archStr = fmt.Sprintf(" [%v]", strings.Join(arches, ",")) 189 } 190 fmt.Fprintf(buf, "%v: %v%v\n", warn.typ, warn.msg, archStr) 191 } 192 warnFile := file + ".warn" 193 if err := osutil.WriteFile(warnFile, buf.Bytes()); err != nil { 194 return err 195 } 196 delete(toRemove, warnFile) 197 } 198 for file := range toRemove { 199 os.Remove(file) 200 } 201 return nil 202 } 203 204 func checkImpl(structs map[string]*dwarf.StructType, structTypes []prog.Type, 205 locs map[string]*ast.Struct) ([]Warn, error) { 206 var warnings []Warn 207 for _, typ := range structTypes { 208 name := typ.TemplateName() 209 astStruct := locs[name] 210 if astStruct == nil { 211 continue 212 } 213 if _, ok := isNetlinkPolicy(typ); ok { 214 continue // netlink policies are not structs even if we describe them as structs 215 } 216 // In some cases we split a single struct into multiple ones 217 // (more precise description), so try to match our foo$bar with kernel foo. 218 kernelStruct := structs[name] 219 if delim := strings.LastIndexByte(name, '$'); kernelStruct == nil && delim != -1 { 220 kernelStruct = structs[name[:delim]] 221 } 222 warns, err := checkStruct(typ, astStruct, kernelStruct) 223 if err != nil { 224 return nil, err 225 } 226 warnings = append(warnings, warns...) 227 } 228 return warnings, nil 229 } 230 231 func checkStruct(typ prog.Type, astStruct *ast.Struct, str *dwarf.StructType) ([]Warn, error) { 232 var warnings []Warn 233 warn := func(pos ast.Pos, typ, msg string, args ...interface{}) { 234 warnings = append(warnings, Warn{pos: pos, typ: typ, msg: fmt.Sprintf(msg, args...)}) 235 } 236 name := typ.TemplateName() 237 if str == nil { 238 // Varlen structs are frequently not described in kernel (not possible in C). 239 if !typ.Varlen() { 240 warn(astStruct.Pos, WarnNoSuchStruct, "%v", name) 241 } 242 return warnings, nil 243 } 244 if !typ.Varlen() && typ.Size() != uint64(str.ByteSize) { 245 warn(astStruct.Pos, WarnBadStructSize, "%v: syz=%v kernel=%v", name, typ.Size(), str.ByteSize) 246 } 247 // TODO: handle unions, currently we should report some false errors. 248 if _, ok := typ.(*prog.UnionType); ok || str.Kind == "union" { 249 return warnings, nil 250 } 251 // Ignore structs with out_overlay attribute. 252 // They are never described in the kernel as a simple struct. 253 // We could only match and check fields based on some common conventions, 254 // but since we have very few of them it's unclear what are these conventions 255 // and implementing something complex will have low RoI. 256 if typ.(*prog.StructType).OverlayField != 0 { 257 return warnings, nil 258 } 259 // TODO: we could also check enums (elements match corresponding flags in syzkaller). 260 // TODO: we could also check values of literal constants (dwarf should have that, right?). 261 // TODO: handle nested structs/unions, e.g.: 262 // struct foo { 263 // union { 264 // ... 265 // } bar; 266 // }; 267 // should be matched with: 268 // foo_bar [ 269 // ... 270 // ] 271 // TODO: consider making guesses about semantic types of fields, 272 // e.g. if a name contains filedes/uid/pid/gid that may be the corresponding resource. 273 ai := 0 274 offset := uint64(0) 275 for _, field := range typ.(*prog.StructType).Fields { 276 if field.Type.Varlen() { 277 ai = len(str.Field) 278 break 279 } 280 if prog.IsPad(field.Type) { 281 offset += field.Type.Size() 282 continue 283 } 284 if ai < len(str.Field) { 285 fld := str.Field[ai] 286 pos := astStruct.Fields[ai].Pos 287 desc := fmt.Sprintf("%v.%v", name, field.Name) 288 if field.Name != fld.Name { 289 desc += "/" + fld.Name 290 } 291 if field.Type.UnitSize() != uint64(fld.Type.Size()) { 292 warn(pos, WarnBadFieldSize, "%v: syz=%v kernel=%v", 293 desc, field.Type.UnitSize(), fld.Type.Size()) 294 } 295 byteOffset := offset - field.Type.UnitOffset() 296 if byteOffset != uint64(fld.ByteOffset) { 297 warn(pos, WarnBadFieldOffset, "%v: syz=%v kernel=%v", 298 desc, byteOffset, fld.ByteOffset) 299 } 300 // How would you define bitfield offset? 301 // Offset of the beginning of the field from the beginning of the memory location, right? 302 // No, DWARF defines it as offset of the end of the field from the end of the memory location. 303 bitOffset := fld.Type.Size()*8 - fld.BitOffset - fld.BitSize 304 if fld.BitSize == 0 { 305 // And to make things even more interesting this calculation 306 // does not work for normal variables. 307 bitOffset = 0 308 } 309 if field.Type.BitfieldLength() != uint64(fld.BitSize) || 310 field.Type.BitfieldOffset() != uint64(bitOffset) { 311 warn(pos, WarnBadBitfield, "%v: size/offset: syz=%v/%v kernel=%v/%v", 312 desc, field.Type.BitfieldLength(), field.Type.BitfieldOffset(), 313 fld.BitSize, bitOffset) 314 } 315 } 316 ai++ 317 offset += field.Size() 318 } 319 if ai != len(str.Field) { 320 warn(astStruct.Pos, WarnBadFieldNumber, "%v: syz=%v kernel=%v", name, ai, len(str.Field)) 321 } 322 return warnings, nil 323 } 324 325 func parseDescriptions(OS, arch string) ([]prog.Type, map[string]*ast.Struct, []Warn, error) { 326 errorBuf := new(bytes.Buffer) 327 var warnings []Warn 328 eh := func(pos ast.Pos, msg string) { 329 warnings = append(warnings, Warn{pos: pos, typ: WarnCompiler, msg: msg}) 330 fmt.Fprintf(errorBuf, "%v: %v\n", pos, msg) 331 } 332 top := ast.ParseGlob(filepath.Join("sys", OS, "*.txt"), eh) 333 if top == nil { 334 return nil, nil, nil, fmt.Errorf("failed to parse txt files:\n%s", errorBuf.Bytes()) 335 } 336 consts := compiler.DeserializeConstFile(filepath.Join("sys", OS, "*.const"), eh).Arch(arch) 337 if consts == nil { 338 return nil, nil, nil, fmt.Errorf("failed to parse const files:\n%s", errorBuf.Bytes()) 339 } 340 prg := compiler.Compile(top, consts, targets.Get(OS, arch), eh) 341 if prg == nil { 342 return nil, nil, nil, fmt.Errorf("failed to compile descriptions:\n%s", errorBuf.Bytes()) 343 } 344 prog.RestoreLinks(prg.Syscalls, prg.Resources, prg.Types) 345 locs := make(map[string]*ast.Struct) 346 for _, decl := range top.Nodes { 347 switch n := decl.(type) { 348 case *ast.Struct: 349 locs[n.Name.Name] = n 350 case *ast.TypeDef: 351 if n.Struct != nil { 352 locs[n.Name.Name] = n.Struct 353 } 354 } 355 } 356 var structs []prog.Type 357 for _, typ := range prg.Types { 358 switch typ.(type) { 359 case *prog.StructType, *prog.UnionType: 360 structs = append(structs, typ) 361 } 362 } 363 return structs, locs, warnings, nil 364 } 365 366 // Overall idea of netlink checking. 367 // Currnetly we check netlink policies for common detectable mistakes. 368 // First, we detect what looks like a netlink policy in our descriptions 369 // (these are structs/unions only with nlattr/nlnext/nlnetw fields). 370 // Then we find corresponding symbols (offset/size) in vmlinux using nm. 371 // Then we read elf headers and locate where these symbols are in the rodata section. 372 // Then read in the symbol data, which is an array of nla_policy structs. 373 // These structs allow to easily figure out type/size of attributes. 374 // Finally we compare our descriptions with the kernel policy description. 375 func checkNetlink(arch, obj string, structTypes []prog.Type, 376 locs map[string]*ast.Struct) ([]Warn, error) { 377 if arch != targets.AMD64 { 378 // Netlink policies are arch-independent (?), 379 // so no need to check all arches. 380 // Also our definition of nlaPolicy below is 64-bit specific. 381 return nil, nil 382 } 383 ef, err := elf.Open(obj) 384 if err != nil { 385 return nil, err 386 } 387 rodata := ef.Section(".rodata") 388 if rodata == nil { 389 return nil, fmt.Errorf("object file %v does not contain .rodata section", obj) 390 } 391 symbols, err := symbolizer.ReadRodataSymbols(obj) 392 if err != nil { 393 return nil, err 394 } 395 var warnings []Warn 396 structMap := make(map[string]prog.Type) 397 for _, typ := range structTypes { 398 structMap[typ.Name()] = typ 399 } 400 checkedAttrs := make(map[string]*checkAttr) 401 for _, typ := range structTypes { 402 warnings1, err := checkNetlinkStruct(locs, symbols, rodata, structMap, checkedAttrs, typ) 403 if err != nil { 404 return nil, err 405 } 406 warnings = append(warnings, warnings1...) 407 } 408 warnings = append(warnings, checkMissingAttrs(checkedAttrs)...) 409 return warnings, nil 410 } 411 412 func checkNetlinkStruct(locs map[string]*ast.Struct, symbols map[string][]symbolizer.Symbol, rodata *elf.Section, 413 structMap map[string]prog.Type, checkedAttrs map[string]*checkAttr, typ prog.Type) ([]Warn, error) { 414 name := typ.TemplateName() 415 astStruct := locs[name] 416 if astStruct == nil { 417 return nil, nil 418 } 419 fields, ok := isNetlinkPolicy(typ) 420 if !ok { 421 return nil, nil 422 } 423 // In some cases we split a single policy into multiple ones (more precise description), 424 // so try to match our foo$bar with kernel foo as well. 425 kernelName, ss := name, symbols[name] 426 if delim := strings.LastIndexByte(name, '$'); len(ss) == 0 && delim != -1 { 427 kernelName = name[:delim] 428 ss = symbols[kernelName] 429 } 430 if len(ss) == 0 { 431 return []Warn{{pos: astStruct.Pos, typ: WarnNoNetlinkPolicy, msg: name}}, nil 432 } 433 var warnings []Warn 434 var warnings1 *[]Warn 435 var policy1 []nlaPolicy 436 var attrs1 map[int]bool 437 // We may have several symbols with the same name (they frequently have internal linking), 438 // in such case we choose the one that produces fewer warnings. 439 for _, symb := range ss { 440 if symb.Size == 0 || symb.Size%int(unsafe.Sizeof(nlaPolicy{})) != 0 { 441 warnings = append(warnings, Warn{pos: astStruct.Pos, typ: WarnNetlinkBadSize, 442 msg: fmt.Sprintf("%v (%v), size %v", kernelName, name, ss[0].Size)}) 443 continue 444 } 445 binary := make([]byte, symb.Size) 446 addr := symb.Addr - rodata.Addr 447 if _, err := rodata.ReadAt(binary, int64(addr)); err != nil { 448 return nil, fmt.Errorf("failed to read policy %v (%v) at %v: %w", 449 kernelName, name, symb.Addr, err) 450 } 451 policy := (*[1e6]nlaPolicy)(unsafe.Pointer(&binary[0]))[:symb.Size/int(unsafe.Sizeof(nlaPolicy{}))] 452 warnings2, attrs2, err := checkNetlinkPolicy(structMap, typ, fields, astStruct, policy) 453 if err != nil { 454 return nil, err 455 } 456 if warnings1 == nil || len(*warnings1) > len(warnings2) { 457 warnings1 = &warnings2 458 policy1 = policy 459 attrs1 = attrs2 460 } 461 } 462 if warnings1 != nil { 463 warnings = append(warnings, *warnings1...) 464 ca := checkedAttrs[kernelName] 465 if ca == nil { 466 ca = &checkAttr{ 467 pos: astStruct.Pos, 468 name: name, 469 policy: policy1, 470 attrs: make(map[int]bool), 471 } 472 checkedAttrs[kernelName] = ca 473 } 474 for attr := range attrs1 { 475 ca.attrs[attr] = true 476 } 477 } 478 return warnings, nil 479 } 480 481 type checkAttr struct { 482 pos ast.Pos 483 name string 484 policy []nlaPolicy 485 attrs map[int]bool 486 } 487 488 func checkMissingAttrs(checkedAttrs map[string]*checkAttr) []Warn { 489 // Missing attribute checking is a bit tricky because we may split a single 490 // kernel policy into several policies for better precision. 491 // They have different names, but map to the same kernel policy. 492 // We want to report a missing attribute iff it's missing in all copies of the policy. 493 var warnings []Warn 494 for _, ca := range checkedAttrs { 495 var missing []int 496 for i, pol := range ca.policy { 497 // Ignore attributes that are not described in the policy 498 // (some of them are unused at all, however there are cases where 499 // they are not described but used as inputs, and these are actually 500 // the worst ones). 501 if !ca.attrs[i] && (pol.typ != NLA_UNSPEC && pol.typ != NLA_REJECT || pol.len != 0) { 502 missing = append(missing, i) 503 } 504 } 505 // If we miss too many, there is probably something else going on. 506 if len(missing) != 0 && len(missing) <= 5 { 507 warnings = append(warnings, Warn{ 508 pos: ca.pos, 509 typ: WarnNetlinkBadAttr, 510 msg: fmt.Sprintf("%v: missing attributes: %v", ca.name, missing), 511 }) 512 } 513 } 514 return warnings 515 } 516 517 func isNetlinkPolicy(typ prog.Type) ([]prog.Field, bool) { 518 var fields []prog.Field 519 switch t := typ.(type) { 520 case *prog.StructType: 521 fields = t.Fields 522 case *prog.UnionType: 523 fields = t.Fields 524 default: 525 return nil, false 526 } 527 haveAttr := false 528 for _, fld := range fields { 529 field := fld.Type 530 if prog.IsPad(field) { 531 continue 532 } 533 if isNlattr(field) { 534 haveAttr = true 535 continue 536 } 537 if arr, ok := field.(*prog.ArrayType); ok { 538 field = arr.Elem 539 } 540 if _, ok := isNetlinkPolicy(field); ok { 541 continue 542 } 543 return nil, false 544 } 545 return fields, haveAttr 546 } 547 548 const ( 549 nlattrT = "nlattr_t" 550 nlattrTT = "nlattr_tt" 551 ) 552 553 func isNlattr(typ prog.Type) bool { 554 name := typ.TemplateName() 555 return name == nlattrT || name == nlattrTT 556 } 557 558 func checkNetlinkPolicy(structMap map[string]prog.Type, typ prog.Type, fields []prog.Field, 559 astStruct *ast.Struct, policy []nlaPolicy) ([]Warn, map[int]bool, error) { 560 var warnings []Warn 561 warn := func(pos ast.Pos, typ, msg string, args ...interface{}) { 562 warnings = append(warnings, Warn{pos: pos, typ: typ, msg: fmt.Sprintf(msg, args...)}) 563 } 564 checked := make(map[int]bool) 565 ai := 0 566 for _, field := range fields { 567 if prog.IsPad(field.Type) { 568 continue 569 } 570 fld := astStruct.Fields[ai] 571 ai++ 572 if !isNlattr(field.Type) { 573 continue 574 } 575 ft := field.Type.(*prog.StructType) 576 attr := int(ft.Fields[1].Type.(*prog.ConstType).Val) 577 if attr >= len(policy) { 578 warn(fld.Pos, WarnNetlinkBadAttrType, "%v.%v: type %v, kernel policy size %v", 579 typ.TemplateName(), field.Name, attr, len(policy)) 580 continue 581 } 582 if checked[attr] { 583 warn(fld.Pos, WarnNetlinkBadAttr, "%v.%v: duplicate attribute", 584 typ.TemplateName(), field.Name) 585 } 586 checked[attr] = true 587 w := checkNetlinkAttr(ft, policy[attr]) 588 if w != "" { 589 warn(fld.Pos, WarnNetlinkBadAttr, "%v.%v: %v", 590 typ.TemplateName(), field.Name, w) 591 } 592 } 593 return warnings, checked, nil 594 } 595 596 func checkNetlinkAttr(typ *prog.StructType, policy nlaPolicy) string { 597 payload := typ.Fields[2].Type 598 if typ.TemplateName() == nlattrTT { 599 payload = typ.Fields[4].Type 600 } 601 if warn := checkAttrType(typ, payload, policy); warn != "" { 602 return warn 603 } 604 size, minSize, maxSize := attrSize(policy) 605 payloadSize := minTypeSize(payload) 606 if size != -1 && size != payloadSize { 607 return fmt.Sprintf("bad size %v, expect %v", payloadSize, size) 608 } 609 if minSize != -1 && minSize > payloadSize { 610 return fmt.Sprintf("bad size %v, expect min %v", payloadSize, minSize) 611 } 612 if maxSize != -1 && maxSize < payloadSize { 613 return fmt.Sprintf("bad size %v, expect max %v", payloadSize, maxSize) 614 } 615 616 valMin, valMax, haveVal := typeMinMaxValue(payload) 617 if haveVal { 618 if policy.validation == NLA_VALIDATE_RANGE || policy.validation == NLA_VALIDATE_MIN { 619 if int64(valMin) < int64(policy.minVal) { 620 // This is a common case that occurs several times: limit on min value of 1. 621 // Not worth fixing (at least not in initial batch), it just crosses out a 622 // single value of 0, which we shuold test anyway. 623 if policy.validation != NLA_VALIDATE_MIN || policy.minVal != 1 { 624 return fmt.Sprintf("bad min value %v, expect %v", 625 int64(valMin), policy.minVal) 626 } 627 } 628 } 629 if policy.validation == NLA_VALIDATE_RANGE || policy.validation == NLA_VALIDATE_MAX { 630 if int64(valMax) > int64(policy.maxVal) { 631 return fmt.Sprintf("bad max value %v, expect %v", 632 int64(valMax), policy.maxVal) 633 } 634 } 635 } 636 return "" 637 } 638 639 func minTypeSize(t prog.Type) int { 640 if !t.Varlen() { 641 return int(t.Size()) 642 } 643 switch typ := t.(type) { 644 case *prog.StructType: 645 if typ.OverlayField != 0 { 646 // Overlayed structs are not supported here 647 // (and should not be used in netlink). 648 // Make this always produce a warning. 649 return -1 650 } 651 // Some struct args has trailing arrays, but are only checked for min size. 652 // Try to get some estimation for min size of this struct. 653 size := 0 654 for _, field := range typ.Fields { 655 if !field.Varlen() { 656 size += int(field.Size()) 657 } 658 } 659 return size 660 case *prog.ArrayType: 661 if typ.Kind == prog.ArrayRangeLen && !typ.Elem.Varlen() { 662 return int(typ.RangeBegin * typ.Elem.Size()) 663 } 664 case *prog.UnionType: 665 size := 0 666 for _, field := range typ.Fields { 667 if size1 := minTypeSize(field.Type); size1 != -1 && size > size1 || size == 0 { 668 size = size1 669 } 670 } 671 return size 672 } 673 return -1 674 } 675 676 func checkAttrType(typ *prog.StructType, payload prog.Type, policy nlaPolicy) string { 677 switch policy.typ { 678 case NLA_STRING, NLA_NUL_STRING: 679 if _, ok := payload.(*prog.BufferType); !ok { 680 return "expect string" 681 } 682 case NLA_NESTED: 683 if typ.TemplateName() != nlattrTT || typ.Fields[3].Type.(*prog.ConstType).Val != 1 { 684 return "should be nlnest" 685 } 686 case NLA_BITFIELD32: 687 if typ.TemplateName() != nlattrT || payload.TemplateName() != "nla_bitfield32" { 688 return "should be nlattr[nla_bitfield32]" 689 } 690 case NLA_NESTED_ARRAY: 691 if _, ok := payload.(*prog.ArrayType); !ok { 692 return "expect array" 693 } 694 case NLA_REJECT: 695 return "NLA_REJECT attribute will always be rejected" 696 } 697 return "" 698 } 699 700 func attrSize(policy nlaPolicy) (int, int, int) { 701 switch policy.typ { 702 case NLA_UNSPEC: 703 if policy.len != 0 { 704 return -1, int(policy.len), -1 705 } 706 case NLA_MIN_LEN: 707 return -1, int(policy.len), -1 708 case NLA_EXACT_LEN, NLA_EXACT_LEN_WARN: 709 return int(policy.len), -1, -1 710 case NLA_U8, NLA_S8: 711 return 1, -1, -1 712 case NLA_U16, NLA_S16: 713 return 2, -1, -1 714 case NLA_U32, NLA_S32: 715 return 4, -1, -1 716 case NLA_U64, NLA_S64, NLA_MSECS: 717 return 8, -1, -1 718 case NLA_FLAG: 719 return 0, -1, -1 720 case NLA_BINARY: 721 if policy.len != 0 { 722 return -1, -1, int(policy.len) 723 } 724 } 725 return -1, -1, -1 726 } 727 728 func typeMinMaxValue(payload prog.Type) (minVal, maxVal uint64, ok bool) { 729 switch typ := payload.(type) { 730 case *prog.ConstType: 731 return typ.Val, typ.Val, true 732 case *prog.IntType: 733 if typ.Kind == prog.IntRange { 734 return typ.RangeBegin, typ.RangeEnd, true 735 } 736 return 0, ^uint64(0), true 737 case *prog.FlagsType: 738 minVal, maxVal := ^uint64(0), uint64(0) 739 for _, v := range typ.Vals { 740 minVal = min(minVal, v) 741 maxVal = max(maxVal, v) 742 } 743 return minVal, maxVal, true 744 } 745 return 0, 0, false 746 } 747 748 type nlaPolicy struct { 749 typ uint8 750 validation uint8 751 len uint16 752 _ uint32 753 minVal int16 754 maxVal int16 755 _ int32 756 } 757 758 // nolint 759 const ( 760 NLA_UNSPEC = iota 761 NLA_U8 762 NLA_U16 763 NLA_U32 764 NLA_U64 765 NLA_STRING 766 NLA_FLAG 767 NLA_MSECS 768 NLA_NESTED 769 NLA_NESTED_ARRAY 770 NLA_NUL_STRING 771 NLA_BINARY 772 NLA_S8 773 NLA_S16 774 NLA_S32 775 NLA_S64 776 NLA_BITFIELD32 777 NLA_REJECT 778 NLA_EXACT_LEN 779 NLA_EXACT_LEN_WARN 780 NLA_MIN_LEN 781 ) 782 783 // nolint 784 const ( 785 _ = iota 786 NLA_VALIDATE_RANGE 787 NLA_VALIDATE_MIN 788 NLA_VALIDATE_MAX 789 )