github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/build/linux.go (about) 1 // Copyright 2017 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 //go:generate ./linux_gen.sh 5 6 package build 7 8 import ( 9 "crypto/sha256" 10 "debug/elf" 11 "encoding/hex" 12 "fmt" 13 "os" 14 "path" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "time" 19 20 "github.com/google/syzkaller/pkg/debugtracer" 21 "github.com/google/syzkaller/pkg/osutil" 22 "github.com/google/syzkaller/sys/targets" 23 ) 24 25 type linux struct{} 26 27 func (linux linux) build(params Params) (ImageDetails, error) { 28 details := ImageDetails{} 29 err := linux.buildKernel(params) 30 // Even if the build fails, autogenerated files would still be present (unless the build is really broken). 31 if err != nil { 32 details.CompilerID, _ = queryLinuxCompiler(params.KernelDir) 33 return details, err 34 } 35 36 details.CompilerID, err = queryLinuxCompiler(params.KernelDir) 37 if err != nil { 38 return details, err 39 } 40 41 kernelPath := filepath.Join(params.KernelDir, filepath.FromSlash(LinuxKernelImage(params.TargetArch))) 42 43 // Copy the kernel image to let it be uploaded to the asset storage. If the asset storage is not enabled, 44 // let the file just stay in the output folder -- it is usually very small compared to vmlinux anyway. 45 if err := osutil.CopyFile(kernelPath, filepath.Join(params.OutputDir, "kernel")); err != nil { 46 return details, err 47 } 48 49 if fileInfo, err := os.Stat(params.UserspaceDir); err == nil && fileInfo.IsDir() { 50 // The old way of assembling the image from userspace dir. 51 // It should be removed once all syzbot instances are switched. 52 if err := linux.createImage(params, kernelPath); err != nil { 53 return details, err 54 } 55 } else if params.VMType == "qemu" { 56 // If UserspaceDir is a file (image) and we use qemu, we just copy image to the output dir assuming 57 // that qemu will use injected kernel boot. In this mode we also assume password/key-less ssh. 58 // The kernel image was already uploaded above. 59 if err := osutil.CopyFile(params.UserspaceDir, filepath.Join(params.OutputDir, "image")); err != nil { 60 return details, err 61 } 62 } else if err := embedLinuxKernel(params, kernelPath); err != nil { 63 return details, err 64 } 65 vmlinux := filepath.Join(params.OutputDir, "obj", "vmlinux") 66 details.Signature, err = elfBinarySignature(vmlinux, params.Tracer) 67 return details, err 68 } 69 70 func (linux linux) buildKernel(params Params) error { 71 configFile := filepath.Join(params.KernelDir, ".config") 72 if err := linux.writeFile(configFile, params.Config); err != nil { 73 return fmt.Errorf("failed to write config file: %w", err) 74 } 75 // One would expect olddefconfig here, but olddefconfig is not present in v3.6 and below. 76 // oldconfig is the same as olddefconfig if stdin is not set. 77 if err := runMake(params, "oldconfig"); err != nil { 78 return err 79 } 80 // Write updated kernel config early, so that it's captured on build failures. 81 outputConfig := filepath.Join(params.OutputDir, "kernel.config") 82 if err := osutil.CopyFile(configFile, outputConfig); err != nil { 83 return err 84 } 85 // Ensure CONFIG_GCC_PLUGIN_RANDSTRUCT doesn't prevent ccache usage. 86 // See /Documentation/kbuild/reproducible-builds.rst. 87 const seed = `const char *randstruct_seed = "e9db0ca5181da2eedb76eba144df7aba4b7f9359040ee58409765f2bdc4cb3b8";` 88 gccPluginsDir := filepath.Join(params.KernelDir, "scripts", "gcc-plugins") 89 if osutil.IsExist(gccPluginsDir) { 90 if err := linux.writeFile(filepath.Join(gccPluginsDir, "randomize_layout_seed.h"), []byte(seed)); err != nil { 91 return err 92 } 93 } 94 95 // Different key is generated for each build if key is not provided. 96 // see Documentation/reproducible-builds.rst. This is causing problems to our signature calculation. 97 certsDir := filepath.Join(params.KernelDir, "certs") 98 if osutil.IsExist(certsDir) { 99 if err := linux.writeFile(filepath.Join(certsDir, "signing_key.pem"), []byte(moduleSigningKey)); err != nil { 100 return err 101 } 102 } 103 target := path.Base(LinuxKernelImage(params.TargetArch)) 104 if err := runMake(params, target); err != nil { 105 return err 106 } 107 vmlinux := filepath.Join(params.KernelDir, "vmlinux") 108 outputVmlinux := filepath.Join(params.OutputDir, "obj", "vmlinux") 109 if err := osutil.Rename(vmlinux, outputVmlinux); err != nil { 110 return fmt.Errorf("failed to rename vmlinux: %w", err) 111 } 112 return nil 113 } 114 115 func (linux) createImage(params Params, kernelPath string) error { 116 tempDir, err := os.MkdirTemp("", "syz-build") 117 if err != nil { 118 return err 119 } 120 defer os.RemoveAll(tempDir) 121 scriptFile := filepath.Join(tempDir, "create.sh") 122 if err := osutil.WriteExecFile(scriptFile, []byte(createImageScript)); err != nil { 123 return fmt.Errorf("failed to write script file: %w", err) 124 } 125 cmd := osutil.Command(scriptFile, params.UserspaceDir, kernelPath, params.TargetArch) 126 cmd.Dir = tempDir 127 cmd.Env = append([]string{}, os.Environ()...) 128 cmd.Env = append(cmd.Env, 129 "SYZ_VM_TYPE="+params.VMType, 130 "SYZ_CMDLINE_FILE="+osutil.Abs(params.CmdlineFile), 131 "SYZ_SYSCTL_FILE="+osutil.Abs(params.SysctlFile), 132 ) 133 if _, err = osutil.Run(time.Hour, cmd); err != nil { 134 return fmt.Errorf("image build failed: %w", err) 135 } 136 // Note: we use CopyFile instead of Rename because src and dst can be on different filesystems. 137 imageFile := filepath.Join(params.OutputDir, "image") 138 if err := osutil.CopyFile(filepath.Join(tempDir, "disk.raw"), imageFile); err != nil { 139 return err 140 } 141 return nil 142 } 143 144 func (linux) clean(kernelDir, targetArch string) error { 145 return runMakeImpl(targetArch, "", "", "", kernelDir, []string{"distclean"}) 146 } 147 148 func (linux) writeFile(file string, data []byte) error { 149 if err := osutil.WriteFile(file, data); err != nil { 150 return err 151 } 152 return osutil.SandboxChown(file) 153 } 154 155 func runMakeImpl(arch, compiler, linker, ccache, kernelDir string, extraArgs []string) error { 156 target := targets.Get(targets.Linux, arch) 157 args := LinuxMakeArgs(target, compiler, linker, ccache, "") 158 args = append(args, extraArgs...) 159 cmd := osutil.Command("make", args...) 160 if err := osutil.Sandbox(cmd, true, true); err != nil { 161 return err 162 } 163 cmd.Dir = kernelDir 164 cmd.Env = append([]string{}, os.Environ()...) 165 // This makes the build [more] deterministic: 166 // 2 builds from the same sources should result in the same vmlinux binary. 167 // Build on a release commit and on the previous one should result in the same vmlinux too. 168 // We use it for detecting no-op changes during bisection. 169 cmd.Env = append(cmd.Env, 170 "KBUILD_BUILD_VERSION=0", 171 "KBUILD_BUILD_TIMESTAMP=now", 172 "KBUILD_BUILD_USER=syzkaller", 173 "KBUILD_BUILD_HOST=syzkaller", 174 "KERNELVERSION=syzkaller", 175 "LOCALVERSION=-syzkaller", 176 ) 177 _, err := osutil.Run(time.Hour, cmd) 178 return err 179 } 180 181 func runMake(params Params, extraArgs ...string) error { 182 return runMakeImpl(params.TargetArch, params.Compiler, params.Linker, params.Ccache, params.KernelDir, extraArgs) 183 } 184 185 func LinuxMakeArgs(target *targets.Target, compiler, linker, ccache, buildDir string) []string { 186 args := []string{ 187 "-j", fmt.Sprint(runtime.NumCPU()), 188 "ARCH=" + target.KernelArch, 189 } 190 if target.Triple != "" { 191 args = append(args, "CROSS_COMPILE="+target.Triple+"-") 192 } 193 if compiler == "" { 194 compiler = target.KernelCompiler 195 if target.KernelLinker != "" { 196 linker = target.KernelLinker 197 } 198 } 199 if compiler != "" { 200 if ccache != "" { 201 compiler = ccache + " " + compiler 202 } 203 args = append(args, "CC="+compiler) 204 } 205 if linker != "" { 206 args = append(args, "LD="+linker) 207 } 208 if buildDir != "" { 209 args = append(args, "O="+buildDir) 210 } 211 return args 212 } 213 214 func LinuxKernelImage(arch string) string { 215 // We build only zImage/bzImage as we currently don't use modules. 216 switch arch { 217 case targets.AMD64: 218 return "arch/x86/boot/bzImage" 219 case targets.I386: 220 return "arch/x86/boot/bzImage" 221 case targets.S390x: 222 return "arch/s390/boot/bzImage" 223 case targets.PPC64LE: 224 return "arch/powerpc/boot/zImage.pseries" 225 case targets.ARM: 226 return "arch/arm/boot/zImage" 227 case targets.ARM64: 228 return "arch/arm64/boot/Image.gz" 229 case targets.RiscV64: 230 return "arch/riscv/boot/Image" 231 case targets.MIPS64LE: 232 return "vmlinux" 233 default: 234 panic(fmt.Sprintf("pkg/build: unsupported arch %v", arch)) 235 } 236 } 237 238 var linuxCompilerRegexp = regexp.MustCompile(`#define\s+LINUX_COMPILER\s+"(.*)"`) 239 240 func queryLinuxCompiler(kernelDir string) (string, error) { 241 bytes, err := os.ReadFile(filepath.Join(kernelDir, "include", "generated", "compile.h")) 242 if err != nil { 243 return "", err 244 } 245 result := linuxCompilerRegexp.FindSubmatch(bytes) 246 if result == nil { 247 return "", fmt.Errorf("include/generated/compile.h does not contain build information") 248 } 249 return string(result[1]), nil 250 } 251 252 // elfBinarySignature calculates signature of an elf binary aiming at runtime behavior 253 // (text/data, debug info is ignored). 254 func elfBinarySignature(bin string, tracer debugtracer.DebugTracer) (string, error) { 255 f, err := os.Open(bin) 256 if err != nil { 257 return "", fmt.Errorf("failed to open binary for signature: %w", err) 258 } 259 ef, err := elf.NewFile(f) 260 if err != nil { 261 return "", fmt.Errorf("failed to open elf binary: %w", err) 262 } 263 hasher := sha256.New() 264 for _, sec := range ef.Sections { 265 // Hash allocated sections (e.g. no debug info as it's not allocated) 266 // with file data (e.g. no bss). We also ignore .notes section as it 267 // contains some small changing binary blob that seems irrelevant. 268 // It's unclear if it's better to check NOTE type, 269 // or ".notes" name or !PROGBITS type. 270 if sec.Flags&elf.SHF_ALLOC == 0 || sec.Type == elf.SHT_NOBITS || sec.Type == elf.SHT_NOTE { 271 continue 272 } 273 data, err := sec.Data() 274 if err != nil { 275 return "", fmt.Errorf("failed to read ELF section %v: %w", sec.Name, err) 276 } 277 hasher1 := sha256.New() 278 hasher1.Write(data) 279 hash := hasher1.Sum(nil) 280 hasher.Write(hash) 281 tracer.Log("section %v: size %v signature %v", sec.Name, len(data), hex.EncodeToString(hash[:8])) 282 tracer.SaveFile(sec.Name, data) 283 } 284 return hex.EncodeToString(hasher.Sum(nil)), nil 285 } 286 287 // moduleSigningKey is a constant module signing key for reproducible builds. 288 const moduleSigningKey = `-----BEGIN PRIVATE KEY----- 289 MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxu5GRXw7d13xTLlZ 290 GT1y63U4Firk3WjXapTgf9radlfzpqheFr5HWO8f11U/euZQWXDzi+Bsq+6s/2lJ 291 AU9XWQIDAQABAkB24ZxTGBv9iMGURUvOvp83wRRkgvvEqUva4N+M6MAXagav3GRi 292 K/gl3htzQVe+PLGDfbIkstPJUvI2izL8ZWmBAiEA/P72IitEYE4NQj4dPcYglEYT 293 Hbh2ydGYFbYxvG19DTECIQDJSvg7NdAaZNd9faE5UIAcLF35k988m9hSqBjtz0tC 294 qQIgGOJC901mJkrHBxLw8ViBb9QMoUm5dVRGLyyCa9QhDqECIQCQGLX4lP5DVrsY 295 X43BnMoI4Q3o8x1Uou/JxAIMg1+J+QIgamNCPBLeP8Ce38HtPcm8BXmhPKkpCXdn 296 uUf4bYtfSSw= 297 -----END PRIVATE KEY----- 298 -----BEGIN CERTIFICATE----- 299 MIIBvzCCAWmgAwIBAgIUKoM7Idv4nw571nWDgYFpw6I29u0wDQYJKoZIhvcNAQEF 300 BQAwLjEsMCoGA1UEAwwjQnVpbGQgdGltZSBhdXRvZ2VuZXJhdGVkIGtlcm5lbCBr 301 ZXkwIBcNMjAxMDA4MTAzMzIwWhgPMjEyMDA5MTQxMDMzMjBaMC4xLDAqBgNVBAMM 302 I0J1aWxkIHRpbWUgYXV0b2dlbmVyYXRlZCBrZXJuZWwga2V5MFwwDQYJKoZIhvcN 303 AQEBBQADSwAwSAJBAMbuRkV8O3dd8Uy5WRk9cut1OBYq5N1o12qU4H/a2nZX86ao 304 Xha+R1jvH9dVP3rmUFlw84vgbKvurP9pSQFPV1kCAwEAAaNdMFswDAYDVR0TAQH/ 305 BAIwADALBgNVHQ8EBAMCB4AwHQYDVR0OBBYEFPhQx4etmYw5auCJwIO5QP8Kmrt3 306 MB8GA1UdIwQYMBaAFPhQx4etmYw5auCJwIO5QP8Kmrt3MA0GCSqGSIb3DQEBBQUA 307 A0EAK5moCH39eLLn98pBzSm3MXrHpLtOWuu2p696fg/ZjiUmRSdHK3yoRONxMHLJ 308 1nL9cAjWPantqCm5eoyhj7V7gg== 309 -----END CERTIFICATE-----`