/
linux.go
309 lines (286 loc) · 10.9 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
// Copyright 2017 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.
//go:generate ./linux_gen.sh
package build
import (
"crypto/sha256"
"debug/elf"
"encoding/hex"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"time"
"github.com/google/syzkaller/pkg/debugtracer"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/sys/targets"
)
type linux struct{}
func (linux linux) build(params Params) (ImageDetails, error) {
details := ImageDetails{}
err := linux.buildKernel(params)
// Even if the build fails, autogenerated files would still be present (unless the build is really broken).
if err != nil {
details.CompilerID, _ = queryLinuxCompiler(params.KernelDir)
return details, err
}
details.CompilerID, err = queryLinuxCompiler(params.KernelDir)
if err != nil {
return details, err
}
kernelPath := filepath.Join(params.KernelDir, filepath.FromSlash(LinuxKernelImage(params.TargetArch)))
// Copy the kernel image to let it be uploaded to the asset storage. If the asset storage is not enabled,
// let the file just stay in the output folder -- it is usually very small compared to vmlinux anyway.
if err := osutil.CopyFile(kernelPath, filepath.Join(params.OutputDir, "kernel")); err != nil {
return details, err
}
if fileInfo, err := os.Stat(params.UserspaceDir); err == nil && fileInfo.IsDir() {
// The old way of assembling the image from userspace dir.
// It should be removed once all syzbot instances are switched.
if err := linux.createImage(params, kernelPath); err != nil {
return details, err
}
} else if params.VMType == "qemu" {
// If UserspaceDir is a file (image) and we use qemu, we just copy image to the output dir assuming
// that qemu will use injected kernel boot. In this mode we also assume password/key-less ssh.
// The kernel image was already uploaded above.
if err := osutil.CopyFile(params.UserspaceDir, filepath.Join(params.OutputDir, "image")); err != nil {
return details, err
}
} else if err := embedLinuxKernel(params, kernelPath); err != nil {
return details, err
}
vmlinux := filepath.Join(params.OutputDir, "obj", "vmlinux")
details.Signature, err = elfBinarySignature(vmlinux, params.Tracer)
return details, err
}
func (linux linux) buildKernel(params Params) error {
configFile := filepath.Join(params.KernelDir, ".config")
if err := linux.writeFile(configFile, params.Config); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
// One would expect olddefconfig here, but olddefconfig is not present in v3.6 and below.
// oldconfig is the same as olddefconfig if stdin is not set.
if err := runMake(params, "oldconfig"); err != nil {
return err
}
// Write updated kernel config early, so that it's captured on build failures.
outputConfig := filepath.Join(params.OutputDir, "kernel.config")
if err := osutil.CopyFile(configFile, outputConfig); err != nil {
return err
}
// Ensure CONFIG_GCC_PLUGIN_RANDSTRUCT doesn't prevent ccache usage.
// See /Documentation/kbuild/reproducible-builds.rst.
const seed = `const char *randstruct_seed = "e9db0ca5181da2eedb76eba144df7aba4b7f9359040ee58409765f2bdc4cb3b8";`
gccPluginsDir := filepath.Join(params.KernelDir, "scripts", "gcc-plugins")
if osutil.IsExist(gccPluginsDir) {
if err := linux.writeFile(filepath.Join(gccPluginsDir, "randomize_layout_seed.h"), []byte(seed)); err != nil {
return err
}
}
// Different key is generated for each build if key is not provided.
// see Documentation/reproducible-builds.rst. This is causing problems to our signature calculation.
certsDir := filepath.Join(params.KernelDir, "certs")
if osutil.IsExist(certsDir) {
if err := linux.writeFile(filepath.Join(certsDir, "signing_key.pem"), []byte(moduleSigningKey)); err != nil {
return err
}
}
target := path.Base(LinuxKernelImage(params.TargetArch))
if err := runMake(params, target); err != nil {
return err
}
vmlinux := filepath.Join(params.KernelDir, "vmlinux")
outputVmlinux := filepath.Join(params.OutputDir, "obj", "vmlinux")
if err := osutil.Rename(vmlinux, outputVmlinux); err != nil {
return fmt.Errorf("failed to rename vmlinux: %w", err)
}
return nil
}
func (linux) createImage(params Params, kernelPath string) error {
tempDir, err := os.MkdirTemp("", "syz-build")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
scriptFile := filepath.Join(tempDir, "create.sh")
if err := osutil.WriteExecFile(scriptFile, []byte(createImageScript)); err != nil {
return fmt.Errorf("failed to write script file: %w", err)
}
cmd := osutil.Command(scriptFile, params.UserspaceDir, kernelPath, params.TargetArch)
cmd.Dir = tempDir
cmd.Env = append([]string{}, os.Environ()...)
cmd.Env = append(cmd.Env,
"SYZ_VM_TYPE="+params.VMType,
"SYZ_CMDLINE_FILE="+osutil.Abs(params.CmdlineFile),
"SYZ_SYSCTL_FILE="+osutil.Abs(params.SysctlFile),
)
if _, err = osutil.Run(time.Hour, cmd); err != nil {
return fmt.Errorf("image build failed: %w", err)
}
// Note: we use CopyFile instead of Rename because src and dst can be on different filesystems.
imageFile := filepath.Join(params.OutputDir, "image")
if err := osutil.CopyFile(filepath.Join(tempDir, "disk.raw"), imageFile); err != nil {
return err
}
return nil
}
func (linux) clean(kernelDir, targetArch string) error {
return runMakeImpl(targetArch, "", "", "", kernelDir, []string{"distclean"})
}
func (linux) writeFile(file string, data []byte) error {
if err := osutil.WriteFile(file, data); err != nil {
return err
}
return osutil.SandboxChown(file)
}
func runMakeImpl(arch, compiler, linker, ccache, kernelDir string, extraArgs []string) error {
target := targets.Get(targets.Linux, arch)
args := LinuxMakeArgs(target, compiler, linker, ccache, "")
args = append(args, extraArgs...)
cmd := osutil.Command("make", args...)
if err := osutil.Sandbox(cmd, true, true); err != nil {
return err
}
cmd.Dir = kernelDir
cmd.Env = append([]string{}, os.Environ()...)
// This makes the build [more] deterministic:
// 2 builds from the same sources should result in the same vmlinux binary.
// Build on a release commit and on the previous one should result in the same vmlinux too.
// We use it for detecting no-op changes during bisection.
cmd.Env = append(cmd.Env,
"KBUILD_BUILD_VERSION=0",
"KBUILD_BUILD_TIMESTAMP=now",
"KBUILD_BUILD_USER=syzkaller",
"KBUILD_BUILD_HOST=syzkaller",
"KERNELVERSION=syzkaller",
"LOCALVERSION=-syzkaller",
)
_, err := osutil.Run(time.Hour, cmd)
return err
}
func runMake(params Params, extraArgs ...string) error {
return runMakeImpl(params.TargetArch, params.Compiler, params.Linker, params.Ccache, params.KernelDir, extraArgs)
}
func LinuxMakeArgs(target *targets.Target, compiler, linker, ccache, buildDir string) []string {
args := []string{
"-j", fmt.Sprint(runtime.NumCPU()),
"ARCH=" + target.KernelArch,
}
if target.Triple != "" {
args = append(args, "CROSS_COMPILE="+target.Triple+"-")
}
if compiler == "" {
compiler = target.KernelCompiler
if target.KernelLinker != "" {
linker = target.KernelLinker
}
}
if compiler != "" {
if ccache != "" {
compiler = ccache + " " + compiler
}
args = append(args, "CC="+compiler)
}
if linker != "" {
args = append(args, "LD="+linker)
}
if buildDir != "" {
args = append(args, "O="+buildDir)
}
return args
}
func LinuxKernelImage(arch string) string {
// We build only zImage/bzImage as we currently don't use modules.
switch arch {
case targets.AMD64:
return "arch/x86/boot/bzImage"
case targets.I386:
return "arch/x86/boot/bzImage"
case targets.S390x:
return "arch/s390/boot/bzImage"
case targets.PPC64LE:
return "arch/powerpc/boot/zImage.pseries"
case targets.ARM:
return "arch/arm/boot/zImage"
case targets.ARM64:
return "arch/arm64/boot/Image.gz"
case targets.RiscV64:
return "arch/riscv/boot/Image"
case targets.MIPS64LE:
return "vmlinux"
default:
panic(fmt.Sprintf("pkg/build: unsupported arch %v", arch))
}
}
var linuxCompilerRegexp = regexp.MustCompile(`#define\s+LINUX_COMPILER\s+"(.*)"`)
func queryLinuxCompiler(kernelDir string) (string, error) {
bytes, err := os.ReadFile(filepath.Join(kernelDir, "include", "generated", "compile.h"))
if err != nil {
return "", err
}
result := linuxCompilerRegexp.FindSubmatch(bytes)
if result == nil {
return "", fmt.Errorf("include/generated/compile.h does not contain build information")
}
return string(result[1]), nil
}
// elfBinarySignature calculates signature of an elf binary aiming at runtime behavior
// (text/data, debug info is ignored).
func elfBinarySignature(bin string, tracer debugtracer.DebugTracer) (string, error) {
f, err := os.Open(bin)
if err != nil {
return "", fmt.Errorf("failed to open binary for signature: %w", err)
}
ef, err := elf.NewFile(f)
if err != nil {
return "", fmt.Errorf("failed to open elf binary: %w", err)
}
hasher := sha256.New()
for _, sec := range ef.Sections {
// Hash allocated sections (e.g. no debug info as it's not allocated)
// with file data (e.g. no bss). We also ignore .notes section as it
// contains some small changing binary blob that seems irrelevant.
// It's unclear if it's better to check NOTE type,
// or ".notes" name or !PROGBITS type.
if sec.Flags&elf.SHF_ALLOC == 0 || sec.Type == elf.SHT_NOBITS || sec.Type == elf.SHT_NOTE {
continue
}
data, err := sec.Data()
if err != nil {
return "", fmt.Errorf("failed to read ELF section %v: %w", sec.Name, err)
}
hasher1 := sha256.New()
hasher1.Write(data)
hash := hasher1.Sum(nil)
hasher.Write(hash)
tracer.Log("section %v: size %v signature %v", sec.Name, len(data), hex.EncodeToString(hash[:8]))
tracer.SaveFile(sec.Name, data)
}
return hex.EncodeToString(hasher.Sum(nil)), nil
}
// moduleSigningKey is a constant module signing key for reproducible builds.
const moduleSigningKey = `-----BEGIN PRIVATE KEY-----
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxu5GRXw7d13xTLlZ
GT1y63U4Firk3WjXapTgf9radlfzpqheFr5HWO8f11U/euZQWXDzi+Bsq+6s/2lJ
AU9XWQIDAQABAkB24ZxTGBv9iMGURUvOvp83wRRkgvvEqUva4N+M6MAXagav3GRi
K/gl3htzQVe+PLGDfbIkstPJUvI2izL8ZWmBAiEA/P72IitEYE4NQj4dPcYglEYT
Hbh2ydGYFbYxvG19DTECIQDJSvg7NdAaZNd9faE5UIAcLF35k988m9hSqBjtz0tC
qQIgGOJC901mJkrHBxLw8ViBb9QMoUm5dVRGLyyCa9QhDqECIQCQGLX4lP5DVrsY
X43BnMoI4Q3o8x1Uou/JxAIMg1+J+QIgamNCPBLeP8Ce38HtPcm8BXmhPKkpCXdn
uUf4bYtfSSw=
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIBvzCCAWmgAwIBAgIUKoM7Idv4nw571nWDgYFpw6I29u0wDQYJKoZIhvcNAQEF
BQAwLjEsMCoGA1UEAwwjQnVpbGQgdGltZSBhdXRvZ2VuZXJhdGVkIGtlcm5lbCBr
ZXkwIBcNMjAxMDA4MTAzMzIwWhgPMjEyMDA5MTQxMDMzMjBaMC4xLDAqBgNVBAMM
I0J1aWxkIHRpbWUgYXV0b2dlbmVyYXRlZCBrZXJuZWwga2V5MFwwDQYJKoZIhvcN
AQEBBQADSwAwSAJBAMbuRkV8O3dd8Uy5WRk9cut1OBYq5N1o12qU4H/a2nZX86ao
Xha+R1jvH9dVP3rmUFlw84vgbKvurP9pSQFPV1kCAwEAAaNdMFswDAYDVR0TAQH/
BAIwADALBgNVHQ8EBAMCB4AwHQYDVR0OBBYEFPhQx4etmYw5auCJwIO5QP8Kmrt3
MB8GA1UdIwQYMBaAFPhQx4etmYw5auCJwIO5QP8Kmrt3MA0GCSqGSIb3DQEBBQUA
A0EAK5moCH39eLLn98pBzSm3MXrHpLtOWuu2p696fg/ZjiUmRSdHK3yoRONxMHLJ
1nL9cAjWPantqCm5eoyhj7V7gg==
-----END CERTIFICATE-----`