// Copyright 2019 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// syz-check does best-effort static correctness checking of the syscall descriptions in sys/os/*.txt.
// Use:
//
//	$ go install ./tools/syz-check
//	$ syz-check -obj-amd64 /linux_amd64/vmlinux -obj-arm64 /linux_arm64/vmlinux \
//		-obj-386 /linux_386/vmlinux -obj-arm /linux_arm/vmlinux
//
// The vmlinux files should include debug info, enable all relevant configs (since we parse dwarf),
// and be compiled with -gdwarf-3 -fno-eliminate-unused-debug-types -fno-eliminate-unused-debug-symbols flags.
// -gdwarf-3 is required because version 4 changes the way bitfields are encoded and Go before 1.18
// does not support then new encoding and at least earlier versions mis-handle it, see:
// https://go-review.googlesource.com/c/go/+/328709/comments/edf0619d_daec236f
//
// Use the following configs for kernels (x86_64 config for i386 as well):
// upstream-apparmor-kasan.config, upstream-arm-full.config, upstream-arm64-full.config
//
// You may check only one arch as well (but then don't commit changes to warn files):
//
//	$ syz-check -obj-amd64 /linux_amd64/vmlinux
//
// You may also disable dwarf or netlink checks with the corresponding flags.
// E.g. -dwarf=0 greatly speeds up checking if you are only interested in netlink warnings
// (but then again don't commit changes).
//
// The results are produced in sys/os/*.warn files.
// On implementation level syz-check parses vmlinux dwarf, extracts struct descriptions
// and compares them with what we have (size, fields, alignment, etc). Netlink checking extracts policy symbols
// from the object files and parses them.
package main

import (
	"bytes"
	"debug/dwarf"
	"debug/elf"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"unsafe"

	"github.com/google/syzkaller/pkg/ast"
	"github.com/google/syzkaller/pkg/compiler"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/pkg/symbolizer"
	"github.com/google/syzkaller/pkg/tool"
	"github.com/google/syzkaller/prog"
	"github.com/google/syzkaller/sys/targets"
)

func main() {
	var (
		flagOS      = flag.String("os", runtime.GOOS, "OS")
		flagDWARF   = flag.Bool("dwarf", true, "do checking based on DWARF")
		flagNetlink = flag.Bool("netlink", true, "do checking of netlink policies")
	)
	arches := make(map[string]*string)
	for arch := range targets.List[targets.Linux] {
		arches[arch] = flag.String("obj-"+arch, "", arch+" kernel object file")
	}
	defer tool.Init()()
	var warnings []Warn
	for arch, obj := range arches {
		if *obj == "" {
			delete(arches, arch)
			continue
		}
		warnings1, err := check(*flagOS, arch, *obj, *flagDWARF, *flagNetlink)
		if err != nil {
			tool.Fail(err)
		}
		warnings = append(warnings, warnings1...)
		runtime.GC()
	}
	if len(arches) == 0 {
		fmt.Fprintf(os.Stderr, "specify at least one -obj-arch flag\n")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if err := writeWarnings(*flagOS, len(arches), warnings); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func check(OS, arch, obj string, dwarf, netlink bool) ([]Warn, error) {
	var warnings []Warn
	if obj == "" {
		return nil, fmt.Errorf("no object file in -obj-%v flag", arch)
	}
	structTypes, locs, warnings1, err := parseDescriptions(OS, arch)
	if err != nil {
		return nil, err
	}
	warnings = append(warnings, warnings1...)
	if dwarf {
		structs, err := parseKernelObject(obj)
		if err != nil {
			return nil, err
		}
		warnings2, err := checkImpl(structs, structTypes, locs)
		if err != nil {
			return nil, err
		}
		warnings = append(warnings, warnings2...)
	}
	if netlink {
		warnings3, err := checkNetlink(OS, arch, obj, structTypes, locs)
		if err != nil {
			return nil, err
		}
		warnings = append(warnings, warnings3...)
	}
	for i := range warnings {
		warnings[i].arch = arch
	}
	return warnings, nil
}

const (
	WarnCompiler           = "compiler"
	WarnNoSuchStruct       = "no-such-struct"
	WarnBadStructSize      = "bad-struct-size"
	WarnBadFieldNumber     = "bad-field-number"
	WarnBadFieldSize       = "bad-field-size"
	WarnBadFieldOffset     = "bad-field-offset"
	WarnBadBitfield        = "bad-bitfield"
	WarnNoNetlinkPolicy    = "no-such-netlink-policy"
	WarnNetlinkBadSize     = "bad-kernel-netlink-policy-size"
	WarnNetlinkBadAttrType = "bad-netlink-attr-type"
	WarnNetlinkBadAttr     = "bad-netlink-attr"
)

type Warn struct {
	pos  ast.Pos
	arch string
	typ  string
	msg  string
}

func writeWarnings(OS string, narches int, warnings []Warn) error {
	allFiles, err := filepath.Glob(filepath.Join("sys", OS, "*.warn"))
	if err != nil {
		return err
	}
	toRemove := make(map[string]bool)
	for _, file := range allFiles {
		toRemove[file] = true
	}
	byFile := make(map[string][]Warn)
	for _, warn := range warnings {
		byFile[warn.pos.File] = append(byFile[warn.pos.File], warn)
	}
	for file, warns := range byFile {
		sort.Slice(warns, func(i, j int) bool {
			w1, w2 := warns[i], warns[j]
			if w1.pos.Line != w2.pos.Line {
				return w1.pos.Line < w2.pos.Line
			}
			if w1.typ != w2.typ {
				return w1.typ < w2.typ
			}
			if w1.msg != w2.msg {
				return w1.msg < w2.msg
			}
			return w1.arch < w2.arch
		})
		buf := new(bytes.Buffer)
		for i := 0; i < len(warns); i++ {
			warn := warns[i]
			arch := warn.arch
			arches := []string{warn.arch}
			for i < len(warns)-1 && warn.msg == warns[i+1].msg {
				if arch != warns[i+1].arch {
					arch = warns[i+1].arch
					arches = append(arches, arch)
				}
				i++
			}
			archStr := ""
			// We do netlink checking only on amd64, so don't add arch.
			if len(arches) < narches && !strings.Contains(warn.typ, "netlink") {
				archStr = fmt.Sprintf(" [%v]", strings.Join(arches, ","))
			}
			fmt.Fprintf(buf, "%v: %v%v\n", warn.typ, warn.msg, archStr)
		}
		warnFile := file + ".warn"
		if err := osutil.WriteFile(warnFile, buf.Bytes()); err != nil {
			return err
		}
		delete(toRemove, warnFile)
	}
	for file := range toRemove {
		os.Remove(file)
	}
	return nil
}

func checkImpl(structs map[string]*dwarf.StructType, structTypes []prog.Type,
	locs map[string]*ast.Struct) ([]Warn, error) {
	var warnings []Warn
	for _, typ := range structTypes {
		name := typ.TemplateName()
		astStruct := locs[name]
		if astStruct == nil {
			continue
		}
		if _, ok := isNetlinkPolicy(typ); ok {
			continue // netlink policies are not structs even if we describe them as structs
		}
		// In some cases we split a single struct into multiple ones
		// (more precise description), so try to match our foo$bar with kernel foo.
		kernelStruct := structs[name]
		if delim := strings.LastIndexByte(name, '$'); kernelStruct == nil && delim != -1 {
			kernelStruct = structs[name[:delim]]
		}
		warns, err := checkStruct(typ, astStruct, kernelStruct)
		if err != nil {
			return nil, err
		}
		warnings = append(warnings, warns...)
	}
	return warnings, nil
}

func checkStruct(typ prog.Type, astStruct *ast.Struct, str *dwarf.StructType) ([]Warn, error) {
	var warnings []Warn
	warn := func(pos ast.Pos, typ, msg string, args ...interface{}) {
		warnings = append(warnings, Warn{pos: pos, typ: typ, msg: fmt.Sprintf(msg, args...)})
	}
	name := typ.TemplateName()
	if str == nil {
		// Varlen structs are frequently not described in kernel (not possible in C).
		if !typ.Varlen() {
			warn(astStruct.Pos, WarnNoSuchStruct, "%v", name)
		}
		return warnings, nil
	}
	if !typ.Varlen() && typ.Size() != uint64(str.ByteSize) {
		warn(astStruct.Pos, WarnBadStructSize, "%v: syz=%v kernel=%v", name, typ.Size(), str.ByteSize)
	}
	// TODO: handle unions, currently we should report some false errors.
	if _, ok := typ.(*prog.UnionType); ok || str.Kind == "union" {
		return warnings, nil
	}
	// Ignore structs with out_overlay attribute.
	// They are never described in the kernel as a simple struct.
	// We could only match and check fields based on some common conventions,
	// but since we have very few of them it's unclear what are these conventions
	// and implementing something complex will have low RoI.
	if typ.(*prog.StructType).OverlayField != 0 {
		return warnings, nil
	}
	// TODO: we could also check enums (elements match corresponding flags in syzkaller).
	// TODO: we could also check values of literal constants (dwarf should have that, right?).
	// TODO: handle nested structs/unions, e.g.:
	// struct foo {
	//	union {
	//		...
	//	} bar;
	// };
	// should be matched with:
	// foo_bar [
	//	...
	// ]
	// TODO: consider making guesses about semantic types of fields,
	// e.g. if a name contains filedes/uid/pid/gid that may be the corresponding resource.
	ai := 0
	offset := uint64(0)
	for _, field := range typ.(*prog.StructType).Fields {
		if field.Type.Varlen() {
			ai = len(str.Field)
			break
		}
		if prog.IsPad(field.Type) {
			offset += field.Type.Size()
			continue
		}
		if ai < len(str.Field) {
			fld := str.Field[ai]
			pos := astStruct.Fields[ai].Pos
			desc := fmt.Sprintf("%v.%v", name, field.Name)
			if field.Name != fld.Name {
				desc += "/" + fld.Name
			}
			if field.Type.UnitSize() != uint64(fld.Type.Size()) {
				warn(pos, WarnBadFieldSize, "%v: syz=%v kernel=%v",
					desc, field.Type.UnitSize(), fld.Type.Size())
			}
			byteOffset := offset - field.Type.UnitOffset()
			if byteOffset != uint64(fld.ByteOffset) {
				warn(pos, WarnBadFieldOffset, "%v: syz=%v kernel=%v",
					desc, byteOffset, fld.ByteOffset)
			}
			// How would you define bitfield offset?
			// Offset of the beginning of the field from the beginning of the memory location, right?
			// No, DWARF defines it as offset of the end of the field from the end of the memory location.
			bitOffset := fld.Type.Size()*8 - fld.BitOffset - fld.BitSize
			if fld.BitSize == 0 {
				// And to make things even more interesting this calculation
				// does not work for normal variables.
				bitOffset = 0
			}
			if field.Type.BitfieldLength() != uint64(fld.BitSize) ||
				field.Type.BitfieldOffset() != uint64(bitOffset) {
				warn(pos, WarnBadBitfield, "%v: size/offset: syz=%v/%v kernel=%v/%v",
					desc, field.Type.BitfieldLength(), field.Type.BitfieldOffset(),
					fld.BitSize, bitOffset)
			}
		}
		ai++
		offset += field.Size()
	}
	if ai != len(str.Field) {
		warn(astStruct.Pos, WarnBadFieldNumber, "%v: syz=%v kernel=%v", name, ai, len(str.Field))
	}
	return warnings, nil
}

func parseDescriptions(OS, arch string) ([]prog.Type, map[string]*ast.Struct, []Warn, error) {
	errorBuf := new(bytes.Buffer)
	var warnings []Warn
	eh := func(pos ast.Pos, msg string) {
		warnings = append(warnings, Warn{pos: pos, typ: WarnCompiler, msg: msg})
		fmt.Fprintf(errorBuf, "%v: %v\n", pos, msg)
	}
	top := ast.ParseGlob(filepath.Join("sys", OS, "*.txt"), eh)
	if top == nil {
		return nil, nil, nil, fmt.Errorf("failed to parse txt files:\n%s", errorBuf.Bytes())
	}
	consts := compiler.DeserializeConstFile(filepath.Join("sys", OS, "*.const"), eh).Arch(arch)
	if consts == nil {
		return nil, nil, nil, fmt.Errorf("failed to parse const files:\n%s", errorBuf.Bytes())
	}
	prg := compiler.Compile(top, consts, targets.Get(OS, arch), eh)
	if prg == nil {
		return nil, nil, nil, fmt.Errorf("failed to compile descriptions:\n%s", errorBuf.Bytes())
	}
	prog.RestoreLinks(prg.Syscalls, prg.Resources, prg.Types)
	locs := make(map[string]*ast.Struct)
	for _, decl := range top.Nodes {
		switch n := decl.(type) {
		case *ast.Struct:
			locs[n.Name.Name] = n
		case *ast.TypeDef:
			if n.Struct != nil {
				locs[n.Name.Name] = n.Struct
			}
		}
	}
	var structs []prog.Type
	for _, typ := range prg.Types {
		switch typ.(type) {
		case *prog.StructType, *prog.UnionType:
			structs = append(structs, typ)
		}
	}
	return structs, locs, warnings, nil
}

// Overall idea of netlink checking.
// Currnetly we check netlink policies for common detectable mistakes.
// First, we detect what looks like a netlink policy in our descriptions
// (these are structs/unions only with nlattr/nlnext/nlnetw fields).
// Then we find corresponding symbols (offset/size) in vmlinux using nm.
// Then we read elf headers and locate where these symbols are in the rodata section.
// Then read in the symbol data, which is an array of nla_policy structs.
// These structs allow to easily figure out type/size of attributes.
// Finally we compare our descriptions with the kernel policy description.
func checkNetlink(OS, arch, obj string, structTypes []prog.Type,
	locs map[string]*ast.Struct) ([]Warn, error) {
	if arch != targets.AMD64 {
		// Netlink policies are arch-independent (?),
		// so no need to check all arches.
		// Also our definition of nlaPolicy below is 64-bit specific.
		return nil, nil
	}
	ef, err := elf.Open(obj)
	if err != nil {
		return nil, err
	}
	rodata := ef.Section(".rodata")
	if rodata == nil {
		return nil, fmt.Errorf("object file %v does not contain .rodata section", obj)
	}
	symb := symbolizer.NewSymbolizer(targets.Get(OS, arch))
	symbols, err := symb.ReadRodataSymbols(obj)
	if err != nil {
		return nil, err
	}
	var warnings []Warn
	structMap := make(map[string]prog.Type)
	for _, typ := range structTypes {
		structMap[typ.Name()] = typ
	}
	checkedAttrs := make(map[string]*checkAttr)
	for _, typ := range structTypes {
		warnings1, err := checkNetlinkStruct(locs, symbols, rodata, structMap, checkedAttrs, typ)
		if err != nil {
			return nil, err
		}
		warnings = append(warnings, warnings1...)
	}
	warnings = append(warnings, checkMissingAttrs(checkedAttrs)...)
	return warnings, nil
}

func checkNetlinkStruct(locs map[string]*ast.Struct, symbols map[string][]symbolizer.Symbol, rodata *elf.Section,
	structMap map[string]prog.Type, checkedAttrs map[string]*checkAttr, typ prog.Type) ([]Warn, error) {
	name := typ.TemplateName()
	astStruct := locs[name]
	if astStruct == nil {
		return nil, nil
	}
	fields, ok := isNetlinkPolicy(typ)
	if !ok {
		return nil, nil
	}
	// In some cases we split a single policy into multiple ones (more precise description),
	// so try to match our foo$bar with kernel foo as well.
	kernelName, ss := name, symbols[name]
	if delim := strings.LastIndexByte(name, '$'); len(ss) == 0 && delim != -1 {
		kernelName = name[:delim]
		ss = symbols[kernelName]
	}
	if len(ss) == 0 {
		return []Warn{{pos: astStruct.Pos, typ: WarnNoNetlinkPolicy, msg: name}}, nil
	}
	var warnings []Warn
	var warnings1 *[]Warn
	var policy1 []nlaPolicy
	var attrs1 map[int]bool
	// We may have several symbols with the same name (they frequently have internal linking),
	// in such case we choose the one that produces fewer warnings.
	for _, symb := range ss {
		if symb.Size == 0 || symb.Size%int(unsafe.Sizeof(nlaPolicy{})) != 0 {
			warnings = append(warnings, Warn{pos: astStruct.Pos, typ: WarnNetlinkBadSize,
				msg: fmt.Sprintf("%v (%v), size %v", kernelName, name, ss[0].Size)})
			continue
		}
		binary := make([]byte, symb.Size)
		addr := symb.Addr - rodata.Addr
		if _, err := rodata.ReadAt(binary, int64(addr)); err != nil {
			return nil, fmt.Errorf("failed to read policy %v (%v) at %v: %v",
				kernelName, name, symb.Addr, err)
		}
		policy := (*[1e6]nlaPolicy)(unsafe.Pointer(&binary[0]))[:symb.Size/int(unsafe.Sizeof(nlaPolicy{}))]
		warnings2, attrs2, err := checkNetlinkPolicy(structMap, typ, fields, astStruct, policy)
		if err != nil {
			return nil, err
		}
		if warnings1 == nil || len(*warnings1) > len(warnings2) {
			warnings1 = &warnings2
			policy1 = policy
			attrs1 = attrs2
		}
	}
	if warnings1 != nil {
		warnings = append(warnings, *warnings1...)
		ca := checkedAttrs[kernelName]
		if ca == nil {
			ca = &checkAttr{
				pos:    astStruct.Pos,
				name:   name,
				policy: policy1,
				attrs:  make(map[int]bool),
			}
			checkedAttrs[kernelName] = ca
		}
		for attr := range attrs1 {
			ca.attrs[attr] = true
		}
	}
	return warnings, nil
}

type checkAttr struct {
	pos    ast.Pos
	name   string
	policy []nlaPolicy
	attrs  map[int]bool
}

func checkMissingAttrs(checkedAttrs map[string]*checkAttr) []Warn {
	// Missing attribute checking is a bit tricky because we may split a single
	// kernel policy into several policies for better precision.
	// They have different names, but map to the same kernel policy.
	// We want to report a missing attribute iff it's missing in all copies of the policy.
	var warnings []Warn
	for _, ca := range checkedAttrs {
		var missing []int
		for i, pol := range ca.policy {
			// Ignore attributes that are not described in the policy
			// (some of them are unused at all, however there are cases where
			// they are not described but used as inputs, and these are actually
			// the worst ones).
			if !ca.attrs[i] && (pol.typ != NLA_UNSPEC && pol.typ != NLA_REJECT || pol.len != 0) {
				missing = append(missing, i)
			}
		}
		// If we miss too many, there is probably something else going on.
		if len(missing) != 0 && len(missing) <= 5 {
			warnings = append(warnings, Warn{
				pos: ca.pos,
				typ: WarnNetlinkBadAttr,
				msg: fmt.Sprintf("%v: missing attributes: %v", ca.name, missing),
			})
		}
	}
	return warnings
}

func isNetlinkPolicy(typ prog.Type) ([]prog.Field, bool) {
	var fields []prog.Field
	switch t := typ.(type) {
	case *prog.StructType:
		fields = t.Fields
	case *prog.UnionType:
		fields = t.Fields
	default:
		return nil, false
	}
	haveAttr := false
	for _, fld := range fields {
		field := fld.Type
		if prog.IsPad(field) {
			continue
		}
		if isNlattr(field) {
			haveAttr = true
			continue
		}
		if arr, ok := field.(*prog.ArrayType); ok {
			field = arr.Elem
		}
		if _, ok := isNetlinkPolicy(field); ok {
			continue
		}
		return nil, false
	}
	return fields, haveAttr
}

const (
	nlattrT  = "nlattr_t"
	nlattrTT = "nlattr_tt"
)

func isNlattr(typ prog.Type) bool {
	name := typ.TemplateName()
	return name == nlattrT || name == nlattrTT
}

func checkNetlinkPolicy(structMap map[string]prog.Type, typ prog.Type, fields []prog.Field,
	astStruct *ast.Struct, policy []nlaPolicy) ([]Warn, map[int]bool, error) {
	var warnings []Warn
	warn := func(pos ast.Pos, typ, msg string, args ...interface{}) {
		warnings = append(warnings, Warn{pos: pos, typ: typ, msg: fmt.Sprintf(msg, args...)})
	}
	checked := make(map[int]bool)
	ai := 0
	for _, field := range fields {
		if prog.IsPad(field.Type) {
			continue
		}
		fld := astStruct.Fields[ai]
		ai++
		if !isNlattr(field.Type) {
			continue
		}
		ft := field.Type.(*prog.StructType)
		attr := int(ft.Fields[1].Type.(*prog.ConstType).Val)
		if attr >= len(policy) {
			warn(fld.Pos, WarnNetlinkBadAttrType, "%v.%v: type %v, kernel policy size %v",
				typ.TemplateName(), field.Name, attr, len(policy))
			continue
		}
		if checked[attr] {
			warn(fld.Pos, WarnNetlinkBadAttr, "%v.%v: duplicate attribute",
				typ.TemplateName(), field.Name)
		}
		checked[attr] = true
		w := checkNetlinkAttr(ft, policy[attr])
		if w != "" {
			warn(fld.Pos, WarnNetlinkBadAttr, "%v.%v: %v",
				typ.TemplateName(), field.Name, w)
		}
	}
	return warnings, checked, nil
}

func checkNetlinkAttr(typ *prog.StructType, policy nlaPolicy) string {
	payload := typ.Fields[2].Type
	if typ.TemplateName() == nlattrTT {
		payload = typ.Fields[4].Type
	}
	if warn := checkAttrType(typ, payload, policy); warn != "" {
		return warn
	}
	size, minSize, maxSize := attrSize(policy)
	payloadSize := minTypeSize(payload)
	if size != -1 && size != payloadSize {
		return fmt.Sprintf("bad size %v, expect %v", payloadSize, size)
	}
	if minSize != -1 && minSize > payloadSize {
		return fmt.Sprintf("bad size %v, expect min %v", payloadSize, minSize)
	}
	if maxSize != -1 && maxSize < payloadSize {
		return fmt.Sprintf("bad size %v, expect max %v", payloadSize, maxSize)
	}

	valMin, valMax, haveVal := typeMinMaxValue(payload)
	if haveVal {
		if policy.validation == NLA_VALIDATE_RANGE || policy.validation == NLA_VALIDATE_MIN {
			if int64(valMin) < int64(policy.minVal) {
				// This is a common case that occurs several times: limit on min value of 1.
				// Not worth fixing (at least not in initial batch), it just crosses out a
				// single value of 0, which we shuold test anyway.
				if !(policy.validation == NLA_VALIDATE_MIN && policy.minVal == 1) {
					return fmt.Sprintf("bad min value %v, expect %v",
						int64(valMin), policy.minVal)
				}
			}
		}
		if policy.validation == NLA_VALIDATE_RANGE || policy.validation == NLA_VALIDATE_MAX {
			if int64(valMax) > int64(policy.maxVal) {
				return fmt.Sprintf("bad max value %v, expect %v",
					int64(valMax), policy.maxVal)
			}
		}
	}
	return ""
}

func minTypeSize(t prog.Type) int {
	if !t.Varlen() {
		return int(t.Size())
	}
	switch typ := t.(type) {
	case *prog.StructType:
		if typ.OverlayField != 0 {
			// Overlayed structs are not supported here
			// (and should not be used in netlink).
			// Make this always produce a warning.
			return -1
		}
		// Some struct args has trailing arrays, but are only checked for min size.
		// Try to get some estimation for min size of this struct.
		size := 0
		for _, field := range typ.Fields {
			if !field.Varlen() {
				size += int(field.Size())
			}
		}
		return size
	case *prog.ArrayType:
		if typ.Kind == prog.ArrayRangeLen && !typ.Elem.Varlen() {
			return int(typ.RangeBegin * typ.Elem.Size())
		}
	case *prog.UnionType:
		size := 0
		for _, field := range typ.Fields {
			if size1 := minTypeSize(field.Type); size1 != -1 && size > size1 || size == 0 {
				size = size1
			}
		}
		return size
	}
	return -1
}

func checkAttrType(typ *prog.StructType, payload prog.Type, policy nlaPolicy) string {
	switch policy.typ {
	case NLA_STRING, NLA_NUL_STRING:
		if _, ok := payload.(*prog.BufferType); !ok {
			return "expect string"
		}
	case NLA_NESTED:
		if typ.TemplateName() != nlattrTT || typ.Fields[3].Type.(*prog.ConstType).Val != 1 {
			return "should be nlnest"
		}
	case NLA_BITFIELD32:
		if typ.TemplateName() != nlattrT || payload.TemplateName() != "nla_bitfield32" {
			return "should be nlattr[nla_bitfield32]"
		}
	case NLA_NESTED_ARRAY:
		if _, ok := payload.(*prog.ArrayType); !ok {
			return "expect array"
		}
	case NLA_REJECT:
		return "NLA_REJECT attribute will always be rejected"
	}
	return ""
}

func attrSize(policy nlaPolicy) (int, int, int) {
	switch policy.typ {
	case NLA_UNSPEC:
		if policy.len != 0 {
			return -1, int(policy.len), -1
		}
	case NLA_MIN_LEN:
		return -1, int(policy.len), -1
	case NLA_EXACT_LEN, NLA_EXACT_LEN_WARN:
		return int(policy.len), -1, -1
	case NLA_U8, NLA_S8:
		return 1, -1, -1
	case NLA_U16, NLA_S16:
		return 2, -1, -1
	case NLA_U32, NLA_S32:
		return 4, -1, -1
	case NLA_U64, NLA_S64, NLA_MSECS:
		return 8, -1, -1
	case NLA_FLAG:
		return 0, -1, -1
	case NLA_BINARY:
		if policy.len != 0 {
			return -1, -1, int(policy.len)
		}
	}
	return -1, -1, -1
}

func typeMinMaxValue(payload prog.Type) (min, max uint64, ok bool) {
	switch typ := payload.(type) {
	case *prog.ConstType:
		return typ.Val, typ.Val, true
	case *prog.IntType:
		if typ.Kind == prog.IntRange {
			return typ.RangeBegin, typ.RangeEnd, true
		}
		return 0, ^uint64(0), true
	case *prog.FlagsType:
		min, max := ^uint64(0), uint64(0)
		for _, v := range typ.Vals {
			if min > v {
				min = v
			}
			if max < v {
				max = v
			}
		}
		return min, max, true
	}
	return 0, 0, false
}

type nlaPolicy struct {
	typ        uint8
	validation uint8
	len        uint16
	_          uint32
	minVal     int16
	maxVal     int16
	_          int32
}

// nolint
const (
	NLA_UNSPEC = iota
	NLA_U8
	NLA_U16
	NLA_U32
	NLA_U64
	NLA_STRING
	NLA_FLAG
	NLA_MSECS
	NLA_NESTED
	NLA_NESTED_ARRAY
	NLA_NUL_STRING
	NLA_BINARY
	NLA_S8
	NLA_S16
	NLA_S32
	NLA_S64
	NLA_BITFIELD32
	NLA_REJECT
	NLA_EXACT_LEN
	NLA_EXACT_LEN_WARN
	NLA_MIN_LEN
)

// nolint
const (
	_ = iota
	NLA_VALIDATE_RANGE
	NLA_VALIDATE_MIN
	NLA_VALIDATE_MAX
)
