syzkaller/prog/analysis.go

398 lines
10 KiB
Go
Raw Normal View History

2015-10-12 08:16:57 +00:00
// 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"
"github.com/google/syzkaller/sys"
)
const (
maxPages = 4 << 10
)
type state struct {
2015-10-14 14:55:09 +00:00
ct *ChoiceTable
files map[string]bool
resources map[string][]*Arg
2015-10-14 14:55:09 +00:00
strings map[string]bool
pages [maxPages]bool
2015-10-12 08:16:57 +00:00
}
// analyze analyzes the program p up to but not including call c.
2015-10-14 14:55:09 +00:00
func analyze(ct *ChoiceTable, p *Prog, c *Call) *state {
s := newState(ct)
2015-10-12 08:16:57 +00:00
for _, c1 := range p.Calls {
if c1 == c {
break
}
s.analyze(c1)
}
return s
}
2015-10-14 14:55:09 +00:00
func newState(ct *ChoiceTable) *state {
2015-10-12 08:16:57 +00:00
s := &state{
2015-10-14 14:55:09 +00:00
ct: ct,
files: make(map[string]bool),
resources: make(map[string][]*Arg),
2015-10-14 14:55:09 +00:00
strings: make(map[string]bool),
2015-10-12 08:16:57 +00:00
}
return s
}
func (s *state) analyze(c *Call) {
foreachArgArray(&c.Args, c.Ret, func(arg, base *Arg, _ *[]*Arg) {
switch typ := arg.Type.(type) {
case sys.FilenameType:
if arg.Kind == ArgData && arg.Dir != DirOut {
s.files[string(arg.Data)] = true
}
case sys.ResourceType:
if arg.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).
2015-10-12 08:16:57 +00:00
}
case sys.BufferType:
if typ.Kind == sys.BufferString && arg.Kind == ArgData && len(arg.Data) != 0 {
s.strings[string(arg.Data)] = true
}
}
})
switch c.Meta.Name {
case "mmap":
// Filter out only very wrong arguments.
length := c.Args[1]
if length.AddrPage == 0 && length.AddrOffset == 0 {
break
}
if flags, fd := c.Args[4], c.Args[3]; flags.Val&sys.MAP_ANONYMOUS == 0 && fd.Kind == ArgConst && fd.Val == sys.InvalidFD {
2015-10-12 08:16:57 +00:00
break
}
s.addressable(c.Args[0], length, true)
case "munmap":
s.addressable(c.Args[0], c.Args[1], false)
case "mremap":
s.addressable(c.Args[4], c.Args[2], true)
2015-10-13 15:06:01 +00:00
case "io_submit":
if arr := c.Args[2].Res; arr != nil {
for _, ptr := range arr.Inner {
if ptr.Kind == ArgPointer {
if ptr.Res != nil && ptr.Res.Type.Name() == "iocb" {
s.resources["iocbptr"] = append(s.resources["iocbptr"], ptr)
2015-10-13 15:06:01 +00:00
}
}
}
}
2015-10-12 08:16:57 +00:00
}
}
func (s *state) addressable(addr, size *Arg, ok bool) {
if addr.Kind != ArgPointer || size.Kind != ArgPageSize {
panic("mmap/munmap/mremap args are not pages")
}
n := size.AddrPage
if size.AddrOffset != 0 {
n++
}
if addr.AddrPage+n > uintptr(len(s.pages)) {
2016-10-11 12:24:25 +00:00
panic(fmt.Sprintf("address is out of bounds: page=%v len=%v (%v, %v) bound=%v, addr: %+v, size: %+v",
addr.AddrPage, n, size.AddrPage, size.AddrOffset, len(s.pages), addr, size))
2015-10-12 08:16:57 +00:00
}
for i := uintptr(0); i < n; i++ {
s.pages[addr.AddrPage+i] = ok
}
}
2015-12-31 14:24:08 +00:00
func foreachSubargImpl(arg *Arg, parent *[]*Arg, f func(arg, base *Arg, parent *[]*Arg)) {
2015-10-12 08:16:57 +00:00
var rec func(arg, base *Arg, parent *[]*Arg)
rec = func(arg, base *Arg, parent *[]*Arg) {
f(arg, base, parent)
for _, arg1 := range arg.Inner {
parent1 := parent
if _, ok := arg.Type.(*sys.StructType); ok {
2015-10-12 08:16:57 +00:00
parent1 = &arg.Inner
}
rec(arg1, base, parent1)
}
if arg.Kind == ArgPointer && arg.Res != nil {
rec(arg.Res, arg, parent)
}
2015-12-29 14:00:57 +00:00
if arg.Kind == ArgUnion {
rec(arg.Option, base, parent)
}
2015-10-12 08:16:57 +00:00
}
2015-12-31 14:24:08 +00:00
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)) {
2015-10-12 08:16:57 +00:00
for _, arg := range *args {
2015-12-31 14:24:08 +00:00
foreachSubargImpl(arg, args, f)
2015-10-12 08:16:57 +00:00
}
if ret != nil {
2015-12-31 14:24:08 +00:00
foreachSubargImpl(ret, nil, f)
2015-10-12 08:16:57 +00:00
}
}
func foreachArg(c *Call, f func(arg, base *Arg, parent *[]*Arg)) {
foreachArgArray(&c.Args, nil, f)
}
func assignTypeAndDir(c *Call) error {
var rec func(arg *Arg, typ sys.Type, dir ArgDir) error
rec = func(arg *Arg, typ sys.Type, dir ArgDir) error {
2015-10-12 08:16:57 +00:00
if arg.Call != nil && arg.Call != c {
panic(fmt.Sprintf("different call is already assigned: %p %p %v %v", arg.Call, c, arg.Call.Meta.Name, c.Meta.Name))
}
arg.Call = c
if arg.Type != nil && arg.Type.Name() != typ.Name() {
2016-01-15 23:23:47 +00:00
panic("different type is already assigned: " + arg.Type.Name() + " vs " + typ.Name())
2015-10-12 08:16:57 +00:00
}
arg.Type = typ
switch arg.Kind {
case ArgPointer:
arg.Dir = DirIn
switch typ1 := typ.(type) {
case sys.PtrType:
if arg.Res != nil {
if err := rec(arg.Res, typ1.Type, ArgDir(typ1.Dir)); err != nil {
return err
}
2015-10-12 08:16:57 +00:00
}
}
case ArgGroup:
arg.Dir = dir
switch typ1 := typ.(type) {
case *sys.StructType:
if len(arg.Inner) != len(typ1.Fields) {
return fmt.Errorf("wrong struct field count: %v, want %v", len(arg.Inner), len(typ1.Fields))
}
2015-10-12 08:16:57 +00:00
for i, arg1 := range arg.Inner {
if err := rec(arg1, typ1.Fields[i], dir); err != nil {
return err
}
2015-10-12 08:16:57 +00:00
}
case sys.ArrayType:
for _, arg1 := range arg.Inner {
if err := rec(arg1, typ1.Type, dir); err != nil {
return err
}
2015-10-12 08:16:57 +00:00
}
}
2015-12-29 14:00:57 +00:00
case ArgUnion:
arg.Dir = dir
if err := rec(arg.Option, arg.OptionType, dir); err != nil {
return err
}
2015-10-12 08:16:57 +00:00
default:
arg.Dir = dir
}
return nil
2015-10-12 08:16:57 +00:00
}
for i, arg := range c.Args {
if c.Meta == nil {
panic("nil meta")
}
if err := rec(arg, c.Meta.Args[i], DirIn); err != nil {
return err
}
2015-10-12 08:16:57 +00:00
}
if c.Ret == nil {
c.Ret = returnArg()
c.Ret.Call = c
c.Ret.Type = c.Meta.Ret
c.Ret.Dir = DirOut
}
return nil
2015-10-12 08:16:57 +00:00
}
2016-10-11 12:24:25 +00:00
func generateSize(typ sys.Type, arg *Arg, lenType sys.LenType) *Arg {
if arg == nil {
// Arg is an optional pointer, set size to 0.
return constArg(0)
}
switch typ.(type) {
case sys.VmaType:
return pageSizeArg(arg.AddrPagesNum, 0)
case sys.ArrayType:
if lenType.ByteSize {
return constArg(arg.Size(typ))
} else {
return constArg(uintptr(len(arg.Inner)))
}
default:
return constArg(arg.Size(typ))
}
}
func assignSizes(types []sys.Type, args []*Arg) {
argsMap := make(map[string]*Arg)
typesMap := make(map[string]sys.Type)
// Create a map of args and types.
for i, typ := range types {
if sys.IsPad(typ) {
continue
}
if typ.Name() == "parent" {
panic("parent is reserved len name")
}
innerArg := args[i].InnerArg(typ)
innerType := typ.InnerType()
if _, ok := argsMap[typ.Name()]; ok {
panic(fmt.Sprintf("mutiple args with the same name '%v', types: %+v, args: %+v", typ.Name(), types, args))
}
argsMap[typ.Name()] = innerArg
typesMap[typ.Name()] = innerType
}
// Calculate size of the whole struct.
var parentSize uintptr
for i, typ := range types {
parentSize += args[i].Size(typ)
}
// Fill in size arguments.
for i, typ := range types {
if lenType, ok := typ.InnerType().(sys.LenType); ok {
lenArg := args[i].InnerArg(typ)
if lenArg == nil {
// Pointer to optional len field, no need to fill in value.
continue
}
if lenType.Buf == "parent" {
*lenArg = *constArg(parentSize)
continue
}
arg, ok := argsMap[lenType.Buf]
if !ok {
panic(fmt.Sprintf("len field '%v' references non existent field '%v', argsMap: %+v, typesMap: %+v",
lenType.Name(), lenType.Buf, argsMap, typesMap))
}
typ := typesMap[lenType.Buf]
*lenArg = *generateSize(typ, arg, lenType)
}
}
}
func assignSizesCall(c *Call) {
var rec func(arg *Arg, typ sys.Type)
rec = func(arg *Arg, typ sys.Type) {
switch arg.Kind {
case ArgPointer:
switch typ1 := typ.(type) {
case sys.PtrType:
if arg.Res != nil {
rec(arg.Res, typ1.Type)
}
}
case ArgGroup:
switch typ1 := typ.(type) {
case *sys.StructType:
if len(arg.Inner) != len(typ1.Fields) {
panic(fmt.Sprintf("wrong struct field count: %v, want %v", len(arg.Inner), len(typ1.Fields)))
}
for i, arg1 := range arg.Inner {
rec(arg1, typ1.Fields[i])
}
assignSizes(typ1.Fields, arg.Inner)
case sys.ArrayType:
for _, arg1 := range arg.Inner {
rec(arg1, typ1.Type)
}
}
case ArgUnion:
rec(arg.Option, arg.OptionType)
}
}
if c.Meta == nil {
panic("nil meta")
}
for i, arg := range c.Args {
rec(arg, c.Meta.Args[i])
}
assignSizes(c.Meta.Args, c.Args)
}
2015-10-12 08:16:57 +00:00
func sanitizeCall(c *Call) {
switch c.Meta.CallName {
2015-10-12 08:16:57 +00:00
case "mmap":
// Add MAP_FIXED flag, otherwise it produces non-deterministic results.
addr := c.Args[0]
if addr.Kind != ArgPointer {
panic("mmap address is not ArgPointer")
}
length := c.Args[1]
if length.Kind != ArgPageSize {
panic("mmap length is not ArgPageSize")
}
flags := c.Args[3]
if flags.Kind != ArgConst {
panic("mmap flag arg is not const")
}
flags.Val |= sys.MAP_FIXED
2015-10-12 08:16:57 +00:00
case "mremap":
// Add MREMAP_FIXED flag, otherwise it produces non-deterministic results.
flags := c.Args[3]
if flags.Kind != ArgConst {
panic("mremap flag arg is not const")
}
if flags.Val&sys.MREMAP_MAYMOVE != 0 {
flags.Val |= sys.MREMAP_FIXED
2015-10-12 08:16:57 +00:00
}
case "mknod":
mode := c.Args[1]
if mode.Kind != ArgConst {
panic("mknod mode is not const")
}
// Char and block devices read/write io ports, kernel memory and do other nasty things.
// TODO: not required if executor drops privileges.
if mode.Val != sys.S_IFREG && mode.Val != sys.S_IFIFO && mode.Val != sys.S_IFSOCK {
mode.Val = sys.S_IFIFO
}
2015-10-12 08:16:57 +00:00
case "syslog":
cmd := c.Args[0]
// These disable console output, but we need it.
if cmd.Val == sys.SYSLOG_ACTION_CONSOLE_OFF || cmd.Val == sys.SYSLOG_ACTION_CONSOLE_ON {
cmd.Val = sys.SYSLOG_ACTION_SIZE_UNREAD
2015-10-12 08:16:57 +00:00
}
case "ioctl":
cmd := c.Args[1]
// Freeze kills machine. Though, it is an interesting functions,
// so we need to test it somehow.
// TODO: not required if executor drops privileges.
if uint32(cmd.Val) == sys.FIFREEZE {
cmd.Val = sys.FITHAW
}
case "ptrace":
// PTRACE_TRACEME leads to unkillable processes, see:
// https://groups.google.com/forum/#!topic/syzkaller/uGzwvhlCXAw
if c.Args[0].Val == sys.PTRACE_TRACEME {
c.Args[0].Val = ^uintptr(0)
}
2015-10-12 08:16:57 +00:00
case "exit", "exit_group":
code := c.Args[0]
// These codes are reserved by executor.
if code.Val%128 == 67 || code.Val%128 == 68 {
code.Val = 1
}
}
}