| // 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 |
| ) |