blob: 80a127108757e14b6c9d0e55c743b7180c1c1df8 [file] [log] [blame]
// 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
}