mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-27 13:20:34 +00:00
e7021ac638
ioctl(FIFREEZE) renders machine dead. FIFREEZE is an interesting thing, and we could test it in namespace (?) or on manually mounted file systems (?). But that will require more complex handling. Disable it until we have that logic. mknod of char/block devices can do all kinds of nasty stuff (read/write to IO ports, kernel memory, etc). Disable it for now.
288 lines
7.6 KiB
Go
288 lines
7.6 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"
|
|
|
|
"github.com/google/syzkaller/sys"
|
|
)
|
|
|
|
const (
|
|
maxPages = 4 << 10
|
|
)
|
|
|
|
type state struct {
|
|
ct *ChoiceTable
|
|
files map[string]bool
|
|
resources map[sys.ResourceKind]map[sys.ResourceSubkind][]*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(ct)
|
|
for _, c1 := range p.Calls {
|
|
if c1 == c {
|
|
break
|
|
}
|
|
s.analyze(c1)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func newState(ct *ChoiceTable) *state {
|
|
s := &state{
|
|
ct: ct,
|
|
files: make(map[string]bool),
|
|
resources: make(map[sys.ResourceKind]map[sys.ResourceSubkind][]*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 sys.FilenameType:
|
|
if arg.Kind == ArgData && arg.Dir != DirOut {
|
|
s.files[string(arg.Data)] = true
|
|
}
|
|
case sys.ResourceType:
|
|
if arg.Dir != DirIn {
|
|
if s.resources[typ.Kind] == nil {
|
|
s.resources[typ.Kind] = make(map[sys.ResourceSubkind][]*Arg)
|
|
}
|
|
s.resources[typ.Kind][typ.Subkind] = append(s.resources[typ.Kind][typ.Subkind], arg)
|
|
}
|
|
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&MAP_ANONYMOUS == 0 && fd.Kind == ArgConst && fd.Val == sys.InvalidFD {
|
|
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)
|
|
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" {
|
|
if s.resources[sys.ResIocbPtr] == nil {
|
|
s.resources[sys.ResIocbPtr] = make(map[sys.ResourceSubkind][]*Arg)
|
|
}
|
|
s.resources[sys.ResIocbPtr][sys.ResAny] = append(s.resources[sys.ResIocbPtr][sys.ResAny], ptr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
panic(fmt.Sprintf("address is out of bounds: page=%v len=%v (%v, %v) bound=%v", addr.AddrPage, n, size.AddrPage, size.AddrOffset, len(s.pages)))
|
|
}
|
|
for i := uintptr(0); i < n; i++ {
|
|
s.pages[addr.AddrPage+i] = ok
|
|
}
|
|
}
|
|
|
|
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)
|
|
for _, arg1 := range arg.Inner {
|
|
parent1 := parent
|
|
if _, ok := arg.Type.(sys.StructType); ok {
|
|
parent1 = &arg.Inner
|
|
}
|
|
rec(arg1, base, parent1)
|
|
}
|
|
if arg.Kind == ArgPointer && arg.Res != nil {
|
|
rec(arg.Res, arg, parent)
|
|
}
|
|
if arg.Kind == ArgUnion {
|
|
rec(arg.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 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 {
|
|
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() {
|
|
panic("different type is already assigned: " + arg.Type.Name() + " vs " + typ.Name())
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
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))
|
|
}
|
|
for i, arg1 := range arg.Inner {
|
|
if err := rec(arg1, typ1.Fields[i], dir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case sys.ArrayType:
|
|
for _, arg1 := range arg.Inner {
|
|
if err := rec(arg1, typ1.Type, dir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
case ArgUnion:
|
|
arg.Dir = dir
|
|
if err := rec(arg.Option, arg.OptionType, dir); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
arg.Dir = dir
|
|
}
|
|
return nil
|
|
}
|
|
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
|
|
}
|
|
}
|
|
if c.Ret == nil {
|
|
c.Ret = returnArg()
|
|
c.Ret.Call = c
|
|
c.Ret.Type = c.Meta.Ret
|
|
c.Ret.Dir = DirOut
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sanitizeCall(c *Call) {
|
|
switch c.Meta.CallName {
|
|
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 |= MAP_FIXED
|
|
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&MREMAP_MAYMOVE != 0 {
|
|
flags.Val |= MREMAP_FIXED
|
|
}
|
|
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 != S_IFREG && mode.Val != S_IFIFO && mode.Val != S_IFSOCK {
|
|
mode.Val = S_IFIFO
|
|
}
|
|
case "syslog":
|
|
cmd := c.Args[0]
|
|
// These disable console output, but we need it.
|
|
if cmd.Val == SYSLOG_ACTION_CONSOLE_OFF || cmd.Val == SYSLOG_ACTION_CONSOLE_ON {
|
|
cmd.Val = SYSLOG_ACTION_SIZE_UNREAD
|
|
}
|
|
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) == uint32(FIFREEZE) {
|
|
cmd.Val = FITHAW
|
|
}
|
|
case "ptrace":
|
|
// PTRACE_TRACEME leads to unkillable processes, see:
|
|
// https://groups.google.com/forum/#!topic/syzkaller/uGzwvhlCXAw
|
|
if c.Args[0].Val == PTRACE_TRACEME {
|
|
c.Args[0].Val = ^uintptr(0)
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|