From 1856cdc9b3652a082c5bfa0e08a9f883baece8ec Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Thu, 15 Sep 2022 09:05:13 +0000 Subject: 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. --- sys/linux/init.go | 2 + sys/linux/init_images.go | 176 ++++++++++++++++++++++++++++++++++++++++++ sys/linux/init_images_test.go | 38 +++++++++ 3 files changed, 216 insertions(+) create mode 100644 sys/linux/init_images.go create mode 100644 sys/linux/init_images_test.go (limited to 'sys') 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)`, + }, + }) +} -- cgit mrf-deployment