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 }