github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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  	"time"
    18  
    19  	"github.com/google/syzkaller/pkg/debugtracer"
    20  	"github.com/google/syzkaller/pkg/osutil"
    21  	"github.com/google/syzkaller/sys/targets"
    22  )
    23  
    24  type linux struct{}
    25  
    26  func (linux linux) build(params Params) (ImageDetails, error) {
    27  	details := ImageDetails{}
    28  	err := linux.buildKernel(params)
    29  	// Even if the build fails, autogenerated files would still be present (unless the build is really broken).
    30  	if err != nil {
    31  		details.CompilerID, _ = queryLinuxCompiler(params.KernelDir)
    32  		return details, err
    33  	}
    34  
    35  	details.CompilerID, err = queryLinuxCompiler(params.KernelDir)
    36  	if err != nil {
    37  		return details, err
    38  	}
    39  
    40  	kernelPath := filepath.Join(params.KernelDir, filepath.FromSlash(LinuxKernelImage(params.TargetArch)))
    41  
    42  	// Copy the kernel image to let it be uploaded to the asset storage. If the asset storage is not enabled,
    43  	// let the file just stay in the output folder -- it is usually very small compared to vmlinux anyway.
    44  	if err := osutil.CopyFile(kernelPath, filepath.Join(params.OutputDir, "kernel")); err != nil {
    45  		return details, err
    46  	}
    47  
    48  	if fileInfo, err := os.Stat(params.UserspaceDir); err == nil && fileInfo.IsDir() {
    49  		// The old way of assembling the image from userspace dir.
    50  		// It should be removed once all syzbot instances are switched.
    51  		if err := linux.createImage(params, kernelPath); err != nil {
    52  			return details, err
    53  		}
    54  	} else if params.VMType == "qemu" {
    55  		// If UserspaceDir is a file (image) and we use qemu, we just copy image to the output dir assuming
    56  		// that qemu will use injected kernel boot. In this mode we also assume password/key-less ssh.
    57  		// The kernel image was already uploaded above.
    58  		if err := osutil.CopyFile(params.UserspaceDir, filepath.Join(params.OutputDir, "image")); err != nil {
    59  			return details, err
    60  		}
    61  	} else if err := embedLinuxKernel(params, kernelPath); err != nil {
    62  		return details, err
    63  	}
    64  	vmlinux := filepath.Join(params.OutputDir, "obj", "vmlinux")
    65  	details.Signature, err = elfBinarySignature(vmlinux, params.Tracer)
    66  	return details, err
    67  }
    68  
    69  func (linux linux) buildKernel(params Params) error {
    70  	configFile := filepath.Join(params.KernelDir, ".config")
    71  	if err := linux.writeFile(configFile, params.Config); err != nil {
    72  		return fmt.Errorf("failed to write config file: %w", err)
    73  	}
    74  	// One would expect olddefconfig here, but olddefconfig is not present in v3.6 and below.
    75  	// oldconfig is the same as olddefconfig if stdin is not set.
    76  	if err := runMake(params, "oldconfig"); err != nil {
    77  		return err
    78  	}
    79  	// Write updated kernel config early, so that it's captured on build failures.
    80  	outputConfig := filepath.Join(params.OutputDir, "kernel.config")
    81  	if err := osutil.CopyFile(configFile, outputConfig); err != nil {
    82  		return err
    83  	}
    84  	// Ensure CONFIG_GCC_PLUGIN_RANDSTRUCT doesn't prevent ccache usage.
    85  	// See /Documentation/kbuild/reproducible-builds.rst.
    86  	const seed = `const char *randstruct_seed = "e9db0ca5181da2eedb76eba144df7aba4b7f9359040ee58409765f2bdc4cb3b8";`
    87  	gccPluginsDir := filepath.Join(params.KernelDir, "scripts", "gcc-plugins")
    88  	if osutil.IsExist(gccPluginsDir) {
    89  		if err := linux.writeFile(filepath.Join(gccPluginsDir, "randomize_layout_seed.h"), []byte(seed)); err != nil {
    90  			return err
    91  		}
    92  	}
    93  
    94  	// Different key is generated for each build if key is not provided.
    95  	// see Documentation/reproducible-builds.rst. This is causing problems to our signature calculation.
    96  	certsDir := filepath.Join(params.KernelDir, "certs")
    97  	if osutil.IsExist(certsDir) {
    98  		if err := linux.writeFile(filepath.Join(certsDir, "signing_key.pem"), []byte(moduleSigningKey)); err != nil {
    99  			return err
   100  		}
   101  	}
   102  	target := path.Base(LinuxKernelImage(params.TargetArch))
   103  	if err := runMake(params, target); err != nil {
   104  		return err
   105  	}
   106  	vmlinux := filepath.Join(params.KernelDir, "vmlinux")
   107  	outputVmlinux := filepath.Join(params.OutputDir, "obj", "vmlinux")
   108  	if err := osutil.Rename(vmlinux, outputVmlinux); err != nil {
   109  		return fmt.Errorf("failed to rename vmlinux: %w", err)
   110  	}
   111  	return nil
   112  }
   113  
   114  func (linux) createImage(params Params, kernelPath string) error {
   115  	tempDir, err := os.MkdirTemp("", "syz-build")
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer os.RemoveAll(tempDir)
   120  	scriptFile := filepath.Join(tempDir, "create.sh")
   121  	if err := osutil.WriteExecFile(scriptFile, []byte(createImageScript)); err != nil {
   122  		return fmt.Errorf("failed to write script file: %w", err)
   123  	}
   124  	cmd := osutil.Command(scriptFile, params.UserspaceDir, kernelPath, params.TargetArch)
   125  	cmd.Dir = tempDir
   126  	cmd.Env = append([]string{}, os.Environ()...)
   127  	cmd.Env = append(cmd.Env,
   128  		"SYZ_VM_TYPE="+params.VMType,
   129  		"SYZ_CMDLINE_FILE="+osutil.Abs(params.CmdlineFile),
   130  		"SYZ_SYSCTL_FILE="+osutil.Abs(params.SysctlFile),
   131  	)
   132  	if _, err = osutil.Run(time.Hour, cmd); err != nil {
   133  		return fmt.Errorf("image build failed: %w", err)
   134  	}
   135  	// Note: we use CopyFile instead of Rename because src and dst can be on different filesystems.
   136  	imageFile := filepath.Join(params.OutputDir, "image")
   137  	if err := osutil.CopyFile(filepath.Join(tempDir, "disk.raw"), imageFile); err != nil {
   138  		return err
   139  	}
   140  	return nil
   141  }
   142  
   143  func (linux) clean(params Params) error {
   144  	return runMake(params, "distclean")
   145  }
   146  
   147  func (linux) writeFile(file string, data []byte) error {
   148  	if err := osutil.WriteFile(file, data); err != nil {
   149  		return err
   150  	}
   151  	return osutil.SandboxChown(file)
   152  }
   153  
   154  func runMake(params Params, extraArgs ...string) error {
   155  	target := targets.Get(targets.Linux, params.TargetArch)
   156  	args := LinuxMakeArgs(target, params.Compiler, params.Linker, params.Ccache, "", params.BuildCPUs)
   157  	args = append(args, extraArgs...)
   158  	makeBin := params.Make
   159  	if makeBin == "" {
   160  		makeBin = "make"
   161  	}
   162  	cmd := osutil.Command(makeBin, args...)
   163  	if err := osutil.Sandbox(cmd, true, true); err != nil {
   164  		return err
   165  	}
   166  	cmd.Dir = params.KernelDir
   167  	cmd.Env = append([]string{}, os.Environ()...)
   168  	// This makes the build [more] deterministic:
   169  	// 2 builds from the same sources should result in the same vmlinux binary.
   170  	// Build on a release commit and on the previous one should result in the same vmlinux too.
   171  	// We use it for detecting no-op changes during bisection.
   172  	cmd.Env = append(cmd.Env,
   173  		"KBUILD_BUILD_VERSION=0",
   174  		"KBUILD_BUILD_TIMESTAMP=now",
   175  		"KBUILD_BUILD_USER=syzkaller",
   176  		"KBUILD_BUILD_HOST=syzkaller",
   177  	)
   178  	output, err := osutil.Run(time.Hour, cmd)
   179  	params.Tracer.Log("Build log:\n%s", output)
   180  	return err
   181  }
   182  
   183  func LinuxMakeArgs(target *targets.Target, compiler, linker, ccache, buildDir string, jobs int) []string {
   184  	args := []string{
   185  		// Make still overrides these if they are passed as env variables.
   186  		// Let's pass them directly as make arguments.
   187  		"KERNELVERSION=syzkaller",
   188  		"KERNELRELEASE=syzkaller",
   189  		"LOCALVERSION=-syzkaller",
   190  		"-j", fmt.Sprint(jobs),
   191  		"ARCH=" + target.KernelArch,
   192  	}
   193  	if target.Triple != "" {
   194  		args = append(args, "CROSS_COMPILE="+target.Triple+"-")
   195  	}
   196  	if compiler == "" {
   197  		compiler = target.KernelCompiler
   198  		if target.KernelLinker != "" {
   199  			linker = target.KernelLinker
   200  		}
   201  	}
   202  	if compiler != "" {
   203  		if ccache != "" {
   204  			compiler = ccache + " " + compiler
   205  		}
   206  	}
   207  	// The standard way to build Linux with clang is to pass LLVM=1 instead of CC= and LD=.
   208  	if compiler == targets.DefaultLLVMCompiler && (linker == "" || linker == targets.DefaultLLVMLinker) {
   209  		args = append(args, "LLVM=1")
   210  	} else {
   211  		if compiler != "" {
   212  			args = append(args, "CC="+compiler)
   213  		}
   214  		if linker != "" {
   215  			args = append(args, "LD="+linker)
   216  		}
   217  	}
   218  	if buildDir != "" {
   219  		args = append(args, "O="+buildDir)
   220  	}
   221  	return args
   222  }
   223  
   224  func LinuxKernelImage(arch string) string {
   225  	// We build only zImage/bzImage as we currently don't use modules.
   226  	switch arch {
   227  	case targets.AMD64:
   228  		return "arch/x86/boot/bzImage"
   229  	case targets.I386:
   230  		return "arch/x86/boot/bzImage"
   231  	case targets.S390x:
   232  		return "arch/s390/boot/bzImage"
   233  	case targets.PPC64LE:
   234  		return "arch/powerpc/boot/zImage.pseries"
   235  	case targets.ARM:
   236  		return "arch/arm/boot/zImage"
   237  	case targets.ARM64:
   238  		return "arch/arm64/boot/Image.gz"
   239  	case targets.RiscV64:
   240  		return "arch/riscv/boot/Image"
   241  	case targets.MIPS64LE:
   242  		return "vmlinux"
   243  	default:
   244  		panic(fmt.Sprintf("pkg/build: unsupported arch %v", arch))
   245  	}
   246  }
   247  
   248  var linuxCompilerRegexp = regexp.MustCompile(`#define\s+LINUX_COMPILER\s+"(.*)"`)
   249  
   250  func queryLinuxCompiler(kernelDir string) (string, error) {
   251  	bytes, err := os.ReadFile(filepath.Join(kernelDir, "include", "generated", "compile.h"))
   252  	if err != nil {
   253  		return "", err
   254  	}
   255  	result := linuxCompilerRegexp.FindSubmatch(bytes)
   256  	if result == nil {
   257  		return "", fmt.Errorf("include/generated/compile.h does not contain build information")
   258  	}
   259  	return string(result[1]), nil
   260  }
   261  
   262  type SectionHashes struct {
   263  	Text map[string]string `json:"text"`
   264  	Data map[string]string `json:"data"` // Merged .data and .rodata.
   265  }
   266  
   267  // ElfSymbolHashes returns a map of sha256 hashes per section per symbol contained in the elf file.
   268  // It's best to call it on vmlinux.o since PCs in the binary code are not patched yet.
   269  func ElfSymbolHashes(bin string) (SectionHashes, error) {
   270  	result := SectionHashes{
   271  		Text: make(map[string]string),
   272  		Data: make(map[string]string),
   273  	}
   274  
   275  	file, err := elf.Open(bin)
   276  	if err != nil {
   277  		return SectionHashes{}, err
   278  	}
   279  	defer file.Close()
   280  
   281  	symbols, err := file.Symbols()
   282  	if err != nil {
   283  		return SectionHashes{}, err
   284  	}
   285  
   286  	rawFile, err := os.Open(bin)
   287  	if err != nil {
   288  		return SectionHashes{}, err
   289  	}
   290  	defer rawFile.Close()
   291  
   292  	sections := make(map[elf.SectionIndex]*elf.Section)
   293  	for i, s := range file.Sections {
   294  		sections[elf.SectionIndex(i)] = s
   295  	}
   296  
   297  	for _, s := range symbols {
   298  		if s.Name == "" || s.Size == 0 || s.Section >= elf.SHN_LORESERVE {
   299  			continue
   300  		}
   301  
   302  		symbolSection, ok := sections[s.Section]
   303  		if !ok || symbolSection.Type == elf.SHT_NOBITS {
   304  			continue
   305  		}
   306  
   307  		var targetMap map[string]string
   308  
   309  		symbolType := elf.ST_TYPE(s.Info)
   310  		sectionFlags := symbolSection.Flags
   311  		switch {
   312  		case symbolType == elf.STT_FUNC && (sectionFlags&elf.SHF_EXECINSTR) != 0:
   313  			targetMap = result.Text
   314  		case symbolType == elf.STT_OBJECT && (sectionFlags&elf.SHF_ALLOC) != 0 &&
   315  			(sectionFlags&elf.SHF_EXECINSTR) == 0:
   316  			targetMap = result.Data
   317  		default:
   318  			continue
   319  		}
   320  
   321  		offset := s.Value - symbolSection.Addr
   322  		if offset+s.Size > symbolSection.Size {
   323  			continue
   324  		}
   325  
   326  		data := make([]byte, s.Size)
   327  		_, err := rawFile.ReadAt(data, int64(symbolSection.Offset+offset))
   328  		if err != nil {
   329  			continue
   330  		}
   331  
   332  		hash := sha256.Sum256(data)
   333  		targetMap[s.Name] = hex.EncodeToString(hash[:])
   334  	}
   335  	return result, nil
   336  }
   337  
   338  // elfBinarySignature calculates signature of an elf binary aiming at runtime behavior
   339  // (text/data, debug info is ignored).
   340  func elfBinarySignature(bin string, tracer debugtracer.DebugTracer) (string, error) {
   341  	f, err := os.Open(bin)
   342  	if err != nil {
   343  		return "", fmt.Errorf("failed to open binary for signature: %w", err)
   344  	}
   345  	ef, err := elf.NewFile(f)
   346  	if err != nil {
   347  		return "", fmt.Errorf("failed to open elf binary: %w", err)
   348  	}
   349  	hasher := sha256.New()
   350  	for _, sec := range ef.Sections {
   351  		// Hash allocated sections (e.g. no debug info as it's not allocated)
   352  		// with file data (e.g. no bss). We also ignore .notes section as it
   353  		// contains some small changing binary blob that seems irrelevant.
   354  		// It's unclear if it's better to check NOTE type,
   355  		// or ".notes" name or !PROGBITS type.
   356  		if sec.Flags&elf.SHF_ALLOC == 0 || sec.Type == elf.SHT_NOBITS || sec.Type == elf.SHT_NOTE {
   357  			continue
   358  		}
   359  		data, err := sec.Data()
   360  		if err != nil {
   361  			return "", fmt.Errorf("failed to read ELF section %v: %w", sec.Name, err)
   362  		}
   363  		hasher1 := sha256.New()
   364  		hasher1.Write(data)
   365  		hash := hasher1.Sum(nil)
   366  		hasher.Write(hash)
   367  		tracer.Log("section %v: size %v signature %v", sec.Name, len(data), hex.EncodeToString(hash[:8]))
   368  		tracer.SaveFile(sec.Name, data)
   369  	}
   370  	return hex.EncodeToString(hasher.Sum(nil)), nil
   371  }
   372  
   373  // moduleSigningKey is a constant module signing key for reproducible builds.
   374  const moduleSigningKey = `-----BEGIN PRIVATE KEY-----
   375  MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxu5GRXw7d13xTLlZ
   376  GT1y63U4Firk3WjXapTgf9radlfzpqheFr5HWO8f11U/euZQWXDzi+Bsq+6s/2lJ
   377  AU9XWQIDAQABAkB24ZxTGBv9iMGURUvOvp83wRRkgvvEqUva4N+M6MAXagav3GRi
   378  K/gl3htzQVe+PLGDfbIkstPJUvI2izL8ZWmBAiEA/P72IitEYE4NQj4dPcYglEYT
   379  Hbh2ydGYFbYxvG19DTECIQDJSvg7NdAaZNd9faE5UIAcLF35k988m9hSqBjtz0tC
   380  qQIgGOJC901mJkrHBxLw8ViBb9QMoUm5dVRGLyyCa9QhDqECIQCQGLX4lP5DVrsY
   381  X43BnMoI4Q3o8x1Uou/JxAIMg1+J+QIgamNCPBLeP8Ce38HtPcm8BXmhPKkpCXdn
   382  uUf4bYtfSSw=
   383  -----END PRIVATE KEY-----
   384  -----BEGIN CERTIFICATE-----
   385  MIIBvzCCAWmgAwIBAgIUKoM7Idv4nw571nWDgYFpw6I29u0wDQYJKoZIhvcNAQEF
   386  BQAwLjEsMCoGA1UEAwwjQnVpbGQgdGltZSBhdXRvZ2VuZXJhdGVkIGtlcm5lbCBr
   387  ZXkwIBcNMjAxMDA4MTAzMzIwWhgPMjEyMDA5MTQxMDMzMjBaMC4xLDAqBgNVBAMM
   388  I0J1aWxkIHRpbWUgYXV0b2dlbmVyYXRlZCBrZXJuZWwga2V5MFwwDQYJKoZIhvcN
   389  AQEBBQADSwAwSAJBAMbuRkV8O3dd8Uy5WRk9cut1OBYq5N1o12qU4H/a2nZX86ao
   390  Xha+R1jvH9dVP3rmUFlw84vgbKvurP9pSQFPV1kCAwEAAaNdMFswDAYDVR0TAQH/
   391  BAIwADALBgNVHQ8EBAMCB4AwHQYDVR0OBBYEFPhQx4etmYw5auCJwIO5QP8Kmrt3
   392  MB8GA1UdIwQYMBaAFPhQx4etmYw5auCJwIO5QP8Kmrt3MA0GCSqGSIb3DQEBBQUA
   393  A0EAK5moCH39eLLn98pBzSm3MXrHpLtOWuu2p696fg/ZjiUmRSdHK3yoRONxMHLJ
   394  1nL9cAjWPantqCm5eoyhj7V7gg==
   395  -----END CERTIFICATE-----`