aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2022-09-15 09:05:13 +0000
committerAleksandr Nogikh <wp32pw@gmail.com>2022-09-27 13:07:37 +0200
commit1856cdc9b3652a082c5bfa0e08a9f883baece8ec (patch)
treeb929711cc5e8da196a11dd85141f7d374b132eff
parent87840e0023f7adfb7ff928a8a5057932ea9aeab9 (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.h23
-rw-r--r--pkg/csource/generated.go23
-rw-r--r--prog/minimization_test.go4
-rw-r--r--prog/prog.go5
-rw-r--r--prog/test_util.go9
-rw-r--r--sys/linux/init.go2
-rw-r--r--sys/linux/init_images.go176
-rw-r--r--sys/linux/init_images_test.go38
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)`,
+ },
+ })
+}