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