diff --git a/Makefile b/Makefile index 00d6629..d4da310 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ GOLANGCI := $(GOPATH)/bin/golangci-lint .PHONY: analyzer analyzer: - go build -o ./bin/analyzer ./main.go + go build -o ./bin/vm-compact ./main.go .PHONY: get get: @@ -22,4 +22,9 @@ lint: get_lint .PHONY: test test: @echo " > \033[32mRunning sprinter-api tests...\033[0m " - go test -v ./... \ No newline at end of file + go test -v ./... + +# Run e2e tests +.PHONY: e2e-test +e2e-test: + go test ./e2e_tests -tags=integration -v diff --git a/analyzer/opcode/opcode.go b/analyzer/opcode/opcode.go index ba33203..8e23166 100644 --- a/analyzer/opcode/opcode.go +++ b/analyzer/opcode/opcode.go @@ -45,10 +45,6 @@ func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error if err != nil { // non-reachable portion ignored continue } - if !withTrace { - source.CallStack = nil - } - issue := &analyzer.Issue{ Severity: analyzer.IssueSeverityCritical, CallStack: source, @@ -58,6 +54,9 @@ func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error if common.ShouldIgnoreSource(source, op.profile.IgnoredFunctions) { issue.Severity = analyzer.IssueSeverityWarning } + if !withTrace { + source.CallStack = nil + } issues = append(issues, issue) } } diff --git a/analyzer/syscall/asm_syscall.go b/analyzer/syscall/asm_syscall.go index 430c618..e6d61dc 100644 --- a/analyzer/syscall/asm_syscall.go +++ b/analyzer/syscall/asm_syscall.go @@ -67,9 +67,6 @@ func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.I if err != nil { // non-reachable portion ignored continue } - if !withTrace { - source.CallStack = nil - } severity := analyzer.IssueSeverityCritical if common.ShouldIgnoreSource(source, a.profile.IgnoredFunctions) { @@ -80,7 +77,9 @@ func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.I message = fmt.Sprintf("Potential NOOP Syscall Detected: %d", syscall.Number) severity = analyzer.IssueSeverityWarning } - + if !withTrace { + source.CallStack = nil + } issues = append(issues, &analyzer.Issue{ Severity: severity, Message: message, diff --git a/common/entrypoint.go b/common/entrypoint.go index 7b462af..4d7d116 100644 --- a/common/entrypoint.go +++ b/common/entrypoint.go @@ -13,14 +13,14 @@ func ProgramEntrypoint(arch string) func(function string) bool { function == "runtime.schedinit" || function == "runtime.newproc" || function == "runtime.mstart" || - function == "main.main" || // main + strings.Contains(function, "main.main") || // main and closures or anonymous functions strings.Contains(function, ".init.") || // all init functions strings.HasSuffix(function, ".init") // vars } case "mips64": return func(function string) bool { return function == "runtime.rt0_go" || // start point of a go program - function == "main.main" || // main + strings.Contains(function, "main.main") || // main and closures or anonymous functions strings.Contains(function, ".init.") || // all init functions strings.HasSuffix(function, ".init") // vars } diff --git a/e2e_tests/e2e_test.go b/e2e_tests/e2e_test.go new file mode 100644 index 0000000..21239c2 --- /dev/null +++ b/e2e_tests/e2e_test.go @@ -0,0 +1,115 @@ +//go:build integration + +package e2etest + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "path/filepath" + "testing" + + "github.com/ChainSafe/vm-compat/analyzer" + "github.com/stretchr/testify/assert" +) + +const testdataDir = "testdata" + +type testcase struct { + path string + isPassing bool +} + +func runTest(t *testing.T, vmProfile string, cases map[string]testcase) { + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + cmd := exec.Command("../bin/vm-compact", "analyze", "-vm-profile", vmProfile, "-format", "json", tc.path) + + var out bytes.Buffer + var errOut bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errOut + err := cmd.Run() + if err != nil { + t.Fatalf("Failed to run CLI: %v. errorOutput: %s", err, errOut.String()) + } + + issues := []*analyzer.Issue{} + json.Unmarshal(out.Bytes(), &issues) + + if tc.isPassing { + for i := range issues { + assert.NotEqual(t, analyzer.IssueSeverityCritical, issues[i].Severity, fmt.Sprintf("Found Critical issue %v", issues[i])) + } + } else { + var criticalIssueFound bool + for i := range issues { + if issues[i].Severity == analyzer.IssueSeverityCritical { + criticalIssueFound = true + } + } + + assert.True(t, criticalIssueFound, "No critical issues found") + } + }) + } +} + +func TestSinglethreadedMips(t *testing.T) { + cases := map[string]testcase{ + "hello_world": { + path: filepath.Join(testdataDir, "hello"), + isPassing: true, + }, + "sys-clockgettime": { + path: filepath.Join(testdataDir, "sys-clockgettime"), + }, + "sys-getrandom": { + path: filepath.Join(testdataDir, "sys-getrandom"), + }, + "sys-statx": { + path: filepath.Join(testdataDir, "sys-statx"), + isPassing: true, + }, + } + runTest(t, "../profile/cannon/cannon-singlethreaded-32.yaml", cases) +} + +func TestMultithreadedMips(t *testing.T) { + cases := map[string]testcase{ + "hello_world": { + path: filepath.Join(testdataDir, "hello"), + isPassing: true, + }, + "sys-clockgettime": { + path: filepath.Join(testdataDir, "sys-clockgettime"), + }, + "sys-getrandom": { + path: filepath.Join(testdataDir, "sys-getrandom"), + }, + "sys-statx": { + path: filepath.Join(testdataDir, "sys-statx"), + }, + } + runTest(t, "../profile/cannon/cannon-multithreaded-32.yaml", cases) +} + +func TestMultithreadedMips64(t *testing.T) { + cases := map[string]testcase{ + "hello_world": { + path: filepath.Join(testdataDir, "hello"), + isPassing: true, + }, + "sys-clockgettime": { + path: filepath.Join(testdataDir, "sys-clockgettime"), + }, + "sys-getrandom": { + path: filepath.Join(testdataDir, "sys-getrandom"), + }, + "sys-statx": { + path: filepath.Join(testdataDir, "sys-statx"), + }, + } + runTest(t, "../profile/cannon/cannon-multithreaded-64.yaml", cases) +} diff --git a/e2e_tests/testdata/hello/go.mod b/e2e_tests/testdata/hello/go.mod new file mode 100644 index 0000000..853b93f --- /dev/null +++ b/e2e_tests/testdata/hello/go.mod @@ -0,0 +1,3 @@ +module hello + +go 1.22.2 diff --git a/e2e_tests/testdata/hello/hello.go b/e2e_tests/testdata/hello/hello.go new file mode 100644 index 0000000..20bfebe --- /dev/null +++ b/e2e_tests/testdata/hello/hello.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func hello() { + fmt.Println("Hello World") +} diff --git a/e2e_tests/testdata/hello/main.go b/e2e_tests/testdata/hello/main.go new file mode 100644 index 0000000..0a4df15 --- /dev/null +++ b/e2e_tests/testdata/hello/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + hello() +} diff --git a/e2e_tests/testdata/sys-clockgettime/go.mod b/e2e_tests/testdata/sys-clockgettime/go.mod new file mode 100644 index 0000000..b80110e --- /dev/null +++ b/e2e_tests/testdata/sys-clockgettime/go.mod @@ -0,0 +1,3 @@ +module sys-clockgettime + +go 1.22.2 diff --git a/e2e_tests/testdata/sys-clockgettime/main.go b/e2e_tests/testdata/sys-clockgettime/main.go new file mode 100644 index 0000000..9eea1db --- /dev/null +++ b/e2e_tests/testdata/sys-clockgettime/main.go @@ -0,0 +1,14 @@ +package main + +import "syscall" + +const ( + SYS_CLOCK_GETTIME = 228 +) + +func main() { + _, _, err := syscall.Syscall(SYS_CLOCK_GETTIME, 0, 0, 0) + if err != 0 { + panic(err) + } +} diff --git a/e2e_tests/testdata/sys-getrandom/go.mod b/e2e_tests/testdata/sys-getrandom/go.mod new file mode 100644 index 0000000..4193c86 --- /dev/null +++ b/e2e_tests/testdata/sys-getrandom/go.mod @@ -0,0 +1,3 @@ +module sys-getrandom + +go 1.22.2 diff --git a/e2e_tests/testdata/sys-getrandom/main.go b/e2e_tests/testdata/sys-getrandom/main.go new file mode 100644 index 0000000..1298347 --- /dev/null +++ b/e2e_tests/testdata/sys-getrandom/main.go @@ -0,0 +1,14 @@ +package main + +import "syscall" + +const ( + SYS_GETRANDOM = 318 +) + +func main() { + _, _, err := syscall.Syscall(SYS_GETRANDOM, 0, 0, 0) + if err != 0 { + panic(err) + } +} diff --git a/e2e_tests/testdata/sys-getrandom/mybinary_mips64 b/e2e_tests/testdata/sys-getrandom/mybinary_mips64 new file mode 100755 index 0000000..3ba3610 Binary files /dev/null and b/e2e_tests/testdata/sys-getrandom/mybinary_mips64 differ diff --git a/e2e_tests/testdata/sys-statx/go.mod b/e2e_tests/testdata/sys-statx/go.mod new file mode 100644 index 0000000..c238dde --- /dev/null +++ b/e2e_tests/testdata/sys-statx/go.mod @@ -0,0 +1,3 @@ +module sys-statx + +go 1.22.2 diff --git a/e2e_tests/testdata/sys-statx/main.go b/e2e_tests/testdata/sys-statx/main.go new file mode 100644 index 0000000..39684ed --- /dev/null +++ b/e2e_tests/testdata/sys-statx/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "syscall" +) + +func main() { + const SYS_STATX = 4075 + + // Dummy arguments + dirfd := uintptr(0) // 0 is a valid dummy value + path := uintptr(0) // NULL pointer to simulate a dummy invoke + flags := uintptr(0) + mask := uintptr(0) + statxbuf := uintptr(0) // NULL pointer instead of actual struct + + // Invoke the syscall with dummy values + _, _, err := syscall.RawSyscall6(SYS_STATX, dirfd, path, flags, mask, statxbuf, 0) + + // Expected to fail, but still confirms the syscall was invoked + fmt.Println("Syscall 4075 (statx) invoked. Error:", err) +} diff --git a/profile/cannon/cannon-multithreaded-64.yaml b/profile/cannon/cannon-multithreaded-64.yaml index bb7defe..c0caf5d 100644 --- a/profile/cannon/cannon-multithreaded-64.yaml +++ b/profile/cannon/cannon-multithreaded-64.yaml @@ -5,6 +5,7 @@ ignored_functions: - 'syscall.setrlimit' - 'runtime.morestack' - 'runtime.abort' + - 'runtime.switchToCrashStack0' allowed_opcodes: - opcode: '0x2'