github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/prog/analysis.go (about)

     1  // Copyright 2015 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  // Conservative resource-related analysis of programs.
     5  // The analysis figures out what files descriptors are [potentially] opened
     6  // at a particular point in program, what pages are [potentially] mapped,
     7  // what files were already referenced in calls, etc.
     8  
     9  package prog
    10  
    11  import (
    12  	"bytes"
    13  	"fmt"
    14  	"io"
    15  
    16  	"github.com/google/syzkaller/pkg/image"
    17  )
    18  
    19  type state struct {
    20  	target    *Target
    21  	ct        *ChoiceTable
    22  	corpus    []*Prog
    23  	files     map[string]bool
    24  	resources map[string][]*ResultArg
    25  	strings   map[string]bool
    26  	ma        *memAlloc
    27  	va        *vmaAlloc
    28  }
    29  
    30  // analyze analyzes the program p up to but not including call c.
    31  func analyze(ct *ChoiceTable, corpus []*Prog, p *Prog, c *Call) *state {
    32  	s := newState(p.Target, ct, corpus)
    33  	resources := true
    34  	for _, c1 := range p.Calls {
    35  		if c1 == c {
    36  			resources = false
    37  		}
    38  		s.analyzeImpl(c1, resources)
    39  	}
    40  	return s
    41  }
    42  
    43  func newState(target *Target, ct *ChoiceTable, corpus []*Prog) *state {
    44  	s := &state{
    45  		target:    target,
    46  		ct:        ct,
    47  		corpus:    corpus,
    48  		files:     make(map[string]bool),
    49  		resources: make(map[string][]*ResultArg),
    50  		strings:   make(map[string]bool),
    51  		ma:        newMemAlloc(target.NumPages * target.PageSize),
    52  		va:        newVmaAlloc(target.NumPages),
    53  	}
    54  	return s
    55  }
    56  
    57  func (s *state) analyze(c *Call) {
    58  	s.analyzeImpl(c, true)
    59  }
    60  
    61  func (s *state) analyzeImpl(c *Call, resources bool) {
    62  	ForeachArg(c, func(arg Arg, _ *ArgCtx) {
    63  		switch a := arg.(type) {
    64  		case *PointerArg:
    65  			switch {
    66  			case a.IsSpecial():
    67  			case a.VmaSize != 0:
    68  				s.va.noteAlloc(a.Address/s.target.PageSize, a.VmaSize/s.target.PageSize)
    69  			case a.Res != nil:
    70  				s.ma.noteAlloc(a.Address, a.Res.Size())
    71  			}
    72  		}
    73  		switch typ := arg.Type().(type) {
    74  		case *ResourceType:
    75  			a := arg.(*ResultArg)
    76  			if resources && a.Dir() != DirIn {
    77  				s.resources[typ.Desc.Name] = append(s.resources[typ.Desc.Name], a)
    78  				// TODO: negative PIDs and add them as well (that's process groups).
    79  			}
    80  		case *BufferType:
    81  			a := arg.(*DataArg)
    82  			if a.Dir() != DirOut && len(a.Data()) != 0 &&
    83  				(typ.Kind == BufferString || typ.Kind == BufferFilename) {
    84  				val := string(a.Data())
    85  				// Remove trailing zero padding.
    86  				for len(val) >= 2 && val[len(val)-1] == 0 && val[len(val)-2] == 0 {
    87  					val = val[:len(val)-1]
    88  				}
    89  				switch typ.Kind {
    90  				case BufferString:
    91  					s.strings[val] = true
    92  				case BufferFilename:
    93  					if len(val) < 3 || escapingFilename(val) {
    94  						// This is not our file, probalby one of specialFiles.
    95  						return
    96  					}
    97  					if val[len(val)-1] == 0 {
    98  						val = val[:len(val)-1]
    99  					}
   100  					s.files[val] = true
   101  				}
   102  			}
   103  		}
   104  	})
   105  }
   106  
   107  type parentStack []Arg
   108  
   109  func allocStack() parentStack {
   110  	// Let's save some allocations during stack traversal.
   111  	return make([]Arg, 0, 4)
   112  }
   113  
   114  func pushStack(ps parentStack, a Arg) parentStack {
   115  	return append(ps, a)
   116  }
   117  
   118  func popStack(ps parentStack) (parentStack, Arg) {
   119  	if len(ps) > 0 {
   120  		return ps[:len(ps)-1], ps[len(ps)-1]
   121  	}
   122  	return ps, nil
   123  }
   124  
   125  type ArgCtx struct {
   126  	Parent      *[]Arg      // GroupArg.Inner (for structs) or Call.Args containing this arg.
   127  	Fields      []Field     // Fields of the parent struct/syscall.
   128  	Field       *Field      // Syscall field for this arg, nil if there it's not a field.
   129  	Base        *PointerArg // Pointer to the base of the heap object containing this arg.
   130  	Offset      uint64      // Offset of this arg from the base.
   131  	Stop        bool        // If set by the callback, subargs of this arg are not visited.
   132  	parentStack parentStack // Struct and union arguments by which the argument can be reached.
   133  }
   134  
   135  func ForeachSubArg(arg Arg, f func(Arg, *ArgCtx)) {
   136  	foreachArgImpl(arg, nil, &ArgCtx{}, f)
   137  }
   138  
   139  func foreachSubArgWithStack(arg Arg, f func(Arg, *ArgCtx)) {
   140  	foreachArgImpl(arg, nil, &ArgCtx{parentStack: allocStack()}, f)
   141  }
   142  
   143  func ForeachArg(c *Call, f func(Arg, *ArgCtx)) {
   144  	ctx := &ArgCtx{}
   145  	if c.Ret != nil {
   146  		foreachArgImpl(c.Ret, nil, ctx, f)
   147  	}
   148  	ctx.Parent = &c.Args
   149  	ctx.Fields = c.Meta.Args
   150  	for i, arg := range c.Args {
   151  		foreachArgImpl(arg, &ctx.Fields[i], ctx, f)
   152  	}
   153  }
   154  
   155  func foreachArgImpl(arg Arg, field *Field, ctx *ArgCtx, f func(Arg, *ArgCtx)) {
   156  	ctx0 := *ctx
   157  	defer func() { *ctx = ctx0 }()
   158  
   159  	if ctx.parentStack != nil {
   160  		switch arg.Type().(type) {
   161  		case *StructType, *UnionType:
   162  			ctx.parentStack = pushStack(ctx.parentStack, arg)
   163  		}
   164  	}
   165  	ctx.Field = field
   166  	f(arg, ctx)
   167  	if ctx.Stop {
   168  		return
   169  	}
   170  	switch a := arg.(type) {
   171  	case *GroupArg:
   172  		overlayField := 0
   173  		if typ, ok := a.Type().(*StructType); ok {
   174  			ctx.Parent = &a.Inner
   175  			ctx.Fields = typ.Fields
   176  			overlayField = typ.OverlayField
   177  		}
   178  		var totalSize uint64
   179  		for i, arg1 := range a.Inner {
   180  			if i == overlayField {
   181  				ctx.Offset = ctx0.Offset
   182  			}
   183  			foreachArgImpl(arg1, nil, ctx, f)
   184  			size := arg1.Size()
   185  			ctx.Offset += size
   186  			if totalSize < ctx.Offset {
   187  				totalSize = ctx.Offset - ctx0.Offset
   188  			}
   189  		}
   190  		if debug {
   191  			claimedSize := a.Size()
   192  			varlen := a.Type().Varlen()
   193  			if varlen && totalSize > claimedSize || !varlen && totalSize != claimedSize {
   194  				panic(fmt.Sprintf("bad group arg size %v, should be <= %v for %#v type %#v",
   195  					totalSize, claimedSize, a, a.Type().Name()))
   196  			}
   197  		}
   198  	case *PointerArg:
   199  		if a.Res != nil {
   200  			ctx.Base = a
   201  			ctx.Offset = 0
   202  			foreachArgImpl(a.Res, nil, ctx, f)
   203  		}
   204  	case *UnionArg:
   205  		foreachArgImpl(a.Option, nil, ctx, f)
   206  	}
   207  }
   208  
   209  type RequiredFeatures struct {
   210  	Bitmasks       bool
   211  	Csums          bool
   212  	FaultInjection bool
   213  	Async          bool
   214  }
   215  
   216  func (p *Prog) RequiredFeatures() RequiredFeatures {
   217  	features := RequiredFeatures{}
   218  	for _, c := range p.Calls {
   219  		ForeachArg(c, func(arg Arg, _ *ArgCtx) {
   220  			if a, ok := arg.(*ConstArg); ok {
   221  				if a.Type().BitfieldOffset() != 0 || a.Type().BitfieldLength() != 0 {
   222  					features.Bitmasks = true
   223  				}
   224  			}
   225  			if _, ok := arg.Type().(*CsumType); ok {
   226  				features.Csums = true
   227  			}
   228  		})
   229  		if c.Props.FailNth > 0 {
   230  			features.FaultInjection = true
   231  		}
   232  		if c.Props.Async {
   233  			features.Async = true
   234  		}
   235  	}
   236  	return features
   237  }
   238  
   239  type CallFlags int
   240  
   241  const (
   242  	CallExecuted CallFlags = 1 << iota // was started at all
   243  	CallFinished                       // finished executing (rather than blocked forever)
   244  	CallBlocked                        // finished but blocked during execution
   245  )
   246  
   247  type CallInfo struct {
   248  	Flags  CallFlags
   249  	Errno  int
   250  	Signal []uint64
   251  }
   252  
   253  const (
   254  	fallbackSignalErrno = iota
   255  	fallbackSignalErrnoBlocked
   256  	fallbackSignalCtor
   257  	fallbackSignalFlags
   258  	// This allows us to have 2M syscalls and leaves 8 bits for 256 errno values.
   259  	// Linux currently have 133 errno's. Larger errno values will be truncated,
   260  	// which is acceptable for fallback coverage.
   261  	fallbackCallMask = 0x1fffff
   262  )
   263  
   264  func (p *Prog) FallbackSignal(info []CallInfo) {
   265  	resources := make(map[*ResultArg]*Call)
   266  	for i, c := range p.Calls {
   267  		inf := &info[i]
   268  		if inf.Flags&CallExecuted == 0 {
   269  			continue
   270  		}
   271  		id := c.Meta.ID
   272  		typ := fallbackSignalErrno
   273  		if inf.Flags&CallFinished != 0 && inf.Flags&CallBlocked != 0 {
   274  			typ = fallbackSignalErrnoBlocked
   275  		}
   276  		inf.Signal = append(inf.Signal, encodeFallbackSignal(typ, id, inf.Errno))
   277  		if c.Meta.Attrs.BreaksReturns {
   278  			break
   279  		}
   280  		if inf.Errno != 0 {
   281  			continue
   282  		}
   283  		ForeachArg(c, func(arg Arg, _ *ArgCtx) {
   284  			if a, ok := arg.(*ResultArg); ok {
   285  				resources[a] = c
   286  			}
   287  		})
   288  		// Specifically look only at top-level arguments,
   289  		// deeper arguments can produce too much false signal.
   290  		flags := 0
   291  		for _, arg := range c.Args {
   292  			flags = extractArgSignal(arg, id, flags, inf, resources)
   293  		}
   294  		if flags != 0 {
   295  			inf.Signal = append(inf.Signal,
   296  				encodeFallbackSignal(fallbackSignalFlags, id, flags))
   297  		}
   298  	}
   299  }
   300  
   301  func extractArgSignal(arg Arg, callID, flags int, inf *CallInfo, resources map[*ResultArg]*Call) int {
   302  	switch a := arg.(type) {
   303  	case *ResultArg:
   304  		flags <<= 1
   305  		if a.Res != nil {
   306  			ctor := resources[a.Res]
   307  			if ctor != nil {
   308  				inf.Signal = append(inf.Signal,
   309  					encodeFallbackSignal(fallbackSignalCtor, callID, ctor.Meta.ID))
   310  			}
   311  		} else {
   312  			if a.Val != a.Type().(*ResourceType).SpecialValues()[0] {
   313  				flags |= 1
   314  			}
   315  		}
   316  	case *ConstArg:
   317  		const width = 3
   318  		flags <<= width
   319  		switch typ := a.Type().(type) {
   320  		case *FlagsType:
   321  			if typ.BitMask {
   322  				for i, v := range typ.Vals {
   323  					if a.Val&v != 0 {
   324  						flags ^= 1 << (uint(i) % width)
   325  					}
   326  				}
   327  			} else {
   328  				for i, v := range typ.Vals {
   329  					if a.Val == v {
   330  						flags |= i % (1 << width)
   331  						break
   332  					}
   333  				}
   334  			}
   335  		case *LenType:
   336  			flags <<= 1
   337  			if a.Val == 0 {
   338  				flags |= 1
   339  			}
   340  		}
   341  	case *PointerArg:
   342  		flags <<= 1
   343  		if a.IsSpecial() {
   344  			flags |= 1
   345  		}
   346  	}
   347  	return flags
   348  }
   349  
   350  func DecodeFallbackSignal(s uint64) (callID, errno int) {
   351  	typ, id, aux := decodeFallbackSignal(s)
   352  	switch typ {
   353  	case fallbackSignalErrno, fallbackSignalErrnoBlocked:
   354  		return id, aux
   355  	case fallbackSignalCtor, fallbackSignalFlags:
   356  		return id, 0
   357  	default:
   358  		panic(fmt.Sprintf("bad fallback signal type %v", typ))
   359  	}
   360  }
   361  
   362  func encodeFallbackSignal(typ, id, aux int) uint64 {
   363  	checkMaxCallID(id)
   364  	if typ & ^7 != 0 {
   365  		panic(fmt.Sprintf("bad fallback signal type %v", typ))
   366  	}
   367  	return uint64(typ) | uint64(id&fallbackCallMask)<<3 | uint64(aux)<<24
   368  }
   369  
   370  func decodeFallbackSignal(s uint64) (typ, id, aux int) {
   371  	return int(s & 7), int((s >> 3) & fallbackCallMask), int(s >> 24)
   372  }
   373  
   374  func checkMaxCallID(id int) {
   375  	if id & ^fallbackCallMask != 0 {
   376  		panic(fmt.Sprintf("too many syscalls, have %v, max supported %v", id, fallbackCallMask+1))
   377  	}
   378  }
   379  
   380  type AssetType int
   381  
   382  const (
   383  	MountInRepro AssetType = iota
   384  )
   385  
   386  func (p *Prog) ForEachAsset(cb func(name string, typ AssetType, r io.Reader, c *Call)) {
   387  	for id, c := range p.Calls {
   388  		ForeachArg(c, func(arg Arg, _ *ArgCtx) {
   389  			a, ok := arg.(*DataArg)
   390  			if !ok || a.Type().(*BufferType).Kind != BufferCompressed {
   391  				return
   392  			}
   393  			data, dtor := image.MustDecompress(a.Data())
   394  			defer dtor()
   395  			if len(data) == 0 {
   396  				return
   397  			}
   398  			cb(fmt.Sprintf("mount_%v", id), MountInRepro, bytes.NewReader(data), c)
   399  		})
   400  	}
   401  }
   402  
   403  func (p *Prog) ContainsAny() bool {
   404  	for _, c := range p.Calls {
   405  		if p.Target.CallContainsAny(c) {
   406  			return true
   407  		}
   408  	}
   409  	return false
   410  }