From fb1fed72556fcc8fbe60d75a7e70a188f373aa19 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 24 Nov 2022 15:31:23 +0100 Subject: prog: mutate compressed images with hints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Images are very large so the generic algorithm for data arguments can produce too many mutants. For images we consider only 4/8-byte aligned ints. This is enough to handle all magic numbers and checksums. We also ignore 0 and ^uint64(0) source bytes, because there are too many of these in lots of images. With this change the fuzzer was able to get past magic checks in all of the following functions with our fake images: - in fs/befs/super.c befs_check_sb()  - in fs/freevxfs/vxfs_super.c vxfs_fill_super()  - in fs/hpfs/super.c hpfs_fill_super()  - in fs/omfs/inode.c omfs_fill_super()  - in fs/qnx6/inode.c qnx6_check_first_superblock()  - in fs/ufs/super.c ufs_fill_super() And even successfully mounted sysv filesystem and triggered "sleeping function called from invalid context in __getblk_gfp" when opening a file in the mounted filesystem. --- prog/hints.go | 63 ++++++++++++++++++++++++++++------- prog/hints_test.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 12 deletions(-) (limited to 'prog') diff --git a/prog/hints.go b/prog/hints.go index ef7ee17a5..0055e9005 100644 --- a/prog/hints.go +++ b/prog/hints.go @@ -23,6 +23,8 @@ import ( "encoding/binary" "fmt" "sort" + + "github.com/google/syzkaller/pkg/image" ) // Example: for comparisons {(op1, op2), (op1, op3), (op1, op4), (op2, op1)} @@ -109,11 +111,6 @@ func generateHints(compMap CompMap, arg Arg, exec func()) { // (and filter out file names). return } - case BufferCompressed: - // We can reconsider this in the future, e.g. by decompressing, applying - // hints, then re-compressing. We will need to ensure this doesn't - // produce too many mutants given the current handling of buffers. - return } } @@ -121,7 +118,11 @@ func generateHints(compMap CompMap, arg Arg, exec func()) { case *ConstArg: checkConstArg(a, compMap, exec) case *DataArg: - checkDataArg(a, compMap, exec) + if typ.(*BufferType).Kind == BufferCompressed { + checkCompressedArg(a, compMap, exec) + } else { + checkDataArg(a, compMap, exec) + } } } @@ -129,7 +130,7 @@ func checkConstArg(arg *ConstArg, compMap CompMap, exec func()) { original := arg.Val // Note: because shrinkExpand returns a map, order of programs is non-deterministic. // This can affect test coverage reports. - for _, replacer := range shrinkExpand(original, compMap, arg.Type().TypeBitSize()) { + for _, replacer := range shrinkExpand(original, compMap, arg.Type().TypeBitSize(), false) { arg.Val = replacer exec() } @@ -147,13 +148,41 @@ func checkDataArg(arg *DataArg, compMap CompMap, exec func()) { original := make([]byte, 8) copy(original, data[i:]) val := binary.LittleEndian.Uint64(original) - for _, replacer := range shrinkExpand(val, compMap, 64) { + for _, replacer := range shrinkExpand(val, compMap, 64, false) { + binary.LittleEndian.PutUint64(bytes, replacer) + copy(data[i:], bytes) + exec() + } + copy(data[i:], original) + } +} + +func checkCompressedArg(arg *DataArg, compMap CompMap, exec func()) { + data0 := arg.Data() + if len(data0) == 0 { + return + } + data, dtor := image.MustDecompress(data0) + defer dtor() + // Images are very large so the generic algorithm for data arguments + // can produce too many mutants. For images we consider only + // 4/8-byte aligned ints. This is enough to handle all magic + // numbers and checksums. We also ignore 0 and ^uint64(0) source bytes, + // because there are too many of these in lots of images. + bytes := make([]byte, 8) + for i := 0; i < len(data); i += 4 { + original := make([]byte, 8) + copy(original, data[i:]) + val := binary.LittleEndian.Uint64(original) + for _, replacer := range shrinkExpand(val, compMap, 64, true) { binary.LittleEndian.PutUint64(bytes, replacer) copy(data[i:], bytes) + arg.SetData(image.Compress(data)) exec() } copy(data[i:], original) } + arg.SetData(data0) } // Shrink and expand mutations model the cases when the syscall arguments @@ -185,7 +214,7 @@ func checkDataArg(arg *DataArg, compMap CompMap, exec func()) { // check the extension. // As with shrink we ignore cases when the other operand is wider. // Note that executor sign extends all the comparison operands to int64. -func shrinkExpand(v uint64, compMap CompMap, bitsize uint64) []uint64 { +func shrinkExpand(v uint64, compMap CompMap, bitsize uint64, image bool) []uint64 { v = truncateToBitSize(v, bitsize) limit := uint64(1<