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-----`