github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/tools/syz-imagegen/imagegen.go (about)

     1  // Copyright 2018 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  // As we use syscall package:
     5  //go:build linux
     6  
     7  // syz-imagegen generates sys/linux/test/syz_mount_image_* test files.
     8  // It requires the following packages to be installed:
     9  //
    10  //	f2fs-tools xfsprogs reiserfsprogs gfs2-utils ocfs2-tools genromfs erofs-utils makefs udftools
    11  //	mtd-utils nilfs-tools squashfs-tools genisoimage jfsutils exfat-utils ntfs-3g hfsprogs
    12  //	bcachefs-tools.
    13  package main
    14  
    15  import (
    16  	"bufio"
    17  	"bytes"
    18  	"encoding/binary"
    19  	"encoding/json"
    20  	"errors"
    21  	"flag"
    22  	"fmt"
    23  	"hash/crc32"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"runtime"
    28  	"slices"
    29  	"strings"
    30  	"syscall"
    31  	"time"
    32  	"unsafe"
    33  
    34  	"github.com/google/syzkaller/pkg/image"
    35  	"github.com/google/syzkaller/pkg/osutil"
    36  	"github.com/google/syzkaller/pkg/tool"
    37  	"github.com/google/syzkaller/prog"
    38  	_ "github.com/google/syzkaller/sys"
    39  	"github.com/google/syzkaller/sys/targets"
    40  )
    41  
    42  // FileSystem represents one file system.
    43  // Each FileSystem produces multiple images, see MkfsFlagCombinations and Image type.
    44  type FileSystem struct {
    45  	// Name of the file system. Needs to match the name passed to mount().
    46  	Name string `json:"name"`
    47  	// By default, syz_mount_image$NAME are generated. SyscallSuffix overrides the part after $.
    48  	SyscallSuffix string `json:"syscall_suffix"`
    49  	// Imagegen autodetects size for images starting from MinSize and then repeatedly doubling it if mkfs fails.
    50  	MinSize int `json:"min_size"`
    51  	// Don't populate this image with files (can't mount read-write).
    52  	ReadOnly bool `json:"read_only"`
    53  	// These flags are always appended to mkfs as is.
    54  	MkfsFlags []string `json:"mkfs_flags"`
    55  	// Generate images for all possible permutations of these flag combinations.
    56  	MkfsFlagCombinations [][]string `json:"mkfs_flag_combinations"`
    57  	// Limit the resulting number of seeds (in case there are too many possible combinations).
    58  	MaxSeeds int `json:"max_seeds"`
    59  	// Custom mkfs invocation, if nil then mkfs.name is invoked in a standard way.
    60  	Mkfs func(img *Image) error
    61  }
    62  
    63  // nolint:lll
    64  var fileSystems = []FileSystem{
    65  	{
    66  		Name:      "f2fs",
    67  		MinSize:   64 << 20,
    68  		MkfsFlags: []string{"-e cold"},
    69  		MkfsFlagCombinations: [][]string{
    70  			{"-a 0", "-a 1"},
    71  			{"-s 1", "-s 2"},
    72  			{
    73  				"",
    74  				"-O encrypt",
    75  				"-O extra_attr",
    76  				"-O extra_attr -O flexible_inline_xattr -O inode_checksum -O inode_crtime -O project_quota",
    77  			},
    78  		},
    79  	},
    80  	{
    81  		Name:    "btrfs",
    82  		MinSize: 16 << 20,
    83  		MkfsFlagCombinations: [][]string{
    84  			{"", "-M"},
    85  			{"", "-K"},
    86  			{"--csum crc32c", "--csum xxhash", "--csum sha256", "--csum blake2"},
    87  			{"--nodesize 4096 -O mixed-bg", "-O extref", "-O raid56", "-O no-holes", "-O raid1c34"},
    88  		},
    89  	},
    90  	{
    91  		Name:      "vfat",
    92  		MinSize:   64 << 10,
    93  		MkfsFlags: []string{"-n", "SYZKALLER"},
    94  		MkfsFlagCombinations: [][]string{
    95  			{"", "-a -I"},
    96  			{"", "-h 3 -f 4"},
    97  			{"-s 1", "-s 8", "-s 64"},
    98  			{
    99  				"-F 12 -r 64 -S 512",
   100  				"-F 12 -r 64 -S 2048 -A",
   101  				"-F 16 -r 112 -S 512",
   102  				"-F 32 -r 768 -S 512",
   103  				"-F 32 -r 768 -S 2048 -A",
   104  			},
   105  		},
   106  	},
   107  	{
   108  		Name:      "msdos",
   109  		MinSize:   64 << 10,
   110  		MkfsFlags: []string{"-n", "SYZKALLER"},
   111  		MkfsFlagCombinations: [][]string{
   112  			{"", "-a -I"},
   113  			{"", "-h 3 -f 4"},
   114  			{"-s 1", "-s 8", "-s 64"},
   115  			{
   116  				"-F 12 -r 64 -S 512",
   117  				"-F 12 -r 64 -S 2048 -A",
   118  				"-F 16 -r 112 -S 512",
   119  				"-F 32 -r 768 -S 512",
   120  				"-F 32 -r 768 -S 2048 -A",
   121  			},
   122  		},
   123  	},
   124  	{
   125  		Name:      "exfat",
   126  		MinSize:   128 << 10,
   127  		MkfsFlags: []string{"-i", "0x12341234"},
   128  		MkfsFlagCombinations: [][]string{
   129  			{"", "-p 3"},
   130  		},
   131  	},
   132  	{
   133  		Name:      "bfs",
   134  		MinSize:   4 << 10,
   135  		ReadOnly:  true, // creating files fails with ENOPERM
   136  		MkfsFlags: []string{"-V", "syzkal", "-F", "syzkal"},
   137  		MkfsFlagCombinations: [][]string{
   138  			{"-N 48", "-N 127", "-N 512"},
   139  		},
   140  	},
   141  	{
   142  		Name:      "xfs",
   143  		MinSize:   16 << 20,
   144  		MkfsFlags: []string{"-l", "internal", "--unsupported"},
   145  		MkfsFlagCombinations: [][]string{
   146  			// Most XFS options are inter-dependent and total number of combinations is huge,
   147  			// so we enumerate some combinations that don't produce errors.
   148  			{
   149  				"-b size=512 -i size=256  -d agcount=2 -m crc=0 -m finobt=0 -m rmapbt=0 -m reflink=0 -i sparse=0 -i maxpct=25  -i attr=1 -i projid32bit=0 -l lazy-count=0",
   150  				"-b size=2k  -i size=1024 -d agcount=2 -m crc=0 -m finobt=0 -m rmapbt=0 -m reflink=0 -i sparse=0 -i maxpct=5   -i attr=1 -i projid32bit=0 -l lazy-count=1",
   151  				"-b size=4k  -i size=2048 -d agcount=4 -m crc=0 -m finobt=0 -m rmapbt=0 -m reflink=0 -i sparse=0 -i maxpct=90  -i attr=2 -i projid32bit=1 -l lazy-count=0",
   152  				"-b size=1k  -i size=512  -d agcount=2 -m crc=1 -m finobt=0 -m rmapbt=0 -m reflink=0 -i sparse=0 -i maxpct=20  -i attr=2 -i projid32bit=1 -l lazy-count=1",
   153  				"-b size=2k  -i size=1024 -d agcount=4 -m crc=1 -m finobt=1 -m rmapbt=1 -m reflink=1 -i sparse=0 -i maxpct=3   -i attr=2 -i projid32bit=1 -l lazy-count=1",
   154  				"-b size=4k  -i size=2048 -d agcount=1 -m crc=1 -m finobt=0 -m rmapbt=1 -m reflink=0 -i sparse=0 -i maxpct=100 -i attr=2 -i projid32bit=1 -l lazy-count=1",
   155  				"-b size=1k  -i size=512  -d agcount=2 -m crc=1 -m finobt=0 -m rmapbt=0 -m reflink=0 -i sparse=1 -i maxpct=99  -i attr=2 -i projid32bit=1 -l lazy-count=1",
   156  				"-b size=2k  -i size=1024 -d agcount=1 -m crc=1 -m finobt=1 -m rmapbt=1 -m reflink=1 -i sparse=1 -i maxpct=50  -i attr=2 -i projid32bit=1 -l lazy-count=1",
   157  				"-b size=4k  -i size=1024 -d agcount=1 -m crc=1 -m finobt=1 -m rmapbt=0 -m reflink=1 -i sparse=1 -i maxpct=10  -i attr=2 -i projid32bit=1 -l lazy-count=1",
   158  			},
   159  			{"-l sunit=16", "-l sunit=64", "-l sunit=128", "-l su=8k"},
   160  		},
   161  	},
   162  	{
   163  		Name:    "minix",
   164  		MinSize: 16 << 10,
   165  		MkfsFlagCombinations: [][]string{
   166  			{
   167  				"-1 -n 14",
   168  				"-1 -n 30",
   169  				"-2 -n 14",
   170  				"-2 -n 30",
   171  				"-3 -n 60",
   172  			},
   173  			{"-i 16", "-i 64", "-i 1024"},
   174  		},
   175  	},
   176  	{
   177  		Name:      "reiserfs",
   178  		MinSize:   4 << 20,
   179  		ReadOnly:  true, // mounting this crashes my host kernel
   180  		MkfsFlags: []string{"-f", "-f", "-l", "syzkaller"},
   181  		MkfsFlagCombinations: [][]string{
   182  			{"-b 4096", "-b 8192"},
   183  			{"-h r5", "-h rupasov", "-h tea"},
   184  			{"--format 3.5", "--format 3.6 -u 12312312-1233-1233-1231-123413412412"},
   185  			{
   186  				"-s 513",
   187  				"-s 8193 -t 128",
   188  				"-s 8193 -t 1024",
   189  				"-s 15749 -t 128",
   190  				"-s 15749 -t 1024",
   191  			},
   192  		},
   193  	},
   194  	{
   195  		Name:      "jfs",
   196  		MinSize:   16 << 20,
   197  		MkfsFlags: []string{"-q"},
   198  		MkfsFlagCombinations: [][]string{
   199  			{"", "-s 1M"},
   200  			{"", "-O"},
   201  		},
   202  	},
   203  	{
   204  		Name:      "ntfs",
   205  		MinSize:   1 << 20,
   206  		MkfsFlags: []string{"-f", "-F", "-L", "syzkaller"},
   207  		MkfsFlagCombinations: [][]string{
   208  			{
   209  				"-s 256 -c 2048",
   210  				"-s 512 -c 1024",
   211  				"-s 512 -c 4096",
   212  				"-s 1024 -c 4096",
   213  				"-s 1024 -c 65536",
   214  				"-s 2048 -c 2048",
   215  				"-s 2048 -c 4096",
   216  				"-s 4096 -c 4096",
   217  				"-s 4096 -c 131072",
   218  			},
   219  			{"", "-I"},
   220  		},
   221  	},
   222  	{
   223  		Name:      "ntfs3",
   224  		MinSize:   1 << 20,
   225  		MkfsFlags: []string{"-f", "-F", "-L", "syzkaller"},
   226  		MkfsFlagCombinations: [][]string{
   227  			{
   228  				"-s 512 -c 1024",
   229  				"-s 512 -c 4096",
   230  				"-s 1024 -c 4096",
   231  				"-s 1024 -c 65536",
   232  				"-s 2048 -c 2048",
   233  				"-s 2048 -c 4096",
   234  				"-s 4096 -c 4096",
   235  				"-s 4096 -c 131072",
   236  			},
   237  			{"", "-I"},
   238  		},
   239  		Mkfs: func(img *Image) error {
   240  			_, err := runCmd("mkfs.ntfs", append(img.flags, img.disk)...)
   241  			return err
   242  		},
   243  	},
   244  	{
   245  		Name:      "ext4",
   246  		MinSize:   64 << 10,
   247  		MaxSeeds:  64,
   248  		MkfsFlags: []string{"-L", "syzkaller", "-U", "clear", "-E", "test_fs"},
   249  		MkfsFlagCombinations: [][]string{
   250  			{"-t ext2", "-t ext3", "-t ext4"},
   251  			// Total number of combinations is too large and there are lots of dependencies between flags,
   252  			// so we create just few permutations generated with fair dice rolls.
   253  			// TODO: We also need to give some combination of -E encoding=utf8/utf8-12.1 and -E encoding_flags=strict,
   254  			// but mounting such fs on my host fails with "Filesystem with casefold feature cannot be mounted without CONFIG_UNICODE".
   255  			{"-b 1024", "-b 2048", "-b 4096"},
   256  			{"-I 128", "-I 256", "-I 512", "-I 1024"},
   257  			{"-E lazy_itable_init=0", "-E lazy_itable_init=1"},
   258  			{"-E packed_meta_blocks=0", "-E packed_meta_blocks=1"},
   259  			// Otherwise we get: invalid blocks '^64bit' on device 'num_backup_sb=0'.
   260  			// And: "Extents MUST be enabled for a 64-bit filesystem. Pass -O extents to rectify".
   261  			{"-O ^64bit -E num_backup_sb=0", "-O 64bit -E num_backup_sb=1 -O extents"},
   262  			// Can't support bigalloc feature without extents feature.
   263  			{"-O ^bigalloc", "-O bigalloc -O extents"},
   264  			{"-O ^dir_index", "-O dir_index"},
   265  			{"-O ^dir_nlink", "-O dir_nlink"},
   266  			{"-O ^ea_inode", "-O ea_inode"},
   267  			{"-O ^encrypt", "-O encrypt"},
   268  			{"-O ^ext_attr", "-O ext_attr"},
   269  			{"-O ^extra_isize", "-O extra_isize"},
   270  			{"-O ^flex_bg", "-O flex_bg"},
   271  			{"-O ^huge_file", "-O huge_file"},
   272  			// 128 byte inodes are too small for inline data; specify larger size.
   273  			{"-O ^inline_data", "-O inline_data -I 512"},
   274  			{"-O ^large_dir", "-O large_dir"},
   275  			{"-O ^metadata_csum", "-O metadata_csum"},
   276  			{"", "-O ^sparse_super"},
   277  			// Otherwise we get: The resize_inode and meta_bg features are not compatible.
   278  			// Another dependency: reserved online resize blocks not supported on non-sparse filesystem.
   279  			{"-O ^meta_bg -O ^resize_inode", "-O meta_bg -O ^resize_inode", "-O ^meta_bg -O resize_inode -O sparse_super"},
   280  			{"-O ^mmp", "-O mmp"},
   281  			{"-O ^quota", "-O quota"},
   282  			{"-O ^uninit_bg", "-O uninit_bg"},
   283  			{"-O ^verity", "-O verity -O extents"},
   284  			{"-O ^has_journal", "-O has_journal  -J size=1024"},
   285  		},
   286  	},
   287  	{
   288  		Name:      "gfs2",
   289  		MinSize:   16 << 20,
   290  		ReadOnly:  true, // mounting this crashes my host kernel
   291  		MkfsFlags: []string{"-O", "-t", "syz:syz"},
   292  		MkfsFlagCombinations: [][]string{
   293  			{
   294  				// Lots of combinations lead to huge images that don't fit into 4MB encodingexec buffer.
   295  				"-b 1024 -o sunit=1024 -o swidth=1024 -c 1M -j 1 -J 8 -o align=0 -p lock_dlm",
   296  				"-b 1024 -o sunit=1024 -o swidth=1024 -c 1M -j 1 -J 8 -o align=1 -p lock_nolock",
   297  				"-b 1024 -o sunit=1024 -o swidth=1024 -c 4M -j 1 -J 8 -o align=0 -p lock_dlm",
   298  				"-b 1024 -o sunit=1024 -o swidth=1024 -c 4M -j 1 -J 8 -o align=1 -p lock_nolock",
   299  				"-b 1024 -o sunit=4096 -o swidth=8192 -c 1M -j 1 -J 8 -o align=0 -p lock_dlm",
   300  				"-b 1024 -o sunit=4096 -o swidth=8192 -c 1M -j 1 -J 8 -o align=1 -p lock_nolock",
   301  				"-b 1024 -o sunit=4096 -o swidth=8192 -c 4M -j 1 -J 8 -o align=0 -p lock_dlm",
   302  				"-b 1024 -o sunit=4096 -o swidth=8192 -c 4M -j 1 -J 8 -o align=1 -p lock_nolock",
   303  				"-b 2048 -o sunit=2048 -o swidth=4096 -c 1M -j 1 -J 8 -o align=0 -p lock_dlm",
   304  				"-b 2048 -o sunit=2048 -o swidth=4096 -c 1M -j 1 -J 8 -o align=1 -p lock_nolock",
   305  				"-b 2048 -o sunit=2048 -o swidth=4096 -c 4M -j 1 -J 8 -o align=0 -p lock_dlm",
   306  				"-b 2048 -o sunit=2048 -o swidth=4096 -c 4M -j 1 -J 8 -o align=1 -p lock_nolock",
   307  				"-b 2048 -o sunit=4096 -o swidth=4096 -c 1M -j 1 -J 8 -o align=0 -p lock_dlm",
   308  				"-b 2048 -o sunit=4096 -o swidth=4096 -c 1M -j 1 -J 8 -o align=1 -p lock_nolock",
   309  				"-b 2048 -o sunit=4096 -o swidth=4096 -c 4M -j 1 -J 8 -o align=0 -p lock_dlm",
   310  				"-b 2048 -o sunit=4096 -o swidth=4096 -c 4M -j 1 -J 8 -o align=1 -p lock_nolock",
   311  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 1M -j 1 -J 8 -o align=0 -p lock_dlm",
   312  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 1M -j 1 -J 8 -o align=1 -p lock_nolock",
   313  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 1M -j 2 -J 16 -o align=0 -p lock_dlm",
   314  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 1M -j 2 -J 16 -o align=1 -p lock_nolock",
   315  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 4M -j 1 -J 8 -o align=0 -p lock_dlm",
   316  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 4M -j 1 -J 8 -o align=1 -p lock_nolock",
   317  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 4M -j 2 -J 16 -o align=0 -p lock_dlm",
   318  				"-b 4096 -o sunit=4096 -o swidth=4096 -c 4M -j 2 -J 16 -o align=1 -p lock_nolock",
   319  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 1M -j 1 -J 8 -o align=0 -p lock_dlm",
   320  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 1M -j 1 -J 8 -o align=1 -p lock_nolock",
   321  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 1M -j 2 -J 16 -o align=0 -p lock_dlm",
   322  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 1M -j 2 -J 16 -o align=1 -p lock_nolock",
   323  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 4M -j 1 -J 8 -o align=0 -p lock_dlm",
   324  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 4M -j 1 -J 8 -o align=1 -p lock_nolock",
   325  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 4M -j 2 -J 16 -o align=0 -p lock_dlm",
   326  				"-b 4096 -o sunit=8192 -o swidth=16384 -c 4M -j 2 -J 16 -o align=1 -p lock_nolock",
   327  			},
   328  		},
   329  	},
   330  	{
   331  		Name:     "ocfs2",
   332  		MinSize:  8 << 20,
   333  		ReadOnly: true, // mounting this crashes my host kernel
   334  		MkfsFlagCombinations: [][]string{
   335  			{"-b 512", "-b 4096"},
   336  			{"-C 4K", "-C 16K", "-C 1M"},
   337  			{"-J block32", "-J block64"},
   338  			{"-T mail -N 1 -M local", "-T datafiles -N 2 -M local", "-T vmstore -N 2 -M cluster"},
   339  			{"", "--fs-features backup-super,sparse,unwritten,inline-data,extended-slotmap,metaecc,refcount,xattr,usrquota,grpquota,indexed-dirs,discontig-bg"},
   340  		},
   341  	},
   342  	{
   343  		Name:    "cramfs",
   344  		MinSize: 1 << 10,
   345  		// The file system is read-only and requires a root directory at creation time.
   346  		ReadOnly:  true,
   347  		MkfsFlags: []string{},
   348  		MkfsFlagCombinations: [][]string{
   349  			{"-b 4096", "-b 8192"},
   350  			{"-N big", "-N little"},
   351  		},
   352  		Mkfs: func(img *Image) error {
   353  			_, err := runCmd("mkfs.cramfs", append(img.flags, img.templateDir, img.disk)...)
   354  			return err
   355  		},
   356  	},
   357  	{
   358  		Name:    "romfs",
   359  		MinSize: 1 << 10,
   360  		// The file system is read-only and requires a root directory at creation time.
   361  		ReadOnly: true,
   362  		MkfsFlagCombinations: [][]string{
   363  			{"-a 16", "-a 256"},
   364  		},
   365  		Mkfs: func(img *Image) error {
   366  			_, err := runCmd("genromfs", append(img.flags, "-f", img.disk, "-d", img.templateDir)...)
   367  			return err
   368  		},
   369  	},
   370  	{
   371  		Name:    "erofs",
   372  		MinSize: 1 << 10,
   373  		// The file system is read-only and requires a root directory at creation time.
   374  		ReadOnly:  true,
   375  		MkfsFlags: []string{"-T1000"},
   376  		MkfsFlagCombinations: [][]string{
   377  			{"-z lz4", "-z lz4hc,1", "-z lz4hc,9"},
   378  			{"-x 1", "-x 2"},
   379  			{"", "-E legacy-compress"},
   380  		},
   381  		Mkfs: func(img *Image) error {
   382  			_, err := runCmd("mkfs.erofs", append(img.flags, img.disk, img.templateDir)...)
   383  			return err
   384  		},
   385  	},
   386  	{
   387  		Name:    "efs",
   388  		MinSize: 1 << 10,
   389  		// The file system is read-only and requires a root directory at creation time.
   390  		ReadOnly:  true,
   391  		MkfsFlags: []string{"-M", "65536"},
   392  		MkfsFlagCombinations: [][]string{
   393  			{
   394  				"-t ffs -B big    -S 128  -o bsize=4k,version=1,optimization=space",
   395  				"-t ffs -B big    -S 512  -o bsize=8k,version=1,optimization=time",
   396  				"-t ffs -B big    -S 2048 -o bsize=8k,version=1,optimization=space",
   397  				"-t ffs -B little -S 128  -o bsize=4k,version=1,optimization=time",
   398  				"-t ffs -B little -S 512  -o bsize=4k,version=1,optimization=space",
   399  				"-t ffs -B little -S 2048 -o bsize=8k,version=1,optimization=time",
   400  				"-t ffs -B big    -S 128  -o bsize=4k,version=2,optimization=space",
   401  				"-t ffs -B big    -S 512  -o bsize=8k,version=2,optimization=time",
   402  				"-t ffs -B big    -S 2048 -o bsize=8k,version=2,optimization=space",
   403  				"-t ffs -B little -S 128  -o bsize=4k,version=2,optimization=time",
   404  				"-t ffs -B little -S 512  -o bsize=4k,version=2,optimization=space",
   405  				"-t ffs -B little -S 2048 -o bsize=8k,version=2,optimization=time",
   406  				"-t cd9660",
   407  				"-t cd9660 -o rockridge",
   408  			},
   409  		},
   410  		Mkfs: func(img *Image) error {
   411  			_, err := runCmd("makefs", append(img.flags, img.disk, img.templateDir)...)
   412  			return err
   413  		},
   414  	},
   415  	{
   416  		Name:      "udf",
   417  		MinSize:   64 << 10,
   418  		MkfsFlags: []string{"-u", "1234567812345678"},
   419  		MkfsFlagCombinations: [][]string{
   420  			{"-b 512", "-b 1024", "-b 4096"},
   421  			{
   422  				"-m hd -r 1.01",
   423  				"-m hd -r 1.01 --ad=short",
   424  				"-m hd -r 1.50 --ad=long",
   425  				"-m hd -r 2.01",
   426  				"-m hd -r 2.01 --space=unallocbitmap",
   427  				"-m mo -r 1.01",
   428  				"-m mo -r 1.01  --ad=long",
   429  				"-m mo -r 1.50 --space=unalloctable",
   430  				"-m mo -r 1.50 --space=unallocbitmap",
   431  				"-m mo -r 2.01 --noefe --ad=short",
   432  				"-m cdrw -r 1.50",
   433  				"-m cdrw -r 1.50 --space=unalloctable --ad=long",
   434  				"-m cdrw -r 2.01",
   435  				"-m dvdrw -r 1.50 --space=unallocbitmap --ad=short",
   436  				"-m dvdrw -r 2.01 --sparspace=512 --space=unalloctable --noefe",
   437  				"-m dvdrw -r 2.01 --sparspace=512",
   438  				"-m dvdram -r 1.50  --ad=long",
   439  				"-m dvdram -r 2.01 ",
   440  				"-m dvdram -r 2.01 --space=unallocbitmap  --ad=long",
   441  			},
   442  		},
   443  	},
   444  	{
   445  		Name:      "jffs2",
   446  		MinSize:   1 << 10,
   447  		ReadOnly:  true,
   448  		MkfsFlags: []string{"--squash", "--faketime", "--with-xattr"},
   449  		MkfsFlagCombinations: [][]string{
   450  			{"--pagesize 4096", "--pagesize 8192"},
   451  			{"--little-endian", "--big-endian"},
   452  			{"--compression-mode=none", "--compression-mode=size"},
   453  		},
   454  		Mkfs: func(img *Image) error {
   455  			_, err := runCmd("mkfs.jffs2", append(img.flags, "-o", img.disk, "--root", img.templateDir)...)
   456  			return err
   457  		},
   458  	},
   459  	{
   460  		Name:    "nilfs2",
   461  		MinSize: 1 << 20,
   462  		MkfsFlagCombinations: [][]string{
   463  			{"-b 1024", "-b 2048", "-b 4096"},
   464  			{"-B 16", "-B 64", "-B 512"},
   465  			{"-O none", "-O block_count"},
   466  		},
   467  	},
   468  	{
   469  		Name:     "squashfs",
   470  		MinSize:  1 << 10,
   471  		ReadOnly: true,
   472  		MkfsFlagCombinations: [][]string{
   473  			{"-comp gzip -b 4k", "-comp lzo -b 16k", "-comp xz -b 1M"},
   474  			{"", "-noI -noX", "-noI -noD -noF -noX"},
   475  			{"-no-fragments", "-always-use-fragments -nopad"},
   476  		},
   477  		Mkfs: func(img *Image) error {
   478  			os.Remove(img.disk)
   479  			_, err := runCmd("mksquashfs", append([]string{img.templateDir, img.disk}, img.flags...)...)
   480  			return err
   481  		},
   482  	},
   483  	{
   484  		Name:      "iso9660",
   485  		MinSize:   1 << 10,
   486  		ReadOnly:  true,
   487  		MkfsFlags: []string{"-abstract", "file1", "-biblio", "file2", "-copyright", "file3", "-publisher", "syzkaller"},
   488  		MkfsFlagCombinations: [][]string{
   489  			{"", "-J", "-J -udf"},
   490  			{"-pad", "-no-pad"},
   491  			{"", "-hfs", "-apple -r"},
   492  		},
   493  		Mkfs: func(img *Image) error {
   494  			_, err := runCmd("genisoimage", append(img.flags, "-o", img.disk, img.templateDir)...)
   495  			return err
   496  		},
   497  	},
   498  	{
   499  		Name:    "hfs",
   500  		MinSize: 16 << 10,
   501  		MkfsFlagCombinations: [][]string{
   502  			{"", "-P"},
   503  			{"", "-c a=1024,b=512,c=128,d=256"},
   504  		},
   505  	},
   506  	{
   507  		Name:    "hfsplus",
   508  		MinSize: 512 << 10,
   509  		MkfsFlagCombinations: [][]string{
   510  			{"", "-P"},
   511  			{"", "-s"},
   512  			{"-b 512", "-b 1024", "-b 2048"},
   513  		},
   514  	},
   515  	{
   516  		Name:    "bcachefs",
   517  		MinSize: 512 << 10,
   518  		MkfsFlagCombinations: [][]string{
   519  			{"", "--encrypted --no_passphrase"},
   520  			{"", "--compression=lz4"},
   521  			{"", "--data_checksum=none --metadata_checksum=none"},
   522  		},
   523  	},
   524  	{
   525  		Name:     parttable,
   526  		MinSize:  1 << 20,
   527  		ReadOnly: true,
   528  		MkfsFlagCombinations: [][]string{{
   529  			// You can test/explore these commands with:
   530  			// $ rm -f disk && touch disk && fallocate -l 16M disk && fdisk disk
   531  			"g n 1 34 47 n 2 48 96 n 3 97 2013 t 1 uefi t 2 linux t 3 swap w",
   532  			"g n 1 100 200 n 2 50 80 n 3 1000 1900 M a c r x n 1 syzkaller A 1 r w",
   533  			"g n 3 200 300 n 7 400 500 n 2 600 700 x l 700 r w",
   534  			"G n 1 16065 16265 w",
   535  			"G n 16 20000 30000 i w",
   536  			"o n p 1 2048 4096 n p 3 4097 4100 n e 2 4110 4200 a 1 t 2 a5 t 3 uefi b y x 1 e r w",
   537  			"o n p 1 2048 3071 n e 3 3072 32767 n 6200 7999 n 10240 20000 w",
   538  			"s c 1 a 2 w",
   539  		}},
   540  		Mkfs: func(img *Image) error {
   541  			cmd := exec.Command("fdisk", "--noauto-pt", "--color=always", img.disk)
   542  			cmd.Stdin = strings.NewReader(strings.Join(img.flags, "\n"))
   543  			output, err := osutil.Run(10*time.Minute, cmd)
   544  			if err != nil {
   545  				return err
   546  			}
   547  			// It's hard to understand if any of the commands fail,
   548  			// fdisk does not exit with an error and print assorted error messages.
   549  			// So instead we run it with --color=always and grep for red color marker
   550  			// in the output (ESC[31m).
   551  			if bytes.Contains(output, []byte{0x1b, 0x5b, 0x33, 0x31, 0x6d}) {
   552  				return errors.New(string(output))
   553  			}
   554  			return err
   555  		},
   556  	},
   557  }
   558  
   559  const (
   560  	syzMountImage    = "syz_mount_image"
   561  	syzReadPartTable = "syz_read_part_table"
   562  	parttable        = "parttable"
   563  )
   564  
   565  func (fs FileSystem) filePrefix() string {
   566  	if fs.Name == parttable {
   567  		return syzReadPartTable
   568  	}
   569  	return syzMountImage + "_" + fs.suffix()
   570  }
   571  
   572  func (fs FileSystem) suffix() string {
   573  	if fs.SyscallSuffix != "" {
   574  		return fs.SyscallSuffix
   575  	}
   576  	return fs.Name
   577  }
   578  
   579  // Image represents one image we generate for a file system.
   580  type Image struct {
   581  	target      *prog.Target
   582  	fs          FileSystem
   583  	flags       []string // mkfs flags
   584  	index       int      // index within the file system
   585  	size        int      // image size (autodetected starting from fs.MinSize)
   586  	hash        uint32   // crc32 hash of the resulting image to detect duplicates
   587  	disk        string   // disk image file name
   588  	templateDir string   // name of a directory with contents for the file system (shared across all images)
   589  	done        chan error
   590  }
   591  
   592  func (img *Image) String() string {
   593  	size := fmt.Sprintf("%vKB", img.size>>10)
   594  	if img.size >= 1<<20 {
   595  		size = fmt.Sprintf("%vMB", img.size>>20)
   596  	}
   597  	return fmt.Sprintf("#%02v: mkfs.%v[%5v] %v", img.index, img.fs.Name, size, img.flags)
   598  }
   599  
   600  var errShutdown = errors.New("shutdown")
   601  
   602  func main() {
   603  	var (
   604  		flagList      = flag.Bool("list", false, "list supported file systems and exit")
   605  		flagVerbose   = flag.Bool("v", false, "print successfully created images")
   606  		flagDebug     = flag.Bool("debug", false, "print lots of debugging output")
   607  		flagPopulate  = flag.String("populate", "", "populate the specified image with files (for internal use)")
   608  		flagKeepImage = flag.Bool("keep", false, "save disk images as .img files")
   609  		flagFS        = flag.String("fs", "", "comma-separated list of filesystems to generate, all if empty")
   610  		flagFromJSON  = flag.String("from_json", "", "load the fs config from the file and generate seeds for it")
   611  	)
   612  	flag.Parse()
   613  	if *flagDebug {
   614  		*flagVerbose = true
   615  	}
   616  	if *flagPopulate != "" {
   617  		if err := populate(*flagPopulate, *flagFS); err != nil {
   618  			tool.Fail(err)
   619  		}
   620  		return
   621  	}
   622  	target, err := prog.GetTarget(targets.Linux, targets.AMD64)
   623  	if err != nil {
   624  		tool.Fail(err)
   625  	}
   626  	if *flagFromJSON != "" {
   627  		fs := loadFilesystem(*flagFromJSON)
   628  		fileSystems = append(fileSystems, fs)
   629  		// Generate seeds only for the specific filesystem.
   630  		*flagFS = fs.suffix()
   631  	}
   632  	addEmptyImages(target)
   633  	images, err := generateImages(target, *flagFS, *flagList)
   634  	if err != nil {
   635  		tool.Fail(err)
   636  	}
   637  	if *flagList {
   638  		return
   639  	}
   640  	// Create a single template dir for file systems that need the root dir at creation time.
   641  	templateDir, err := os.MkdirTemp("", "syz-imagegen")
   642  	if err != nil {
   643  		tool.Fail(err)
   644  	}
   645  	defer os.RemoveAll(templateDir)
   646  	if err := populateDir(templateDir); err != nil {
   647  		tool.Fail(err)
   648  	}
   649  	shutdown := make(chan struct{})
   650  	osutil.HandleInterrupts(shutdown)
   651  	procs := runtime.NumCPU()
   652  	requests := make(chan *Image, procs)
   653  	go func() {
   654  		for _, img := range images {
   655  			img.templateDir = templateDir
   656  			requests <- img
   657  		}
   658  		close(requests)
   659  	}()
   660  	for p := 0; p < procs; p++ {
   661  		go func() {
   662  			for img := range requests {
   663  				select {
   664  				case <-shutdown:
   665  					img.done <- errShutdown
   666  				default:
   667  					img.done <- img.generate()
   668  				}
   669  			}
   670  		}()
   671  	}
   672  	printResults(images, shutdown, *flagKeepImage, *flagVerbose)
   673  }
   674  
   675  func addEmptyImages(target *prog.Target) {
   676  	// Since syz_mount_image calls are no_generate we need to add at least some
   677  	// empty seeds for all the filesystems.
   678  	have := make(map[string]bool)
   679  	for _, fs := range fileSystems {
   680  		have[fs.Name] = true
   681  	}
   682  	for _, call := range target.Syscalls {
   683  		if call.CallName != syzMountImage {
   684  			continue
   685  		}
   686  		name := call.Attrs.Filesystem
   687  		if name == "" {
   688  			name = strings.TrimPrefix(call.Name, syzMountImage+"$")
   689  		}
   690  		if have[name] {
   691  			continue
   692  		}
   693  		fileSystems = append(fileSystems, FileSystem{
   694  			Name:      name,
   695  			MinSize:   64 << 10,
   696  			ReadOnly:  true,
   697  			MkfsFlags: []string{"fake image"},
   698  			Mkfs: func(img *Image) error {
   699  				// Fill the image with unique 4-byte values.
   700  				// This allows hints mutation to easily guess magic numbers and checksums.
   701  				f, err := os.Create(img.disk)
   702  				if err != nil {
   703  					return err
   704  				}
   705  				defer f.Close()
   706  				buf := bufio.NewWriter(f)
   707  				defer buf.Flush()
   708  				for i := uint32(0); i < uint32(img.size/int(unsafe.Sizeof(i))); i++ {
   709  					if err := binary.Write(buf, binary.LittleEndian, i+0x7b3184b5); err != nil {
   710  						return err
   711  					}
   712  				}
   713  				return nil
   714  			},
   715  		})
   716  	}
   717  }
   718  
   719  func printResults(images []*Image, shutdown chan struct{}, keepImage, verbose bool) {
   720  	good, failed := 0, 0
   721  	hashes := make(map[uint32][]*Image)
   722  	for _, img := range images {
   723  		err := <-img.done
   724  		if img.disk != "" && !keepImage {
   725  			os.Remove(img.disk)
   726  		}
   727  		select {
   728  		case <-shutdown:
   729  			err = errShutdown
   730  		default:
   731  		}
   732  		if err == errShutdown {
   733  			continue
   734  		}
   735  		res := "ok"
   736  		if err != nil {
   737  			res = fmt.Sprintf("failed:\n\t%v", err)
   738  		}
   739  		if verbose || err != nil {
   740  			fmt.Printf("%v: %v\n", img, res)
   741  		}
   742  		if err != nil {
   743  			failed++
   744  			continue
   745  		}
   746  		hashes[img.hash] = append(hashes[img.hash], img)
   747  		good++
   748  	}
   749  	fmt.Printf("generated images: %v/%v\n", good, len(images))
   750  	for _, img := range images {
   751  		group := hashes[img.hash]
   752  		if len(group) <= 1 {
   753  			continue
   754  		}
   755  		delete(hashes, img.hash)
   756  		fmt.Printf("equal images:\n")
   757  		for _, img := range group {
   758  			fmt.Printf("\tmkfs.%v %v\n", img.fs.Name, img.flags)
   759  		}
   760  	}
   761  	if failed != 0 {
   762  		os.Exit(1)
   763  	}
   764  }
   765  
   766  func generateImages(target *prog.Target, flagFS string, list bool) ([]*Image, error) {
   767  	var images []*Image
   768  	for _, fs := range fileSystems {
   769  		if flagFS != "" && !strings.Contains(","+flagFS+",", ","+fs.suffix()+",") {
   770  			continue
   771  		}
   772  		newImages := enumerateFlags(target, fs)
   773  		images = append(images, newImages...)
   774  		if list {
   775  			fmt.Printf("%v [%v images]\n", fs.Name, len(newImages))
   776  			for i, image := range newImages {
   777  				fmt.Printf("#%d: %q\n", i, image.flags)
   778  			}
   779  			continue
   780  		}
   781  		files, err := filepath.Glob(filepath.Join("sys", targets.Linux, "test", fs.filePrefix()+"_*"))
   782  		if err != nil {
   783  			return nil, fmt.Errorf("error reading output dir: %w", err)
   784  		}
   785  		for _, file := range files {
   786  			if err := os.Remove(file); err != nil {
   787  				return nil, fmt.Errorf("error removing output file: %w", err)
   788  			}
   789  		}
   790  	}
   791  	for i, image := range images {
   792  		image.index = i
   793  	}
   794  	return images, nil
   795  }
   796  
   797  func enumerateFlags(target *prog.Target, fs FileSystem) []*Image {
   798  	var images []*Image
   799  	for _, flags := range CoveringArray(fs.MkfsFlagCombinations, fs.MaxSeeds) {
   800  		imageFlags := slices.Clone(fs.MkfsFlags)
   801  		for _, rawFlag := range flags {
   802  			for _, f := range strings.Split(rawFlag, " ") {
   803  				if f == "" {
   804  					continue
   805  				}
   806  				imageFlags = append(imageFlags, f)
   807  			}
   808  		}
   809  		images = append(images, &Image{
   810  			target: target,
   811  			fs:     fs,
   812  			flags:  imageFlags,
   813  			done:   make(chan error, 1),
   814  		})
   815  	}
   816  	return images
   817  }
   818  
   819  func (img *Image) generate() error {
   820  	var err error
   821  	for img.size = img.fs.MinSize; img.size <= 128<<20; img.size *= 2 {
   822  		if err = img.generateSize(); err == nil {
   823  			return nil
   824  		}
   825  	}
   826  	return err
   827  }
   828  
   829  func (img *Image) generateSize() error {
   830  	outFile := filepath.Join("sys", targets.Linux, "test",
   831  		fmt.Sprintf("%v_%v", img.fs.filePrefix(), img.index))
   832  	img.disk = outFile + ".img"
   833  	f, err := os.Create(img.disk)
   834  	if err != nil {
   835  		return err
   836  	}
   837  	f.Close()
   838  	if err := os.Truncate(img.disk, int64(img.size)); err != nil {
   839  		return err
   840  	}
   841  	if img.fs.Mkfs == nil {
   842  		if _, err := runCmd("mkfs."+img.fs.Name, append(img.flags, img.disk)...); err != nil {
   843  			return err
   844  		}
   845  	} else {
   846  		if err := img.fs.Mkfs(img); err != nil {
   847  			return err
   848  		}
   849  	}
   850  	if !img.fs.ReadOnly {
   851  		// This does not work with runCmd -- sudo does not show password prompt on console.
   852  		cmd := exec.Command("sudo", os.Args[0], "-populate", img.disk, "-fs", img.fs.Name)
   853  		if out, err := cmd.CombinedOutput(); err != nil {
   854  			return fmt.Errorf("image population failed: %w\n%s", err, out)
   855  		}
   856  	}
   857  	data, err := os.ReadFile(img.disk)
   858  	if err != nil {
   859  		return err
   860  	}
   861  	img.hash = crc32.ChecksumIEEE(data)
   862  
   863  	// Write out image *with* change of directory.
   864  	out, err := writeImage(img, data)
   865  	if err != nil {
   866  		return fmt.Errorf("failed to write image: %w", err)
   867  	}
   868  
   869  	p, err := img.target.Deserialize(out, prog.Strict)
   870  	if err != nil {
   871  		return fmt.Errorf("failed to deserialize resulting program: %w", err)
   872  	}
   873  	if _, err := p.SerializeForExec(); err != nil {
   874  		return fmt.Errorf("failed to serialize for execution: %w", err)
   875  	}
   876  
   877  	return osutil.WriteFile(outFile, out)
   878  }
   879  
   880  // Runs under sudo in a subprocess.
   881  func populate(disk, fs string) error {
   882  	output, err := runCmd("losetup", "-f", "--show", "-P", disk)
   883  	if err != nil {
   884  		return err
   885  	}
   886  	loop := strings.TrimSpace(string(output))
   887  	defer runCmd("losetup", "-d", loop)
   888  
   889  	dir, err := os.MkdirTemp("", "syz-imagegen")
   890  	if err != nil {
   891  		return err
   892  	}
   893  	defer os.RemoveAll(dir)
   894  	if _, err := runCmd("mount", "-t", fs, loop, dir); err != nil {
   895  		return fmt.Errorf("%w\n%s", err, output)
   896  	}
   897  	defer runCmd("umount", dir)
   898  	return populateDir(dir)
   899  }
   900  
   901  func populateDir(dir string) error {
   902  	zeros := func(size int) []byte {
   903  		return make([]byte, size)
   904  	}
   905  	nonzeros := func(size int) []byte {
   906  		const fill = "syzkaller"
   907  		return bytes.Repeat([]byte(fill), size/len(fill)+1)[:size]
   908  	}
   909  	if err := os.Mkdir(filepath.Join(dir, "file0"), 0777); err != nil {
   910  		return err
   911  	}
   912  	if err := os.WriteFile(filepath.Join(dir, "file0", "file0"), nonzeros(1050), 0777); err != nil {
   913  		return err
   914  	}
   915  	os.Symlink(filepath.Join(dir, "file0", "file0"), filepath.Join(dir, "file0", "file1"))
   916  	if err := os.WriteFile(filepath.Join(dir, "file1"), nonzeros(10), 0777); err != nil {
   917  		return err
   918  	}
   919  	// Note: some errors are not checked because some file systems don't have support for links/attrs.
   920  	// TODO: does it make sense to create other attribute types (system./trusted./security./btrfs.)?
   921  	syscall.Setxattr(filepath.Join(dir, "file1"), "user.xattr1", []byte("xattr1"), 0)
   922  	syscall.Setxattr(filepath.Join(dir, "file1"), "user.xattr2", []byte("xattr2"), 0)
   923  	if err := os.WriteFile(filepath.Join(dir, "file2"), zeros(9000), 0777); err != nil {
   924  		return err
   925  	}
   926  	os.Link(filepath.Join(dir, "file2"), filepath.Join(dir, "file3"))
   927  	// f2fs considers .cold extension specially.
   928  	if err := os.WriteFile(filepath.Join(dir, "file.cold"), nonzeros(100), 0777); err != nil {
   929  		return err
   930  	}
   931  	return nil
   932  }
   933  
   934  func runCmd(cmd string, args ...string) ([]byte, error) {
   935  	return osutil.RunCmd(10*time.Minute, "", cmd, args...)
   936  }
   937  
   938  func writeImage(img *Image, data []byte) ([]byte, error) {
   939  	buf := new(bytes.Buffer)
   940  	fmt.Fprintf(buf, "# Code generated by tools/syz-imagegen. DO NOT EDIT.\n")
   941  	fmt.Fprintf(buf, "# requires: manual\n\n")
   942  	fmt.Fprintf(buf, "# %v\n\n", img)
   943  	compressedData := image.Compress(data)
   944  	b64Data := image.EncodeB64(compressedData)
   945  	if img.fs.Name == parttable {
   946  		fmt.Fprintf(buf, `%s(AUTO, &AUTO="$`, syzReadPartTable)
   947  	} else {
   948  		fmt.Fprintf(buf, `%s$%v(&AUTO='%v\x00', &AUTO='./file0\x00', 0x0, &AUTO, 0x1, AUTO, &AUTO="$`,
   949  			syzMountImage, img.fs.suffix(), img.fs.Name)
   950  	}
   951  	buf.Write(b64Data)
   952  	fmt.Fprintf(buf, "\")\n")
   953  
   954  	return buf.Bytes(), nil
   955  }
   956  
   957  func loadFilesystem(fileName string) FileSystem {
   958  	file, err := os.Open(fileName)
   959  	if err != nil {
   960  		tool.Fail(err)
   961  	}
   962  	defer file.Close()
   963  	var fs FileSystem
   964  	err = json.NewDecoder(file).Decode(&fs)
   965  	if err != nil {
   966  		tool.Failf("failed to parse JSON: %v", err)
   967  	}
   968  	return fs
   969  }