Skip to content

Commit b34b265

Browse files
committed
pkg/fuzzer, pkg/managerconfig, prog, syz-manager: logic to enforce syscall dependencies
Syzkaller often breaks dependencies across syscalls (e.g., due minimization, stochastic resource generation, and mutation) when generating programs, thus failing to build fuzzing inputs that exercise deep states in the target program. This patch adds the logic to check whether syscall dependencies are broken in a given program. Everytime a new program is to be generated, we set a flag called EnforceDeps to true with a certain probability. If this flag is set, we enforce that the dependencies of each syscall used in the program is respected.
1 parent c4a9548 commit b34b265

File tree

13 files changed

+293
-60
lines changed

13 files changed

+293
-60
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ Tudor Ambarus
5454
Elektrobit Automotive GmbH
5555
Rivos Inc.
5656
Jeongjun Park
57+
Qualcomm, Inc

CONTRIBUTORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,5 @@ Rivos Inc.
141141
Jeongjun Park
142142
Nikita Zhandarovich
143143
Jiacheng Xu
144+
Qualcomm, Inc
145+
Nilo Redini

pkg/fuzzer/fuzzer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func (fuzzer *Fuzzer) genFuzz() *queue.Request {
284284
if req == nil {
285285
req = genProgRequest(fuzzer, rnd)
286286
}
287-
if fuzzer.Config.Collide && rnd.Intn(3) == 0 {
287+
if !req.Prog.EnforceDeps && fuzzer.Config.Collide && rnd.Intn(3) == 0 {
288288
req = &queue.Request{
289289
Prog: randomCollide(req.Prog, rnd),
290290
Stat: fuzzer.statExecCollide,

pkg/fuzzer/job.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ func (ji *JobInfo) ID() string {
4444
func genProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *queue.Request {
4545
p := fuzzer.target.Generate(rnd,
4646
prog.RecommendedCalls,
47-
fuzzer.ChoiceTable())
47+
fuzzer.ChoiceTable(),
48+
)
4849
return &queue.Request{
4950
Prog: p,
5051
ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal),
@@ -64,6 +65,9 @@ func mutateProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *queue.Request {
6465
fuzzer.Config.NoMutateCalls,
6566
fuzzer.Config.Corpus.Programs(),
6667
)
68+
if !newP.ValidateDeps() {
69+
return nil
70+
}
6771
return &queue.Request{
6872
Prog: newP,
6973
ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal),
@@ -456,6 +460,9 @@ func (job *smashJob) run(fuzzer *Fuzzer) {
456460
fuzzer.ChoiceTable(),
457461
fuzzer.Config.NoMutateCalls,
458462
fuzzer.Config.Corpus.Programs())
463+
if !p.ValidateDeps() {
464+
continue
465+
}
459466
result := fuzzer.execute(job.exec, &queue.Request{
460467
Prog: p,
461468
ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal),

prog/analysis.go

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,95 @@ import (
1212
"bytes"
1313
"fmt"
1414
"io"
15+
"strings"
1516

1617
"github.com/google/syzkaller/pkg/image"
1718
)
1819

1920
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
21+
target *Target
22+
ct *ChoiceTable
23+
corpus []*Prog
24+
files map[string]bool
25+
resources map[string][]*ResultArg
26+
strings map[string]bool
27+
ma *memAlloc
28+
va *vmaAlloc
29+
syscallStack []string
30+
}
31+
32+
// pushFieldToStack pushes a new argument field onto the syscall stack,
33+
// next to the syscall it belongs to, so we can keep track of the
34+
// generated fields and prevent infinite recursion while generating a
35+
// new call with enforced dependencies.
36+
func pushFieldToStack(s *state, fName string) {
37+
if len(s.syscallStack) > 0 {
38+
index := len(s.syscallStack) - 1
39+
s.syscallStack[index] = s.syscallStack[index] + "-" + fName
40+
}
41+
}
42+
43+
// popFieldFromStack pops the latest argument field from the syscall stack.
44+
func popFieldFromStack(s *state) {
45+
if len(s.syscallStack) > 0 {
46+
index := len(s.syscallStack) - 1
47+
lastIndex := strings.LastIndex(s.syscallStack[index], "-")
48+
s.syscallStack[index] = s.syscallStack[index][:lastIndex]
49+
}
50+
}
51+
52+
// pushSyscallToStack pushes a syscall name onto the syscall stack.
53+
// This is used to prevent infinite recursion when
54+
// creating new syscall calls for programs with
55+
// unbroken dependencies.
56+
func pushSyscallToStack(s *state, Syscall string) {
57+
s.syscallStack = append(s.syscallStack, Syscall)
58+
}
59+
60+
// popSyscallFromStack pops a syscall name from the syscall stack.
61+
func popSyscallFromStack(s *state) {
62+
if len(s.syscallStack) > 0 {
63+
s.syscallStack = s.syscallStack[:len(s.syscallStack)-1]
64+
}
65+
}
66+
67+
// getSyscallFieldLoopIterations returns the number
68+
// of times we looped through the current syscall and
69+
// set of arguments. This is used to detect infinite recursion
70+
// when generating a new call with enforced dependencies.
71+
func getSyscallFieldLoopIterations(s *state) int {
72+
recCounter := 0
73+
indexLastEl := len(s.syscallStack) - 1
74+
if indexLastEl >= 0 {
75+
for _, v := range s.syscallStack[:indexLastEl] {
76+
if v == s.syscallStack[indexLastEl] {
77+
recCounter++
78+
}
79+
}
80+
}
81+
return recCounter
82+
}
83+
84+
// isSyscallInStack checks whether a given syscall is already on the stack.
85+
func isSyscallInStack(s *state, syscall string) bool {
86+
currentSyscalls := []string{}
87+
for _, v := range s.syscallStack {
88+
parts := strings.Split(v, "-")
89+
currentSyscalls = append(currentSyscalls, parts[0])
90+
}
91+
92+
for _, v := range currentSyscalls {
93+
if v == syscall {
94+
return true
95+
}
96+
}
97+
98+
return false
99+
}
100+
101+
// clearSyscallStack clears the current syscall stack.
102+
func clearSyscallStack(s *state) {
103+
s.syscallStack = []string{}
28104
}
29105

30106
// analyze analyzes the program p up to but not including call c.

prog/clone.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ func (p *Prog) cloneWithMap(newargs map[*ResultArg]*ResultArg) *Prog {
2121
panic("cloning of unsafe programs is not supposed to be done")
2222
}
2323
p1 := &Prog{
24-
Target: p.Target,
25-
Calls: cloneCalls(p.Calls, newargs),
24+
Target: p.Target,
25+
Calls: cloneCalls(p.Calls, newargs),
26+
EnforceDeps: p.EnforceDeps,
2627
}
2728
p1.debugValidate()
2829
return p1

prog/expr.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func makeArgFinder(t *Target, c *Call, unionArg *UnionArg, parents parentStack)
7272
}
7373

7474
func (r *randGen) patchConditionalFields(c *Call, s *state) (extra []*Call, changed bool) {
75-
if r.patchConditionalDepth > 1 {
75+
if r.patchConditionalDepth > 1 && !r.EnforceDeps {
7676
// Some nested patchConditionalFields() calls are fine as we could trigger a resource
7777
// constructor via generateArg(). But since nested createResource() calls are prohibited,
7878
// patchConditionalFields() should never be nested more than 2 times.

prog/generation.go

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,72 @@ import (
77
"math/rand"
88
)
99

10+
func callHasDependents(p *Prog, idx int) bool {
11+
if !p.EnforceDeps {
12+
return false
13+
}
14+
hasDeps := false
15+
c := p.Calls[idx]
16+
ForeachArg(c, func(arg Arg, _ *ArgCtx) {
17+
if a, ok := arg.(*ResultArg); ok {
18+
if len(a.uses) > 0 && (a.Dir() == DirOut || a.Dir() == DirInOut) {
19+
for key, val := range a.uses {
20+
if val && key.ArgCommon.dir == DirIn {
21+
hasDeps = true
22+
return
23+
}
24+
}
25+
}
26+
}
27+
})
28+
return hasDeps
29+
}
30+
31+
func resizeGeneratedCalls(p *Prog, ncalls int, skipCall *Call) int {
32+
idxToRemove := len(p.Calls) - 1
33+
forceRemoval := false
34+
if idxToRemove < 0 {
35+
return 0
36+
}
37+
removed := 0
38+
for len(p.Calls) > ncalls {
39+
if idxToRemove < 0 {
40+
// We tried to keep dependencies, but we have to remove something.
41+
forceRemoval = true
42+
idxToRemove = len(p.Calls) - 1
43+
}
44+
if skipCall != nil && p.Calls[idxToRemove] == skipCall {
45+
idxToRemove--
46+
continue
47+
}
48+
removeCall := true
49+
if !forceRemoval && p.EnforceDeps && callHasDependents(p, idxToRemove) {
50+
removeCall = false
51+
}
52+
if removeCall {
53+
p.RemoveCall(idxToRemove)
54+
removed++
55+
}
56+
57+
idxToRemove--
58+
}
59+
return removed
60+
}
61+
1062
// Generate generates a random program with ncalls calls.
1163
// ct contains a set of allowed syscalls, if nil all syscalls are used.
1264
func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Prog {
65+
r := newRand(target, rs)
1366
p := &Prog{
14-
Target: target,
67+
Target: target,
68+
EnforceDeps: r.nOutOf(7, 10),
1569
}
16-
r := newRand(target, rs)
70+
1771
s := newState(target, ct, nil)
72+
r.EnforceDeps = p.EnforceDeps
73+
1874
for len(p.Calls) < ncalls {
75+
clearSyscallStack(s)
1976
calls := r.generateCall(s, p, len(p.Calls))
2077
for _, c := range calls {
2178
s.analyze(c)
@@ -26,9 +83,7 @@ func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Pro
2683
// resources and overflow ncalls. Remove some of these calls.
2784
// The resources in the last call will be replaced with the default values,
2885
// which is exactly what we want.
29-
for len(p.Calls) > ncalls {
30-
p.RemoveCall(ncalls - 1)
31-
}
86+
resizeGeneratedCalls(p, ncalls, nil)
3287
p.sanitizeFix()
3388
p.debugValidate()
3489
return p

prog/minimization.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func Minimize(p0 *Prog, callIndex0 int, mode MinimizeMode, pred0 func(*Prog, int
6262
p.debugValidate()
6363
id := hash.String(p.Serialize())
6464
if _, ok := dedup[id]; !ok {
65-
dedup[id] = pred0(p, callIndex)
65+
dedup[id] = p.ValidateDeps() && pred0(p, callIndex)
6666
}
6767
return dedup[id]
6868
}

0 commit comments

Comments
 (0)