Skip to content

Commit 1b5b0f6

Browse files
committed
pkg/fuzzer, pkg/managerconfig, prog, syz-manager: flag 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 addresses this issue by adding a Boolean (called PromoteDeps), which if set, certain measures are taken to enforce that syscall dependencies are respected in any generated program: Mutation does not break dependencies, Resource generation is no longer stochastic, and if a minimized program has broken dependencies, it gets discarded. To this end, I introduced two optional fields in the config file: promote_syscalls_dependency, and dynamic_promote_syscalls_dependency. The former flag is a boolean and when set to true will enable the PromoteDeps flag. The latter field, contains a time expressed in minutes (e.g, 30). Once the manager starts, it sets a timer with the value contained in this flag. Once the timer reaches the 0, the Boolean PromoteDep is switched (i.e., if it contained false it now contains true, and vice versa), and the timer starts again. This switch allows us to introduce more randomness in the generated programs achieving the best of both worlds.
1 parent 803ce19 commit 1b5b0f6

File tree

14 files changed

+360
-78
lines changed

14 files changed

+360
-78
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: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand,
4949
return true
5050
}
5151
}
52+
prog.SetPromoteDeps(cfg.PromoteSyscallsDependency)
5253
f := &Fuzzer{
5354
Stats: newStats(target),
5455
Config: cfg,
@@ -201,19 +202,20 @@ func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags
201202
}
202203

203204
type Config struct {
204-
Debug bool
205-
Corpus *corpus.Corpus
206-
Logf func(level int, msg string, args ...interface{})
207-
Snapshot bool
208-
Coverage bool
209-
FaultInjection bool
210-
Comparisons bool
211-
Collide bool
212-
EnabledCalls map[*prog.Syscall]bool
213-
NoMutateCalls map[int]bool
214-
FetchRawCover bool
215-
NewInputFilter func(call string) bool
216-
PatchTest bool
205+
Debug bool
206+
Corpus *corpus.Corpus
207+
Logf func(level int, msg string, args ...interface{})
208+
Snapshot bool
209+
Coverage bool
210+
FaultInjection bool
211+
Comparisons bool
212+
Collide bool
213+
EnabledCalls map[*prog.Syscall]bool
214+
NoMutateCalls map[int]bool
215+
FetchRawCover bool
216+
NewInputFilter func(call string) bool
217+
PatchTest bool
218+
PromoteSyscallsDependency bool
217219
}
218220

219221
func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call int, triage *map[int]*triageCall) {
@@ -276,6 +278,12 @@ func (fuzzer *Fuzzer) genFuzz() *queue.Request {
276278
// more frequently because fallback signal is weak.
277279
mutateRate = 0.5
278280
}
281+
if prog.IsPromoteDeps() {
282+
// if we want to promote dependencies, we penalize mutation since
283+
// we want to avoid picking and mutating programs with broken
284+
// dependencies
285+
mutateRate = 0.3
286+
}
279287
var req *queue.Request
280288
rnd := fuzzer.rand()
281289
if rnd.Float64() < mutateRate {
@@ -284,7 +292,7 @@ func (fuzzer *Fuzzer) genFuzz() *queue.Request {
284292
if req == nil {
285293
req = genProgRequest(fuzzer, rnd)
286294
}
287-
if fuzzer.Config.Collide && rnd.Intn(3) == 0 {
295+
if !prog.IsPromoteDeps() && fuzzer.Config.Collide && rnd.Intn(3) == 0 {
288296
req = &queue.Request{
289297
Prog: randomCollide(req.Prog, rnd),
290298
Stat: fuzzer.statExecCollide,

pkg/fuzzer/job.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,17 @@ func mutateProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *queue.Request {
5757
if p == nil {
5858
return nil
5959
}
60+
depsValidated := p.ValidateDeps()
6061
newP := p.Clone()
6162
newP.Mutate(rnd,
6263
prog.RecommendedCalls,
6364
fuzzer.ChoiceTable(),
6465
fuzzer.Config.NoMutateCalls,
6566
fuzzer.Config.Corpus.Programs(),
6667
)
68+
if depsValidated && !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),

pkg/mgrconfig/config.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ type Config struct {
167167
// By default it is true, as this is the desired behavior when executing syzkaller
168168
// locally.
169169
PreserveCorpus bool `json:"preserve_corpus"`
170-
170+
// the following fields force the IOCTLs dependencies (expressed through resources)
171+
// to be respected in every generated program
172+
PromoteSyscallsDependency bool `json:"promote_syscalls_dependency,omitempty"`
173+
DependecySwitcheroo int64 `json:"dynamic_promote_syscalls_dependency,omitempty"`
171174
// List of syscalls to test (optional). For example:
172175
// "enable_syscalls": [ "mmap", "openat$ashmem", "ioctl$ASHMEM*" ]
173176
EnabledSyscalls []string `json:"enable_syscalls,omitempty"`

prog/analysis.go

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,79 @@ 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+
ioctlStack []string
30+
}
31+
32+
func pushFieldToStack(s *state, fName string) {
33+
if len(s.ioctlStack) > 0 {
34+
index := len(s.ioctlStack) - 1
35+
s.ioctlStack[index] = s.ioctlStack[index] + "-" + fName
36+
}
37+
}
38+
39+
func popFieldFromStack(s *state) {
40+
if len(s.ioctlStack) > 0 {
41+
index := len(s.ioctlStack) - 1
42+
lastIndex := strings.LastIndex(s.ioctlStack[index], "-")
43+
s.ioctlStack[index] = s.ioctlStack[index][:lastIndex]
44+
}
45+
}
46+
47+
func pushIoctlToStack(s *state, ioctlName string) {
48+
s.ioctlStack = append(s.ioctlStack, ioctlName)
49+
}
50+
51+
func popIoctlFromStack(s *state) {
52+
if len(s.ioctlStack) > 0 {
53+
s.ioctlStack = s.ioctlStack[:len(s.ioctlStack)-1]
54+
}
55+
}
56+
57+
func getIoctlFieldLoopIterations(s *state) int {
58+
recCounter := 0
59+
indexLastEl := len(s.ioctlStack) - 1
60+
if indexLastEl >= 0 {
61+
for _, v := range s.ioctlStack[:indexLastEl] {
62+
if v == s.ioctlStack[indexLastEl] {
63+
recCounter++
64+
}
65+
}
66+
}
67+
return recCounter
68+
}
69+
70+
func isIoctlInStack(s *state, ioctl string) bool {
71+
currentIoctls := []string{}
72+
for _, v := range s.ioctlStack {
73+
parts := strings.Split(v, "-")
74+
currentIoctls = append(currentIoctls, parts[0])
75+
}
76+
77+
for _, v := range currentIoctls {
78+
if v == ioctl {
79+
return true
80+
}
81+
}
82+
83+
return false
84+
}
85+
86+
func clearIoctlStack(s *state) {
87+
s.ioctlStack = []string{}
2888
}
2989

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

prog/expr.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,27 @@ func checkUnionArg(idx int, typ *UnionType, finder ArgFinder) (ok, calculated bo
160160

161161
func matchingUnionArgs(typ *UnionType, finder ArgFinder) []int {
162162
var ret []int
163-
for i := range typ.Fields {
163+
posDef := -1
164+
for i, v := range typ.Fields {
165+
if v.Name == "default" {
166+
posDef = i
167+
}
164168
ok, _ := checkUnionArg(i, typ, finder)
165169
if ok {
166170
ret = append(ret, i)
167171
}
168172
}
173+
174+
if len(ret) > 1 && posDef != -1 && IsPromoteDeps() {
175+
// if we are to promote deps, we remove the default value, if other values
176+
// are present
177+
for i, v := range ret {
178+
if v == posDef {
179+
ret = append(ret[:i], ret[i+1:]...)
180+
break
181+
}
182+
}
183+
}
169184
return ret
170185
}
171186

prog/generation.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,39 @@ import (
77
"math/rand"
88
)
99

10+
func resizeGeneratedCalls(p *Prog, ncalls int, skipCall *Call) {
11+
idxToRemove := len(p.Calls) - 1
12+
forceRemoval := false
13+
if idxToRemove < 0 {
14+
return
15+
}
16+
17+
for len(p.Calls) > ncalls {
18+
if idxToRemove < 0 {
19+
// We tried to keep dependencies, but we have to remove something.
20+
forceRemoval = true
21+
idxToRemove = len(p.Calls) - 1
22+
}
23+
if skipCall != nil && p.Calls[idxToRemove] == skipCall {
24+
idxToRemove--
25+
continue
26+
}
27+
removeCall := true
28+
if IsPromoteDeps() && !forceRemoval {
29+
p1 := p.Clone()
30+
p1.RemoveCall(idxToRemove)
31+
if !p1.ValidateDeps() {
32+
removeCall = false
33+
}
34+
}
35+
if removeCall {
36+
p.RemoveCall(idxToRemove) // it used to be (ncalls-1), but it would remove random dependencies
37+
}
38+
39+
idxToRemove--
40+
}
41+
}
42+
1043
// Generate generates a random program with ncalls calls.
1144
// ct contains a set of allowed syscalls, if nil all syscalls are used.
1245
func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Prog {
@@ -16,6 +49,7 @@ func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Pro
1649
r := newRand(target, rs)
1750
s := newState(target, ct, nil)
1851
for len(p.Calls) < ncalls {
52+
clearIoctlStack(s)
1953
calls := r.generateCall(s, p, len(p.Calls))
2054
for _, c := range calls {
2155
s.analyze(c)
@@ -26,9 +60,13 @@ func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Pro
2660
// resources and overflow ncalls. Remove some of these calls.
2761
// The resources in the last call will be replaced with the default values,
2862
// which is exactly what we want.
29-
for len(p.Calls) > ncalls {
30-
p.RemoveCall(ncalls - 1)
63+
allowCalls := ncalls
64+
if IsPromoteDeps() {
65+
// if we promote depenendencies, and the program is within limits,
66+
// we'll allow it as is
67+
allowCalls = max(MaxCalls, ncalls)
3168
}
69+
resizeGeneratedCalls(p, allowCalls, nil)
3270
p.sanitizeFix()
3371
p.debugValidate()
3472
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)