forked from google/syzkaller
-
Notifications
You must be signed in to change notification settings - Fork 0
/
linux.go
407 lines (376 loc) · 12.1 KB
/
linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// Copyright 2019 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package vcs
import (
"bytes"
"errors"
"fmt"
"net/mail"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"github.com/google/syzkaller/pkg/debugtracer"
"github.com/google/syzkaller/pkg/kconfig"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/report/crash"
"github.com/google/syzkaller/prog"
"github.com/google/syzkaller/sys/targets"
)
type linux struct {
*git
vmType string
}
var (
_ Bisecter = new(linux)
_ ConfigMinimizer = new(linux)
)
func newLinux(dir string, opts []RepoOpt, vmType string) *linux {
ignoreCC := map[string]bool{
"[email protected]": true,
}
return &linux{
git: newGit(dir, ignoreCC, opts),
vmType: vmType,
}
}
func (ctx *linux) PreviousReleaseTags(commit, compilerType string) ([]string, error) {
tags, err := ctx.git.previousReleaseTags(commit, false, false, false)
if err != nil {
return nil, err
}
cutoff := ""
if compilerType == "gcc" {
// Initially we tried to stop at 3.8 because:
// v3.8 does not work with modern perl, and as we go further in history
// make stops to work, then binutils, glibc, etc. So we stop at v3.8.
// Up to that point we only need an ancient gcc.
//
// But kernels don't boot starting from 4.0 and back.
// That was fixed by 99124e4db5b7b70daeaaf1d88a6a8078a0004c6e,
// and it can be cherry-picked into 3.14..4.0 but it conflicts for 3.13 and older.
//
// But starting from 4.0 our user-space binaries start crashing with
// assorted errors which suggests process memory corruption by kernel.
//
// We used to use 4.1 as the oldest tested release (it works in general).
// However, there is correlation between how far back we go and probability
// of getting correct result (see #1532). So we then stopped at 4.6.
// 4.6 is somewhat arbitrary, we've seen lots of wrong results in 4.5..4.6 range,
// but there is definitive reason for 4.6. Most likely later we want to bump it
// even more (as new releases are produced). Next good candidate may be 4.11
// because then we won't need gcc 5.5.
//
// TODO: The buildroot images deployed after #2820 can only boot v4.19+ kernels.
// This has caused lots of bad bisection results, see #3224. We either need a new
// universal image or a kernel version dependant image selection.
cutoff = "v4.18"
} else if compilerType == "clang" {
// v5.3 was the first release with solid clang support, however I was able to
// compile v5.1..v5.3 using a newer defconfig + make oldconfig. Everything older
// would require further cherry-picks.
cutoff = "v5.2"
}
for i, tag := range tags {
if tag == cutoff {
tags = tags[:i]
break
}
}
return tags, nil
}
func gitParseReleaseTags(output []byte, includeRC bool) []string {
var tags []string
for _, tag := range bytes.Split(output, []byte{'\n'}) {
if gitReleaseTagToInt(string(tag), includeRC) != 0 {
tags = append(tags, string(tag))
}
}
sort.Slice(tags, func(i, j int) bool {
return gitReleaseTagToInt(tags[i], includeRC) > gitReleaseTagToInt(tags[j], includeRC)
})
return tags
}
func gitReleaseTagToInt(tag string, includeRC bool) uint64 {
v1, v2, rc, v3 := ParseReleaseTag(tag)
if v1 < 0 {
return 0
}
if v3 < 0 {
v3 = 0
}
if rc >= 0 {
if !includeRC {
return 0
}
} else {
rc = 999
}
return uint64(v1)*1e9 + uint64(v2)*1e6 + uint64(rc)*1e3 + uint64(v3)
}
func (ctx *linux) EnvForCommit(
defaultCompiler, compilerType, binDir, commit string, kernelConfig []byte,
backports []BackportCommit,
) (*BisectEnv, error) {
tagList, err := ctx.previousReleaseTags(commit, true, false, false)
if err != nil {
return nil, err
}
tags := make(map[string]bool)
for _, tag := range tagList {
tags[tag] = true
}
cf, err := kconfig.ParseConfigData(kernelConfig, "config")
if err != nil {
return nil, err
}
setLinuxTagConfigs(cf, tags)
compiler := ""
if compilerType == "gcc" {
compiler = linuxGCCPath(tags, binDir, defaultCompiler)
} else if compilerType == "clang" {
compiler = linuxClangPath(tags, binDir, defaultCompiler)
} else {
return nil, fmt.Errorf("unsupported bisect compiler: %v", compilerType)
}
env := &BisectEnv{
Compiler: compiler,
KernelConfig: cf.Serialize(),
}
err = linuxFixBackports(ctx.git, backports...)
if err != nil {
return nil, fmt.Errorf("failed to cherry pick fixes: %w", err)
}
return env, nil
}
func linuxClangPath(tags map[string]bool, binDir, defaultCompiler string) string {
version := ""
switch {
case tags["v5.9"]:
// Verified to work with 14.0.6.
return defaultCompiler
default:
// everything before v5.3 might not work great
// everything before v5.1 does not work
version = "9.0.1"
}
return filepath.Join(binDir, "llvm-"+version, "bin", "clang")
}
func linuxGCCPath(tags map[string]bool, binDir, defaultCompiler string) string {
version := ""
switch {
case tags["v5.16"]:
// Verified to work with 15.0.7.
return defaultCompiler
case tags["v5.9"]:
version = "10.1.0"
case tags["v4.12"]:
version = "8.1.0"
case tags["v4.11"]:
version = "7.3.0"
default:
version = "5.5.0"
}
return filepath.Join(binDir, "gcc-"+version, "bin", "gcc")
}
func (ctx *linux) PrepareBisect() error {
if ctx.vmType != targets.GVisor {
// Some linux repos we fuzz don't import the upstream release git tags. We need tags
// to decide which compiler versions to use. Let's fetch upstream for its tags.
err := ctx.git.fetchRemote("https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git", "")
if err != nil {
return fmt.Errorf("fetching upstream linux failed: %w", err)
}
}
return nil
}
func (ctx *linux) Bisect(bad, good string, dt debugtracer.DebugTracer, pred func() (BisectResult,
error)) ([]*Commit, error) {
commits, err := ctx.git.Bisect(bad, good, dt, pred)
if len(commits) == 1 {
ctx.addMaintainers(commits[0])
}
return commits, err
}
func (ctx *linux) addMaintainers(com *Commit) {
if len(com.Recipients) > 2 {
return
}
mtrs := ctx.getMaintainers(com.Hash, false)
if len(mtrs) < 3 {
mtrs = ctx.getMaintainers(com.Hash, true)
}
com.Recipients = append(com.Recipients, mtrs...)
sort.Sort(com.Recipients)
}
func (ctx *linux) getMaintainers(hash string, blame bool) Recipients {
// See #1441 re --git-min-percent.
args := "git show " + hash + " | " +
filepath.FromSlash("scripts/get_maintainer.pl") +
" --git-min-percent=20"
if blame {
args += " --git-blame"
}
output, err := osutil.RunCmd(time.Minute, ctx.git.dir, "bash", "-c", args)
if err != nil {
return nil
}
return ParseMaintainersLinux(output)
}
func ParseMaintainersLinux(text []byte) Recipients {
lines := strings.Split(string(text), "\n")
reRole := regexp.MustCompile(` \([^)]+\)$`)
var mtrs Recipients
// LMKL is To by default, but it changes to Cc if there's also a subsystem list.
lkmlType := To
foundLkml := false
for _, line := range lines {
role := reRole.FindString(line)
address := strings.Replace(line, role, "", 1)
addr, err := mail.ParseAddress(address)
if err != nil {
continue
}
var roleType RecipientType
if addr.Address == "[email protected]" {
foundLkml = true
continue
} else if strings.Contains(role, "list") {
lkmlType = Cc
roleType = To
} else if strings.Contains(role, "maintainer") || strings.Contains(role, "supporter") {
roleType = To
} else {
roleType = Cc // Reviewer or other role; default to Cc.
}
mtrs = append(mtrs, RecipientInfo{*addr, roleType})
}
if foundLkml {
mtrs = append(mtrs, RecipientInfo{mail.Address{Address: "[email protected]"}, lkmlType})
}
sort.Sort(mtrs)
return mtrs
}
var ErrBadKconfig = errors.New("failed to parse Kconfig")
const configBisectTag = "# Minimized by syzkaller"
// Minimize() attempts to drop Linux kernel configs that are unnecessary(*) for bug reproduction.
// 1. Remove sanitizers that are not needed to trigger the target class of bugs.
// 2. Disable unrelated kernel subsystems. This is done by bisecting config changes between
// `original` and `baseline`.
// (*) After an unnecessary config is deleted, we still have pred() == BisectBad.
func (ctx *linux) Minimize(target *targets.Target, original, baseline []byte, types []crash.Type,
dt debugtracer.DebugTracer, pred func(test []byte) (BisectResult, error)) ([]byte, error) {
if bytes.HasPrefix(original, []byte(configBisectTag)) {
dt.Log("# configuration already minimized\n")
return original, nil
}
kconf, err := kconfig.Parse(target, filepath.Join(ctx.git.dir, "Kconfig"))
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrBadKconfig, err)
}
config, err := kconfig.ParseConfigData(original, "original")
if err != nil {
return nil, err
}
minimizeCtx := &minimizeLinuxCtx{
kconf: kconf,
config: config,
pred: func(cfg *kconfig.ConfigFile) (bool, error) {
res, err := pred(serialize(cfg))
return res == BisectBad, err
},
transform: func(cfg *kconfig.ConfigFile) {
setLinuxTagConfigs(cfg, nil)
},
DebugTracer: dt,
}
if len(types) > 0 {
// Technically, as almost all sanitizers are Yes/No config options, we could have
// achieved this minimization simply by disabling them all in the baseline config.
// However, we are now trying to make the most out of the few config minimization
// iterations we're ready to make do during the bisection process.
// Since it's possible to quite reliably determine the needed and unneeded sanitizers
// just by looking at crash reports, let's prefer a more complicated logic over worse
// bisection results.
// Once we start doing proper config minimizations for every reproducer, we can delete
// most of the related code.
err := minimizeCtx.dropInstrumentation(types)
if err != nil {
return nil, err
}
}
if len(baseline) > 0 {
baselineConfig, err := kconfig.ParseConfigData(baseline, "baseline")
// If we fail to parse the baseline config proceed with original one as baseline config
// is an optional parameter.
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrBadKconfig, err)
}
err = minimizeCtx.minimizeAgainst(baselineConfig)
if err != nil {
return nil, err
}
}
return minimizeCtx.getConfig(), nil
}
func serialize(cf *kconfig.ConfigFile) []byte {
return []byte(fmt.Sprintf("%v, rev: %v\n%s", configBisectTag, prog.GitRevision, cf.Serialize()))
}
type minimizeLinuxCtx struct {
kconf *kconfig.KConfig
config *kconfig.ConfigFile
pred func(*kconfig.ConfigFile) (bool, error)
transform func(*kconfig.ConfigFile)
debugtracer.DebugTracer
}
func (ctx *minimizeLinuxCtx) minimizeAgainst(base *kconfig.ConfigFile) error {
base = base.Clone()
ctx.transform(base)
// Don't do too many minimization runs, it will make bug bisections too long.
// The purpose is only to reduce the number of build/boot/test errors due to bugs
// in unrelated parts of the kernel.
// Bisection is not getting much faster with smaller configs, only more reliable,
// so there's a trade-off. Try to do best in 5 iterations, that's about 1.5 hours.
const minimizeRuns = 5
minConfig, err := ctx.kconf.Minimize(base, ctx.config, ctx.runPred, minimizeRuns, ctx)
if err != nil {
return err
}
ctx.config = minConfig
return nil
}
func (ctx *minimizeLinuxCtx) dropInstrumentation(types []crash.Type) error {
ctx.Log("check whether we can drop unnecessary instrumentation")
oldTransform := ctx.transform
transform := func(c *kconfig.ConfigFile) {
oldTransform(c)
setLinuxSanitizerConfigs(c, types, ctx)
}
newConfig := ctx.config.Clone()
transform(newConfig)
if bytes.Equal(ctx.config.Serialize(), newConfig.Serialize()) {
ctx.Log("there was nothing we could disable; skip")
return nil
}
ctx.SaveFile("no-instrumentation.config", newConfig.Serialize())
ok, err := ctx.runPred(newConfig)
if err != nil {
return err
}
if ok {
ctx.Log("the bug reproduces without the instrumentation")
ctx.transform = transform
ctx.config = newConfig
}
return nil
}
func (ctx *minimizeLinuxCtx) runPred(cfg *kconfig.ConfigFile) (bool, error) {
cfg = cfg.Clone()
ctx.transform(cfg)
return ctx.pred(cfg)
}
func (ctx *minimizeLinuxCtx) getConfig() []byte {
ctx.transform(ctx.config)
return serialize(ctx.config)
}