// Copyright 2015 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.

//go:build linux
// +build linux

package host

import (
	"fmt"
	"runtime"
	"syscall"
	"testing"

	"github.com/google/syzkaller/prog"
	"github.com/google/syzkaller/sys/targets"
)

func TestSupportedSyscalls(t *testing.T) {
	t.Parallel()
	target, err := prog.GetTarget(targets.Linux, runtime.GOARCH)
	if err != nil {
		t.Fatal(err)
	}
	// These are safe to execute with invalid arguments.
	safe := []string{
		"memfd_create",
		"sendfile",
		"bpf$MAP_CREATE",
		"openat",
		"read",
		"write",
	}
	enabled := make(map[*prog.Syscall]bool)
	for _, name := range safe {
		c := target.SyscallMap[name]
		if c == nil {
			t.Fatalf("can't find syscall '%v'", name)
		}
		enabled[c] = true
	}
	supp, _, err := DetectSupportedSyscalls(target, "none", enabled)
	if err != nil {
		t.Skipf("skipping: %v", err)
	}
	for c := range enabled {
		a := ^uintptr(0) - 4097 // hopefully invalid
		_, _, err := syscall.Syscall6(uintptr(c.NR), a, a, a, a, a, a)
		if err == 0 {
			t.Fatalf("%v did not fail", c.Name)
		}
		if ok := err != syscall.ENOSYS; ok != supp[c] {
			t.Fatalf("syscall %v: perse=%v kallsyms=%v", c.Name, ok, supp[c])
		}
	}
}

func TestKallsymsParse(t *testing.T) {
	tests := []struct {
		Arch              string
		Kallsyms          []byte
		ParsedSyscalls    []string
		SupportedSyscalls []string
	}{
		{
			targets.AMD64,
			[]byte(`
ffffffff817cdcc0 T __sys_bind
ffffffff817cdda0 T __x64_sys_bind
ffffffff817cddc0 T __ia32_sys_bind
ffffffff817cdde0 T __sys_listen
ffffffff817cde80 T __x64_sys_listen
ffffffff817cde90 T __ia32_sys_listen
ffffffff817cdea0 T __sys_accept4
ffffffff817ce080 T __x64_sys_accept4
ffffffff817ce0a0 T __ia32_sys_accept4
			`),
			[]string{"bind", "listen", "accept4"},
			[]string{"bind", "listen", "accept4"},
		},
		{
			targets.ARM64,
			[]byte(`
ffff000010a3ddf8 T __sys_bind
ffff000010a3def8 T __arm64_sys_bind
ffff000010a3df20 T __sys_listen
ffff000010a3dfd8 T __arm64_sys_listen
ffff000010a3e000 T __sys_accept4
ffff000010a3e1f0 T __arm64_sys_accept4
			`),
			[]string{"bind", "listen", "accept4"},
			[]string{"bind", "listen", "accept4"},
		},
		{
			targets.PPC64LE,
			[]byte(`
c0000000011ec810 T __sys_bind
c0000000011eca10 T sys_bind
c0000000011eca10 T __se_sys_bind
c0000000011eca70 T __sys_listen
c0000000011ecc10 T sys_listen
c0000000011ecc10 T __se_sys_listen
c0000000011ecc70 T __sys_accept4
c0000000011ed050 T sys_accept4
c0000000011ed050 T __se_sys_accept4
			`),
			[]string{"bind", "listen", "accept4"},
			[]string{"bind", "listen", "accept4"},
		},
		{
			targets.ARM,
			[]byte(`
c037c67c T __se_sys_setfsuid
c037c694 T __sys_setfsgid
c037c790 T sys_setfsgid
c037c790 T __se_sys_setfsgid
c037c7a8 T sys_getpid
c037c7d0 T sys_gettid
c037c7f8 T sys_getppid
			`),
			[]string{"setfsgid", "getpid", "gettid", "getppid"},
			[]string{"setfsgid", "getpid", "gettid", "getppid"},
		},
		// Test kallsymsRenameMap.
		{
			targets.PPC64LE,
			[]byte(`
c00000000037eb00 T sys_newstat
			`),
			[]string{"newstat"},
			[]string{"stat"},
		},
		{
			targets.S390x,
			[]byte(`
0000000000e4f760 T __sys_bind
0000000000e4f8e8 T __s390_sys_bind
0000000000e4f938 T __s390x_sys_bind
0000000000e4f938 T __se_sys_bind
0000000000e4f988 T __sys_listen
0000000000e4fab0 T __s390_sys_listen
0000000000e4faf8 T __s390x_sys_listen
0000000000e4faf8 T __se_sys_listen
0000000000e4fb40 T __sys_accept4
0000000000e4fe58 T __s390_sys_accept4
0000000000e4feb0 T __s390x_sys_accept4
0000000000e4feb0 T __se_sys_accept4
			`),
			[]string{"bind", "listen", "accept4"},
			[]string{"bind", "listen", "accept4"},
		},
		{
			targets.RiscV64,
			[]byte(`
ffffffe0005c9b02 T __sys_bind
ffffffe0005c9ba0 T sys_bind
ffffffe0005c9ba0 T __se_sys_bind
ffffffe0005c9e72 T __sys_accept4
ffffffe0005c9f00 T sys_accept4
ffffffe0005c9f00 T __se_sys_accept4
ffffffe0005c9bd8 T __sys_listen
ffffffe0005c9c76 T sys_listen
ffffffe0005c9c76 T __se_sys_listen
			`),
			[]string{"bind", "listen", "accept4"},
			[]string{"bind", "listen", "accept4"},
		},
	}

	for _, test := range tests {
		syscallSet := parseKallsyms(test.Kallsyms, test.Arch)
		if len(syscallSet) != len(test.ParsedSyscalls) {
			t.Fatalf("wrong number of parse syscalls, expected: %v, got: %v",
				len(test.ParsedSyscalls), len(syscallSet))
		}
		for _, syscall := range test.ParsedSyscalls {
			if _, ok := syscallSet[syscall]; !ok {
				t.Fatalf("syscall %v not found in parsed syscall list", syscall)
			}
		}
		for _, syscall := range test.SupportedSyscalls {
			if newname := kallsymsRenameMap[syscall]; newname != "" {
				syscall = newname
			}

			if _, ok := syscallSet[syscall]; !ok {
				t.Fatalf("syscall %v not found in supported syscall list", syscall)
			}
		}
	}
}

func TestMatchKernelVersion(t *testing.T) {
	tests := []struct {
		version string
		major   int
		minor   int
		res     bool
		bad     bool
	}{
		{
			version: "5.9.0-rc5-next-20200918",
			major:   5,
			minor:   8,
			res:     true,
		},
		{
			version: "5.9.0-rc5-next-20200918",
			major:   4,
			minor:   10,
			res:     true,
		},
		{
			version: "5.9.0-rc5-next-20200918",
			major:   5,
			minor:   9,
			res:     true,
		},
		{
			version: "5.9.0-rc5-next-20200918",
			major:   5,
			minor:   10,
			res:     false,
		},
		{
			version: "5.9.0-rc5-next-20200918",
			major:   6,
			minor:   0,
			res:     false,
		},
		{
			version: "4.4.246-19567-g2c01e3dada31",
			major:   4,
			minor:   3,
			res:     true,
		},
		{
			version: "4.4.246-19567-g2c01e3dada31",
			major:   4,
			minor:   4,
			res:     true,
		},
		{
			version: "5.17.17-1debian-amd64",
			major:   5,
			minor:   16,
			res:     true,
		},
		{
			version: "5.17.17-1debian-amd64",
			major:   5,
			minor:   17,
			res:     true,
		},
		{
			version: "3.5",
			major:   3,
			minor:   4,
			res:     true,
		},
		{
			version: "3.5",
			major:   3,
			minor:   5,
			res:     true,
		},
		{
			version: "3.5",
			major:   3,
			minor:   6,
			res:     false,
		},
		{
			version: "",
			bad:     true,
		},
		{
			version: "something unparsable",
			bad:     true,
		},
		{
			version: "mykernel 4.17-5",
			bad:     true,
		},
		{
			version: "999999.999999",
			bad:     true,
		},
		{
			version: "4.abc.def",
			bad:     true,
		},
	}
	for i, test := range tests {
		t.Run(fmt.Sprint(i), func(t *testing.T) {
			ok, bad := matchKernelVersion(test.version, test.major, test.minor)
			if test.bad && !bad {
				t.Fatal("want error, but got no error")
			}
			if !test.bad && bad {
				t.Fatalf("want no error, but got error")
			}
			if test.res != ok {
				t.Fatalf("want match %v, but got %v", test.res, ok)
			}
		})
	}
}
