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