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  )