From 6099a1719c3532818617a2616a3380b080c56554 Mon Sep 17 00:00:00 2001 From: Hrutvik Kanabar Date: Mon, 19 Sep 2022 10:19:25 +0000 Subject: prog: add an attribute for syscalls which should not be generated Create a `no_generate` attribute to be used with syscalls that `syzkaller` should not try to generate from scratch. In other words, `syzkaller` will only use seeds of this call. This will be useful for syscalls which are unlikely to be correctly generated. In particular, prevent these syscalls from being included in the choice table or from being considered as possible resource constructors. Also add a test which will attempt to generate programs with a bias towards `no_generate` syscalls, and flag up any that make it into result programs. Currently there are no `no_generate` syscalls, but the next commit will add some. --- prog/any_test.go | 2 +- prog/prio.go | 43 ++++++++++++++++++++++++++----------------- prog/rand.go | 16 +++++++++++++--- prog/rand_test.go | 31 +++++++++++++++++++++++++++++++ prog/types.go | 3 ++- 5 files changed, 73 insertions(+), 22 deletions(-) (limited to 'prog') diff --git a/prog/any_test.go b/prog/any_test.go index 386ce8443..0e2f5e0e4 100644 --- a/prog/any_test.go +++ b/prog/any_test.go @@ -20,7 +20,7 @@ func TestIsComplexPtr(t *testing.T) { r := newRand(target, rs) compl := make(map[string]bool) for _, meta := range target.Syscalls { - if meta.Attrs.Disabled { + if meta.Attrs.Disabled || meta.Attrs.NoGenerate { continue } for i := 0; i < iters; i++ { diff --git a/prog/prio.go b/prog/prio.go index c89869b95..d0013b75d 100644 --- a/prog/prio.go +++ b/prog/prio.go @@ -180,11 +180,12 @@ func normalizePrio(prios [][]int32) { } // ChooseTable allows to do a weighted choice of a syscall for a given syscall -// based on call-to-call priorities and a set of enabled syscalls. +// based on call-to-call priorities and a set of enabled and generatable syscalls. type ChoiceTable struct { - target *Target - runs [][]int32 - calls []*Syscall + target *Target + runs [][]int32 + calls []*Syscall + noGenerateCalls map[int]bool } func (target *Target) BuildChoiceTable(corpus []*Prog, enabled map[*Syscall]bool) *ChoiceTable { @@ -194,24 +195,28 @@ func (target *Target) BuildChoiceTable(corpus []*Prog, enabled map[*Syscall]bool enabled[c] = true } } + noGenerateCalls := make(map[int]bool) for call := range enabled { if call.Attrs.Disabled { delete(enabled, call) + } else if call.Attrs.NoGenerate { + noGenerateCalls[call.ID] = true + delete(enabled, call) } } - var enabledCalls []*Syscall + var generatableCalls []*Syscall for c := range enabled { - enabledCalls = append(enabledCalls, c) + generatableCalls = append(generatableCalls, c) } - if len(enabledCalls) == 0 { - panic("no syscalls enabled") + if len(generatableCalls) == 0 { + panic("no syscalls enabled and generatable") } - sort.Slice(enabledCalls, func(i, j int) bool { - return enabledCalls[i].ID < enabledCalls[j].ID + sort.Slice(generatableCalls, func(i, j int) bool { + return generatableCalls[i].ID < generatableCalls[j].ID }) for _, p := range corpus { for _, call := range p.Calls { - if !enabled[call.Meta] { + if !enabled[call.Meta] && !noGenerateCalls[call.Meta.ID] { fmt.Printf("corpus contains disabled syscall %v\n", call.Meta.Name) panic("disabled syscall") } @@ -232,10 +237,14 @@ func (target *Target) BuildChoiceTable(corpus []*Prog, enabled map[*Syscall]bool run[i][j] = sum } } - return &ChoiceTable{target, run, enabledCalls} + return &ChoiceTable{target, run, generatableCalls, noGenerateCalls} } func (ct *ChoiceTable) Enabled(call int) bool { + return ct.Generatable(call) || ct.noGenerateCalls[call] +} + +func (ct *ChoiceTable) Generatable(call int) bool { return ct.runs[call] != nil } @@ -243,17 +252,17 @@ func (ct *ChoiceTable) choose(r *rand.Rand, bias int) int { if bias < 0 { bias = ct.calls[r.Intn(len(ct.calls))].ID } - if !ct.Enabled(bias) { - fmt.Printf("bias to disabled syscall %v\n", ct.target.Syscalls[bias].Name) - panic("disabled syscall") + if !ct.Generatable(bias) { + fmt.Printf("bias to disabled or non-generatable syscall %v\n", ct.target.Syscalls[bias].Name) + panic("disabled or non-generatable syscall") } run := ct.runs[bias] x := int32(r.Intn(int(run[len(run)-1])) + 1) res := sort.Search(len(run), func(i int) bool { return run[i] >= x }) - if !ct.Enabled(res) { - panic("selected disabled syscall") + if !ct.Generatable(res) { + panic("selected disabled or non-generatable syscall") } return res } diff --git a/prog/rand.go b/prog/rand.go index 1db5cd07a..2b08998c6 100644 --- a/prog/rand.go +++ b/prog/rand.go @@ -436,7 +436,7 @@ func (r *randGen) createResource(s *state, res *ResourceType, dir Dir) (Arg, []* func (r *randGen) enabledCtors(s *state, kind string) []*Syscall { var metas []*Syscall for _, meta := range r.target.resourceCtors[kind] { - if s.ct.Enabled(meta.ID) { + if s.ct.Generatable(meta.ID) { metas = append(metas, meta) } } @@ -562,7 +562,11 @@ func (r *randGen) generateCall(s *state, p *Prog, insertionPoint int) []*Call { biasCall := -1 if insertionPoint > 0 { // Choosing the base call is based on the insertion point of the new calls sequence. - biasCall = p.Calls[r.Intn(insertionPoint)].Meta.ID + insertionCall := p.Calls[r.Intn(insertionPoint)].Meta + if !insertionCall.Attrs.NoGenerate { + // We must be careful not to bias towards a non-generatable call. + biasCall = insertionCall.ID + } } idx := s.ct.choose(r.Rand, biasCall) meta := r.target.Syscalls[idx] @@ -573,6 +577,9 @@ func (r *randGen) generateParticularCall(s *state, meta *Syscall) (calls []*Call if meta.Attrs.Disabled { panic(fmt.Sprintf("generating disabled call %v", meta.Name)) } + if meta.Attrs.NoGenerate { + panic(fmt.Sprintf("generating no_generate call: %v", meta.Name)) + } c := MakeCall(meta, nil) c.Args, calls = r.generateArgs(s, meta.Args, DirIn) r.target.assignSizesCall(c) @@ -588,7 +595,10 @@ func (target *Target) GenerateAllSyzProg(rs rand.Source) *Prog { s := newState(target, target.DefaultChoiceTable(), nil) handled := make(map[string]bool) for _, meta := range target.Syscalls { - if !strings.HasPrefix(meta.CallName, "syz_") || handled[meta.CallName] || meta.Attrs.Disabled { + if !strings.HasPrefix(meta.CallName, "syz_") || + handled[meta.CallName] || + meta.Attrs.Disabled || + meta.Attrs.NoGenerate { continue } handled[meta.CallName] = true diff --git a/prog/rand_test.go b/prog/rand_test.go index 5d9b53d48..cc663bf8b 100644 --- a/prog/rand_test.go +++ b/prog/rand_test.go @@ -203,3 +203,34 @@ func TestTruncateToBitSize(t *testing.T) { }) } } + +// Checks that a generated program does not contain any "no_generate" syscalls. +func TestNoGenerate(t *testing.T) { + target, rs, iters := initTest(t) + + // Enable all "no_generate" syscalls and ~10% of all others. + enabled := make(map[*Syscall]bool) + rnd := newRand(target, rs) + for i := 0; i < len(target.Syscalls); i++ { + syscall := target.Syscalls[rnd.Intn(len(target.Syscalls))] + if syscall.Attrs.NoGenerate || rnd.oneOf(10) { + enabled[syscall] = true + } + } + c := target.SyscallMap["clock_gettime"] + enabled[c] = true + ct := target.BuildChoiceTable(nil, enabled) + + const tries = 10 + for i := 0; i < tries; i++ { + p := target.Generate(rs, 50, ct) + for it := 0; it < iters/tries; it++ { + p.Mutate(rs, 50, ct, nil) + } + for _, c := range p.Calls { + if c.Meta.Attrs.NoGenerate { + t.Fatalf("program contains a no_generate syscall: %v\n", c.Meta.Name) + } + } + } +} diff --git a/prog/types.go b/prog/types.go index cca6f1d6c..a90c1d562 100644 --- a/prog/types.go +++ b/prog/types.go @@ -29,7 +29,7 @@ type Syscall struct { // pkg/compiler uses this structure to parse descriptions. // syz-sysgen uses this structure to generate code for executor. // -// Only bool's and uint64's are currently supported. +// Only `bool`s and `uint64`s are currently supported. // // See docs/syscall_descriptions_syntax.md for description of individual attributes. type SyscallAttrs struct { @@ -38,6 +38,7 @@ type SyscallAttrs struct { ProgTimeout uint64 IgnoreReturn bool BreaksReturns bool + NoGenerate bool } // MaxArgs is maximum number of syscall arguments. -- cgit mrf-deployment