github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/prog/target.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 "math/rand" 9 "sort" 10 "strings" 11 "sync" 12 "sync/atomic" 13 ) 14 15 // Target describes target OS/arch pair. 16 type Target struct { 17 OS string 18 Arch string 19 Revision string // unique hash representing revision of the descriptions 20 PtrSize uint64 21 PageSize uint64 22 NumPages uint64 23 DataOffset uint64 24 LittleEndian bool 25 ExecutorUsesShmem bool 26 27 Syscalls []*Syscall 28 Resources []*ResourceDesc 29 Consts []ConstValue 30 Flags []FlagDesc 31 32 // MakeDataMmap creates calls that mmaps target data memory range. 33 MakeDataMmap func() []*Call 34 35 // Neutralize neutralizes harmful calls by transforming them into non-harmful ones 36 // (e.g. an ioctl that turns off console output is turned into ioctl that turns on output). 37 // fixStructure determines whether it's allowed to make structural changes (e.g. add or 38 // remove arguments). It is helpful e.g. when we do neutralization while iterating over the 39 // arguments. 40 Neutralize func(c *Call, fixStructure bool) error 41 42 // AnnotateCall annotates a syscall invocation in C reproducers. 43 // The returned string will be placed inside a comment except for the 44 // empty string which will omit the comment. 45 AnnotateCall func(c ExecCall) string 46 47 // SpecialTypes allows target to do custom generation/mutation for some struct's and union's. 48 // Map key is struct/union name for which custom generation/mutation is required. 49 // Map value is custom generation/mutation function that will be called 50 // for the corresponding type. g is helper object that allows generate random numbers, 51 // allocate memory, etc. typ is the struct/union type. old is the old value of the struct/union 52 // for mutation, or nil for generation. The function returns a new value of the struct/union, 53 // and optionally any calls that need to be inserted before the arg reference. 54 SpecialTypes map[string]func(g *Gen, typ Type, dir Dir, old Arg) (Arg, []*Call) 55 56 // Resources that play auxiliary role, but widely used throughout all syscalls (e.g. pid/uid). 57 AuxResources map[string]bool 58 59 // Additional special invalid pointer values besides NULL to use. 60 SpecialPointers []uint64 61 62 // Special file name length that can provoke bugs (e.g. PATH_MAX). 63 SpecialFileLenghts []int 64 65 // Filled by prog package: 66 SyscallMap map[string]*Syscall 67 ConstMap map[string]uint64 68 FlagsMap map[string][]string 69 70 init sync.Once 71 initArch func(target *Target) 72 types []Type 73 resourceMap map[string]*ResourceDesc 74 // Maps resource name to a list of calls that can create the resource. 75 resourceCtors map[string][]ResourceCtor 76 any anyTypes 77 78 // The default ChoiceTable is used only by tests and utilities, so we initialize it lazily. 79 defaultOnce sync.Once 80 defaultChoiceTable *ChoiceTable 81 } 82 83 const maxSpecialPointers = 16 84 85 var targets = make(map[string]*Target) 86 87 func RegisterTarget(target *Target, types []Type, initArch func(target *Target)) { 88 key := target.OS + "/" + target.Arch 89 if targets[key] != nil { 90 panic(fmt.Sprintf("duplicate target %v", key)) 91 } 92 target.initArch = initArch 93 target.types = types 94 targets[key] = target 95 } 96 97 func GetTarget(OS, arch string) (*Target, error) { 98 if OS == "android" { 99 OS = "linux" 100 } 101 key := OS + "/" + arch 102 target := targets[key] 103 if target == nil { 104 var supported []string 105 for _, t := range targets { 106 supported = append(supported, fmt.Sprintf("%v/%v", t.OS, t.Arch)) 107 } 108 sort.Strings(supported) 109 return nil, fmt.Errorf("unknown target: %v (supported: %v)", key, supported) 110 } 111 target.init.Do(target.lazyInit) 112 return target, nil 113 } 114 115 func AllTargets() []*Target { 116 var res []*Target 117 for _, target := range targets { 118 target.init.Do(target.lazyInit) 119 res = append(res, target) 120 } 121 sort.Slice(res, func(i, j int) bool { 122 if res[i].OS != res[j].OS { 123 return res[i].OS < res[j].OS 124 } 125 return res[i].Arch < res[j].Arch 126 }) 127 return res 128 } 129 130 func (target *Target) lazyInit() { 131 target.Neutralize = func(c *Call, fixStructure bool) error { return nil } 132 target.AnnotateCall = func(c ExecCall) string { return "" } 133 target.initTarget() 134 target.initArch(target) 135 // Give these 2 known addresses fixed positions and prepend target-specific ones at the end. 136 target.SpecialPointers = append([]uint64{ 137 0x0000000000000000, // NULL pointer (keep this first because code uses special index=0 as NULL) 138 0xffffffffffffffff, // unmapped kernel address (keep second because serialized value will match actual pointer value) 139 0x9999999999999999, // non-canonical address 140 }, target.SpecialPointers...) 141 if len(target.SpecialPointers) > maxSpecialPointers { 142 panic("too many special pointers") 143 } 144 if len(target.SpecialFileLenghts) == 0 { 145 // Just some common lengths that can be used as PATH_MAX/MAX_NAME. 146 target.SpecialFileLenghts = []int{256, 512, 4096} 147 } 148 for _, ln := range target.SpecialFileLenghts { 149 if ln <= 0 || ln >= memAllocMaxMem { 150 panic(fmt.Sprintf("bad special file length %v", ln)) 151 } 152 } 153 // These are used only during lazyInit. 154 target.types = nil 155 } 156 157 func (target *Target) initTarget() { 158 checkMaxCallID(len(target.Syscalls) - 1) 159 target.ConstMap = make(map[string]uint64) 160 for _, c := range target.Consts { 161 target.ConstMap[c.Name] = c.Value 162 } 163 164 target.resourceMap = restoreLinks(target.Syscalls, target.Resources, target.types) 165 target.initAnyTypes() 166 167 target.SyscallMap = make(map[string]*Syscall) 168 for i, c := range target.Syscalls { 169 c.ID = i 170 target.SyscallMap[c.Name] = c 171 } 172 173 target.FlagsMap = make(map[string][]string) 174 for _, c := range target.Flags { 175 target.FlagsMap[c.Name] = c.Values 176 } 177 178 target.populateResourceCtors() 179 target.resourceCtors = make(map[string][]ResourceCtor) 180 for _, res := range target.Resources { 181 target.resourceCtors[res.Name] = target.calcResourceCtors(res, false) 182 } 183 } 184 185 func (target *Target) GetConst(name string) uint64 { 186 v, ok := target.ConstMap[name] 187 if !ok { 188 panic(fmt.Sprintf("const %v is not defined for %v/%v", name, target.OS, target.Arch)) 189 } 190 return v 191 } 192 193 func (target *Target) sanitize(c *Call, fix bool) error { 194 // For now, even though we accept the fix argument, it does not have the full effect. 195 // It de facto only denies structural changes, e.g. deletions of arguments. 196 // TODO: rewrite the corresponding sys/*/init.go code. 197 return target.Neutralize(c, fix) 198 } 199 200 func RestoreLinks(syscalls []*Syscall, resources []*ResourceDesc, types []Type) { 201 restoreLinks(syscalls, resources, types) 202 } 203 204 var ( 205 typeRefMu sync.Mutex 206 typeRefs atomic.Value // []Type 207 ) 208 209 func restoreLinks(syscalls []*Syscall, resources []*ResourceDesc, types []Type) map[string]*ResourceDesc { 210 typeRefMu.Lock() 211 defer typeRefMu.Unlock() 212 refs := []Type{nil} 213 if old := typeRefs.Load(); old != nil { 214 refs = old.([]Type) 215 } 216 for _, typ := range types { 217 typ.setRef(Ref(len(refs))) 218 refs = append(refs, typ) 219 } 220 typeRefs.Store(refs) 221 222 resourceMap := make(map[string]*ResourceDesc) 223 for _, res := range resources { 224 resourceMap[res.Name] = res 225 } 226 227 ForeachType(syscalls, func(typ Type, ctx *TypeCtx) { 228 if ref, ok := typ.(Ref); ok { 229 typ = types[ref] 230 *ctx.Ptr = typ 231 } 232 switch t := typ.(type) { 233 case *ResourceType: 234 t.Desc = resourceMap[t.TypeName] 235 if t.Desc == nil { 236 panic("no resource desc") 237 } 238 } 239 }) 240 return resourceMap 241 } 242 243 func (target *Target) DefaultChoiceTable() *ChoiceTable { 244 target.defaultOnce.Do(func() { 245 target.defaultChoiceTable = target.BuildChoiceTable(nil, nil) 246 }) 247 return target.defaultChoiceTable 248 } 249 250 func (target *Target) RequiredGlobs() []string { 251 globs := make(map[string]bool) 252 ForeachType(target.Syscalls, func(typ Type, ctx *TypeCtx) { 253 switch a := typ.(type) { 254 case *BufferType: 255 if a.Kind == BufferGlob { 256 for _, glob := range requiredGlobs(a.SubKind) { 257 globs[glob] = true 258 } 259 } 260 } 261 }) 262 return stringMapToSlice(globs) 263 } 264 265 func (target *Target) UpdateGlobs(globFiles map[string][]string) { 266 // TODO: make host.DetectSupportedSyscalls below filter out globs with no values. 267 // Also make prog package more strict with respect to generation/mutation of globs 268 // with no values (they still can appear in tests and tools). We probably should 269 // generate an empty string for these and never mutate. 270 ForeachType(target.Syscalls, func(typ Type, ctx *TypeCtx) { 271 switch a := typ.(type) { 272 case *BufferType: 273 if a.Kind == BufferGlob { 274 a.Values = populateGlob(a.SubKind, globFiles) 275 } 276 } 277 }) 278 } 279 280 func requiredGlobs(pattern string) []string { 281 var res []string 282 for _, tok := range strings.Split(pattern, ":") { 283 if tok[0] != '-' { 284 res = append(res, tok) 285 } 286 } 287 return res 288 } 289 290 func populateGlob(pattern string, globFiles map[string][]string) []string { 291 files := make(map[string]bool) 292 parts := strings.Split(pattern, ":") 293 for _, tok := range parts { 294 if tok[0] != '-' { 295 for _, file := range globFiles[tok] { 296 files[file] = true 297 } 298 } 299 } 300 for _, tok := range parts { 301 if tok[0] == '-' { 302 delete(files, tok[1:]) 303 } 304 } 305 return stringMapToSlice(files) 306 } 307 308 func stringMapToSlice(m map[string]bool) []string { 309 var res []string 310 for k := range m { 311 res = append(res, k) 312 } 313 sort.Strings(res) 314 return res 315 } 316 317 type Gen struct { 318 r *randGen 319 s *state 320 } 321 322 func (g *Gen) Target() *Target { 323 return g.r.target 324 } 325 326 func (g *Gen) Rand() *rand.Rand { 327 return g.r.Rand 328 } 329 330 func (g *Gen) NOutOf(n, outOf int) bool { 331 return g.r.nOutOf(n, outOf) 332 } 333 334 func (g *Gen) Alloc(ptrType Type, dir Dir, data Arg) (Arg, []*Call) { 335 return g.r.allocAddr(g.s, ptrType, dir, data.Size(), data), nil 336 } 337 338 func (g *Gen) GenerateArg(typ Type, dir Dir, pcalls *[]*Call) Arg { 339 return g.generateArg(typ, dir, pcalls, false) 340 } 341 342 func (g *Gen) GenerateSpecialArg(typ Type, dir Dir, pcalls *[]*Call) Arg { 343 return g.generateArg(typ, dir, pcalls, true) 344 } 345 346 func (g *Gen) generateArg(typ Type, dir Dir, pcalls *[]*Call, ignoreSpecial bool) Arg { 347 arg, calls := g.r.generateArgImpl(g.s, typ, dir, ignoreSpecial) 348 *pcalls = append(*pcalls, calls...) 349 g.r.target.assignSizesArray([]Arg{arg}, []Field{{Name: "", Type: arg.Type()}}, nil) 350 return arg 351 } 352 353 func (g *Gen) MutateArg(arg0 Arg) (calls []*Call) { 354 updateSizes := true 355 for stop := false; !stop; stop = g.r.oneOf(3) { 356 ma := &mutationArgs{target: g.r.target, ignoreSpecial: true} 357 ForeachSubArg(arg0, ma.collectArg) 358 if len(ma.args) == 0 { 359 // TODO(dvyukov): probably need to return this condition 360 // and updateSizes to caller so that Mutate can act accordingly. 361 return 362 } 363 arg, ctx := ma.chooseArg(g.r.Rand) 364 newCalls, ok := g.r.target.mutateArg(g.r, g.s, arg, ctx, &updateSizes) 365 if !ok { 366 continue 367 } 368 calls = append(calls, newCalls...) 369 } 370 return calls 371 } 372 373 type Builder struct { 374 target *Target 375 ma *memAlloc 376 p *Prog 377 } 378 379 func MakeProgGen(target *Target) *Builder { 380 return &Builder{ 381 target: target, 382 ma: newMemAlloc(target.NumPages * target.PageSize), 383 p: &Prog{ 384 Target: target, 385 }, 386 } 387 } 388 389 func (pg *Builder) Append(c *Call) error { 390 pg.target.assignSizesCall(c) 391 pg.target.sanitize(c, true) 392 pg.p.Calls = append(pg.p.Calls, c) 393 return nil 394 } 395 396 func (pg *Builder) Allocate(size, alignment uint64) uint64 { 397 return pg.ma.alloc(nil, size, alignment) 398 } 399 400 func (pg *Builder) AllocateVMA(npages uint64) uint64 { 401 return pg.ma.alloc(nil, npages*pg.target.PageSize, pg.target.PageSize) 402 } 403 404 func (pg *Builder) Finalize() (*Prog, error) { 405 if err := pg.p.validate(); err != nil { 406 return nil, err 407 } 408 if _, err := pg.p.SerializeForExec(); err != nil { 409 return nil, err 410 } 411 p := pg.p 412 pg.p = nil 413 return p, nil 414 }