diff --git a/csource/csource.go b/csource/csource.go index 08ccae03..183c787a 100644 --- a/csource/csource.go +++ b/csource/csource.go @@ -205,7 +205,7 @@ loop: if bfOff == 0 && bfLen == 0 { fmt.Fprintf(w, "\tNONFAILING(*(uint%v_t*)0x%x = (uint%v_t)0x%x);\n", size*8, addr, size*8, arg) } else { - fmt.Fprintf(w, "\tNONFAILING(STORE_BY_BITMASK(uint%v_t, %v, %v, %v, %v));\n", size*8, addr, arg, bfOff, bfLen) + fmt.Fprintf(w, "\tNONFAILING(STORE_BY_BITMASK(uint%v_t, 0x%x, 0x%x, %v, %v));\n", size*8, addr, arg, bfOff, bfLen) } case prog.ExecArgResult: fmt.Fprintf(w, "\tNONFAILING(*(uint%v_t*)0x%x = %v);\n", size*8, addr, resultRef()) diff --git a/prog/analysis.go b/prog/analysis.go index a4f399f9..81c17c9e 100644 --- a/prog/analysis.go +++ b/prog/analysis.go @@ -174,18 +174,14 @@ func generateSize(arg *Arg, lenType *sys.LenType) *Arg { } } -func assignSizes(args []*Arg) { +func assignSizes(args []*Arg, parentsMap map[*Arg]*Arg) { // Create a map of args and calculate size of the whole struct. argsMap := make(map[string]*Arg) - var parentSize uintptr for _, arg := range args { - if arg.Type.BitfieldLength() == 0 || arg.Type.BitfieldLast() { - parentSize += arg.Size() - } if sys.IsPad(arg.Type) { continue } - argsMap[arg.Type.Name()] = arg + argsMap[arg.Type.FieldName()] = arg } // Fill in size arguments. @@ -194,32 +190,60 @@ func assignSizes(args []*Arg) { continue // Pointer to optional len field, no need to fill in value. } if typ, ok := arg.Type.(*sys.LenType); ok { + buf, ok := argsMap[typ.Buf] + if ok { + *arg = *generateSize(buf.InnerArg(), typ) + continue + } + if typ.Buf == "parent" { - arg.Val = parentSize + arg.Val = parentsMap[arg].Size() if typ.ByteSize != 0 { arg.Val /= typ.ByteSize } continue } - buf, ok := argsMap[typ.Buf] - if !ok { - panic(fmt.Sprintf("len field '%v' references non existent field '%v', argsMap: %+v", - typ.Name(), typ.Buf, argsMap)) + sizeAssigned := false + for parent := parentsMap[arg]; parent != nil; parent = parentsMap[parent] { + if typ.Buf == parent.Type.Name() { + arg.Val = parent.Size() + if typ.ByteSize != 0 { + arg.Val /= typ.ByteSize + } + sizeAssigned = true + break + } + } + if sizeAssigned { + continue } - *arg = *generateSize(buf.InnerArg(), typ) + panic(fmt.Sprintf("len field '%v' references non existent field '%v', argsMap: %+v", + typ.FieldName(), typ.Buf, argsMap)) } } } -func assignSizesCall(c *Call) { - assignSizes(c.Args) - foreachArg(c, func(arg, base *Arg, parent *[]*Arg) { +func assignSizesArray(args []*Arg) { + parentsMap := make(map[*Arg]*Arg) + foreachArgArray(&args, nil, func(arg, base *Arg, _ *[]*Arg) { if _, ok := arg.Type.(*sys.StructType); ok { - assignSizes(arg.Inner) + for _, field := range arg.Inner { + parentsMap[field.InnerArg()] = arg + } } }) + assignSizes(args, parentsMap) + foreachArgArray(&args, nil, func(arg, base *Arg, _ *[]*Arg) { + if _, ok := arg.Type.(*sys.StructType); ok { + assignSizes(arg.Inner, parentsMap) + } + }) +} + +func assignSizesCall(c *Call) { + assignSizesArray(c.Args) } func sanitizeCall(c *Call) { diff --git a/prog/encoding.go b/prog/encoding.go index 8c7c1674..274e94a2 100644 --- a/prog/encoding.go +++ b/prog/encoding.go @@ -108,7 +108,7 @@ func (a *Arg) serialize(buf io.Writer, vars map[*Arg]int, varSeq *int) { } buf.Write([]byte{delims[1]}) case ArgUnion: - fmt.Fprintf(buf, "@%v=", a.OptionType.Name()) + fmt.Fprintf(buf, "@%v=", a.OptionType.FieldName()) a.Option.serialize(buf, vars, varSeq) default: panic("unknown arg kind") @@ -318,7 +318,7 @@ func parseArg(typ sys.Type, p *parser, vars map[string]*Arg) (*Arg, error) { p.Parse('=') var optType sys.Type for _, t2 := range t1.Options { - if name == t2.Name() { + if name == t2.FieldName() { optType = t2 break } diff --git a/prog/mutation.go b/prog/mutation.go index 2136c86f..9841d31c 100644 --- a/prog/mutation.go +++ b/prog/mutation.go @@ -184,8 +184,12 @@ func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Pro } case *sys.UnionType: optType := a.Options[r.Intn(len(a.Options))] - for optType.Name() == arg.OptionType.Name() { + maxIters := 1000 + for i := 0; optType.FieldName() == arg.OptionType.FieldName(); i++ { optType = a.Options[r.Intn(len(a.Options))] + if i >= maxIters { + panic(fmt.Sprintf("couldn't generate a different union option after %v iterations, type: %+v", maxIters, a)) + } } p.removeArg(c, arg.Option) opt, calls := r.generateArg(s, optType) @@ -303,7 +307,7 @@ func Minimize(p0 *Prog, callIndex0 int, pred func(*Prog, int) bool, crash bool) var rec func(p *Prog, call *Call, arg *Arg, path string) bool rec = func(p *Prog, call *Call, arg *Arg, path string) bool { - path += fmt.Sprintf("-%v", arg.Type.Name()) + path += fmt.Sprintf("-%v", arg.Type.FieldName()) switch typ := arg.Type.(type) { case *sys.StructType: for _, innerArg := range arg.Inner { @@ -438,7 +442,7 @@ func (p *Prog) TrimAfter(idx int) { } func mutationArgs(c *Call) (args, bases []*Arg) { - foreachArg(c, func(arg, base *Arg, parent *[]*Arg) { + foreachArg(c, func(arg, base *Arg, _ *[]*Arg) { switch typ := arg.Type.(type) { case *sys.StructType: if isSpecialStruct(typ) == nil { diff --git a/prog/rand.go b/prog/rand.go index 4645db81..4a7bbe08 100644 --- a/prog/rand.go +++ b/prog/rand.go @@ -557,6 +557,7 @@ func (r *randGen) generateParticularCall(s *state, meta *sys.Call) (calls []*Cal Ret: returnArg(meta.Ret), } c.Args, calls = r.generateArgs(s, meta.Args) + assignSizesCall(c) calls = append(calls, c) for _, c1 := range calls { sanitizeCall(c1) @@ -601,8 +602,6 @@ func (r *randGen) generateArgs(s *state, types []sys.Type) ([]*Arg, []*Call) { calls = append(calls, calls1...) } - assignSizes(args) - return args, calls } diff --git a/prog/size_test.go b/prog/size_test.go index 0b7e5fb9..f6e4ab17 100644 --- a/prog/size_test.go +++ b/prog/size_test.go @@ -72,7 +72,7 @@ func TestAssignSize(t *testing.T) { }, { "syz_test$length8(&(0x7f000001f000)={0x00, {0xff, 0x0, 0x00, [0xff, 0xff, 0xff]}, [{0xff, 0x0, 0x00, [0xff, 0xff, 0xff]}], 0x00, 0x0, [0xff, 0xff]})", - "syz_test$length8(&(0x7f000001f000)={0x32, {0xff, 0x1, 0x10, [0xff, 0xff, 0xff]}, [{0xff, 0x1, 0x10, [0xff, 0xff, 0xff]}], 0x10, 0x1, [0xff, 0xff]})", + "syz_test$length8(&(0x7f000001f000)={0x38, {0xff, 0x1, 0x10, [0xff, 0xff, 0xff]}, [{0xff, 0x1, 0x10, [0xff, 0xff, 0xff]}], 0x10, 0x1, [0xff, 0xff]})", }, { "syz_test$length9(&(0x7f000001f000)={&(0x7f0000000000/0x5000)=nil, (0x0000)})", @@ -118,6 +118,10 @@ func TestAssignSize(t *testing.T) { "syz_test$length19(&(0x7f0000000000)={{0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0xff}, 0xff, 0xff, 0xff})", "syz_test$length19(&(0x7f0000000000)={{0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x14}, 0x14, 0x14, 0x5})", }, + { + "syz_test$length20(&(0x7f0000000000)={{{0xff, 0xff, 0xff, 0xff}, 0xff, 0xff, 0xff}, 0xff, 0xff})", + "syz_test$length20(&(0x7f0000000000)={{{0x4, 0x4, 0x7, 0x9}, 0x7, 0x7, 0x9}, 0x9, 0x9})", + }, } for i, test := range tests { diff --git a/prog/validation.go b/prog/validation.go index 5eea42e6..f03ec10b 100644 --- a/prog/validation.go +++ b/prog/validation.go @@ -59,6 +59,9 @@ func (c *Call) validate(ctx *validCtx) error { if arg.Type.Name() != typ.Name() { return fmt.Errorf("syscall %v: type name mismatch: %v vs %v", c.Meta.Name, arg.Type.Name(), typ.Name()) } + if arg.Type.FieldName() != typ.FieldName() { + return fmt.Errorf("syscall %v: field name mismatch: %v vs %v", c.Meta.Name, arg.Type.FieldName(), typ.FieldName()) + } if arg.Type.Dir() == sys.DirOut { if (arg.Val != 0 && arg.Val != arg.Type.Default()) || arg.AddrPage != 0 || arg.AddrOffset != 0 { // We generate output len arguments, which makes sense diff --git a/sys/README.md b/sys/README.md index 2e5f07ec..b5b497bc 100644 --- a/sys/README.md +++ b/sys/README.md @@ -133,6 +133,36 @@ accept(fd sock, ...) sock listen(fd sock, backlog int32) ``` +### Length + +You can specify length of a particular field in struct or a named argument by using `len` and `bytesize` types, for example: +``` +write(fd fd, buf buffer[in], count len[buf]) len[buf] + +sock_fprog { + len len[filter, int16] + filter ptr[in, array[sock_filter]] +} +``` + +If `len`'s argument is a pointer (or a `buffer`), then the length of the pointee argument is used. + +To denote the length of a field in N-byte words use `bytesizeN`, possible values for N are 1, 2, 4 and 8. + +To denote the length of the parent struct, you can use `len[parent, int8]`. +To denote the length of the higher level parent when structs are embedded into one another, you can specify the type name of the particular parent: +``` +struct s1 { + f0 len[s2] # length of s2 +} + +struct s2 { + f0 s1 + f1 array[int32] +} + +``` + ### Proc The `proc` type can be used to denote per process integers. diff --git a/sys/decl.go b/sys/decl.go index a495ffd4..3c98d103 100644 --- a/sys/decl.go +++ b/sys/decl.go @@ -29,6 +29,7 @@ const ( type Type interface { Name() string + FieldName() string Dir() Dir Optional() bool Default() uintptr @@ -49,6 +50,7 @@ func IsPad(t Type) bool { type TypeCommon struct { TypeName string + FldName string // for struct fields and named args ArgDir Dir IsOptional bool } @@ -57,6 +59,10 @@ func (t *TypeCommon) Name() string { return t.TypeName } +func (t *TypeCommon) FieldName() string { + return t.FldName +} + func (t *TypeCommon) Optional() bool { return t.IsOptional } diff --git a/sys/test.txt b/sys/test.txt index e36af549..0ce7ec81 100644 --- a/sys/test.txt +++ b/sys/test.txt @@ -186,6 +186,8 @@ syz_test$length17(a0 ptr[in, syz_length_bytesize2_struct]) syz_test$length18(a0 ptr[in, syz_length_bytesize3_struct]) syz_test$length19(a0 ptr[in, syz_length_bf_struct]) +syz_test$length20(a0 ptr[in, syz_length_parent2_struct]) + syz_length_flags = 0, 1 syz_length_int_struct { @@ -299,6 +301,26 @@ syz_length_bf_struct { f3 bytesize4[f0, int8] } +syz_length_parent2_struct_inner_inner { + f1 len[parent, int8] + f2 len[syz_length_parent2_struct_inner_inner, int8] + f3 len[syz_length_parent2_struct_inner, int8] + f4 len[syz_length_parent2_struct, int8] +} + +syz_length_parent2_struct_inner { + f0 syz_length_parent2_struct_inner_inner + f1 len[parent, int8] + f2 len[syz_length_parent2_struct_inner, int8] + f3 len[syz_length_parent2_struct, int8] +} + +syz_length_parent2_struct { + f0 syz_length_parent2_struct_inner + f1 len[parent, int8] + f2 len[syz_length_parent2_struct, int8] +} + # Big endian syz_test$end0(a0 ptr[in, syz_end_int_struct]) diff --git a/sysgen/sysgen.go b/sysgen/sysgen.go index 867561ad..6906c769 100644 --- a/sysgen/sysgen.go +++ b/sysgen/sysgen.go @@ -265,10 +265,6 @@ func generateStructEntry(str Struct, key structKey, out io.Writer) { if str.IsUnion { typ = "UnionType" } - name := key.field - if name == "" { - name = key.name - } packed := "" if str.Packed { packed = ", packed: true" @@ -281,8 +277,8 @@ func generateStructEntry(str Struct, key structKey, out io.Writer) { if str.Align != 0 { align = fmt.Sprintf(", align: %v", str.Align) } - fmt.Fprintf(out, "\"%v\": &%v{TypeCommon: TypeCommon{TypeName: \"%v\", ArgDir: %v, IsOptional: %v} %v %v %v},\n", - key, typ, name, fmtDir(key.dir), false, packed, align, varlen) + fmt.Fprintf(out, "\"%v\": &%v{TypeCommon: TypeCommon{TypeName: \"%v\", FldName: \"%v\", ArgDir: %v, IsOptional: %v} %v %v %v},\n", + key, typ, key.name, key.field, fmtDir(key.dir), false, packed, align, varlen) } func generateStructFields(str Struct, key structKey, desc *Description, consts map[string]uint64, out io.Writer) { @@ -379,7 +375,7 @@ func generateArg( } } common := func() string { - return fmt.Sprintf("TypeCommon: TypeCommon{TypeName: %v, ArgDir: %v, IsOptional: %v}", name, fmtDir(dir), opt) + return fmt.Sprintf("TypeCommon: TypeCommon{TypeName: \"%v\", FldName: %v, ArgDir: %v, IsOptional: %v}", typ, name, fmtDir(dir), opt) } intCommon := func(typeSize uint64, bigEndian bool, bitfieldLen uint64) string { // BitfieldOff and BitfieldLst will be filled in in initAlign().