From f6ef8c9d1dbba2449eec47d79ae047c30476dff2 Mon Sep 17 00:00:00 2001 From: Kris Alder Date: Wed, 22 Feb 2023 10:20:49 -0800 Subject: pkg/build: add build code for Android devices Booting physical Android devices requires building a few artifacts, as described at https://source.android.com/docs/setup/build/building-kernels. When a ProxyVM type is used, we need to differentiate whether or not to use the Android build logic, so we add an additional mapping which uses a different name but the same VM logic. --- pkg/build/android.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ pkg/build/build.go | 2 + vm/vm.go | 13 ++++- vm/vm_test.go | 16 +++++++ 4 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 pkg/build/android.go diff --git a/pkg/build/android.go b/pkg/build/android.go new file mode 100644 index 000000000..3b1f38561 --- /dev/null +++ b/pkg/build/android.go @@ -0,0 +1,133 @@ +// Copyright 2023 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. + +package build + +import ( + "archive/tar" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "time" + + "github.com/google/syzkaller/pkg/osutil" +) + +type android struct{} + +var ccCompilerRegexp = regexp.MustCompile(`#define\s+CONFIG_CC_VERSION_TEXT\s+"(.*)"`) + +func (a android) readCompiler(kernelDir string) (string, error) { + bytes, err := os.ReadFile(filepath.Join(kernelDir, "out", "mixed", "device-kernel", "private", + "gs-google", "include", "generated", "autoconf.h")) + if err != nil { + return "", err + } + result := ccCompilerRegexp.FindSubmatch(bytes) + if result == nil { + return "", fmt.Errorf("include/generated/autoconf.h does not contain build information") + } + return string(result[1]), nil +} + +func (a android) build(params Params) (ImageDetails, error) { + var details ImageDetails + if params.CmdlineFile != "" { + return details, fmt.Errorf("cmdline file is not supported for android images") + } + if params.SysctlFile != "" { + return details, fmt.Errorf("sysctl file is not supported for android images") + } + + // Build kernel. + cmd := osutil.Command("./build_cloudripper.sh") + cmd.Dir = params.KernelDir + // No cloudripper kasan config; currently only slider has a kasan config. + defconfigFragment := filepath.Join("private", "gs-google", "build.config.slider.kasan") + buildTarget := "cloudripper_gki_kasan" + cmd.Env = append(cmd.Env, "OUT_DIR=out", "DIST_DIR=dist", fmt.Sprintf("GKI_DEFCONFIG_FRAGMENT=%v", + defconfigFragment), fmt.Sprintf("BUILD_TARGET=%v", buildTarget)) + + if _, err := osutil.Run(time.Hour, cmd); err != nil { + return details, fmt.Errorf("failed to build kernel: %s", err) + } + + buildDistDir := filepath.Join(params.KernelDir, "dist") + + vmlinux := filepath.Join(buildDistDir, "vmlinux") + config := filepath.Join(params.KernelDir, "out", "mixed", "device-kernel", "private", "gs-google", ".config") + + var err error + details.CompilerID, err = a.readCompiler(params.KernelDir) + if err != nil { + return details, fmt.Errorf("failed to read compiler: %v", err) + } + + if err := osutil.CopyFile(vmlinux, filepath.Join(params.OutputDir, "obj", "vmlinux")); err != nil { + return details, fmt.Errorf("failed to copy vmlinux: %v", err) + } + if err := osutil.CopyFile(config, filepath.Join(params.OutputDir, "obj", "kernel.config")); err != nil { + return details, fmt.Errorf("failed to copy kernel config: %v", err) + } + + imageFile, err := os.Create(filepath.Join(params.OutputDir, "image")) + if err != nil { + return details, fmt.Errorf("failed to create output file: %v", err) + } + defer imageFile.Close() + + if err := a.embedImages(imageFile, buildDistDir, "boot.img", "dtbo.img", "vendor_kernel_boot.img", + "vendor_dlkm.img"); err != nil { + return details, fmt.Errorf("failed to embed images: %v", err) + } + + details.Signature, err = elfBinarySignature(vmlinux, params.Tracer) + if err != nil { + return details, fmt.Errorf("failed to generate signature: %s", err) + } + + return details, nil +} + +func (a android) embedImages(w io.Writer, srcDir string, imageNames ...string) error { + tw := tar.NewWriter(w) + defer tw.Close() + + for _, name := range imageNames { + path := filepath.Join(srcDir, name) + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read %q: %v", name, err) + } + + if err := tw.WriteHeader(&tar.Header{ + Name: name, + Mode: 0600, + Size: int64(len(data)), + }); err != nil { + return fmt.Errorf("failed to write header for %q: %v", name, err) + } + + if _, err := tw.Write(data); err != nil { + return fmt.Errorf("failed to write data for %q: %v", name, err) + } + } + + if err := tw.Close(); err != nil { + return fmt.Errorf("close archive: %v", err) + } + + return nil +} + +func (a android) clean(kernelDir, targetArch string) error { + if err := osutil.RemoveAll(filepath.Join(kernelDir, "out")); err != nil { + return fmt.Errorf("failed to clean 'out' directory: %v", err) + } + if err := osutil.RemoveAll(filepath.Join(kernelDir, "dist")); err != nil { + return fmt.Errorf("failed to clean 'dist' directory: %v", err) + } + return nil +} diff --git a/pkg/build/build.go b/pkg/build/build.go index 2890894db..2ee0d3aa6 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -134,6 +134,8 @@ func getBuilder(targetOS, targetArch, vmType string) (builder, error) { return gvisor{}, nil } else if vmType == "cuttlefish" { return cuttlefish{}, nil + } else if vmType == "proxyapp:android" { + return android{}, nil } } builders := map[string]builder{ diff --git a/vm/vm.go b/vm/vm.go index c340dfbf6..033e297db 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -14,6 +14,7 @@ import ( "io" "os" "path/filepath" + "strings" "sync/atomic" "time" @@ -65,6 +66,14 @@ type BootErrorer interface { BootError() (string, []byte) } +// vmType splits the VM type from any suffix (separated by ":"). This is mostly +// useful for the "proxyapp" type, where pkg/build needs to specify/handle +// sub-types. +func vmType(fullName string) string { + name, _, _ := strings.Cut(fullName, ":") + return name +} + // AllowsOvercommit returns if the instance type allows overcommit of instances // (i.e. creation of instances out-of-thin-air). Overcommit is used during image // and patch testing in syz-ci when it just asks for more than specified in config @@ -74,12 +83,12 @@ type BootErrorer interface { // override resource limits specified in config (e.g. can OOM). But it works and // makes lots of things much simpler. func AllowsOvercommit(typ string) bool { - return vmimpl.Types[typ].Overcommit + return vmimpl.Types[vmType(typ)].Overcommit } // Create creates a VM pool that can be used to create individual VMs. func Create(cfg *mgrconfig.Config, debug bool) (*Pool, error) { - typ, ok := vmimpl.Types[cfg.Type] + typ, ok := vmimpl.Types[vmType(cfg.Type)] if !ok { return nil, fmt.Errorf("unknown instance type '%v'", cfg.Type) } diff --git a/vm/vm_test.go b/vm/vm_test.go index d3429f5f7..9c190cf50 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -397,3 +397,19 @@ func testMonitorExecution(t *testing.T, test *Test) { t.Fatalf("want output:\n%s\n\ngot output:\n%s\n", test.Report.Output, rep.Output) } } + +func TestVMType(t *testing.T) { + testCases := []struct { + in string + want string + }{ + {"gvisor", "gvisor"}, + {"proxyapp:android", "proxyapp"}, + } + + for _, tc := range testCases { + if got := vmType(tc.in); got != tc.want { + t.Errorf("vmType(%q) = %q, want %q", tc.in, got, tc.want) + } + } +} -- cgit mrf-deployment