syzkaller/prog/analysis.go

308 lines
7.5 KiB
Go

// 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"
)
type state struct {
target *Target
ct *ChoiceTable
files map[string]bool
resources map[string][]*ResultArg
strings map[string]bool
ma *memAlloc
va *vmaAlloc
}
// 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)
resources := true
for _, c1 := range p.Calls {
if c1 == c {
resources = false
}
s.analyzeImpl(c1, resources)
}
return s
}
func newState(target *Target, ct *ChoiceTable) *state {
s := &state{
target: target,
ct: ct,
files: make(map[string]bool),
resources: make(map[string][]*ResultArg),
strings: make(map[string]bool),
ma: newMemAlloc(target.NumPages * target.PageSize),
va: newVmaAlloc(target.NumPages),
}
return s
}
func (s *state) analyze(c *Call) {
s.analyzeImpl(c, true)
}
func (s *state) analyzeImpl(c *Call, resources bool) {
ForeachArg(c, func(arg Arg, _ *ArgCtx) {
switch a := arg.(type) {
case *PointerArg:
switch {
case a.IsSpecial():
case a.VmaSize != 0:
s.va.noteAlloc(a.Address/s.target.PageSize, a.VmaSize/s.target.PageSize)
default:
s.ma.noteAlloc(a.Address, a.Res.Size())
}
}
switch typ := arg.Type().(type) {
case *ResourceType:
a := arg.(*ResultArg)
if resources && typ.Dir() != DirIn {
s.resources[typ.Desc.Name] = append(s.resources[typ.Desc.Name], a)
// 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 {
val := string(a.Data())
// Remove trailing zero padding.
for len(val) >= 2 && val[len(val)-1] == 0 && val[len(val)-2] == 0 {
val = val[:len(val)-1]
}
switch typ.Kind {
case BufferString:
s.strings[val] = true
case BufferFilename:
if len(val) < 3 || escapingFilename(val) {
// This is not our file, probalby one of specialFiles.
return
}
if val[len(val)-1] == 0 {
val = val[:len(val)-1]
}
s.files[val] = true
}
}
}
})
}
type ArgCtx struct {
Parent *[]Arg // GroupArg.Inner (for structs) or Call.Args containing this arg
Base *PointerArg // pointer to the base of the heap object containing this arg
Offset uint64 // offset of this arg from the base
Stop bool // if set by the callback, subargs of this arg are not visited
}
func ForeachSubArg(arg Arg, f func(Arg, *ArgCtx)) {
foreachArgImpl(arg, ArgCtx{}, f)
}
func ForeachArg(c *Call, f func(Arg, *ArgCtx)) {
ctx := ArgCtx{}
if c.Ret != nil {
foreachArgImpl(c.Ret, ctx, f)
}
ctx.Parent = &c.Args
for _, arg := range c.Args {
foreachArgImpl(arg, ctx, f)
}
}
func foreachArgImpl(arg Arg, ctx ArgCtx, f func(Arg, *ArgCtx)) {
f(arg, &ctx)
if ctx.Stop {
return
}
switch a := arg.(type) {
case *GroupArg:
if _, ok := a.Type().(*StructType); ok {
ctx.Parent = &a.Inner
}
var totalSize uint64
for _, arg1 := range a.Inner {
foreachArgImpl(arg1, ctx, f)
if !arg1.Type().BitfieldMiddle() {
size := arg1.Size()
ctx.Offset += size
totalSize += size
}
}
claimedSize := a.Size()
varlen := a.Type().Varlen()
if varlen && totalSize > claimedSize || !varlen && totalSize != claimedSize {
panic(fmt.Sprintf("bad group arg size %v, should be <= %v for %#v type %#v",
totalSize, claimedSize, a, a.Type()))
}
case *PointerArg:
if a.Res != nil {
ctx.Base = a
ctx.Offset = 0
foreachArgImpl(a.Res, ctx, f)
}
case *UnionArg:
foreachArgImpl(a.Option, ctx, f)
}
}
func RequiredFeatures(p *Prog) (bitmasks, csums bool) {
for _, c := range p.Calls {
ForeachArg(c, func(arg Arg, _ *ArgCtx) {
if a, ok := arg.(*ConstArg); ok {
if a.Type().BitfieldOffset() != 0 || a.Type().BitfieldLength() != 0 {
bitmasks = true
}
}
if _, ok := arg.Type().(*CsumType); ok {
csums = true
}
})
}
return
}
type CallFlags int
const (
CallExecuted CallFlags = 1 << iota // was started at all
CallFinished // finished executing (rather than blocked forever)
CallBlocked // finished but blocked during execution
)
type CallInfo struct {
Flags CallFlags
Errno int
Signal []uint32
}
const (
fallbackSignalErrno = iota
fallbackSignalErrnoBlocked
fallbackSignalCtor
fallbackSignalFlags
fallbackCallMask = 0x1fff
)
func (p *Prog) FallbackSignal(info []CallInfo) {
resources := make(map[*ResultArg]*Call)
for i, c := range p.Calls {
inf := &info[i]
if inf.Flags&CallExecuted == 0 {
continue
}
id := c.Meta.ID
typ := fallbackSignalErrno
if inf.Flags&CallFinished != 0 && inf.Flags&CallBlocked != 0 {
typ = fallbackSignalErrnoBlocked
}
inf.Signal = append(inf.Signal, encodeFallbackSignal(typ, id, inf.Errno))
if inf.Errno != 0 {
continue
}
if c.Meta.CallName == "seccomp" {
// seccomp filter can produce arbitrary errno values for subsequent syscalls. Don't trust anything afterwards.
break
}
ForeachArg(c, func(arg Arg, _ *ArgCtx) {
if a, ok := arg.(*ResultArg); ok {
resources[a] = c
}
})
// Specifically look only at top-level arguments,
// deeper arguments can produce too much false signal.
flags := 0
for _, arg := range c.Args {
flags = extractArgSignal(arg, id, flags, inf, resources)
}
if flags != 0 {
inf.Signal = append(inf.Signal,
encodeFallbackSignal(fallbackSignalFlags, id, flags))
}
}
}
func extractArgSignal(arg Arg, callID, flags int, inf *CallInfo, resources map[*ResultArg]*Call) int {
switch a := arg.(type) {
case *ResultArg:
flags <<= 1
if a.Res != nil {
ctor := resources[a.Res]
if ctor != nil {
inf.Signal = append(inf.Signal,
encodeFallbackSignal(fallbackSignalCtor, callID, ctor.Meta.ID))
}
} else {
if a.Val != a.Type().(*ResourceType).SpecialValues()[0] {
flags |= 1
}
}
case *ConstArg:
const width = 3
flags <<= width
switch typ := a.Type().(type) {
case *FlagsType:
if typ.BitMask {
for i, v := range typ.Vals {
if a.Val&v != 0 {
flags ^= 1 << (uint(i) % width)
}
}
} else {
for i, v := range typ.Vals {
if a.Val == v {
flags |= i % (1 << width)
break
}
}
}
case *LenType:
flags <<= 1
if a.Val == 0 {
flags |= 1
}
}
case *PointerArg:
flags <<= 1
if a.IsSpecial() {
flags |= 1
}
}
return flags
}
func DecodeFallbackSignal(s uint32) (callID, errno int) {
typ, id, aux := decodeFallbackSignal(s)
switch typ {
case fallbackSignalErrno, fallbackSignalErrnoBlocked:
return id, aux
case fallbackSignalCtor, fallbackSignalFlags:
return id, 0
default:
panic(fmt.Sprintf("bad fallback signal type %v", typ))
}
}
func encodeFallbackSignal(typ, id, aux int) uint32 {
if typ & ^7 != 0 {
panic(fmt.Sprintf("bad fallback signal type %v", typ))
}
if id & ^fallbackCallMask != 0 {
panic(fmt.Sprintf("bad call id in fallback signal %v", id))
}
return uint32(typ) | uint32(id&fallbackCallMask)<<3 | uint32(aux)<<16
}
func decodeFallbackSignal(s uint32) (typ, id, aux int) {
return int(s & 7), int((s >> 3) & fallbackCallMask), int(s >> 16)
}