// 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" "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(params Params) error { return runMake(params, "distclean") } func (linux) writeFile(file string, data []byte) error { if err := osutil.WriteFile(file, data); err != nil { return err } return osutil.SandboxChown(file) } func runMake(params Params, extraArgs ...string) error { target := targets.Get(targets.Linux, params.TargetArch) args := LinuxMakeArgs(target, params.Compiler, params.Linker, params.Ccache, "", params.BuildCPUs) args = append(args, extraArgs...) makeBin := params.Make if makeBin == "" { makeBin = "make" } cmd := osutil.Command(makeBin, args...) if err := osutil.Sandbox(cmd, true, true); err != nil { return err } cmd.Dir = params.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", ) output, err := osutil.Run(time.Hour, cmd) params.Tracer.Log("Build log:\n%s", output) return err } func LinuxMakeArgs(target *targets.Target, compiler, linker, ccache, buildDir string, jobs int) []string { args := []string{ // Make still overrides these if they are passed as env variables. // Let's pass them directly as make arguments. "KERNELVERSION=syzkaller", "KERNELRELEASE=syzkaller", "LOCALVERSION=-syzkaller", "-j", fmt.Sprint(jobs), "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 } } // The standard way to build Linux with clang is to pass LLVM=1 instead of CC= and LD=. if compiler == targets.DefaultLLVMCompiler && (linker == "" || linker == targets.DefaultLLVMLinker) { args = append(args, "LLVM=1") } else { if 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 } type SectionHashes struct { Text map[string]string `json:"text"` Data map[string]string `json:"data"` // Merged .data and .rodata. } // ElfSymbolHashes returns a map of sha256 hashes per section per symbol contained in the elf file. // It's best to call it on vmlinux.o since PCs in the binary code are not patched yet. func ElfSymbolHashes(bin string) (SectionHashes, error) { result := SectionHashes{ Text: make(map[string]string), Data: make(map[string]string), } file, err := elf.Open(bin) if err != nil { return SectionHashes{}, err } defer file.Close() symbols, err := file.Symbols() if err != nil { return SectionHashes{}, err } rawFile, err := os.Open(bin) if err != nil { return SectionHashes{}, err } defer rawFile.Close() sections := make(map[elf.SectionIndex]*elf.Section) for i, s := range file.Sections { sections[elf.SectionIndex(i)] = s } for _, s := range symbols { if s.Name == "" || s.Size == 0 || s.Section >= elf.SHN_LORESERVE { continue } symbolSection, ok := sections[s.Section] if !ok || symbolSection.Type == elf.SHT_NOBITS { continue } var targetMap map[string]string symbolType := elf.ST_TYPE(s.Info) sectionFlags := symbolSection.Flags switch { case symbolType == elf.STT_FUNC && (sectionFlags&elf.SHF_EXECINSTR) != 0: targetMap = result.Text case symbolType == elf.STT_OBJECT && (sectionFlags&elf.SHF_ALLOC) != 0 && (sectionFlags&elf.SHF_EXECINSTR) == 0: targetMap = result.Data default: continue } offset := s.Value - symbolSection.Addr if offset+s.Size > symbolSection.Size { continue } data := make([]byte, s.Size) _, err := rawFile.ReadAt(data, int64(symbolSection.Offset+offset)) if err != nil { continue } hash := sha256.Sum256(data) targetMap[s.Name] = hex.EncodeToString(hash[:]) } return result, 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-----`