From 7236594a2c63f3be360ed0a3feb63b4621530e27 Mon Sep 17 00:00:00 2001 From: Paul Chaignon Date: Thu, 5 Oct 2023 12:32:04 +0200 Subject: prog: skip optional input resources If trying to fuzz only bpf$PROG_LOAD, the executors fail with: SYZFATAL: Manager.Check call failed: machine check failed: all system calls are disabled That is happening because it detects a dependency on fd_bpf_map via two paths: 1. bpf_prog_t.fd_array is an optional pointer to an array of fd_bpf_map. 2. The bpf_insn union contains descriptions for two instructions, bpf_insn_map_fd and bpf_insn_map_value, that reference fd_bpf_map. Both of those cases point to optional uses of fd_bpf_map, but syzkaller isn't able to recognize that today. This commit addresses the first case, when a resource or one of the types using it are explicitly marked as optional. Before this commit, syzkaller was only able to recognize the case where the resource itself is marked as optional. However, in the case of e.g. bpf_prog_t.fd_array, it's the pointer to the array of fd_bpf_map that is marked optional. To fix this, we propagate the optional bit when walking down the AST. We then pass this propagated bit to the callback function via the context. This change was tested on the above bpf$PROG_LOAD case 1, by removing bpf_insn_map_fd and bpf_insn_map_value from the bpf(2) description to avoid hitting case 2. Addressing case 2 will require more changes to the same logic. Signed-off-by: Paul Chaignon --- prog/resources.go | 2 +- prog/resources_test.go | 2 +- prog/types.go | 30 +++++++++++++++++------------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/prog/resources.go b/prog/resources.go index 95480a8a0..61b30581f 100644 --- a/prog/resources.go +++ b/prog/resources.go @@ -151,7 +151,7 @@ func (target *Target) getInputResources(c *Syscall) []*ResourceDesc { } switch typ1 := typ.(type) { case *ResourceType: - if !typ1.IsOptional && !dedup[typ1.Desc] { + if !ctx.Optional && !dedup[typ1.Desc] { dedup[typ1.Desc] = true resources = append(resources, typ1.Desc) } diff --git a/prog/resources_test.go b/prog/resources_test.go index e48efa65c..f0ba81adf 100644 --- a/prog/resources_test.go +++ b/prog/resources_test.go @@ -153,7 +153,7 @@ func testCreateResource(t *testing.T, target *Target, calls map[*Syscall]bool, r if res, ok := typ.(*ResourceType); ok && ctx.Dir != DirOut { s := newState(target, ct, nil) arg, calls := r.createResource(s, res, DirIn) - if arg == nil && !res.Optional() { + if arg == nil && !ctx.Optional { t.Fatalf("failed to create resource %v", res.Name()) } if arg != nil && len(calls) == 0 { diff --git a/prog/types.go b/prog/types.go index 4fd6a9daa..45136f079 100644 --- a/prog/types.go +++ b/prog/types.go @@ -680,10 +680,11 @@ type ConstValue struct { } type TypeCtx struct { - Meta *Syscall - Dir Dir - Ptr *Type - Stop bool // If set by the callback, subtypes of this type are not visited. + Meta *Syscall + Dir Dir + Ptr *Type + Optional bool + Stop bool // If set by the callback, subtypes of this type are not visited. } func ForeachType(syscalls []*Syscall, f func(t Type, ctx *TypeCtx)) { @@ -707,9 +708,12 @@ func foreachTypeImpl(meta *Syscall, preorder bool, f func(t Type, ctx *TypeCtx)) // It would prune recursion more (across syscalls), but lots of users need to // visit each struct per-syscall (e.g. prio, used resources). seen := make(map[Type]bool) - var rec func(*Type, Dir) - rec = func(ptr *Type, dir Dir) { - ctx := &TypeCtx{Meta: meta, Dir: dir, Ptr: ptr} + var rec func(*Type, Dir, bool) + rec = func(ptr *Type, dir Dir, optional bool) { + if _, ref := (*ptr).(Ref); !ref { + optional = optional || (*ptr).Optional() + } + ctx := &TypeCtx{Meta: meta, Dir: dir, Ptr: ptr, Optional: optional} if preorder { f(*ptr, ctx) if ctx.Stop { @@ -718,16 +722,16 @@ func foreachTypeImpl(meta *Syscall, preorder bool, f func(t Type, ctx *TypeCtx)) } switch a := (*ptr).(type) { case *PtrType: - rec(&a.Elem, a.ElemDir) + rec(&a.Elem, a.ElemDir, optional) case *ArrayType: - rec(&a.Elem, dir) + rec(&a.Elem, dir, optional) case *StructType: if seen[a] { break // prune recursion via pointers to structs/unions } seen[a] = true for i, f := range a.Fields { - rec(&a.Fields[i].Type, f.Dir(dir)) + rec(&a.Fields[i].Type, f.Dir(dir), optional) } case *UnionType: if seen[a] { @@ -735,7 +739,7 @@ func foreachTypeImpl(meta *Syscall, preorder bool, f func(t Type, ctx *TypeCtx)) } seen[a] = true for i, f := range a.Fields { - rec(&a.Fields[i].Type, f.Dir(dir)) + rec(&a.Fields[i].Type, f.Dir(dir), optional) } case *ResourceType, *BufferType, *VmaType, *LenType, *FlagsType, *ConstType, *IntType, *ProcType, *CsumType: @@ -752,10 +756,10 @@ func foreachTypeImpl(meta *Syscall, preorder bool, f func(t Type, ctx *TypeCtx)) } } for i := range meta.Args { - rec(&meta.Args[i].Type, DirIn) + rec(&meta.Args[i].Type, DirIn, false) } if meta.Ret != nil { - rec(&meta.Ret, DirOut) + rec(&meta.Ret, DirOut, false) } } -- cgit mrf-deployment