github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/prog/resources.go (about)

     1  // Copyright 2017 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  package prog
     5  
     6  import (
     7  	"fmt"
     8  )
     9  
    10  var (
    11  	// We need to support structs as resources,
    12  	// but for now we just special-case timespec/timeval.
    13  	timespecRes = &ResourceDesc{
    14  		Name: "timespec",
    15  		Kind: []string{"timespec"},
    16  	}
    17  	// On one hand these are resources, but they don't have constructors.
    18  	// It can make sense to provide generic support for such things,
    19  	// but for now we just special-case them.
    20  	filenameRes = &ResourceDesc{
    21  		Name: "filename",
    22  		Kind: []string{"filename"},
    23  	}
    24  	vmaRes = &ResourceDesc{
    25  		Name: "vma",
    26  		Kind: []string{"vma"},
    27  	}
    28  )
    29  
    30  func (target *Target) calcResourceCtors(res *ResourceDesc, preciseOnly bool) []ResourceCtor {
    31  	var ret []ResourceCtor
    32  	for _, ctor := range res.Ctors {
    33  		if !preciseOnly || ctor.Precise {
    34  			ret = append(ret, ctor)
    35  		}
    36  	}
    37  	if res.Kind[0] == timespecRes.Name {
    38  		if c := target.SyscallMap["clock_gettime"]; c != nil {
    39  			ret = append(ret, ResourceCtor{c, true})
    40  		}
    41  	}
    42  	return ret
    43  }
    44  
    45  func (target *Target) populateResourceCtors() {
    46  	// Find resources that are created by each call.
    47  	callsResources := make([][]*ResourceDesc, len(target.Syscalls))
    48  	for _, meta := range target.Syscalls {
    49  		dedup := make(map[*ResourceDesc]bool)
    50  		ForeachCallType(meta, func(typ Type, ctx *TypeCtx) {
    51  			if typ.Optional() {
    52  				ctx.Stop = true
    53  				return
    54  			}
    55  			switch typ1 := typ.(type) {
    56  			case *UnionType:
    57  				ctx.Stop = true
    58  			case *ResourceType:
    59  				if ctx.Dir == DirIn || dedup[typ1.Desc] || meta.Attrs.Disabled {
    60  					break
    61  				}
    62  				dedup[typ1.Desc] = true
    63  				meta.usesResources = append(meta.usesResources, typ1.Desc)
    64  				if !meta.Attrs.NoGenerate {
    65  					callsResources[meta.ID] = append(callsResources[meta.ID], typ1.Desc)
    66  					meta.createsResources = append(meta.createsResources, typ1.Desc)
    67  				}
    68  			}
    69  		})
    70  	}
    71  
    72  	if c := target.SyscallMap["clock_gettime"]; c != nil {
    73  		c.usesResources = append(c.usesResources, timespecRes)
    74  		c.createsResources = append(c.createsResources, timespecRes)
    75  		callsResources[c.ID] = append(callsResources[c.ID], timespecRes)
    76  	}
    77  
    78  	for _, c := range target.Syscalls {
    79  		c.inputResources = target.getInputResources(c)
    80  		c.usesResources = append(c.usesResources, c.inputResources...)
    81  	}
    82  
    83  	// Populate resource ctors accounting for resource compatibility.
    84  	for _, res := range target.Resources {
    85  		for call, callResources := range callsResources {
    86  			preciseOk := false
    87  			impreciseOk := false
    88  			for _, callRes := range callResources {
    89  				if preciseOk && impreciseOk {
    90  					break
    91  				}
    92  				if isCompatibleResourceImpl(res.Kind, callRes.Kind, true) {
    93  					preciseOk = true
    94  				}
    95  				if isCompatibleResourceImpl(res.Kind, callRes.Kind, false) {
    96  					impreciseOk = true
    97  				}
    98  			}
    99  			if preciseOk {
   100  				res.Ctors = append(res.Ctors, ResourceCtor{target.Syscalls[call], true})
   101  			}
   102  			if impreciseOk {
   103  				res.Ctors = append(res.Ctors, ResourceCtor{target.Syscalls[call], false})
   104  			}
   105  		}
   106  	}
   107  }
   108  
   109  // isCompatibleResource returns true if resource of kind src can be passed as an argument of kind dst.
   110  func (target *Target) isCompatibleResource(dst, src string) bool {
   111  	if target.isAnyRes(dst) {
   112  		return true
   113  	}
   114  	if target.isAnyRes(src) {
   115  		return false
   116  	}
   117  	dstRes := target.resourceMap[dst]
   118  	if dstRes == nil {
   119  		panic(fmt.Sprintf("unknown resource %q", dst))
   120  	}
   121  	srcRes := target.resourceMap[src]
   122  	if srcRes == nil {
   123  		panic(fmt.Sprintf("unknown resource %q", src))
   124  	}
   125  	return isCompatibleResourceImpl(dstRes.Kind, srcRes.Kind, false)
   126  }
   127  
   128  // isCompatibleResourceImpl returns true if resource of kind src can be passed as an argument of kind dst.
   129  // If precise is true, then it does not allow passing a less specialized resource (e.g. fd)
   130  // as a more specialized resource (e.g. socket). Otherwise it does.
   131  func isCompatibleResourceImpl(dst, src []string, precise bool) bool {
   132  	if len(dst) > len(src) {
   133  		// Destination resource is more specialized, e.g dst=socket, src=fd.
   134  		if precise {
   135  			return false
   136  		}
   137  		dst = dst[:len(src)]
   138  	}
   139  	if len(src) > len(dst) {
   140  		// Source resource is more specialized, e.g dst=fd, src=socket.
   141  		src = src[:len(dst)]
   142  	}
   143  	for i, k := range dst {
   144  		if k != src[i] {
   145  			return false
   146  		}
   147  	}
   148  	return true
   149  }
   150  
   151  func (target *Target) getInputResources(c *Syscall) []*ResourceDesc {
   152  	dedup := make(map[*ResourceDesc]bool)
   153  	var resources []*ResourceDesc
   154  	ForeachCallType(c, func(typ Type, ctx *TypeCtx) {
   155  		if ctx.Dir == DirOut {
   156  			return
   157  		}
   158  		switch typ1 := typ.(type) {
   159  		case *ResourceType:
   160  			if !ctx.Optional && !dedup[typ1.Desc] {
   161  				dedup[typ1.Desc] = true
   162  				resources = append(resources, typ1.Desc)
   163  			}
   164  		case *StructType:
   165  			if target.OS == "linux" && !dedup[timespecRes] && (typ1.Name() == "timespec" || typ1.Name() == "timeval") {
   166  				dedup[timespecRes] = true
   167  				resources = append(resources, timespecRes)
   168  			}
   169  		}
   170  	})
   171  	return resources
   172  }
   173  
   174  func (target *Target) transitivelyEnabled(enabled map[*Syscall]bool) (map[*Syscall]bool, map[string]bool) {
   175  	supported := make(map[*Syscall]bool, len(enabled))
   176  	canCreate := make(map[string]bool, len(enabled))
   177  	for {
   178  		n := len(supported)
   179  	nextCall:
   180  		for c := range enabled {
   181  			if supported[c] {
   182  				continue
   183  			}
   184  			for _, res := range c.inputResources {
   185  				if !canCreate[res.Name] {
   186  					continue nextCall
   187  				}
   188  			}
   189  			supported[c] = true
   190  			for _, res := range c.createsResources {
   191  				for _, kind := range res.Kind {
   192  					canCreate[kind] = true
   193  				}
   194  			}
   195  		}
   196  		if n == len(supported) {
   197  			break
   198  		}
   199  	}
   200  	return supported, canCreate
   201  }
   202  
   203  func (target *Target) TransitivelyEnabledCalls(enabled map[*Syscall]bool) (map[*Syscall]bool, map[*Syscall]string) {
   204  	supported, canCreate := target.transitivelyEnabled(enabled)
   205  	disabled := make(map[*Syscall]string)
   206  	ctors := make(map[string][]string)
   207  	for c := range enabled {
   208  		if supported[c] {
   209  			continue
   210  		}
   211  		for _, res := range c.inputResources {
   212  			if canCreate[res.Name] {
   213  				continue
   214  			}
   215  			if ctors[res.Name] == nil {
   216  				var names []string
   217  				for _, ctor := range target.calcResourceCtors(res, true) {
   218  					names = append(names, ctor.Call.Name)
   219  				}
   220  				if len(names) > 5 {
   221  					names = append(names[:3], "...")
   222  				}
   223  				ctors[res.Name] = names
   224  			}
   225  			disabled[c] = fmt.Sprintf("%v %v", res.Name, ctors[res.Name])
   226  			break
   227  		}
   228  	}
   229  	if len(enabled) != len(supported)+len(disabled) {
   230  		panic("lost syscalls")
   231  	}
   232  	return supported, disabled
   233  }