// Copyright 2015 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. // Conservative resource-related analysis of programs. // The analysis figures out what files descriptors are [potentially] opened // at a particular point in program, what pages are [potentially] mapped, // what files were already referenced in calls, etc. package prog import ( "fmt" ) const ( maxPages = 4 << 10 ) type state struct { target *Target ct *ChoiceTable files map[string]bool resources map[string][]Arg strings map[string]bool pages [maxPages]bool } // analyze analyzes the program p up to but not including call c. func analyze(ct *ChoiceTable, p *Prog, c *Call) *state { s := newState(p.Target, ct) for _, c1 := range p.Calls { if c1 == c { break } s.analyze(c1) } return s } func newState(target *Target, ct *ChoiceTable) *state { s := &state{ target: target, ct: ct, files: make(map[string]bool), resources: make(map[string][]Arg), strings: make(map[string]bool), } return s } func (s *state) analyze(c *Call) { foreachArgArray(&c.Args, c.Ret, func(arg, base Arg, _ *[]Arg) { switch typ := arg.Type().(type) { case *ResourceType: if typ.Dir() != DirIn { s.resources[typ.Desc.Name] = append(s.resources[typ.Desc.Name], arg) // TODO: negative PIDs and add them as well (that's process groups). } case *BufferType: a := arg.(*DataArg) if typ.Dir() != DirOut && len(a.Data()) != 0 { switch typ.Kind { case BufferString: s.strings[string(a.Data())] = true case BufferFilename: s.files[string(a.Data())] = true } } } }) start, npages, mapped := s.target.AnalyzeMmap(c) if npages != 0 { if start+npages > uint64(len(s.pages)) { panic(fmt.Sprintf("address is out of bounds: page=%v len=%v bound=%v", start, npages, len(s.pages))) } for i := uint64(0); i < npages; i++ { s.pages[start+i] = mapped } } } func foreachSubargImpl(arg Arg, parent *[]Arg, f func(arg, base Arg, parent *[]Arg)) { var rec func(arg, base Arg, parent *[]Arg) rec = func(arg, base Arg, parent *[]Arg) { f(arg, base, parent) switch a := arg.(type) { case *GroupArg: for _, arg1 := range a.Inner { parent1 := parent if _, ok := arg.Type().(*StructType); ok { parent1 = &a.Inner } rec(arg1, base, parent1) } case *PointerArg: if a.Res != nil { rec(a.Res, arg, parent) } case *UnionArg: rec(a.Option, base, parent) } } rec(arg, nil, parent) } func foreachSubarg(arg Arg, f func(arg, base Arg, parent *[]Arg)) { foreachSubargImpl(arg, nil, f) } func foreachArgArray(args *[]Arg, ret Arg, f func(arg, base Arg, parent *[]Arg)) { for _, arg := range *args { foreachSubargImpl(arg, args, f) } if ret != nil { foreachSubargImpl(ret, nil, f) } } func foreachArg(c *Call, f func(arg, base Arg, parent *[]Arg)) { foreachArgArray(&c.Args, nil, f) } func foreachSubargOffset(arg Arg, f func(arg Arg, offset uint64)) { var rec func(Arg, uint64) uint64 rec = func(arg1 Arg, offset uint64) uint64 { switch a := arg1.(type) { case *GroupArg: f(arg1, offset) var totalSize uint64 for _, arg2 := range a.Inner { size := rec(arg2, offset) if !arg2.Type().BitfieldMiddle() { offset += size totalSize += size } } if totalSize > arg1.Size() { panic(fmt.Sprintf("bad group arg size %v, should be <= %v for %+v", totalSize, arg1.Size(), arg1)) } case *UnionArg: f(arg1, offset) size := rec(a.Option, offset) offset += size if size > arg1.Size() { panic(fmt.Sprintf("bad union arg size %v, should be <= %v for arg %+v with type %+v", size, arg1.Size(), arg1, arg1.Type())) } default: f(arg1, offset) } return arg1.Size() } rec(arg, 0) } func RequiresBitmasks(p *Prog) bool { result := false for _, c := range p.Calls { foreachArg(c, func(arg, _ Arg, _ *[]Arg) { if a, ok := arg.(*ConstArg); ok { if a.Type().BitfieldOffset() != 0 || a.Type().BitfieldLength() != 0 { result = true } } }) } return result } func RequiresChecksums(p *Prog) bool { result := false for _, c := range p.Calls { foreachArg(c, func(arg, _ Arg, _ *[]Arg) { if _, ok := arg.Type().(*CsumType); ok { result = true } }) } return result }