github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/build/android.go (about)

     1  // Copyright 2023 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  package build
     5  
     6  import (
     7  	"archive/tar"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"regexp"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/google/syzkaller/pkg/config"
    17  	"github.com/google/syzkaller/pkg/osutil"
    18  )
    19  
    20  type android struct{}
    21  
    22  type BuildParams struct {
    23  	BuildScript      string   `json:"build_script"`
    24  	EnvVars          []string `json:"env_vars"`
    25  	Flags            []string `json:"flags"`
    26  	AdditionalImages []string `json:"additional_images"`
    27  	AutoconfPath     string   `json:"autoconf_path"`
    28  	ConfigPath       string   `json:"config_path"`
    29  }
    30  
    31  var ccCompilerRegexp = regexp.MustCompile(`#define\s+CONFIG_CC_VERSION_TEXT\s+"(.*)"`)
    32  
    33  func parseConfig(conf []byte) (*BuildParams, error) {
    34  	buildCfg := new(BuildParams)
    35  	if err := config.LoadData(conf, buildCfg); err != nil {
    36  		return nil, fmt.Errorf("failed to parse build config: %w", err)
    37  	}
    38  
    39  	if buildCfg.BuildScript == "" {
    40  		return nil, fmt.Errorf("build script not specified for Android build")
    41  	}
    42  
    43  	if buildCfg.ConfigPath == "" {
    44  		return nil, fmt.Errorf("kernel config path not specified for Android build")
    45  	}
    46  
    47  	return buildCfg, nil
    48  }
    49  
    50  func (a android) readCompiler(path string) (string, error) {
    51  	bytes, err := os.ReadFile(path)
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  	result := ccCompilerRegexp.FindSubmatch(bytes)
    56  	if result == nil {
    57  		return "", fmt.Errorf("%s does not contain build information", path)
    58  	}
    59  	return string(result[1]), nil
    60  }
    61  
    62  func (a android) build(params Params) (ImageDetails, error) {
    63  	var details ImageDetails
    64  	if params.CmdlineFile != "" {
    65  		return details, fmt.Errorf("cmdline file is not supported for android images")
    66  	}
    67  	if params.SysctlFile != "" {
    68  		return details, fmt.Errorf("sysctl file is not supported for android images")
    69  	}
    70  
    71  	buildCfg, err := parseConfig(params.Build)
    72  	if err != nil {
    73  		return details, fmt.Errorf("error parsing android configs: %w", err)
    74  	}
    75  
    76  	// Build kernel.
    77  	cmd := osutil.Command(fmt.Sprintf("./%v", buildCfg.BuildScript), buildCfg.Flags...)
    78  	cmd.Dir = params.KernelDir
    79  	cmd.Env = append(cmd.Env, buildCfg.EnvVars...)
    80  
    81  	if _, err := osutil.Run(time.Hour, cmd); err != nil {
    82  		return details, fmt.Errorf("failed to build kernel: %w", err)
    83  	}
    84  
    85  	buildDistDir := filepath.Join(params.KernelDir, "dist")
    86  
    87  	vmlinux := filepath.Join(buildDistDir, "vmlinux")
    88  
    89  	if buildCfg.AutoconfPath != "" {
    90  		details.CompilerID, err = a.readCompiler(filepath.Join(params.KernelDir, buildCfg.AutoconfPath))
    91  		if err != nil {
    92  			return details, fmt.Errorf("failed to read compiler: %w", err)
    93  		}
    94  	}
    95  
    96  	if err := osutil.CopyFile(vmlinux, filepath.Join(params.OutputDir, "obj", "vmlinux")); err != nil {
    97  		return details, fmt.Errorf("failed to copy vmlinux: %w", err)
    98  	}
    99  	if err := osutil.CopyFile(filepath.Join(params.KernelDir, buildCfg.ConfigPath),
   100  		filepath.Join(params.OutputDir, "obj", "kernel.config")); err != nil {
   101  		return details, fmt.Errorf("failed to copy kernel config: %w", err)
   102  	}
   103  
   104  	imageFile, err := os.Create(filepath.Join(params.OutputDir, "image"))
   105  	if err != nil {
   106  		return details, fmt.Errorf("failed to create output file: %w", err)
   107  	}
   108  	defer imageFile.Close()
   109  
   110  	if err := a.copyModuleFiles(filepath.Join(params.KernelDir, "out"), params.OutputDir); err != nil {
   111  		return details, fmt.Errorf("failed copying module files: %w", err)
   112  	}
   113  
   114  	images := append(buildCfg.AdditionalImages, "boot.img")
   115  	if err := a.embedImages(imageFile, buildDistDir, images...); err != nil {
   116  		return details, fmt.Errorf("failed to embed images: %w", err)
   117  	}
   118  
   119  	details.Signature, err = elfBinarySignature(vmlinux, params.Tracer)
   120  	if err != nil {
   121  		return details, fmt.Errorf("failed to generate signature: %w", err)
   122  	}
   123  
   124  	return details, nil
   125  }
   126  
   127  func (a android) copyModuleFiles(srcDir, dstDir string) error {
   128  	err := filepath.Walk(srcDir,
   129  		func(path string, info os.FileInfo, err error) error {
   130  			if err != nil {
   131  				return fmt.Errorf("error walking out dir: %w", err)
   132  			}
   133  			// Staging directories contain stripped module object files.
   134  			if strings.Contains(path, "staging") {
   135  				return nil
   136  			}
   137  
   138  			if filepath.Ext(path) == ".ko" {
   139  				if err := osutil.CopyFile(path, filepath.Join(dstDir, info.Name())); err != nil {
   140  					return fmt.Errorf("error copying file: %w", err)
   141  				}
   142  			}
   143  			return nil
   144  		})
   145  	if err != nil {
   146  		return fmt.Errorf("failed to copy module objects: %w", err)
   147  	}
   148  	return nil
   149  }
   150  
   151  func (a android) embedImages(w io.Writer, srcDir string, imageNames ...string) error {
   152  	tw := tar.NewWriter(w)
   153  	defer tw.Close()
   154  
   155  	for _, name := range imageNames {
   156  		path := filepath.Join(srcDir, name)
   157  		data, err := os.ReadFile(path)
   158  		if err != nil {
   159  			return fmt.Errorf("failed to read %q: %w", name, err)
   160  		}
   161  
   162  		if err := tw.WriteHeader(&tar.Header{
   163  			Name: name,
   164  			Mode: 0600,
   165  			Size: int64(len(data)),
   166  		}); err != nil {
   167  			return fmt.Errorf("failed to write header for %q: %w", name, err)
   168  		}
   169  
   170  		if _, err := tw.Write(data); err != nil {
   171  			return fmt.Errorf("failed to write data for %q: %w", name, err)
   172  		}
   173  	}
   174  
   175  	if err := tw.Close(); err != nil {
   176  		return fmt.Errorf("close archive: %w", err)
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func (a android) clean(kernelDir, targetArch string) error {
   183  	if err := osutil.RemoveAll(filepath.Join(kernelDir, "out")); err != nil {
   184  		return fmt.Errorf("failed to clean 'out' directory: %w", err)
   185  	}
   186  	if err := osutil.RemoveAll(filepath.Join(kernelDir, "dist")); err != nil {
   187  		return fmt.Errorf("failed to clean 'dist' directory: %w", err)
   188  	}
   189  	return nil
   190  }