diff options
Diffstat (limited to 'pkg/declextract/netlink.go')
| -rw-r--r-- | pkg/declextract/netlink.go | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/pkg/declextract/netlink.go b/pkg/declextract/netlink.go new file mode 100644 index 000000000..de60ae30e --- /dev/null +++ b/pkg/declextract/netlink.go @@ -0,0 +1,236 @@ +// Copyright 2024 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. + +package declextract + +import ( + "fmt" + "sort" + "strings" +) + +func (ctx *context) fabricateNetlinkPolicies() { + for _, pol := range ctx.NetlinkPolicies { + if len(pol.Attrs) == 0 { + continue + } + str := &Struct{ + Name: pol.Name, + IsUnion: true, + isVarlen: true, + } + for _, attr := range pol.Attrs { + str.Fields = append(str.Fields, &Field{ + Name: attr.Name, + syzType: ctx.nlattrType(attr), + }) + } + ctx.Structs = append(ctx.Structs, str) + } + ctx.Structs = sortAndDedupSlice(ctx.Structs) +} + +func (ctx *context) emitNetlinkTypes() { + for _, fam := range ctx.NetlinkFamilies { + if isEmptyFamily(fam) { + continue + } + id := stringIdentifier(fam.Name) + ctx.fmt("resource genl_%v_family_id_auto[int16]\n", id) + } + for _, fam := range ctx.NetlinkFamilies { + if isEmptyFamily(fam) { + continue + } + id := stringIdentifier(fam.Name) + ctx.fmt("type msghdr_%v_auto[CMD, POLICY] msghdr_netlink[netlink_msg_t"+ + "[genl_%v_family_id_auto, genlmsghdr_t[CMD], POLICY]]\n", id, id) + } + for _, pol := range ctx.NetlinkPolicies { + if len(pol.Attrs) == 0 { + ctx.fmt("type %v auto_todo\n", pol.Name) + } + } +} + +func (ctx *context) emitNetlinkGetFamily() { + for _, fam := range ctx.NetlinkFamilies { + if isEmptyFamily(fam) { + continue + } + id := stringIdentifier(fam.Name) + ctx.fmt("syz_genetlink_get_family_id$auto_%v(name ptr[in, string[\"%v\"]],"+ + " fd sock_nl_generic) genl_%v_family_id_auto\n", id, fam.Name, id) + } +} + +func (ctx *context) emitNetlinkSendmsgs() { + var syscalls []string + for _, fam := range ctx.NetlinkFamilies { + id := stringIdentifier(fam.Name) + dedup := make(map[string]bool) + for _, op := range fam.Ops { + // TODO: emit these as well, these are dump commands w/o input arguments. + if op.Policy == "" { + continue + } + // TODO: emit all of these with unique names, these should be doit/dump variants. + // They may have different policies. + if dedup[op.Name] { + continue + } + dedup[op.Name] = true + syscalls = append(syscalls, fmt.Sprintf("sendmsg$auto_%v(fd sock_nl_generic,"+ + " msg ptr[in, msghdr_%v_auto[%v, %v]], f flags[send_flags])\n", + op.Name, id, op.Name, op.Policy)) + + ctx.noteInterface(&Interface{ + Type: IfaceNetlinkOp, + Name: op.Name, + IdentifyingConst: op.Name, + Files: []string{fam.SourceFile}, + Func: op.Func, + Access: op.Access, + AutoDescriptions: true, + }) + } + } + sort.Strings(syscalls) + for _, call := range syscalls { + ctx.fmt("%s", call) + } +} + +func isEmptyFamily(fam *NetlinkFamily) bool { + for _, op := range fam.Ops { + if op.Policy != "" { + return false + } + } + return true +} + +func (ctx *context) nlattrType(attr *NetlinkAttr) string { + nlattr, typ := "nlattr", "" + switch attr.Kind { + case "NLA_BITFIELD32": + // TODO: Extract values from NLA_POLICY_BITFIELD32 macro. + typ = "int32" + case "NLA_MSECS": + typ = "int64" + case "NLA_FLAG": + typ = "void" + case "NLA_NESTED", "NLA_NESTED_ARRAY": + nlattr = "nlnest" + policy := "nl_generic_attr" + if attr.NestedPolicy != "" { + policy = attr.NestedPolicy + } + typ = fmt.Sprintf("array[%v]", policy) + if attr.Kind == "NLA_NESTED_ARRAY" { + typ = fmt.Sprintf("array[nlnest[0, %v]]", typ) + } + case "NLA_BINARY", "NLA_UNSPEC", "": + // TODO: also handle size 6 for MAC addresses. + if attr.Elem == nil && (attr.MaxSize == 16 || attr.MaxSize == 0) && + strings.Contains(attr.Name, "IPV6") { + typ = "ipv6_addr" + break + } + fallthrough + default: + field := &Field{ + Name: attr.Name, + Type: ctx.netlinkType(attr), + } + typ = ctx.fieldType(field, nil, "", true) + } + return fmt.Sprintf("%v[%v, %v]", nlattr, attr.Name, typ) +} + +func (ctx *context) netlinkType(attr *NetlinkAttr) *Type { + switch attr.Kind { + case "NLA_STRING", "NLA_NUL_STRING": + return &Type{ + Buffer: &BufferType{ + MaxSize: attr.MaxSize, + IsString: true, + IsNonTerminated: attr.Kind == "NLA_STRING", + }, + } + case "NLA_BINARY", "NLA_UNSPEC", "": + if attr.Elem == nil { + switch attr.MaxSize { + case 1, 2, 4, 8: + attr.Kind = fmt.Sprintf("NLA_U%v", attr.MaxSize*8) + return ctx.netlinkTypeInt(attr) + } + minSize := 0 + if attr.Kind != "NLA_BINARY" { + minSize = attr.MaxSize + } + return &Type{ + Buffer: &BufferType{ + MaxSize: attr.MaxSize, + MinSize: minSize, + }, + } + } + elemSize := 1 + switch { + case attr.Elem.Int != nil: + elemSize = attr.Elem.Int.ByteSize + case attr.Elem.Struct != "": + if str := ctx.structs[attr.Elem.Struct+"$auto_record"]; str != nil { + elemSize = str.ByteSize + } else { + ctx.error("binary nlattr %v referenced non-existing struct %v", + attr.Name, attr.Elem.Struct) + } + default: + ctx.error("binary nlattr %v has unsupported elem type", attr.Name) + } + if attr.MaxSize%elemSize != 0 { + ctx.error("binary nlattr %v has odd size: %v, elem size %v", + attr.Name, attr.MaxSize, elemSize) + } + numElems := attr.MaxSize / elemSize + if numElems == 1 { + return attr.Elem + } + return &Type{ + Array: &ArrayType{ + Elem: attr.Elem, + MaxSize: numElems, + }, + } + default: + return ctx.netlinkTypeInt(attr) + } +} + +func (ctx *context) netlinkTypeInt(attr *NetlinkAttr) *Type { + size, be := 0, false + switch attr.Kind { + case "NLA_U8", "NLA_S8": + size = 1 + case "NLA_U16", "NLA_S16": + size = 2 + case "NLA_U32", "NLA_S32": + size = 4 + case "NLA_U64", "NLA_S64", "NLA_SINT", "NLA_UINT": + size = 8 + case "NLA_BE16": + size, be = 2, true + case "NLA_BE32": + size, be = 4, true + default: + panic(fmt.Sprintf("unhandled netlink attribute kind %v", attr.Kind)) + } + return &Type{ + Int: &IntType{ + ByteSize: size, + isBigEndian: be, + }, + } +} |
