Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions analyzer/opcode/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error
for _, segment := range callGraph.Segments() {
for _, instruction := range segment.Instructions() {
if !op.isAllowedOpcode(instruction.OpcodeHex(), instruction.Funct()) {
source, err := common.TraceAsmCaller(
sources, err := common.TraceAllAsmCaller(
absPath,
callGraph,
segment.Label(),
Expand All @@ -45,23 +45,23 @@ func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error
if err != nil { // non-reachable portion ignored
continue
}

opts := []analyzer.Opt{
analyzer.WithSeverity(analyzer.IssueSeverityCritical),
analyzer.WithCallStack(source),
analyzer.WithMessage(
fmt.Sprintf("Potential Incompatible Opcode Detected: Opcode: %s, Funct: %s",
instruction.OpcodeHex(), instruction.Funct()),
),
}
if common.ShouldIgnoreSource(source, op.profile.IgnoredFunctions) {
opts = append(opts, analyzer.WithSeverity(analyzer.IssueSeverityWarning))
for _, source := range sources {
opts := []analyzer.Opt{
analyzer.WithSeverity(analyzer.IssueSeverityCritical),
analyzer.WithCallStack(source),
analyzer.WithMessage(
fmt.Sprintf("Potential Incompatible Opcode Detected: Opcode: %s, Funct: %s",
instruction.OpcodeHex(), instruction.Funct()),
),
}
if common.ShouldIgnoreSource(source, op.profile.IgnoredFunctions) {
opts = append(opts, analyzer.WithSeverity(analyzer.IssueSeverityWarning))
}
if !withTrace {
source.CallStack = nil
}
issues = append(issues, analyzer.NewIssue(opts...))
}
if !withTrace {
source.CallStack = nil
}

issues = append(issues, analyzer.NewIssue(opts...))
}
}
}
Expand Down
42 changes: 21 additions & 21 deletions analyzer/syscall/asm_syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.I
if slices.Contains(a.profile.AllowedSycalls, syscall.Number) {
continue
}
source, err := common.TraceAsmCaller(
sources, err := common.TraceAllAsmCaller(
absPath,
callGraph,
syscall.Segment.Label(),
Expand All @@ -67,27 +67,27 @@ func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.I
if err != nil { // non-reachable portion ignored
continue
}

severity := analyzer.IssueSeverityCritical
if common.ShouldIgnoreSource(source, a.profile.IgnoredFunctions) {
severity = analyzer.IssueSeverityWarning
}
message := fmt.Sprintf("Potential Incompatible Syscall Detected: %d", syscall.Number)
if slices.Contains(a.profile.NOOPSyscalls, syscall.Number) {
message = fmt.Sprintf("Potential NOOP Syscall Detected: %d", syscall.Number)
severity = analyzer.IssueSeverityWarning
for _, source := range sources {
severity := analyzer.IssueSeverityCritical
if common.ShouldIgnoreSource(source, a.profile.IgnoredFunctions) {
severity = analyzer.IssueSeverityWarning
}
message := fmt.Sprintf("Potential Incompatible Syscall Detected: %d", syscall.Number)
if slices.Contains(a.profile.NOOPSyscalls, syscall.Number) {
message = fmt.Sprintf("Potential NOOP Syscall Detected: %d", syscall.Number)
severity = analyzer.IssueSeverityWarning
}
if !withTrace {
source.CallStack = nil
}
issues = append(issues, analyzer.NewIssue(
analyzer.WithImpact(potentialImpactMsg),
analyzer.WithReference(analyzerWorkingPrincipalURL),
analyzer.WithCallStack(source),
analyzer.WithSeverity(severity),
analyzer.WithMessage(message),
))
}
if !withTrace {
source.CallStack = nil
}

issues = append(issues, analyzer.NewIssue(
analyzer.WithImpact(potentialImpactMsg),
analyzer.WithReference(analyzerWorkingPrincipalURL),
analyzer.WithCallStack(source),
analyzer.WithSeverity(severity),
analyzer.WithMessage(message),
))
}
}
}
Expand Down
24 changes: 20 additions & 4 deletions common/entrypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,30 @@ func ProgramEntrypoint(arch string) func(function string) bool {
function == "runtime.mstart" ||
strings.Contains(function, "main.main") || // main and closures or anonymous functions
strings.Contains(function, ".init.") || // all init functions
strings.HasSuffix(function, ".init") // vars
strings.HasSuffix(function, ".init") || // vars
// Bellow functions though are not the starting point, but those have a lot of trigger points
// and for a sample go program is expected to be called. So, to reduce stack trace, it's fine to make them as entry points
function == "runtime.gcStart" ||
function == "runtime.mallocgc" ||
function == "runtime.morestack" ||
function == "runtime.systemstack" ||
function == "runtime.gopanic" ||
function == "runtime.chanrecv"
}
case "mips64":
return func(function string) bool {
return function == "runtime.rt0_go" || // start point of a go program
strings.Contains(function, "main.main") || // main and closures or anonymous functions
return strings.Contains(function, "main.main") || // main and closures or anonymous functions
strings.Contains(function, ".init.") || // all init functions
strings.HasSuffix(function, ".init") // vars
strings.HasSuffix(function, ".init") || // vars
function == "runtime.rt0_go" || // start point of any go program
// Bellow functions though are not the starting point, but those have a lot of trigger points
// and for a sample go program is expected to be called. So, to reduce stack trace, it's fine to make them as entry points
function == "runtime.gcStart" ||
function == "runtime.mallocgc" ||
function == "runtime.morestack" ||
function == "runtime.systemstack" ||
function == "runtime.gopanic" ||
function == "runtime.chanrecv"
}
}
return func(function string) bool {
Expand Down
114 changes: 114 additions & 0 deletions common/stack_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ package common

import (
"fmt"
"os/exec"
"path/filepath"
"slices"
"strings"

"github.com/ChainSafe/vm-compat/analyzer"
"github.com/ChainSafe/vm-compat/asmparser"
"github.com/ChainSafe/vm-compat/common/lifo"
)

var (
stdPkgs, _ = getStandardPackages()
)

// TraceAsmCaller correctly tracks function calls in the execution stack.
Expand Down Expand Up @@ -60,6 +67,113 @@ func TraceAsmCaller(
return src, nil
}

// TraceAllAsmCaller correctly tracks all possible function calls in the execution stack.
func TraceAllAsmCaller(
filePath string,
graph asmparser.CallGraph,
function string,
endCond func(string) bool,
) ([]*analyzer.CallStack, error) {
var segment asmparser.Segment
for _, seg := range graph.Segments() {
if seg.Label() == function {
segment = seg
break
}
}
if segment == nil {
return nil, fmt.Errorf("could not find %s in %s", function, filePath)
}
sources := make([]*lifo.Stack[asmparser.Segment], 0)
currentStack := lifo.Stack[asmparser.Segment]{}
seen := make(map[asmparser.Segment]bool)

var visit func(segment asmparser.Segment)

visit = func(segment asmparser.Segment) {
if seen[segment] {
return
}
seen[segment] = true
currentStack.Push(segment)

parents := graph.ParentsOf(segment)

if endCond(segment.Label()) {
sources = append(sources, currentStack.Copy())
} else {
// sort the parents for consistent output
slices.SortFunc(parents, func(a, b asmparser.Segment) int {
if a.Address() > b.Address() {
return 1
} else if a.Address() < b.Address() {
return -1
}
return 0
})

for _, seg := range parents {
visit(seg)
}
}

currentStack.Pop()

// We don't want to revisit the sdk packages as the number of paths can be up to billions.
pkg := strings.Split(segment.Label(), ".")[0]
if !stdPkgs[pkg] {
seen[segment] = false
}
}

visit(segment)

if len(sources) == 0 {
return nil, fmt.Errorf("no trace found to root for the given function")
}
// build call stacks from the sources
traces := make([]*analyzer.CallStack, len(sources))
for i, source := range sources {
var callStack *analyzer.CallStack
for !source.IsEmpty() {
if seg, ok := source.Pop(); ok {
stack := &analyzer.CallStack{
File: filepath.Base(filePath),
Line: seg.Instructions()[0].Line() - 1, // function start line
AbsPath: filePath,
Function: seg.Label(),
}
if callStack == nil {
callStack = stack
} else {
stack.CallStack = callStack
callStack = stack
}
}
}
traces[i] = callStack
}

return traces, nil
}

// getStandardPackages fetches all standard library packages and stores them in a map for fast lookup.
func getStandardPackages() (map[string]bool, error) {
cmd := exec.Command("go", "list", "std")
output, err := cmd.Output()
if err != nil {
return nil, err
}

stdPackages := make(map[string]bool)
for _, pkg := range strings.Split(string(output), "\n") {
if pkg != "" {
stdPackages[pkg] = true
}
}
return stdPackages, nil
}

func ShouldIgnoreSource(callStack *analyzer.CallStack, functions []string) bool {
if callStack != nil {
if slices.Contains(functions, callStack.Function) {
Expand Down
2 changes: 2 additions & 0 deletions profile/cannon/cannon-multithreaded-32.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ ignored_functions:
- 'runtime.rtsigprocmask'
- 'runtime.munmap'
- 'runtime.exit'
- 'runtime.sysFaultOS'
- 'runtime.netpollinit'
allowed_opcodes:
- opcode: '0x2'
funct: []
Expand Down
2 changes: 2 additions & 0 deletions profile/cannon/cannon-multithreaded-64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ignored_functions:
- 'syscall.setrlimit'
- 'runtime.morestack'
- 'runtime.abort'
- 'runtime.sysFaultOS'
- 'runtime.netpollinit'

allowed_opcodes:
- opcode: '0x2'
Expand Down
2 changes: 2 additions & 0 deletions profile/cannon/cannon-singlethreaded-32.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ ignored_functions:
- 'github.com/prometheus/client_model/go.init.1'
- 'flag.init'
- 'runtime.check'
- 'runtime.sysFaultOS'
- 'runtime.netpollinit'
allowed_opcodes:
- opcode: '0x2'
funct: []
Expand Down
10 changes: 8 additions & 2 deletions renderer/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,16 @@ func (r *TextRenderer) Render(issues []*analyzer.Issue, output io.Writer) error
if len(groupedIssue[0].Reference) > 0 {
report.WriteString(fmt.Sprintf(" - Referance: %s \n", groupedIssue[0].Reference))
}
report.WriteString(" - CallStack:")
report.WriteString(" - CallStacks:")

for _, issue := range groupedIssue {
length := len(groupedIssue)
for i, issue := range groupedIssue {
report.WriteString(fmt.Sprintf("%s\n", buildCallStack(output, issue.CallStack, "")))
// Don't spam the text output
if i > 1 {
report.WriteString(fmt.Sprintf("\n ..... and %d more \n", length-i))
break
}
}
issueCounter++
}
Expand Down
Loading