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 }