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

     1  // Copyright 2021 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  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"syscall"
    11  	"unsafe"
    12  
    13  	"github.com/google/syzkaller/pkg/osutil"
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  // embedLinuxKernel copies a new kernel into an existing disk image.
    18  // There are several assumptions about the image:
    19  // - the image is ext4 (may be inferred from image name if necessary, e.g. "image.btrfs")
    20  // - the data is on partition 1 (we could see what partitions we got and use the last one)
    21  // - ssh works without password (we don't copy the key)
    22  // - cmdline file is not supported (should be moved to kernel config)
    23  // - the kernel is stored in the image in /vmlinuz file.
    24  func embedLinuxKernel(params Params, kernelPath string) error {
    25  	return embedFiles(params, func(mountDir string) error {
    26  		if err := copyKernel(mountDir, kernelPath); err != nil {
    27  			return err
    28  		}
    29  		return nil
    30  	})
    31  }
    32  
    33  // embedFiles mounts the disk image specified by params.UserspaceDir and then calls the given
    34  // callback function which should copy files into the image as needed.
    35  func embedFiles(params Params, callback func(mountDir string) error) error {
    36  	if params.CmdlineFile != "" {
    37  		return fmt.Errorf("cmdline file is not supported for linux images")
    38  	}
    39  	tempDir, err := os.MkdirTemp("", "syz-build")
    40  	if err != nil {
    41  		return err
    42  	}
    43  	defer os.RemoveAll(tempDir)
    44  	imageFile := filepath.Join(tempDir, "image")
    45  	if err := osutil.CopyFile(params.UserspaceDir, imageFile); err != nil {
    46  		return err
    47  	}
    48  	loop, loopFile, err := linuxSetupLoop(imageFile)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	defer func() {
    53  		unix.IoctlGetInt(loop, unix.LOOP_CLR_FD)
    54  		unix.Close(loop)
    55  	}()
    56  	mountDir := filepath.Join(tempDir, "mnt")
    57  	if err := osutil.MkdirAll(mountDir); err != nil {
    58  		return err
    59  	}
    60  	if err := tryMount(loopFile+"p1", mountDir); err != nil {
    61  		return fmt.Errorf("mount(%vp1, %v) failed: %w", loopFile, mountDir, err)
    62  	}
    63  	defer unix.Unmount(mountDir, 0)
    64  	if err := callback(mountDir); err != nil {
    65  		return err
    66  	}
    67  	if params.SysctlFile != "" {
    68  		if err := copySysctlFile(params.SysctlFile, loopFile, mountDir); err != nil {
    69  			return err
    70  		}
    71  	}
    72  	if err := unix.Unmount(mountDir, 0); err != nil {
    73  		return err
    74  	}
    75  	return osutil.CopyFile(imageFile, filepath.Join(params.OutputDir, "image"))
    76  }
    77  
    78  func copySysctlFile(sysctlFile, loopFile, mountDir string) error {
    79  	etcFolder := filepath.Join(mountDir, "etc")
    80  	for idx := 2; ; idx++ {
    81  		if osutil.IsExist(etcFolder) {
    82  			break
    83  		}
    84  		err := tryMount(fmt.Sprintf("%sp%d", loopFile, idx), mountDir)
    85  		if err != nil {
    86  			// Most likely we've just run out of partitions.
    87  			return fmt.Errorf("didn't find a partition that has /etc")
    88  		}
    89  		defer unix.Unmount(mountDir, 0)
    90  	}
    91  	return osutil.CopyFile(sysctlFile, filepath.Join(etcFolder, "sysctl.conf"))
    92  }
    93  
    94  func tryMount(device, mountDir string) error {
    95  	var err error
    96  loop:
    97  	for _, fsType := range []string{"ext4", "vfat"} {
    98  		err = unix.Mount(device, mountDir, fsType, 0, "")
    99  		switch err {
   100  		case syscall.EINVAL:
   101  			// Most likely it just an invalid superblock error - try another fstype.
   102  			continue
   103  		case nil:
   104  			break loop
   105  		}
   106  	}
   107  	return err
   108  }
   109  
   110  func copyKernel(mountDir, kernelPath string) error {
   111  	// Try several common locations where the kernel can be.
   112  	for _, targetPath := range []string{"boot/vmlinuz", "boot/bzImage", "vmlinuz", "bzImage", "Image.gz"} {
   113  		fullPath := filepath.Join(mountDir, filepath.FromSlash(targetPath))
   114  		if !osutil.IsExist(fullPath) {
   115  			continue
   116  		}
   117  		return osutil.CopyFile(kernelPath, fullPath)
   118  	}
   119  	return fmt.Errorf("did not find kernel in the template image")
   120  }
   121  
   122  func linuxSetupLoop(imageFile string) (int, string, error) {
   123  	image, err := unix.Open(imageFile, unix.O_RDWR, 0)
   124  	if err != nil {
   125  		return 0, "", fmt.Errorf("failed to open %v: %w", imageFile, err)
   126  	}
   127  	defer unix.Close(image)
   128  	loopControl, err := unix.Open("/dev/loop-control", unix.O_RDWR, 0)
   129  	if err != nil {
   130  		return 0, "", fmt.Errorf("failed to open /dev/loop-control: %w", err)
   131  	}
   132  	defer unix.Close(loopControl)
   133  	loopIndex, err := unix.IoctlRetInt(loopControl, unix.LOOP_CTL_GET_FREE)
   134  	if err != nil {
   135  		return 0, "", fmt.Errorf("LOOP_CTL_GET_FREE failed: %w", err)
   136  	}
   137  	loopFile := fmt.Sprintf("/dev/loop%v", loopIndex)
   138  	loop, err := unix.Open(loopFile, unix.O_RDWR, 0)
   139  	if err != nil {
   140  		return 0, "", fmt.Errorf("failed to open %v: %w", loopFile, err)
   141  	}
   142  	if err := unix.IoctlSetInt(loop, unix.LOOP_SET_FD, image); err != nil {
   143  		unix.Close(loop)
   144  		return 0, "", fmt.Errorf("LOOP_SET_FD failed: %w", err)
   145  	}
   146  	info := &unix.LoopInfo64{
   147  		Flags: unix.LO_FLAGS_PARTSCAN,
   148  	}
   149  	for i := 0; i < len(imageFile); i++ {
   150  		info.File_name[i] = imageFile[i]
   151  	}
   152  	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(loop), unix.LOOP_SET_STATUS64,
   153  		uintptr(unsafe.Pointer(info))); err != 0 {
   154  		unix.Close(loop)
   155  		return 0, "", fmt.Errorf("LOOP_SET_STATUS64 failed: %w", err)
   156  	}
   157  	return loop, loopFile, nil
   158  }