diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2022-09-15 09:05:13 +0000 |
|---|---|---|
| committer | Aleksandr Nogikh <wp32pw@gmail.com> | 2022-09-27 13:07:37 +0200 |
| commit | 1856cdc9b3652a082c5bfa0e08a9f883baece8ec (patch) | |
| tree | b929711cc5e8da196a11dd85141f7d374b132eff | |
| parent | 87840e0023f7adfb7ff928a8a5057932ea9aeab9 (diff) | |
executor: move syz_mount_image's sanity checks to syz-fuzzer
It will simplify the C code and let us extract the raw images in a more
convenient way.
| -rw-r--r-- | executor/common_linux.h | 23 | ||||
| -rw-r--r-- | pkg/csource/generated.go | 23 | ||||
| -rw-r--r-- | prog/minimization_test.go | 4 | ||||
| -rw-r--r-- | prog/prog.go | 5 | ||||
| -rw-r--r-- | prog/test_util.go | 9 | ||||
| -rw-r--r-- | sys/linux/init.go | 2 | ||||
| -rw-r--r-- | sys/linux/init_images.go | 176 | ||||
| -rw-r--r-- | sys/linux/init_images_test.go | 38 |
8 files changed, 230 insertions, 50 deletions
diff --git a/executor/common_linux.h b/executor/common_linux.h index d3f4b6ea1..e5587d1c7 100644 --- a/executor/common_linux.h +++ b/executor/common_linux.h @@ -2872,27 +2872,6 @@ struct fs_image_segment { uintptr_t offset; }; -#define IMAGE_MAX_SEGMENTS 4096 -#define IMAGE_MAX_SIZE (129 << 20) - -static unsigned long fs_image_segment_check(unsigned long size, unsigned long nsegs, struct fs_image_segment* segs) -{ - if (nsegs > IMAGE_MAX_SEGMENTS) - nsegs = IMAGE_MAX_SEGMENTS; - for (size_t i = 0; i < nsegs; i++) { - if (segs[i].size > IMAGE_MAX_SIZE) - segs[i].size = IMAGE_MAX_SIZE; - segs[i].offset %= IMAGE_MAX_SIZE; - if (segs[i].offset > IMAGE_MAX_SIZE - segs[i].size) - segs[i].offset = IMAGE_MAX_SIZE - segs[i].size; - if (size < segs[i].offset + segs[i].offset) - size = segs[i].offset + segs[i].offset; - } - if (size > IMAGE_MAX_SIZE) - size = IMAGE_MAX_SIZE; - return size; -} - // Setup the loop device needed for mounting a filesystem image. Takes care of // creating and initializing the underlying file backing the loop device and // returns the fds to the file and device. @@ -2900,8 +2879,6 @@ static unsigned long fs_image_segment_check(unsigned long size, unsigned long ns static int setup_loop_device(long unsigned size, long unsigned nsegs, struct fs_image_segment* segs, const char* loopname, int* memfd_p, int* loopfd_p) { int err = 0, loopfd = -1; - - size = fs_image_segment_check(size, nsegs, segs); int memfd = syscall(__NR_memfd_create, "syzkaller", 0); if (memfd == -1) { err = errno; diff --git a/pkg/csource/generated.go b/pkg/csource/generated.go index cf9454104..60f94fd30 100644 --- a/pkg/csource/generated.go +++ b/pkg/csource/generated.go @@ -6304,32 +6304,9 @@ struct fs_image_segment { uintptr_t size; uintptr_t offset; }; - -#define IMAGE_MAX_SEGMENTS 4096 -#define IMAGE_MAX_SIZE (129 << 20) - -static unsigned long fs_image_segment_check(unsigned long size, unsigned long nsegs, struct fs_image_segment* segs) -{ - if (nsegs > IMAGE_MAX_SEGMENTS) - nsegs = IMAGE_MAX_SEGMENTS; - for (size_t i = 0; i < nsegs; i++) { - if (segs[i].size > IMAGE_MAX_SIZE) - segs[i].size = IMAGE_MAX_SIZE; - segs[i].offset %= IMAGE_MAX_SIZE; - if (segs[i].offset > IMAGE_MAX_SIZE - segs[i].size) - segs[i].offset = IMAGE_MAX_SIZE - segs[i].size; - if (size < segs[i].offset + segs[i].offset) - size = segs[i].offset + segs[i].offset; - } - if (size > IMAGE_MAX_SIZE) - size = IMAGE_MAX_SIZE; - return size; -} static int setup_loop_device(long unsigned size, long unsigned nsegs, struct fs_image_segment* segs, const char* loopname, int* memfd_p, int* loopfd_p) { int err = 0, loopfd = -1; - - size = fs_image_segment_check(size, nsegs, segs); int memfd = syscall(__NR_memfd_create, "syzkaller", 0); if (memfd == -1) { err = errno; diff --git a/prog/minimization_test.go b/prog/minimization_test.go index de58ad217..93ccdc7bb 100644 --- a/prog/minimization_test.go +++ b/prog/minimization_test.go @@ -231,13 +231,13 @@ func TestMinimize(t *testing.T) { // Ensure `no_minimize` calls are untouched. { "linux", "amd64", - "syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x40000, 0x2a, &(0x7f0000000200)=[{&(0x7f0000010000)='test\\x00'/32, 0x20, 0x400}], 0x0, &(0x7f0000010020), 0x1)\n", + "syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x40000, 0x1, &(0x7f0000000200)=[{&(0x7f0000010000)='test\\x00'/32, 0x20, 0x400}], 0x0, &(0x7f0000010020), 0x1)\n", 0, func(p *Prog, callIndex int) bool { // Anything is allowed except removing a call. return len(p.Calls) > 0 }, - "syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x40000, 0x2a, &(0x7f0000000200)=[{&(0x7f0000010000)='test\\x00'/32, 0x20, 0x400}], 0x0, &(0x7f0000010020), 0x1)\n", + "syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x40000, 0x1, &(0x7f0000000200)=[{&(0x7f0000010000)='test\\x00'/32, 0x20, 0x400}], 0x0, &(0x7f0000010020), 0x1)\n", 0, }, } diff --git a/prog/prog.go b/prog/prog.go index 87b9998b8..bb7caa7c5 100644 --- a/prog/prog.go +++ b/prog/prog.go @@ -414,6 +414,11 @@ func removeArg(arg0 Arg) { }) } +// The public alias for the removeArg method. +func RemoveArg(arg Arg) { + removeArg(arg) +} + // removeCall removes call idx from p. func (p *Prog) RemoveCall(idx int) { c := p.Calls[idx] diff --git a/prog/test_util.go b/prog/test_util.go index 07f24141a..71a52c809 100644 --- a/prog/test_util.go +++ b/prog/test_util.go @@ -66,9 +66,14 @@ func TestDeserializeHelper(t *testing.T, OS, arch string, transform func(*Target transform(target, p) } output := strings.TrimSpace(string(p.Serialize())) + outputVerbose := strings.TrimSpace(string(p.SerializeVerbose())) want := strings.TrimSpace(test.Out) - if want != output { - t.Fatalf("wrong serialized data:\n%s\nexpect:\n%s\n", output, want) + // We want to compare both verbose & non verbose mode. + // Otherwise we cannot have just In: field for the calls where + // the verbose and non-verbose output don't match -- the strict parsing + // mode does not accept the non-verbose output as input. + if want != output && want != outputVerbose { + t.Fatalf("wrong serialized data:\n%s\nexpect:\n%s\n", outputVerbose, want) } p.SerializeForExec(buf) } diff --git a/sys/linux/init.go b/sys/linux/init.go index 480ac1d96..9080cbd7c 100644 --- a/sys/linux/init.go +++ b/sys/linux/init.go @@ -241,6 +241,8 @@ func (arch *arch) neutralize(c *prog.Call) { case "sched_setattr": // Enabling a SCHED_FIFO or a SCHED_RR policy may lead to false positive stall-related crashes. neutralizeSchedAttr(c.Args[1]) + case "syz_mount_image": + arch.fixUpSyzMountImage(c) } switch c.Meta.Name { diff --git a/sys/linux/init_images.go b/sys/linux/init_images.go new file mode 100644 index 000000000..e914b8b4d --- /dev/null +++ b/sys/linux/init_images.go @@ -0,0 +1,176 @@ +// Copyright 2022 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package linux + +import ( + "fmt" + "io" + + "github.com/google/syzkaller/prog" +) + +type zeroReader struct { + left int +} + +func (zr *zeroReader) Read(p []byte) (n int, err error) { + if zr.left == 0 { + return 0, io.EOF + } + toRead := zr.left + if toRead > len(p) { + toRead = len(p) + } + for i := 0; i < toRead; i++ { + p[i] = 0 + } + return toRead, nil +} + +func newZeroReader(size int) io.Reader { + return &zeroReader{left: size} +} + +const imageMaxSize = 129 << 20 + +func fixUpImageSegments(parsed *mountImageArgs) { + parsed.filterSegments(func(i int, segment *mountImageSegment) bool { + if segment.parseError != nil { + return false + } + return segment.offset.Val < imageMaxSize && segment.size.Val < imageMaxSize + }) + newSize := parsed.size.Val + for _, segment := range parsed.segments { + actualSize := uint64(len(segment.data.Data())) + if segment.size.Val > actualSize { + segment.size.Val = actualSize + } + if segment.offset.Val+segment.size.Val > imageMaxSize { + segment.offset.Val = imageMaxSize - segment.size.Val + } + if segment.offset.Val+segment.size.Val > newSize { + newSize = segment.offset.Val + segment.size.Val + } + } + if newSize > imageMaxSize { + newSize = imageMaxSize + } + parsed.size.Val = newSize +} + +func (arch *arch) fixUpSyzMountImage(c *prog.Call) { + // Previously we did such a sanitization right in the common_linux.h, but this was problematic + // for two reasons: + // 1) It further complicates the already complicated executor code. + // 2) We'd need to duplicate the logic in Go for raw image extraction. + // So now we do all the initialization in Go and let the C code only interpret the commands. + ret, err := parseSyzMountImage(c) + if err != nil { + deactivateSyzMountImage(c) + return + } + const maxImageSegments = 4096 + ret.filterSegments(func(i int, _ *mountImageSegment) bool { + return i < maxImageSegments + }) + fixUpImageSegments(ret) +} + +type mountImageArgs struct { + size *prog.ConstArg + segmentsCount *prog.ConstArg + segmentsGroup *prog.GroupArg + segments []*mountImageSegment +} + +func (m *mountImageArgs) filterSegments(filter func(int, *mountImageSegment) bool) { + newArgs := []prog.Arg{} + newSegments := []*mountImageSegment{} + for i, segment := range m.segments { + if filter(i, segment) { + newSegments = append(newSegments, segment) + newArgs = append(newArgs, m.segmentsGroup.Inner[i]) + } + } + m.segments = newSegments + m.segmentsGroup.Inner = newArgs + m.segmentsCount.Val = uint64(len(newArgs)) +} + +type mountImageSegment struct { + data *prog.DataArg + offset *prog.ConstArg + size *prog.ConstArg + parseError error +} + +func parseImageSegment(segmentArg prog.Arg) *mountImageSegment { + ret := &mountImageSegment{} + segmentFields, ok := segmentArg.(*prog.GroupArg) + if segmentFields == nil || !ok { + return &mountImageSegment{parseError: fmt.Errorf("it is not a group")} + } + if len(segmentFields.Inner) != 3 { + return &mountImageSegment{parseError: fmt.Errorf("invalid number of nested fields")} + } + dataPtr, ok := segmentFields.Inner[0].(*prog.PointerArg) + if dataPtr == nil || dataPtr.Res == nil || !ok { + return &mountImageSegment{parseError: fmt.Errorf("invalid data field ptr")} + } + ret.data, ok = dataPtr.Res.(*prog.DataArg) + if ret.data == nil || !ok { + return &mountImageSegment{parseError: fmt.Errorf("invalid data arg")} + } + ret.size, ok = segmentFields.Inner[1].(*prog.ConstArg) + if ret.size == nil || !ok { + return &mountImageSegment{parseError: fmt.Errorf("invalid size arg")} + } + ret.offset, ok = segmentFields.Inner[2].(*prog.ConstArg) + if ret.offset == nil || !ok { + return &mountImageSegment{parseError: fmt.Errorf("invalid offset arg")} + } + return ret +} + +func deactivateSyzMountImage(c *prog.Call) { + groupArg := c.Args[4] + newArg := groupArg.Type().DefaultArg(groupArg.Dir()) + prog.RemoveArg(groupArg) + c.Args[4] = newArg + // Also set the segments count field to 0. + c.Args[3].(*prog.ConstArg).Val = 0 +} + +func parseSyzMountImage(c *prog.Call) (*mountImageArgs, error) { + if len(c.Args) < 5 { + panic("invalid number of arguments in syz_mount_image") + } + segmentsCountArg, ok := c.Args[3].(*prog.ConstArg) + if !ok { + panic("syz_mount_image's segment count was expected to be const") + } + sizeArg, ok := c.Args[2].(*prog.ConstArg) + if !ok { + panic("syz_mount_image's size arg is not const") + } + segmentsPtrArg, ok := c.Args[4].(*prog.PointerArg) + if !ok { + return nil, fmt.Errorf("invalid segments arg") + } + segmentsGroup, ok := segmentsPtrArg.Res.(*prog.GroupArg) + if segmentsGroup == nil || !ok { + return nil, fmt.Errorf("segments are not a group") + } + ret := &mountImageArgs{ + segmentsCount: segmentsCountArg, + segmentsGroup: segmentsGroup, + size: sizeArg, + } + for _, segmentArg := range segmentsGroup.Inner { + parsed := parseImageSegment(segmentArg) + ret.segments = append(ret.segments, parsed) + } + return ret, nil +} diff --git a/sys/linux/init_images_test.go b/sys/linux/init_images_test.go new file mode 100644 index 000000000..816d59902 --- /dev/null +++ b/sys/linux/init_images_test.go @@ -0,0 +1,38 @@ +// Copyright 2022 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package linux + +import ( + "testing" + + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +func TestSyzMountImageNeutralize(t *testing.T) { + prog.TestDeserializeHelper(t, targets.Linux, targets.AMD64, nil, []prog.DeserializeTest{ + { + // A valid call, nothing should change. + In: `syz_mount_image$bfs(&(0x7f0000000000)='bfs\x00', &(0x7f0000000100)='./file0\x00', 0x2220, 0x2, &(0x7f0000000200)=[{&(0x7f0000010000)="cefaad1bc0210000ff0f0000ffffffffffffffffffffffffffffffff73797a6b616c73797a6b616c00"/64, 0x40, 0x0}, {&(0x7f0000010040)="0200000011000000140000001f22000002000000ed4100000000000001000000020000005ffb19635ffb19635ffb196300"/64, 0x40, 0x200}], 0x0, &(0x7f00000100a0)={[], [], 0x0}, 0x0)`, + }, + { + // Invalid total size. + In: `syz_mount_image$bfs(&(0x7f0000000000)='bfs\x00', &(0x7f0000000100)='./file1\x00', 0x20, 0x2, &(0x7f0000000200)=[{&(0x7f0000010000)="cefaad1bc0210000ff0f0000ffffffffffffffffffffffffffffffff73797a6b616c73797a6b616c00"/64, 0x40, 0x0}, {&(0x7f0000010040)="0200000011000000140000001f22000002000000ed4100000000000001000000020000005ffb19635ffb19635ffb196300"/64, 0x40, 0x200}], 0x0, &(0x7f00000100a0)={[], [], 0x0}, 0x0)`, + // It should be able to fix up the size. + Out: `syz_mount_image$bfs(&(0x7f0000000000)='bfs\x00', &(0x7f0000000100)='./file1\x00', 0x240, 0x2, &(0x7f0000000200)=[{&(0x7f0000010000)="cefaad1bc0210000ff0f0000ffffffffffffffffffffffffffffffff73797a6b616c73797a6b616c00"/64, 0x40, 0x0}, {&(0x7f0000010040)="0200000011000000140000001f22000002000000ed4100000000000001000000020000005ffb19635ffb19635ffb196300"/64, 0x40, 0x200}], 0x0, &(0x7f00000100a0)={[], [], 0x0}, 0x0)`, + }, + { + // Overflow over the max image size. + In: `syz_mount_image$bfs(&(0x7f0000000000)='bfs\x00', &(0x7f0000000100)='./file2\x00', 0x8200000, 0x2, &(0x7f0000000200)=[{&(0x7f0000010000)="cefaad1bc0210000ff0f0000ffffffffffffffffffffffffffffffff73797a6b616c73797a6b616c00"/64, 0x40, 0x0}, {&(0x7f0000010040)="0200000011000000140000001f22000002000000ed4100000000000001000000020000005ffb19635ffb19635ffb196300"/64, 0x400, 0x80fffff}], 0x0, &(0x7f00000100a0)={[], [], 0x0}, 0x0)`, + // It should shift the overflowing segment and adjust the total size. + Out: `syz_mount_image$bfs(&(0x7f0000000000)='bfs\x00', &(0x7f0000000100)='./file2\x00', 0x8100000, 0x2, &(0x7f0000000200)=[{&(0x7f0000010000)="cefaad1bc0210000ff0f0000ffffffffffffffffffffffffffffffff73797a6b616c73797a6b616c00"/64, 0x40, 0x0}, {&(0x7f0000010040)="0200000011000000140000001f22000002000000ed4100000000000001000000020000005ffb19635ffb19635ffb196300"/64, 0x40, 0x80fffc0}], 0x0, &(0x7f00000100a0)={[], [], 0x0}, 0x0)`, + }, + { + // Invalid offset. + In: `syz_mount_image$bfs(&(0x7f0000000000)='bfs\x00', &(0x7f0000000100)='./file1\x00', 0x20, 0x2, &(0x7f0000000200)=[{&(0x7f0000010000)="cefaad1bc0210000ff0f0000ffffffffffffffffffffffffffffffff73797a6b616c73797a6b616c00"/64, 0x40, 0x0}, {&(0x7f0000010040)="0200000011000000140000001f22000002000000ed4100000000000001000000020000005ffb19635ffb19635ffb196300"/64, 0x40, 0x9100000}], 0x0, &(0x7f00000100a0)={[], [], 0x0}, 0x0)`, + // The segment is deleted. + Out: `syz_mount_image$bfs(&(0x7f0000000000)='bfs\x00', &(0x7f0000000100)='./file1\x00', 0x40, 0x1, &(0x7f0000000200)=[{&(0x7f0000010000)="cefaad1bc0210000ff0f0000ffffffffffffffffffffffffffffffff73797a6b616c73797a6b616c00"/64, 0x40, 0x0}], 0x0, &(0x7f00000100a0)={[], [], 0x0}, 0x0)`, + }, + }) +} |
